Skip to content

Commit 63cc59c

Browse files
committed
feat(syscall): add inotify syscalls
1 parent b602b01 commit 63cc59c

File tree

5 files changed

+374
-2
lines changed

5 files changed

+374
-2
lines changed

api/src/file/inotify.rs

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
use alloc::{
2+
borrow::Cow,
3+
collections::{BTreeMap, VecDeque},
4+
string::String,
5+
sync::Arc,
6+
vec::Vec,
7+
};
8+
use core::{
9+
any::Any,
10+
mem::size_of,
11+
sync::atomic::{AtomicBool, Ordering},
12+
};
13+
14+
use axerrno::{AxError, AxResult};
15+
use axio::{BufMut, Write};
16+
use axpoll::{IoEvents, PollSet, Pollable};
17+
use axsync::Mutex;
18+
use axtask::future::{block_on, poll_io};
19+
use bitflags::bitflags;
20+
21+
use crate::{
22+
alloc::string::ToString,
23+
file::{FileLike, Kstat, SealedBuf, SealedBufMut},
24+
};
25+
26+
/// ========== Inotify event flags ==========
27+
pub const IN_ACCESS: u32 = 0x00000001; // File was accessed
28+
pub const IN_MODIFY: u32 = 0x00000002; // File was modified
29+
pub const IN_ATTRIB: u32 = 0x00000004; // Metadata changed
30+
pub const IN_CLOSE_WRITE: u32 = 0x00000008; // Writtable file was closed
31+
pub const IN_CLOSE_NOWRITE: u32 = 0x00000010; // Unwrittable file closed
32+
pub const IN_OPEN: u32 = 0x00000020; // File was opened
33+
pub const IN_MOVED_FROM: u32 = 0x00000040; // File was moved from X
34+
pub const IN_MOVED_TO: u32 = 0x00000080; // File was moved to Y
35+
pub const IN_CREATE: u32 = 0x00000100; // Subfile was created
36+
pub const IN_DELETE: u32 = 0x00000200; // Subfile was deleted
37+
pub const IN_DELETE_SELF: u32 = 0x00000400; // Self was deleted
38+
pub const IN_MOVE_SELF: u32 = 0x00000800; // Self was moved
39+
40+
// only set by the kernel
41+
pub const IN_UNMOUNT: u32 = 0x00002000; // Backing fs was unmounted
42+
pub const IN_Q_OVERFLOW: u32 = 0x00004000; // Event queued overflowed
43+
pub const IN_IGNORED: u32 = 0x00008000; // File was ignored
44+
45+
/// ========== Flags for inotify_init1()==========
46+
pub const IN_CLOEXEC: u32 = 0o2000000; // 02000000, Set FD_CLOEXEC
47+
pub const IN_NONBLOCK: u32 = 0o0004000; // 00004000, Set O_NONBLOCK
48+
49+
/// ========== Special flags for inotify_add_watch())==========
50+
pub const IN_DONT_FOLLOW: u32 = 0x02000000; // Don't follow a sym link
51+
pub const IN_EXCL_UNLINK: u32 = 0x04000000; // Exclude events on unlinked objects
52+
pub const IN_MASK_CREATE: u32 = 0x10000000; // Only create watches
53+
pub const IN_MASK_ADD: u32 = 0x20000000; // Add to the mask of an already existing watch
54+
pub const IN_ONLYDIR: u32 = 0x01000000; // Only watch the path if it is a directory
55+
56+
/// ========== Auxiliary flags==========
57+
pub const IN_CLOSE: u32 = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE; // close
58+
pub const IN_MOVE: u32 = IN_MOVED_FROM | IN_MOVED_TO; // moves
59+
pub const IN_ISDIR: u32 = 0x40000000; // event occurred against dir
60+
pub const IN_ONESHOT: u32 = 0x80000000; // only send event once
61+
62+
// Combination flags (not required, but recommended)
63+
pub const IN_ALL_EVENTS: u32 = 0x00000fff; // Combination of the first 12 events
64+
65+
// flags for inotify_syscalls
66+
bitflags! {
67+
#[derive(Debug, Clone, Copy, Default)]
68+
pub struct InotifyFlags: u32 {
69+
/// Create a file descriptor that is closed on `exec`.
70+
const CLOEXEC = IN_CLOEXEC;
71+
/// Create a non-blocking inotify instance.
72+
const NONBLOCK = IN_NONBLOCK;
73+
}
74+
}
75+
76+
/// inotifyEvent(Memory layout fully compatible with Linux)
77+
#[repr(C)]
78+
#[derive(Debug, Clone, Copy)]
79+
pub struct InotifyEvent {
80+
pub wd: i32, // Watch descriptor
81+
pub mask: u32, // Mask describing event
82+
pub cookie: u32, // Unique cookie associating related events
83+
pub len: u32, /* Size of name field (including null terminator)
84+
* attention:The name field is a variable-length array, which does not contain */
85+
}
86+
87+
/// inotify instance
88+
pub struct InotifyInstance {
89+
// event_queue:save serialized event data
90+
event_queue: Mutex<VecDeque<Vec<u8>>>,
91+
92+
// watches: wd -> (path, event mask)
93+
watches: Mutex<BTreeMap<i32, (String, u32)>>,
94+
next_wd: Mutex<i32>,
95+
96+
// blocking/non-blocking mode
97+
non_blocking: AtomicBool,
98+
99+
// poll support
100+
poll_set: PollSet,
101+
}
102+
103+
impl InotifyInstance {
104+
/// create new instance
105+
pub fn new(flags: i32) -> AxResult<Arc<Self>> {
106+
let flags = flags as u32;
107+
// verify flags
108+
let valid_flags = IN_NONBLOCK | IN_CLOEXEC;
109+
if flags & !valid_flags != 0 {
110+
return Err(AxError::InvalidInput);
111+
}
112+
113+
let non_blocking = (flags & IN_NONBLOCK) != 0;
114+
115+
Ok(Arc::new(Self {
116+
event_queue: Mutex::new(VecDeque::new()),
117+
watches: Mutex::new(BTreeMap::new()),
118+
next_wd: Mutex::new(1),
119+
non_blocking: AtomicBool::new(non_blocking),
120+
poll_set: PollSet::new(),
121+
}))
122+
}
123+
124+
/// Serialized events are in binary format for users to read with char[]
125+
fn serialize_event(event: &InotifyEvent, name: Option<&str>) -> Vec<u8> {
126+
// +1 for null terminator
127+
let name_len = name.map(|s| s.len() + 1).unwrap_or(0);
128+
let total_len = size_of::<InotifyEvent>() + name_len;
129+
130+
// Linux requires events to be 4-byte aligned
131+
let aligned_len = (total_len + 3) & !3;
132+
133+
let mut buf = Vec::with_capacity(aligned_len);
134+
135+
// Write event header (native byte order, matching architecture)
136+
buf.extend_from_slice(&event.wd.to_ne_bytes());
137+
buf.extend_from_slice(&event.mask.to_ne_bytes());
138+
buf.extend_from_slice(&event.cookie.to_ne_bytes());
139+
buf.extend_from_slice(&(name_len as u32).to_ne_bytes());
140+
141+
// Write filename (if any)
142+
if let Some(name) = name {
143+
buf.extend_from_slice(name.as_bytes());
144+
buf.push(0); // null terminator
145+
146+
// Padding for alignment (using null bytes)
147+
let padding = aligned_len - total_len;
148+
buf.resize(buf.len() + padding, 0);
149+
}
150+
151+
buf
152+
}
153+
154+
/// add watch for a path
155+
/// Returns watch descriptor (wd)
156+
pub fn add_watch(&self, path: &str, mask: u32) -> AxResult<i32> {
157+
let mut watches = self.watches.lock();
158+
159+
// Check if a watch for this path already exists
160+
for (&existing_wd, (existing_path, _existing_mask)) in watches.iter() {
161+
if existing_path == path {
162+
// Overwrite existing watch (Linux default behavior)
163+
// Note: return the same wd
164+
watches.insert(existing_wd, (path.to_string(), mask));
165+
return Ok(existing_wd);
166+
}
167+
}
168+
169+
// Generate a new watch descriptor
170+
let mut next_wd = self.next_wd.lock();
171+
let wd = *next_wd;
172+
*next_wd += 1;
173+
174+
watches.insert(wd, (path.to_string(), mask));
175+
Ok(wd)
176+
}
177+
178+
/// remove watch (generate IN_IGNORED event)
179+
pub fn remove_watch(&self, wd: i32) -> AxResult<()> {
180+
let mut watches = self.watches.lock();
181+
182+
if watches.remove(&wd).is_some() {
183+
// Generate IN_IGNORED event (required by Linux)
184+
let event = InotifyEvent {
185+
wd,
186+
mask: IN_IGNORED,
187+
cookie: 0,
188+
len: 0,
189+
};
190+
191+
let event_data = Self::serialize_event(&event, None);
192+
self.push_event(event_data);
193+
194+
Ok(())
195+
} else {
196+
Err(AxError::InvalidInput)
197+
}
198+
}
199+
200+
/// Push event to queue
201+
fn push_event(&self, event_data: Vec<u8>) {
202+
let mut queue = self.event_queue.lock();
203+
queue.push_back(event_data);
204+
self.poll_set.wake();
205+
}
206+
}
207+
208+
impl FileLike for InotifyInstance {
209+
fn read(&self, dst: &mut SealedBufMut) -> axio::Result<usize> {
210+
block_on(poll_io(self, IoEvents::IN, self.nonblocking(), || {
211+
let mut queue = self.event_queue.lock();
212+
213+
if queue.is_empty() {
214+
return Err(AxError::WouldBlock);
215+
}
216+
217+
let mut bytes_written = 0;
218+
219+
// Write as many events as possible without exceeding the buffer
220+
while let Some(event_data) = queue.front() {
221+
if dst.remaining_mut() < event_data.len() {
222+
break;
223+
}
224+
225+
dst.write(event_data)?;
226+
bytes_written += event_data.len();
227+
queue.pop_front();
228+
}
229+
230+
if bytes_written > 0 {
231+
Ok(bytes_written)
232+
} else {
233+
// Buffer too small to write a complete event
234+
Err(AxError::InvalidInput)
235+
}
236+
}))
237+
}
238+
239+
fn write(&self, _src: &mut SealedBuf) -> axio::Result<usize> {
240+
Err(AxError::BadFileDescriptor)
241+
}
242+
243+
fn stat(&self) -> axio::Result<Kstat> {
244+
Ok(Kstat::default())
245+
}
246+
247+
fn nonblocking(&self) -> bool {
248+
self.non_blocking.load(Ordering::Acquire)
249+
}
250+
251+
fn set_nonblocking(&self, non_blocking: bool) -> axio::Result {
252+
self.non_blocking.store(non_blocking, Ordering::Release);
253+
Ok(())
254+
}
255+
256+
fn path(&self) -> Cow<str> {
257+
"anon_inode:[inotify]".into()
258+
}
259+
260+
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
261+
self
262+
}
263+
}
264+
265+
impl Pollable for InotifyInstance {
266+
fn poll(&self) -> IoEvents {
267+
let mut events = IoEvents::empty();
268+
let queue = self.event_queue.lock();
269+
270+
// Events available to read
271+
events.set(IoEvents::IN, !queue.is_empty());
272+
// inotify file is not writable
273+
events.set(IoEvents::OUT, false);
274+
275+
events
276+
}
277+
278+
fn register(&self, context: &mut core::task::Context<'_>, events: IoEvents) {
279+
if events.contains(IoEvents::IN) {
280+
self.poll_set.register(context.waker());
281+
}
282+
}
283+
}

api/src/file/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod epoll;
22
pub mod event;
33
mod fs;
4+
pub mod inotify;
45
mod net;
56
mod pidfd;
67
mod pipe;
@@ -23,6 +24,7 @@ use starry_core::{resources::AX_FILE_LIMIT, task::AsThread};
2324

2425
pub use self::{
2526
fs::{Directory, File, ResolveAtResult, metadata_to_kstat, resolve_at, with_fs},
27+
inotify::InotifyInstance,
2628
net::Socket,
2729
pidfd::PidFd,
2830
pipe::Pipe,

api/src/syscall/fs/inotify.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use core::ffi::c_char;
2+
3+
use axerrno::{AxError, AxResult};
4+
5+
use crate::{
6+
file::{
7+
add_file_like, get_file_like,
8+
inotify::{InotifyFlags, InotifyInstance},
9+
},
10+
mm::vm_load_string,
11+
};
12+
pub fn sys_inotify_init1(flags: i32) -> AxResult<isize> {
13+
let instance = InotifyInstance::new(flags)?;
14+
15+
// Use add_file_like to add file descriptor
16+
let cloexec = (flags as u32) & InotifyFlags::CLOEXEC.bits() != 0;
17+
add_file_like(instance, cloexec).map(|fd| {
18+
debug!("sys_inotify_init1: allocated fd {fd}");
19+
fd as isize
20+
})
21+
}
22+
23+
pub fn sys_inotify_add_watch(fd: i32, pathname: *const c_char, mask: u32) -> AxResult<isize> {
24+
debug!("inotify_add_watch called: fd={fd}, mask={mask:#x}");
25+
26+
// Load pathname (using vm_load_string, same as sys_open)
27+
let path = vm_load_string(pathname)?;
28+
// Get file corresponding to file descriptor
29+
let file = get_file_like(fd)?;
30+
// Convert to inotify instance
31+
let inotify = match file.clone().into_any().downcast::<InotifyInstance>() {
32+
Ok(inst) => inst,
33+
Err(_) => {
34+
warn!("inotify_add_watch: fd {fd} is not an inotify instance");
35+
return Err(AxError::InvalidInput);
36+
}
37+
};
38+
// Get current process information (for permission checks)
39+
// let curr = current();
40+
// let proc_data = &curr.as_thread().proc_data;
41+
42+
// Add watch
43+
let wd = inotify.add_watch(&path, mask)?;
44+
45+
info!("inotify watch added: fd={fd}, path={path}, wd={wd}");
46+
Ok(wd as isize)
47+
}
48+
49+
pub fn sys_inotify_rm_watch(fd: i32, wd: i32) -> AxResult<isize> {
50+
debug!("sys_inotify_rm_watch: fd={fd}, wd={wd}");
51+
52+
// Get file
53+
let file = get_file_like(fd)?;
54+
// let file = match get_file_like(fd) {
55+
// Ok(f) => f,
56+
// Err(AxError::BadFileDescriptor) => {
57+
// warn!("inotify_rm_watch: bad file descriptor {}", fd);
58+
// return Err(AxError::BadFileDescriptor);
59+
// }
60+
// Err(e) => {
61+
// warn!("inotify_rm_watch: get_file_like failed: {:?}", e);
62+
// return Err(AxError::InvalidInput);
63+
// }
64+
// };
65+
66+
// Convert to inotify instance
67+
let inotify = match file.clone().into_any().downcast::<InotifyInstance>() {
68+
Ok(inst) => inst,
69+
Err(_) => {
70+
warn!("inotify_rm_watch: fd {fd} is not an inotify instance");
71+
return Err(AxError::InvalidInput);
72+
}
73+
};
74+
75+
// Remove watch
76+
inotify.remove_watch(wd)?;
77+
info!("inotify_rm_watch: removed watch wd={wd} from fd={fd}");
78+
Ok(0)
79+
}

api/src/syscall/fs/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod ctl;
22
mod event;
33
mod fd_ops;
4+
mod inotify;
45
mod io;
56
mod memfd;
67
mod mount;
@@ -10,5 +11,6 @@ mod signalfd;
1011
mod stat;
1112

1213
pub use self::{
13-
ctl::*, event::*, fd_ops::*, io::*, memfd::*, mount::*, pidfd::*, pipe::*, signalfd::*, stat::*,
14+
ctl::*, event::*, fd_ops::*, inotify::*, io::*, memfd::*, mount::*, pidfd::*, pipe::*,
15+
signalfd::*, stat::*,
1416
};

0 commit comments

Comments
 (0)