use alloc::sync::Arc;
use ax_errno::{AxError, AxResult, LinuxError};
use linux_raw_sys::general::{
__kernel_mode_t, __kernel_timespec, O_ACCMODE, O_CREAT, O_EXCL, O_NONBLOCK, O_RDONLY, O_RDWR,
O_WRONLY, RLIMIT_MSGQUEUE, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD, sigevent,
};
use starry_vm::{VmMutPtr, VmPtr, vm_load, vm_write_slice};
use crate::{
file::{add_file_like, get_file_like, netlink::NetlinkSocket},
ipc::mqueue::{
MQ_NSIG, MQ_REGISTRY, MessageQueue, MqAttr, MqDescriptor, NOTIFY_COOKIE_LEN, NotifyRequest,
charge_open_bytes, msg_default, msg_max, msgsize_default, msgsize_max, queues_count,
queues_max, validate_name,
},
mm::vm_load_string,
task::AsThread,
time::TimeValueLike,
};
fn load_deadline(abs_timeout: *const __kernel_timespec) -> AxResult<Option<core::time::Duration>> {
if abs_timeout.is_null() {
return Ok(None);
}
let ts: __kernel_timespec = unsafe { abs_timeout.vm_read_uninit()?.assume_init() };
Ok(Some(ts.try_into_time_value()?))
}
pub fn sys_mq_open(
name: *const core::ffi::c_char,
oflag: i32,
mode: __kernel_mode_t,
attr: *const MqAttr,
) -> AxResult<isize> {
let raw = vm_load_string(name)?;
let short = validate_name(&raw)?;
let key = {
let mut k = alloc::string::String::with_capacity(short.len() + 1);
k.push('/');
k.push_str(short);
k
};
let oflag = oflag as u32;
let curr = ax_task::current();
let thr = curr.as_thread();
let cred = thr.cred();
let (fsuid, fsgid, can_sys_resource, can_dac_override) = (
cred.fsuid,
cred.fsgid,
cred.has_cap_sys_resource(),
cred.has_cap_dac_override(),
);
let umask = thr.proc_data.umask();
let msgqueue_rlimit = thr.proc_data.rlim.read()[RLIMIT_MSGQUEUE].current;
let mut registry = MQ_REGISTRY.lock();
let mut created = false;
let queue = match registry.get(&key) {
Some(existing) => {
if oflag & O_CREAT != 0 && oflag & O_EXCL != 0 {
return Err(LinuxError::EEXIST.into());
}
if oflag & O_ACCMODE == O_RDWR | O_WRONLY {
return Err(LinuxError::EINVAL.into());
}
if !can_dac_override {
existing.check_open_access(oflag & O_ACCMODE, fsuid, |gid| cred.in_group(gid))?;
}
existing.clone()
}
None => {
if oflag & O_CREAT == 0 {
return Err(LinuxError::ENOENT.into());
}
if queues_count() >= queues_max() && !can_sys_resource {
return Err(LinuxError::ENOSPC.into());
}
let (max_msg, msg_size) = if attr.is_null() {
(msg_default(), msgsize_default())
} else {
let a: MqAttr = attr.vm_read()?;
let (msg_cap, size_cap) =
(msg_max(can_sys_resource), msgsize_max(can_sys_resource));
if a.mq_maxmsg <= 0
|| a.mq_msgsize <= 0
|| a.mq_maxmsg as usize > msg_cap
|| a.mq_msgsize as usize > size_cap
{
return Err(LinuxError::EINVAL.into());
}
let (max_msg, msg_size) = (a.mq_maxmsg as usize, a.mq_msgsize as usize);
if msg_size > usize::MAX / max_msg {
return Err(LinuxError::EOVERFLOW.into());
}
(max_msg, msg_size)
};
let charged = charge_open_bytes(fsuid, msgqueue_rlimit, max_msg, msg_size)?;
let perm = ((mode as u16) & !(umask as u16)) & 0o777;
let q = MessageQueue::new(max_msg, msg_size, perm, fsuid, fsgid, charged);
registry.insert(key.clone(), q.clone());
created = true;
q
}
};
drop(registry);
let created_queue = created.then(|| queue.clone());
let cloexec = true; let fd = match add_file_like(Arc::new(MqDescriptor::new(queue, oflag)), cloexec) {
Ok(fd) => fd,
Err(e) => {
if let Some(created_queue) = created_queue {
let mut registry = MQ_REGISTRY.lock();
if registry
.get(&key)
.is_some_and(|q| Arc::ptr_eq(q, &created_queue))
{
registry.remove(&key);
}
}
return Err(e);
}
};
Ok(fd as isize)
}
pub fn sys_mq_unlink(name: *const core::ffi::c_char) -> AxResult<isize> {
let raw = vm_load_string(name)?;
let short = validate_name(&raw)?;
let key = {
let mut k = alloc::string::String::with_capacity(short.len() + 1);
k.push('/');
k.push_str(short);
k
};
let curr = ax_task::current();
let cred = curr.as_thread().cred();
let (fsuid, can_fowner) = (cred.fsuid, cred.has_cap_fowner());
let mut registry = MQ_REGISTRY.lock();
let Some(queue) = registry.get(&key) else {
return Err(LinuxError::ENOENT.into());
};
if fsuid != queue.uid() && fsuid != 0 && !can_fowner {
return Err(LinuxError::EPERM.into());
}
registry.remove(&key);
Ok(0)
}
fn descriptor_from_fd(mqdes: i32) -> AxResult<Arc<MqDescriptor>> {
get_file_like(mqdes)?
.downcast_arc::<MqDescriptor>()
.map_err(|_| AxError::from(LinuxError::EBADF))
}
fn queue_from_fd(mqdes: i32) -> AxResult<Arc<MessageQueue>> {
Ok(descriptor_from_fd(mqdes)?.queue().clone())
}
pub fn sys_mq_timedsend(
mqdes: i32,
msg_ptr: *const u8,
msg_len: usize,
msg_prio: u32,
abs_timeout: *const __kernel_timespec,
) -> AxResult<isize> {
let desc = descriptor_from_fd(mqdes)?;
if desc.access() == O_RDONLY {
return Err(LinuxError::EBADF.into());
}
let queue = desc.queue();
let deadline = load_deadline(abs_timeout)?;
let data = vm_load(msg_ptr, msg_len)?;
queue.send(&data, msg_prio, deadline, desc.is_nonblocking())?;
Ok(0)
}
pub fn sys_mq_timedreceive(
mqdes: i32,
msg_ptr: *mut u8,
msg_len: usize,
msg_prio: *mut u32,
abs_timeout: *const __kernel_timespec,
) -> AxResult<isize> {
let desc = descriptor_from_fd(mqdes)?;
if desc.access() == O_WRONLY {
return Err(LinuxError::EBADF.into());
}
let queue = desc.queue();
let deadline = load_deadline(abs_timeout)?;
let (data, prio) = queue.receive(msg_len, deadline, desc.is_nonblocking())?;
vm_write_slice(msg_ptr, &data)?;
if !msg_prio.is_null() {
msg_prio.vm_write(prio)?;
}
Ok(data.len() as isize)
}
pub fn sys_mq_notify(mqdes: i32, sevp: *const sigevent) -> AxResult<isize> {
let queue = queue_from_fd(mqdes)?;
let pid = ax_task::current().as_thread().proc_data.proc.pid();
let req = if sevp.is_null() {
NotifyRequest::Unregister
} else {
let sev: sigevent = unsafe { sevp.vm_read_uninit()?.assume_init() };
let kind = sev.sigev_notify as u32;
match kind {
SIGEV_SIGNAL => {
let signo = sev.sigev_signo as u32;
if signo > MQ_NSIG {
return Err(LinuxError::EINVAL.into());
}
let value = unsafe { sev.sigev_value.sival_ptr } as i64;
NotifyRequest::Signal {
signo,
sigev_value: value,
}
}
SIGEV_NONE => NotifyRequest::None,
SIGEV_THREAD => {
let fd = sev.sigev_signo;
let sock = get_file_like(fd)?
.downcast_arc::<NetlinkSocket>()
.map_err(|_| AxError::from(LinuxError::EINVAL))?;
let cookie_ptr = unsafe { sev.sigev_value.sival_ptr } as *const u8;
let bytes = vm_load(cookie_ptr, NOTIFY_COOKIE_LEN)?;
let mut cookie = [0u8; NOTIFY_COOKIE_LEN];
cookie.copy_from_slice(&bytes);
NotifyRequest::Thread { sock, cookie }
}
_ => return Err(LinuxError::EINVAL.into()),
}
};
queue.register_notify(req, pid)?;
Ok(0)
}
pub fn sys_mq_getsetattr(
mqdes: i32,
newattr: *const MqAttr,
oldattr: *mut MqAttr,
) -> AxResult<isize> {
let desc = descriptor_from_fd(mqdes)?;
let queue = desc.queue();
let mut previous = queue.attr();
previous.mq_flags = (desc.flags() & O_NONBLOCK) as i64;
if !newattr.is_null() {
let new: MqAttr = newattr.vm_read()?;
if new.mq_flags & !(O_NONBLOCK as i64) != 0 {
return Err(LinuxError::EINVAL.into());
}
desc.set_nonblocking_flag(new.mq_flags & O_NONBLOCK as i64 != 0);
queue.touch_attr();
}
if !oldattr.is_null() {
oldattr.vm_write(previous)?;
}
Ok(0)
}