Skip to content

Commit 756267e

Browse files
committed
feat(syscall): add inotify syscalls
1 parent ddeb03e commit 756267e

File tree

5 files changed

+384
-2
lines changed

5 files changed

+384
-2
lines changed

api/src/file/inotify.rs

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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+
pub const IN_CLOEXEC: u32 = 0x80000;
27+
pub const IN_NONBLOCK: u32 = 0x800;
28+
// flags for inotify_syscalls
29+
bitflags! {
30+
#[derive(Debug, Clone, Copy, Default)]
31+
pub struct InotifyFlags: u32 {
32+
/// Create a file descriptor that is closed on `exec`.
33+
const CLOEXEC = IN_CLOEXEC;
34+
/// Create a non-blocking inotify instance.
35+
const NONBLOCK = IN_NONBLOCK;
36+
}
37+
}
38+
39+
/// inotifyEvent(Memory layout fully compatible with Linux)
40+
#[repr(C)]
41+
#[derive(Debug, Clone, Copy)]
42+
pub struct InotifyEvent {
43+
pub wd: i32, // Watch descriptor
44+
pub mask: u32, // Mask describing event
45+
pub cookie: u32, // Unique cookie associating related events
46+
pub len: u32, /* Size of name field
47+
* attention:The name field is a variable-length array, which does not contain */
48+
}
49+
50+
/// inotify instance
51+
pub struct InotifyInstance {
52+
// event_queue:save serialized event data
53+
event_queue: Mutex<VecDeque<Vec<u8>>>,
54+
55+
// watches: wd -> (path, event mask)
56+
watches: Mutex<BTreeMap<i32, (String, u32)>>,
57+
next_wd: Mutex<i32>,
58+
59+
// blocking/non-blocking mode
60+
non_blocking: AtomicBool,
61+
62+
// poll support
63+
poll_set: PollSet,
64+
}
65+
66+
impl InotifyInstance {
67+
/// create new instance
68+
pub fn new(flags: i32) -> AxResult<Arc<Self>> {
69+
let flags = flags as u32;
70+
// verify flags
71+
let valid_flags = IN_NONBLOCK | IN_CLOEXEC;
72+
if flags & !valid_flags != 0 {
73+
return Err(AxError::InvalidInput);
74+
}
75+
76+
let non_blocking = (flags & IN_NONBLOCK) != 0;
77+
78+
Ok(Arc::new(Self {
79+
event_queue: Mutex::new(VecDeque::new()),
80+
watches: Mutex::new(BTreeMap::new()),
81+
next_wd: Mutex::new(1),
82+
non_blocking: AtomicBool::new(non_blocking),
83+
poll_set: PollSet::new(),
84+
}))
85+
}
86+
87+
/// Serialized events are in binary format for users to read with char[]
88+
fn serialize_event(event: &InotifyEvent, name: Option<&str>) -> Vec<u8> {
89+
let name_len = name.map(|s| s.len()).unwrap_or(0);
90+
let total_len = size_of::<InotifyEvent>() + name_len;
91+
92+
// Linux requires events to be 4-byte aligned
93+
let aligned_len = (total_len + 3) & !3;
94+
95+
let mut buf = Vec::with_capacity(aligned_len);
96+
97+
// Write event header (native byte order, matching architecture)
98+
buf.extend_from_slice(&event.wd.to_ne_bytes());
99+
buf.extend_from_slice(&event.mask.to_ne_bytes());
100+
buf.extend_from_slice(&event.cookie.to_ne_bytes());
101+
buf.extend_from_slice(&(name_len as u32).to_ne_bytes());
102+
103+
// Write filename (if any)
104+
if let Some(name) = name {
105+
buf.extend_from_slice(name.as_bytes());
106+
buf.push(0); // null terminator
107+
108+
// Padding for alignment (using null bytes)
109+
let padding = aligned_len - total_len;
110+
for _ in 0..padding {
111+
buf.push(0);
112+
}
113+
}
114+
115+
buf
116+
}
117+
118+
/// add watch for a path
119+
/// Returns watch descriptor (wd)
120+
pub fn add_watch(&self, path: &str, mask: u32) -> AxResult<i32> {
121+
let mut watches = self.watches.lock();
122+
123+
// Check if a watch for this path already exists
124+
for (&existing_wd, (existing_path, _existing_mask)) in watches.iter() {
125+
if existing_path == path {
126+
// Overwrite existing watch (Linux default behavior)
127+
// Note: return the same wd
128+
watches.insert(existing_wd, (path.to_string(), mask));
129+
return Ok(existing_wd);
130+
}
131+
}
132+
133+
// Generate a new watch descriptor
134+
let mut next_wd = self.next_wd.lock();
135+
let wd = *next_wd;
136+
*next_wd += 1;
137+
138+
watches.insert(wd, (path.to_string(), mask));
139+
Ok(wd)
140+
}
141+
142+
/// remove watch (generate IN_IGNORED event)
143+
pub fn remove_watch(&self, wd: i32) -> AxResult<()> {
144+
let mut watches = self.watches.lock();
145+
146+
if watches.remove(&wd).is_some() {
147+
// Generate IN_IGNORED event (required by Linux)
148+
let event = InotifyEvent {
149+
wd,
150+
mask: 0x8000, // IN_IGNORED
151+
cookie: 0,
152+
len: 0,
153+
};
154+
155+
let event_data = Self::serialize_event(&event, None);
156+
self.push_event(event_data);
157+
158+
Ok(())
159+
} else {
160+
Err(AxError::InvalidInput)
161+
}
162+
}
163+
164+
/// Push event to queue
165+
fn push_event(&self, event_data: Vec<u8>) {
166+
let mut queue = self.event_queue.lock();
167+
queue.push_back(event_data);
168+
self.poll_set.wake();
169+
}
170+
171+
/// For testing: generate a simulated event
172+
pub fn generate_test_event(&self, wd: i32, mask: u32, name: Option<&str>) -> AxResult<()> {
173+
// Verify wd exists
174+
let watches = self.watches.lock();
175+
if !watches.contains_key(&wd) {
176+
return Err(AxError::InvalidInput);
177+
}
178+
179+
let event = InotifyEvent {
180+
wd,
181+
mask,
182+
cookie: 0,
183+
len: name.map(|s| s.len() as u32).unwrap_or(0),
184+
};
185+
186+
let event_data = Self::serialize_event(&event, name);
187+
self.push_event(event_data);
188+
189+
Ok(())
190+
}
191+
}
192+
193+
impl FileLike for InotifyInstance {
194+
fn read(&self, dst: &mut SealedBufMut) -> axio::Result<usize> {
195+
block_on(poll_io(self, IoEvents::IN, self.nonblocking(), || {
196+
let mut queue = self.event_queue.lock();
197+
198+
if queue.is_empty() {
199+
return Err(AxError::WouldBlock);
200+
}
201+
202+
let mut bytes_written = 0;
203+
204+
// Write as many events as possible without exceeding the buffer
205+
while let Some(event_data) = queue.front() {
206+
if dst.remaining_mut() < event_data.len() {
207+
break;
208+
}
209+
210+
dst.write(event_data)?;
211+
bytes_written += event_data.len();
212+
queue.pop_front();
213+
}
214+
215+
if bytes_written > 0 {
216+
Ok(bytes_written)
217+
} else {
218+
// Buffer too small to write a complete event
219+
Err(AxError::InvalidInput)
220+
}
221+
}))
222+
}
223+
224+
fn write(&self, _src: &mut SealedBuf) -> axio::Result<usize> {
225+
Err(AxError::BadFileDescriptor)
226+
}
227+
228+
fn stat(&self) -> axio::Result<Kstat> {
229+
Ok(Kstat::default())
230+
}
231+
232+
fn nonblocking(&self) -> bool {
233+
self.non_blocking.load(Ordering::Acquire)
234+
}
235+
236+
fn set_nonblocking(&self, non_blocking: bool) -> axio::Result {
237+
self.non_blocking.store(non_blocking, Ordering::Release);
238+
Ok(())
239+
}
240+
241+
fn path(&self) -> Cow<str> {
242+
"anon_inode:[inotify]".into()
243+
}
244+
245+
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
246+
self
247+
}
248+
}
249+
250+
impl Pollable for InotifyInstance {
251+
fn poll(&self) -> IoEvents {
252+
let mut events = IoEvents::empty();
253+
let queue = self.event_queue.lock();
254+
255+
// Events available to read
256+
events.set(IoEvents::IN, !queue.is_empty());
257+
// inotify file is not writable
258+
events.set(IoEvents::OUT, false);
259+
260+
events
261+
}
262+
263+
fn register(&self, context: &mut core::task::Context<'_>, events: IoEvents) {
264+
if events.contains(IoEvents::IN) {
265+
self.poll_set.register(context.waker());
266+
}
267+
}
268+
}

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: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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::{UserPtr, vm_load_string},
11+
};
12+
pub fn sys_inotify_init1(flags: i32) -> AxResult<isize> {
13+
let instance = InotifyInstance::new(flags)?;
14+
15+
let flags_u32 = flags as u32;
16+
17+
// Validate flags - only allow CLOEXEC and NONBLOCK
18+
let inotify_flags = InotifyFlags::from_bits(flags_u32).ok_or_else(|| {
19+
warn!("sys_inotify_init1: invalid flags {:#x}", flags);
20+
AxError::InvalidInput
21+
})?;
22+
23+
// Use add_file_like to add file descriptor
24+
let cloexec = inotify_flags.contains(InotifyFlags::CLOEXEC);
25+
add_file_like(instance, cloexec).map(|fd| {
26+
debug!("sys_inotify_init1: allocated fd {}", fd);
27+
fd as isize
28+
})
29+
}
30+
31+
pub fn sys_inotify_add_watch(fd: i32, pathname: UserPtr<u8>, mask: u32) -> AxResult<isize> {
32+
debug!("inotify_add_watch called: fd={}, mask={:#x}", fd, mask);
33+
// Validate mask
34+
if mask == 0 {
35+
warn!("inotify_add_watch: mask contains no valid events");
36+
return Err(AxError::InvalidInput);
37+
}
38+
39+
// Load pathname (using vm_load_string, same as sys_open)
40+
// Get raw pointer from UserPtr
41+
let addr = pathname.address();
42+
let ptr = addr.as_ptr() as *const c_char;
43+
let path = vm_load_string(ptr)?;
44+
// Get file corresponding to file descriptor
45+
let file = get_file_like(fd)?;
46+
// Convert to inotify instance
47+
let inotify = match file.clone().into_any().downcast::<InotifyInstance>() {
48+
Ok(inst) => inst,
49+
Err(_) => {
50+
warn!("inotify_add_watch: fd {} is not an inotify instance", fd);
51+
return Err(AxError::InvalidInput);
52+
}
53+
};
54+
// Get current process information (for permission checks)
55+
// let curr = current();
56+
// let proc_data = &curr.as_thread().proc_data;
57+
58+
// Add watch
59+
let wd = inotify.add_watch(&path, mask)?;
60+
61+
info!("inotify watch added: fd={}, path={}, wd={}", fd, path, wd);
62+
Ok(wd as isize)
63+
}
64+
65+
pub fn sys_inotify_rm_watch(fd: i32, wd: i32) -> AxResult<isize> {
66+
debug!("sys_inotify_rm_watch: fd={}, wd={}", fd, wd);
67+
68+
// Get file
69+
let file = match get_file_like(fd) {
70+
Ok(f) => f,
71+
Err(AxError::BadFileDescriptor) => {
72+
warn!("inotify_rm_watch: bad file descriptor {}", fd);
73+
return Err(AxError::BadFileDescriptor);
74+
}
75+
Err(e) => {
76+
warn!("inotify_rm_watch: get_file_like failed: {:?}", e);
77+
return Err(AxError::InvalidInput);
78+
}
79+
};
80+
81+
// Convert to inotify instance
82+
let inotify = match file.clone().into_any().downcast::<InotifyInstance>() {
83+
Ok(inst) => inst,
84+
Err(_) => {
85+
warn!("inotify_rm_watch: fd {} is not an inotify instance", fd);
86+
return Err(AxError::InvalidInput);
87+
}
88+
};
89+
90+
// Remove watch
91+
match inotify.remove_watch(wd) {
92+
Ok(()) => {
93+
info!("inotify_rm_watch: removed watch wd={} from fd={}", wd, fd);
94+
Ok(0)
95+
}
96+
Err(e) => {
97+
warn!("inotify_rm_watch: remove_watch failed: {:?}", e);
98+
// TODO
99+
// It would be better to create a watch error for inotify
100+
// Error conversion
101+
Err(AxError::InvalidInput)
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)