use alloc::{
borrow::Cow,
collections::{BTreeMap, VecDeque},
string::String,
sync::Arc,
vec::Vec,
};
use core::{
sync::atomic::{AtomicU32, AtomicUsize, Ordering},
task::Context,
time::Duration,
};
use ax_errno::{AxError, AxResult, LinuxError};
use ax_kspin::SpinNoIrq;
use ax_runtime::hal::time::wall_time;
use ax_sync::Mutex;
use ax_task::future::{block_on, poll_io, timeout_at_wall};
use axpoll::{IoEvents, PollSet, Pollable};
use linux_raw_sys::general::{
O_ACCMODE, O_NONBLOCK, O_RDONLY, O_RDWR, O_WRONLY, S_IFREG, SIGEV_NONE, SIGEV_SIGNAL,
SIGEV_THREAD,
};
use starry_process::Pid;
use starry_signal::{SignalInfo, Signo};
use crate::{
file::{FileLike, IoDst, IoSrc, Kstat},
task::{AsThread, send_signal_to_process},
};
pub const MQ_HARD_MSG_MAX: usize = 65536;
pub const MQ_HARD_MSGSIZE_MAX: usize = 16 * 1024 * 1024;
pub const MQ_MIN_MSG_MAX: usize = 1;
pub const MQ_MIN_MSGSIZE_MAX: usize = 128;
pub static MQ_QUEUES_MAX: AtomicUsize = AtomicUsize::new(256);
pub static MQ_MSG_MAX: AtomicUsize = AtomicUsize::new(10);
pub static MQ_MSGSIZE_MAX: AtomicUsize = AtomicUsize::new(8192);
pub static MQ_MSG_DEFAULT: AtomicUsize = AtomicUsize::new(10);
pub static MQ_MSGSIZE_DEFAULT: AtomicUsize = AtomicUsize::new(8192);
const SIZEOF_MSG_MSG: u64 = 48;
const SIZEOF_POSIX_MSG_TREE_NODE: u64 = 48;
pub fn msg_max(privileged: bool) -> usize {
if privileged {
MQ_HARD_MSG_MAX
} else {
MQ_MSG_MAX.load(Ordering::Relaxed)
}
}
pub fn msgsize_max(privileged: bool) -> usize {
if privileged {
MQ_HARD_MSGSIZE_MAX
} else {
MQ_MSGSIZE_MAX.load(Ordering::Relaxed)
}
}
pub fn msg_default() -> usize {
MQ_MSG_MAX
.load(Ordering::Relaxed)
.min(MQ_MSG_DEFAULT.load(Ordering::Relaxed))
}
pub fn msgsize_default() -> usize {
MQ_MSGSIZE_MAX
.load(Ordering::Relaxed)
.min(MQ_MSGSIZE_DEFAULT.load(Ordering::Relaxed))
}
pub fn queues_max() -> usize {
MQ_QUEUES_MAX.load(Ordering::Relaxed)
}
static MQ_QUEUES_COUNT: AtomicUsize = AtomicUsize::new(0);
pub fn queues_count() -> usize {
MQ_QUEUES_COUNT.load(Ordering::Relaxed)
}
fn mq_bytes(max_msg: usize, msg_size: usize) -> Option<u64> {
let max_msg = max_msg as u64;
let msg_size = msg_size as u64;
let tree_nodes = max_msg.min(MQ_PRIO_MAX as u64);
let tree = max_msg
.checked_mul(SIZEOF_MSG_MSG)?
.checked_add(tree_nodes.checked_mul(SIZEOF_POSIX_MSG_TREE_NODE)?)?;
max_msg.checked_mul(msg_size)?.checked_add(tree)
}
static MQ_USER_BYTES: Mutex<BTreeMap<u32, u64>> = Mutex::new(BTreeMap::new());
fn charge_user_bytes(uid: u32, bytes: u64, limit: u64) -> AxResult<()> {
let mut map = MQ_USER_BYTES.lock();
let cur = map.get(&uid).copied().unwrap_or(0);
let next = cur.checked_add(bytes).ok_or(LinuxError::EMFILE)?;
if next > limit {
return Err(LinuxError::EMFILE.into());
}
map.insert(uid, next);
Ok(())
}
fn refund_user_bytes(uid: u32, bytes: u64) {
let mut map = MQ_USER_BYTES.lock();
if let Some(cur) = map.get_mut(&uid) {
*cur = cur.saturating_sub(bytes);
if *cur == 0 {
map.remove(&uid);
}
}
}
pub fn charge_open_bytes(uid: u32, limit: u64, max_msg: usize, msg_size: usize) -> AxResult<u64> {
let bytes = mq_bytes(max_msg, msg_size).ok_or(LinuxError::EMFILE)?;
charge_user_bytes(uid, bytes, limit)?;
Ok(bytes)
}
pub const MQ_PRIO_MAX: u32 = 32768;
pub const MQ_NSIG: u32 = 64;
pub const NOTIFY_COOKIE_LEN: usize = 32;
const NOTIFY_WOKENUP: u8 = 1;
const NOTIFY_REMOVED: u8 = 2;
pub const MQ_NAME_MAX: usize = 255;
const FILENT_SIZE: u64 = 80;
#[repr(C)]
#[derive(Clone, Copy, Default, bytemuck::AnyBitPattern, bytemuck::NoUninit)]
pub struct MqAttr {
pub mq_flags: i64,
pub mq_maxmsg: i64,
pub mq_msgsize: i64,
pub mq_curmsgs: i64,
pub __reserved: [i64; 4],
}
struct Message {
priority: u32,
data: Vec<u8>,
}
struct RecvWaiter {
msg: SpinNoIrq<Option<Message>>,
}
impl RecvWaiter {
fn new() -> Arc<Self> {
Arc::new(Self {
msg: SpinNoIrq::new(None),
})
}
fn take_handed(&self) -> Option<Message> {
self.msg.lock().take()
}
}
struct Notification {
notify: u32,
signo: u32,
pid: Pid,
sigev_value: i64,
thread: Option<ThreadNotify>,
}
struct ThreadNotify {
sock: Arc<crate::file::netlink::NetlinkSocket>,
cookie: [u8; NOTIFY_COOKIE_LEN],
}
pub enum NotifyRequest {
Unregister,
Signal { signo: u32, sigev_value: i64 },
None,
Thread {
sock: Arc<crate::file::netlink::NetlinkSocket>,
cookie: [u8; NOTIFY_COOKIE_LEN],
},
}
struct Inner {
buckets: BTreeMap<u32, VecDeque<Message>>,
len: usize,
max_msg: usize,
msg_size: usize,
notify: Option<Notification>,
recv_waiters: VecDeque<Arc<RecvWaiter>>,
atime: Duration,
ctime: Duration,
mtime: Duration,
}
pub struct MessageQueue {
inner: Mutex<Inner>,
uid: u32,
gid: u32,
mode: u16,
charged_bytes: u64,
poll_send: PollSet,
poll_recv: PollSet,
}
impl MessageQueue {
pub fn new(
max_msg: usize,
msg_size: usize,
mode: u16,
uid: u32,
gid: u32,
charged_bytes: u64,
) -> Arc<Self> {
MQ_QUEUES_COUNT.fetch_add(1, Ordering::Relaxed);
let now = wall_time();
Arc::new(Self {
inner: Mutex::new(Inner {
buckets: BTreeMap::new(),
len: 0,
max_msg,
msg_size,
notify: None,
recv_waiters: VecDeque::new(),
atime: now,
ctime: now,
mtime: now,
}),
uid,
gid,
mode: mode & 0o777,
charged_bytes,
poll_send: PollSet::new(),
poll_recv: PollSet::new(),
})
}
pub fn check_open_access(
&self,
access_mode: u32,
fsuid: u32,
is_group_member: impl Fn(u32) -> bool,
) -> AxResult<()> {
let shift = if fsuid == self.uid {
6
} else if is_group_member(self.gid) {
3
} else {
0
};
let granted = ((self.mode >> shift) & 0o7) as u32;
let need_read = access_mode == O_RDONLY || access_mode == O_RDWR;
let need_write = access_mode == O_WRONLY || access_mode == O_RDWR;
if (need_read && granted & 0o4 == 0) || (need_write && granted & 0o2 == 0) {
return Err(LinuxError::EACCES.into());
}
Ok(())
}
pub fn attr(&self) -> MqAttr {
let inner = self.inner.lock();
MqAttr {
mq_flags: 0,
mq_maxmsg: inner.max_msg as i64,
mq_msgsize: inner.msg_size as i64,
mq_curmsgs: inner.len as i64,
__reserved: [0; 4],
}
}
pub fn send(
&self,
data: &[u8],
priority: u32,
deadline: Option<core::time::Duration>,
non_blocking: bool,
) -> AxResult<()> {
if priority >= MQ_PRIO_MAX {
return Err(LinuxError::EINVAL.into());
}
{
let inner = self.inner.lock();
if data.len() > inner.msg_size {
return Err(LinuxError::EMSGSIZE.into());
}
}
let op = || {
let mut inner = self.inner.lock();
if inner.len >= inner.max_msg {
return Err(AxError::WouldBlock);
}
let msg = Message {
priority,
data: data.to_vec(),
};
let fired = if let Some(waiter) = inner.recv_waiters.pop_front() {
*waiter.msg.lock() = Some(msg);
None
} else {
let was_empty = inner.len == 0;
inner.buckets.entry(priority).or_default().push_back(msg);
inner.len += 1;
was_empty.then(|| inner.notify.take()).flatten()
};
drop(inner);
if let Some(n) = fired {
deliver_notification(&n);
}
unsafe { self.poll_recv.wake(IoEvents::IN) };
Ok(())
};
block_on(timeout_at_wall(
deadline,
poll_io(self, IoEvents::OUT, non_blocking, op),
))
.map_err(|_| AxError::from(LinuxError::ETIMEDOUT))?
}
pub fn receive(
&self,
max_len: usize,
deadline: Option<core::time::Duration>,
non_blocking: bool,
) -> AxResult<(Vec<u8>, u32)> {
{
let inner = self.inner.lock();
if max_len < inner.msg_size {
return Err(LinuxError::EMSGSIZE.into());
}
}
let waiter = RecvWaiter::new();
let op = || {
let mut inner = self.inner.lock();
if let Some(msg) = waiter.take_handed() {
return Ok((msg.data, msg.priority));
}
match inner.pop_highest() {
Some(msg) => {
drop(inner);
unsafe { self.poll_send.wake(IoEvents::OUT) };
Ok((msg.data, msg.priority))
}
None => {
if !inner.recv_waiters.iter().any(|w| Arc::ptr_eq(w, &waiter)) {
inner.recv_waiters.push_back(waiter.clone());
}
Err(AxError::WouldBlock)
}
}
};
let result = match block_on(timeout_at_wall(
deadline,
poll_io(self, IoEvents::IN, non_blocking, op),
)) {
Ok(inner_result) => inner_result,
Err(_) => Err(AxError::from(LinuxError::ETIMEDOUT)),
};
match result {
Ok(msg) => Ok(msg),
Err(err) => {
let mut inner = self.inner.lock();
if let Some(msg) = waiter.take_handed() {
drop(inner);
return Ok((msg.data, msg.priority));
}
inner.recv_waiters.retain(|w| !Arc::ptr_eq(w, &waiter));
Err(err)
}
}
}
pub fn register_notify(&self, req: NotifyRequest, pid: Pid) -> AxResult<()> {
let mut inner = self.inner.lock();
match req {
NotifyRequest::Unregister => {
if inner.notify.as_ref().is_some_and(|n| n.pid == pid) {
let removed = inner.notify.take();
let now = wall_time();
inner.atime = now;
inner.ctime = now;
drop(inner);
if let Some(n) = removed {
notify_thread_teardown(&n);
}
}
Ok(())
}
NotifyRequest::Signal { signo, sigev_value } => {
if inner.notify.is_some() {
return Err(LinuxError::EBUSY.into());
}
inner.notify = Some(Notification {
notify: SIGEV_SIGNAL,
signo,
pid,
sigev_value,
thread: None,
});
Self::stamp_register(&mut inner);
Ok(())
}
NotifyRequest::None => {
if inner.notify.is_some() {
return Err(LinuxError::EBUSY.into());
}
inner.notify = Some(Notification {
notify: SIGEV_NONE,
signo: 0,
pid,
sigev_value: 0,
thread: None,
});
Self::stamp_register(&mut inner);
Ok(())
}
NotifyRequest::Thread { sock, cookie } => {
if inner.notify.is_some() {
return Err(LinuxError::EBUSY.into());
}
inner.notify = Some(Notification {
notify: SIGEV_THREAD,
signo: 0,
pid,
sigev_value: 0,
thread: Some(ThreadNotify { sock, cookie }),
});
Self::stamp_register(&mut inner);
Ok(())
}
}
}
fn stamp_register(inner: &mut Inner) {
let now = wall_time();
inner.atime = now;
inner.ctime = now;
}
pub fn clear_notify_owner(&self, pid: Pid) {
let mut inner = self.inner.lock();
if inner.notify.as_ref().is_some_and(|n| n.pid == pid) {
let removed = inner.notify.take();
drop(inner);
if let Some(n) = removed {
notify_thread_teardown(&n);
}
}
}
pub fn report(&self) -> (usize, u32, u32, u32) {
let mut inner = self.inner.lock();
let now = wall_time();
inner.atime = now;
inner.ctime = now;
let qsize = Self::qsize_of(&inner);
match &inner.notify {
Some(n) => {
let signo = if n.notify == SIGEV_SIGNAL { n.signo } else { 0 };
(qsize, n.notify, signo, n.pid)
}
None => (qsize, 0, 0, 0),
}
}
pub fn uid(&self) -> u32 {
self.uid
}
pub fn gid(&self) -> u32 {
self.gid
}
pub fn mode(&self) -> u16 {
self.mode
}
pub fn times(&self) -> (Duration, Duration, Duration) {
let inner = self.inner.lock();
(inner.atime, inner.ctime, inner.mtime)
}
pub fn inode_size(&self) -> u64 {
FILENT_SIZE
}
pub fn kstat(&self) -> Kstat {
let (atime, ctime, mtime) = self.times();
Kstat {
mode: S_IFREG | self.mode as u32,
uid: self.uid,
gid: self.gid,
size: self.inode_size(),
atime,
ctime,
mtime,
..Default::default()
}
}
fn qsize_of(inner: &Inner) -> usize {
inner
.buckets
.values()
.flat_map(|b| b.iter())
.map(|m| m.data.len())
.sum()
}
pub fn touch_attr(&self) {
let now = wall_time();
let mut inner = self.inner.lock();
inner.atime = now;
inner.ctime = now;
}
}
impl Drop for MessageQueue {
fn drop(&mut self) {
MQ_QUEUES_COUNT.fetch_sub(1, Ordering::Relaxed);
if self.charged_bytes != 0 {
refund_user_bytes(self.uid, self.charged_bytes);
}
}
}
impl Inner {
fn pop_highest(&mut self) -> Option<Message> {
let &prio = self.buckets.keys().next_back()?;
let bucket = self.buckets.get_mut(&prio)?;
let msg = bucket.pop_front();
if bucket.is_empty() {
self.buckets.remove(&prio);
}
if msg.is_some() {
self.len -= 1;
}
msg
}
}
fn deliver_notification(n: &Notification) {
match n.notify {
SIGEV_SIGNAL => {
let Some(signo) = Signo::from_repr(n.signo as u8) else {
return;
};
let sender = ax_task::current();
let sender_pid = sender.as_thread().proc_data.proc.pid();
let sender_uid = sender.as_thread().cred().uid;
let info = SignalInfo::new_mqueue(signo, sender_pid, sender_uid, n.sigev_value);
let _ = send_signal_to_process(n.pid, Some(info));
}
SIGEV_THREAD => {
if let Some(thread) = &n.thread {
let mut cookie = thread.cookie;
cookie[NOTIFY_COOKIE_LEN - 1] = NOTIFY_WOKENUP;
thread.sock.deliver_datagram(cookie.to_vec());
}
}
_ => {}
}
}
fn notify_thread_teardown(n: &Notification) {
if n.notify == SIGEV_THREAD
&& let Some(thread) = &n.thread
{
let mut cookie = thread.cookie;
cookie[NOTIFY_COOKIE_LEN - 1] = NOTIFY_REMOVED;
thread.sock.deliver_datagram(cookie.to_vec());
}
}
impl FileLike for MessageQueue {
fn read(&self, _dst: &mut IoDst) -> AxResult<usize> {
Err(AxError::InvalidInput)
}
fn write(&self, _src: &mut IoSrc) -> AxResult<usize> {
Err(AxError::InvalidInput)
}
fn stat(&self) -> AxResult<Kstat> {
Ok(self.kstat())
}
fn nonblocking(&self) -> bool {
false
}
fn set_nonblocking(&self, _non_blocking: bool) -> AxResult {
Ok(())
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[mqueue]".into()
}
}
impl Pollable for MessageQueue {
fn poll(&self) -> IoEvents {
let inner = self.inner.lock();
let mut events = IoEvents::empty();
events.set(IoEvents::IN, inner.len > 0);
events.set(IoEvents::OUT, inner.len < inner.max_msg);
events
}
fn register(&self, context: &mut Context<'_>, events: IoEvents) {
if events.contains(IoEvents::IN) {
unsafe { self.poll_recv.register(context.waker(), IoEvents::IN) };
}
if events.contains(IoEvents::OUT) {
unsafe { self.poll_send.register(context.waker(), IoEvents::OUT) };
}
}
}
pub struct MqDescriptor {
queue: Arc<MessageQueue>,
flags: AtomicU32,
}
impl MqDescriptor {
pub fn new(queue: Arc<MessageQueue>, flags: u32) -> Self {
Self {
queue,
flags: AtomicU32::new(flags),
}
}
pub fn queue(&self) -> &Arc<MessageQueue> {
&self.queue
}
pub fn access(&self) -> u32 {
self.flags.load(Ordering::Acquire) & O_ACCMODE
}
pub fn is_nonblocking(&self) -> bool {
self.flags.load(Ordering::Acquire) & O_NONBLOCK != 0
}
pub fn set_nonblocking_flag(&self, non_blocking: bool) {
let _ = self
.flags
.try_update(Ordering::AcqRel, Ordering::Acquire, |f| {
Some(if non_blocking {
f | O_NONBLOCK
} else {
f & !O_NONBLOCK
})
});
}
pub fn flags(&self) -> u32 {
self.flags.load(Ordering::Acquire)
}
}
impl FileLike for MqDescriptor {
fn stat(&self) -> AxResult<Kstat> {
Ok(self.queue.kstat())
}
fn nonblocking(&self) -> bool {
self.is_nonblocking()
}
fn set_nonblocking(&self, non_blocking: bool) -> AxResult {
self.set_nonblocking_flag(non_blocking);
Ok(())
}
fn open_flags(&self) -> u32 {
self.flags.load(Ordering::Acquire)
}
fn on_close(&self, owner: Pid) {
self.queue.clear_notify_owner(owner);
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[mqueue]".into()
}
}
impl Pollable for MqDescriptor {
fn poll(&self) -> IoEvents {
self.queue.poll()
}
fn register(&self, context: &mut Context<'_>, events: IoEvents) {
self.queue.register(context, events)
}
}
pub static MQ_REGISTRY: Mutex<BTreeMap<String, Arc<MessageQueue>>> = Mutex::new(BTreeMap::new());
pub fn validate_name(name: &str) -> AxResult<&str> {
if name.is_empty() || name.contains('/') {
return Err(LinuxError::EINVAL.into());
}
if name.len() > MQ_NAME_MAX {
return Err(LinuxError::ENAMETOOLONG.into());
}
Ok(name)
}
pub fn registry_names() -> Vec<String> {
MQ_REGISTRY
.lock()
.keys()
.filter_map(|k| k.strip_prefix('/').map(String::from))
.collect()
}
pub fn lookup_by_short_name(short: &str) -> Option<Arc<MessageQueue>> {
let mut key = String::with_capacity(short.len() + 1);
key.push('/');
key.push_str(short);
MQ_REGISTRY.lock().get(&key).cloned()
}