Skip to content

Commit f87df06

Browse files
committed
feat(syscall): add inotify syscalls
1 parent 513f924 commit f87df06

File tree

5 files changed

+403
-2
lines changed

5 files changed

+403
-2
lines changed

api/src/file/inotify.rs

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
use alloc::{
2+
borrow::Cow,
3+
collections::{BTreeMap, VecDeque},
4+
sync::{Arc, Weak},
5+
vec::Vec,
6+
};
7+
use core::{
8+
any::Any,
9+
mem::size_of,
10+
sync::atomic::{AtomicBool, AtomicU64, Ordering},
11+
};
12+
13+
use axerrno::{AxError, AxResult};
14+
use axfs::ROOT_FS_CONTEXT;
15+
use axfs_ng_vfs::Location;
16+
use axio::{BufMut, Write};
17+
use axpoll::{IoEvents, PollSet, Pollable};
18+
use axsync::Mutex;
19+
use axtask::future::{block_on, poll_io};
20+
use bitflags::bitflags;
21+
use lazy_static::lazy_static;
22+
23+
use crate::file::{FileLike, Kstat, SealedBuf, SealedBufMut};
24+
25+
/// ========== Inotify event flags ==========
26+
pub const IN_IGNORED: u32 = 0x00008000; // File was ignored
27+
/// ========== Flags for inotify_init1()==========
28+
pub const IN_CLOEXEC: u32 = 0o2000000; // 02000000, Set FD_CLOEXEC
29+
pub const IN_NONBLOCK: u32 = 0o0004000; // 00004000, Set O_NONBLOCK
30+
31+
// flags for inotify_syscalls
32+
bitflags! {
33+
#[derive(Debug, Clone, Copy, Default)]
34+
pub struct InotifyFlags: u32 {
35+
/// Create a file descriptor that is closed on `exec`.
36+
const CLOEXEC = IN_CLOEXEC;
37+
/// Create a non-blocking inotify instance.
38+
const NONBLOCK = IN_NONBLOCK;
39+
}
40+
}
41+
42+
/// inotifyEvent(Memory layout fully compatible with Linux)
43+
#[repr(C)]
44+
#[derive(Debug, Clone, Copy)]
45+
pub struct InotifyEvent {
46+
pub wd: i32, // Watch descriptor
47+
pub mask: u32, // Mask describing event
48+
pub cookie: u32, // Unique cookie associating related events
49+
pub len: u32, /* Size of name field (including null terminator)
50+
* note: the name field is a variable-length array and is not contained in this struct */
51+
}
52+
53+
/// Monitoring data of each inode(stored in Location::user_data())
54+
#[derive(Default)]
55+
struct InodeWatchData {
56+
// key: watch descriptor, value: (instance_id, event mask)
57+
watches: Mutex<BTreeMap<i32, (u64, u32)>>, // Using Mutex to wrap
58+
}
59+
impl InodeWatchData {
60+
fn add_watch(&self, wd: i32, instance_id: u64, mask: u32) {
61+
self.watches.lock().insert(wd, (instance_id, mask));
62+
}
63+
64+
fn remove_watch(&self, wd: i32) -> bool {
65+
self.watches.lock().remove(&wd).is_some()
66+
}
67+
68+
fn is_empty(&self) -> bool {
69+
self.watches.lock().is_empty()
70+
}
71+
}
72+
73+
/// inotify instance
74+
pub struct InotifyInstance {
75+
// event_queue:save serialized event data
76+
event_queue: Mutex<VecDeque<Vec<u8>>>,
77+
78+
// Added: weak reference from wd to Location (for quick lookup and path retrieval)
79+
wd_to_location: Mutex<BTreeMap<i32, Weak<Location>>>,
80+
81+
next_wd: Mutex<i32>,
82+
83+
// Instance ID (unique identifier)
84+
instance_id: u64,
85+
86+
// blocking/non-blocking mode
87+
non_blocking: AtomicBool,
88+
89+
// poll support
90+
poll_set: PollSet,
91+
}
92+
93+
impl InotifyInstance {
94+
/// create new instance
95+
pub fn new(flags: i32) -> AxResult<Arc<Self>> {
96+
static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
97+
98+
let flags = flags as u32;
99+
// verify flags
100+
let valid_flags = IN_NONBLOCK | IN_CLOEXEC;
101+
if flags & !valid_flags != 0 {
102+
return Err(AxError::InvalidInput);
103+
}
104+
105+
let non_blocking = (flags & IN_NONBLOCK) != 0;
106+
let instance_id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed);
107+
108+
let instance = Arc::new(Self {
109+
event_queue: Mutex::new(VecDeque::new()),
110+
wd_to_location: Mutex::new(BTreeMap::new()),
111+
next_wd: Mutex::new(1),
112+
instance_id,
113+
non_blocking: AtomicBool::new(non_blocking),
114+
poll_set: PollSet::new(),
115+
});
116+
117+
// Registered to global manager
118+
INOTIFY_MANAGER.register(instance_id, Arc::clone(&instance));
119+
120+
Ok(instance)
121+
}
122+
123+
/// Serialized events are in binary format for users to read with char[]
124+
fn serialize_event(event: &InotifyEvent, name: Option<&str>) -> Vec<u8> {
125+
// +1 for null terminator
126+
let name_len = name.map_or(0, |s| s.len() + 1);
127+
let total_len = size_of::<InotifyEvent>() + name_len;
128+
129+
// Linux requires events to be 4-byte aligned
130+
let aligned_len = (total_len + 3) & !3;
131+
132+
let mut buf = Vec::with_capacity(aligned_len);
133+
134+
// Write event header (native byte order, matching architecture)
135+
buf.extend_from_slice(&event.wd.to_ne_bytes());
136+
buf.extend_from_slice(&event.mask.to_ne_bytes());
137+
buf.extend_from_slice(&event.cookie.to_ne_bytes());
138+
buf.extend_from_slice(&(name_len as u32).to_ne_bytes());
139+
140+
// Write filename (if any)
141+
if let Some(name) = name {
142+
buf.extend_from_slice(name.as_bytes());
143+
buf.push(0); // null terminator
144+
145+
// Padding for alignment (using null bytes)
146+
let padding = aligned_len - total_len;
147+
buf.resize(buf.len() + padding, 0);
148+
}
149+
150+
buf
151+
}
152+
153+
/// add watch for a path
154+
/// Returns watch descriptor (wd)
155+
pub fn add_watch(&self, path: &str, mask: u32) -> AxResult<i32> {
156+
// Convert path to Location
157+
let location = self.resolve_path(path)?;
158+
// Generate a new watch descriptor
159+
let wd = {
160+
let mut next = self.next_wd.lock();
161+
let wd = *next;
162+
*next += 1;
163+
wd
164+
};
165+
166+
// Get user_data of location
167+
let mut user_data = location.user_data();
168+
169+
// Get or create InodeWatchData
170+
// Use get_or_insert_with to get Arc<InodeWatchData>
171+
let inode_data = user_data.get_or_insert_with(InodeWatchData::default);
172+
173+
// Store watch info: wd -> (instance_id, mask)
174+
inode_data.add_watch(wd, self.instance_id, mask);
175+
176+
// Store reverse mapping: wd -> location
177+
self.wd_to_location
178+
.lock()
179+
.insert(wd, Arc::downgrade(&location));
180+
181+
Ok(wd)
182+
}
183+
184+
/// remove watch (generate IN_IGNORED event)
185+
pub fn remove_watch(&self, wd: i32) -> AxResult<()> {
186+
// Get location from wd_to_location
187+
let location_weak = {
188+
let mut wd_map = self.wd_to_location.lock();
189+
wd_map.remove(&wd).ok_or(AxError::InvalidInput)?
190+
};
191+
192+
// Generate IN_IGNORED event
193+
let event = InotifyEvent {
194+
wd,
195+
mask: IN_IGNORED,
196+
cookie: 0,
197+
len: 0,
198+
};
199+
let event_data = Self::serialize_event(&event, None);
200+
self.push_event(event_data);
201+
202+
// If location exists, remove watch from its user_data
203+
if let Some(location) = location_weak.upgrade() {
204+
let user_data = location.user_data();
205+
206+
if let Some(inode_data) = user_data.get::<InodeWatchData>() {
207+
// Remove watch
208+
inode_data.remove_watch(wd);
209+
// If no more watches, remove InodeWatchData
210+
if inode_data.is_empty() {
211+
// Actually TypeMap has no remove, can only leave empty
212+
}
213+
}
214+
}
215+
216+
Ok(())
217+
}
218+
219+
/// Push event to queue
220+
fn push_event(&self, event_data: Vec<u8>) {
221+
let mut queue = self.event_queue.lock();
222+
queue.push_back(event_data);
223+
self.poll_set.wake();
224+
}
225+
226+
fn resolve_path(&self, path: &str) -> AxResult<Arc<Location>> {
227+
let fs_ctx = ROOT_FS_CONTEXT.get().ok_or(AxError::NotFound)?;
228+
let loc = fs_ctx.resolve(path).map_err(|_| AxError::NotFound)?;
229+
Ok(Arc::new(loc))
230+
}
231+
}
232+
233+
impl FileLike for InotifyInstance {
234+
fn read(&self, dst: &mut SealedBufMut) -> axio::Result<usize> {
235+
block_on(poll_io(self, IoEvents::IN, self.nonblocking(), || {
236+
let mut queue = self.event_queue.lock();
237+
238+
if queue.is_empty() {
239+
return Err(AxError::WouldBlock);
240+
}
241+
242+
let mut bytes_written = 0;
243+
244+
// Write as many events as possible without exceeding the buffer
245+
while let Some(event_data) = queue.front() {
246+
if dst.remaining_mut() < event_data.len() {
247+
break;
248+
}
249+
250+
dst.write(event_data)?;
251+
bytes_written += event_data.len();
252+
queue.pop_front();
253+
}
254+
255+
if bytes_written > 0 {
256+
Ok(bytes_written)
257+
} else {
258+
// Buffer too small to write a complete event
259+
Err(AxError::InvalidInput)
260+
}
261+
}))
262+
}
263+
264+
fn write(&self, _src: &mut SealedBuf) -> axio::Result<usize> {
265+
Err(AxError::BadFileDescriptor)
266+
}
267+
268+
fn stat(&self) -> axio::Result<Kstat> {
269+
Ok(Kstat::default())
270+
}
271+
272+
fn nonblocking(&self) -> bool {
273+
self.non_blocking.load(Ordering::Acquire)
274+
}
275+
276+
fn set_nonblocking(&self, non_blocking: bool) -> axio::Result {
277+
self.non_blocking.store(non_blocking, Ordering::Release);
278+
Ok(())
279+
}
280+
281+
fn path(&self) -> Cow<str> {
282+
"anon_inode:[inotify]".into()
283+
}
284+
285+
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
286+
self
287+
}
288+
}
289+
290+
impl Pollable for InotifyInstance {
291+
fn poll(&self) -> IoEvents {
292+
let mut events = IoEvents::empty();
293+
let queue = self.event_queue.lock();
294+
295+
// Events available to read
296+
events.set(IoEvents::IN, !queue.is_empty());
297+
// inotify file is not writable
298+
events.set(IoEvents::OUT, false);
299+
300+
events
301+
}
302+
303+
fn register(&self, context: &mut core::task::Context<'_>, events: IoEvents) {
304+
if events.contains(IoEvents::IN) {
305+
self.poll_set.register(context.waker());
306+
}
307+
}
308+
}
309+
310+
// Global manager (singleton)
311+
struct InotifyManager {
312+
instances: Mutex<BTreeMap<u64, Arc<InotifyInstance>>>,
313+
}
314+
315+
impl InotifyManager {
316+
fn new() -> Self {
317+
Self {
318+
instances: Mutex::new(BTreeMap::new()),
319+
}
320+
}
321+
322+
fn register(&self, instance_id: u64, instance: Arc<InotifyInstance>) {
323+
self.instances.lock().insert(instance_id, instance);
324+
}
325+
}
326+
327+
lazy_static! {
328+
static ref INOTIFY_MANAGER: InotifyManager = InotifyManager::new();
329+
}

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+
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::{InotifyFlags, InotifyInstance},
2628
net::Socket,
2729
pidfd::PidFd,
2830
pipe::Pipe,

0 commit comments

Comments
 (0)