Skip to main content

strat9_kernel/vfs/
pipe.rs

1//! Kernel pipe implementation.
2//!
3//! A pipe is a unidirectional byte stream between two file descriptors.
4//! The read end blocks when empty; the write end returns EPIPE when
5//! the read end is closed.
6
7use crate::{
8    sync::{waitqueue::WaitQueue, SpinLock},
9    syscall::error::SyscallError,
10};
11use alloc::sync::Arc;
12
13const PIPE_BUF_SIZE: usize = 4096;
14
15/// Shared state for one pipe instance.
16struct PipeInner {
17    buf: [u8; PIPE_BUF_SIZE],
18    read_pos: usize,
19    write_pos: usize,
20    /// Number of bytes currently buffered.
21    len: usize,
22    read_closed: bool,
23    write_closed: bool,
24    /// Number of open file-descriptions referencing the read end.
25    /// The end is marked closed only when this reaches zero.
26    read_refs: usize,
27    /// Number of open file-descriptions referencing the write end.
28    write_refs: usize,
29}
30
31impl PipeInner {
32    /// Creates a new instance.
33    fn new() -> Self {
34        PipeInner {
35            buf: [0u8; PIPE_BUF_SIZE],
36            read_pos: 0,
37            write_pos: 0,
38            len: 0,
39            read_closed: false,
40            write_closed: false,
41            read_refs: 1,
42            write_refs: 1,
43        }
44    }
45
46    /// Returns whether empty.
47    #[allow(dead_code)]
48    fn is_empty(&self) -> bool {
49        self.len == 0
50    }
51
52    /// Returns whether full.
53    #[allow(dead_code)]
54    fn is_full(&self) -> bool {
55        self.len >= PIPE_BUF_SIZE
56    }
57
58    /// Performs the available read operation.
59    fn available_read(&self) -> usize {
60        self.len
61    }
62
63    /// Performs the available write operation.
64    fn available_write(&self) -> usize {
65        PIPE_BUF_SIZE - self.len
66    }
67}
68
69/// Shared pipe handle.
70pub struct Pipe {
71    inner: SpinLock<PipeInner>,
72    /// Woken when data becomes available (or write end is closed).
73    readers: WaitQueue,
74    /// Woken when space becomes available (or read end is closed).
75    writers: WaitQueue,
76}
77
78impl Pipe {
79    /// Creates a new instance.
80    pub fn new() -> Arc<Self> {
81        Arc::new(Pipe {
82            inner: SpinLock::new(PipeInner::new()),
83            readers: WaitQueue::new(),
84            writers: WaitQueue::new(),
85        })
86    }
87
88    /// Read from the pipe. Returns 0 on EOF (write end closed + empty).
89    pub fn read(&self, buf: &mut [u8]) -> Result<usize, SyscallError> {
90        loop {
91            // Phase 1: try to consume data without blocking.
92            {
93                let mut inner = self.inner.lock();
94                if inner.available_read() > 0 {
95                    let to_read = core::cmp::min(buf.len(), inner.available_read());
96                    for i in 0..to_read {
97                        buf[i] = inner.buf[inner.read_pos];
98                        inner.read_pos = (inner.read_pos + 1) % PIPE_BUF_SIZE;
99                    }
100                    inner.len -= to_read;
101                    self.writers.wake_one();
102                    return Ok(to_read);
103                }
104                if inner.write_closed {
105                    return Ok(0); // EOF
106                }
107            }
108
109            // Phase 2: wait for data or close. The wait atomically checks the
110            // condition while the task is registered in the wait queue, so a
111            // concurrent close_write() that sets write_closed + wakes will be
112            // observed.
113            let result = self.readers.wait_until(|| {
114                let inner = self.inner.lock();
115                if inner.available_read() > 0 {
116                    return Some(true);
117                }
118                if inner.write_closed {
119                    return Some(false);
120                }
121                None
122            });
123
124            match result {
125                true => {
126                    // Data available : loop back to Phase 1 to copy it.
127                    continue;
128                }
129                false => {
130                    // EOF : write end closed and buffer drained.
131                    return Ok(0);
132                } // result == None should not happen (wait_until returned None
133                  // for timeout, which we don't use), but handle it safely.
134            }
135        }
136    }
137
138    /// Write to the pipe. Returns EPIPE if read end is closed.
139    pub fn write(&self, buf: &[u8]) -> Result<usize, SyscallError> {
140        if buf.is_empty() {
141            return Ok(0);
142        }
143
144        let mut total = 0;
145        while total < buf.len() {
146            let wrote_some = self.writers.wait_until(|| {
147                let mut inner = self.inner.lock();
148
149                if inner.read_closed {
150                    if total > 0 {
151                        return Some(Ok(0)); // return what we have
152                    }
153                    return Some(Err(SyscallError::Pipe));
154                }
155
156                if inner.available_write() > 0 {
157                    let to_write = core::cmp::min(buf.len() - total, inner.available_write());
158                    for i in 0..to_write {
159                        let wp = inner.write_pos;
160                        inner.buf[wp] = buf[total + i];
161                        inner.write_pos = (wp + 1) % PIPE_BUF_SIZE;
162                    }
163                    inner.len += to_write;
164                    return Some(Ok(to_write));
165                }
166
167                None // block
168            });
169            self.readers.wake_one();
170            let n = wrote_some?;
171            if n == 0 {
172                break; // read_closed mid-write, return partial
173            }
174            total += n;
175        }
176
177        Ok(total)
178    }
179
180    /// Increment the read-end refcount (called on dup/fork).
181    pub fn dup_read(&self) {
182        self.inner.lock().read_refs += 1;
183    }
184
185    /// Increment the write-end refcount (called on dup/fork).
186    pub fn dup_write(&self) {
187        self.inner.lock().write_refs += 1;
188    }
189
190    /// Decrement the read-end refcount; marks the end closed only when it
191    /// reaches zero.  Returns true if the end was actually closed.
192    pub fn close_read(&self) -> bool {
193        let mut inner = self.inner.lock();
194        if inner.read_refs == 0 {
195            return false;
196        }
197        inner.read_refs -= 1;
198        if inner.read_refs == 0 {
199            inner.read_closed = true;
200            drop(inner);
201            self.writers.wake_all();
202            true
203        } else {
204            false
205        }
206    }
207
208    /// Decrement the write-end refcount; marks the end closed only when it
209    /// reaches zero.  Returns true if the end was actually closed.
210    pub fn close_write(&self) -> bool {
211        let mut inner = self.inner.lock();
212        if inner.write_refs == 0 {
213            return false;
214        }
215        inner.write_refs -= 1;
216        if inner.write_refs == 0 {
217            inner.write_closed = true;
218            drop(inner);
219            self.readers.wake_all();
220            true
221        } else {
222            false
223        }
224    }
225}
226
227// ============================================================================
228// Pipe as a VFS Scheme
229// ============================================================================
230
231use super::scheme::{
232    finalize_pseudo_stat, DirEntry, FileStat, OpenFlags, OpenResult, Scheme, DEV_PIPEFS,
233};
234use alloc::{collections::BTreeMap, vec::Vec};
235use core::sync::atomic::{AtomicU64, Ordering};
236
237/// A scheme that manages kernel pipes.
238///
239/// Each pipe gets two file_ids: even = read end, odd = write end.
240pub struct PipeScheme {
241    pipes: SpinLock<BTreeMap<u64, Arc<Pipe>>>,
242}
243
244static NEXT_PIPE_ID: AtomicU64 = AtomicU64::new(2); // Start at 2 (even numbers)
245
246impl PipeScheme {
247    /// Creates a new instance.
248    pub fn new() -> Self {
249        PipeScheme {
250            pipes: SpinLock::new(BTreeMap::new()),
251        }
252    }
253
254    /// Create a new pipe pair. Returns (read_file_id, write_file_id).
255    pub fn create_pipe(&self) -> (u64, Arc<Pipe>) {
256        let base_id = NEXT_PIPE_ID.fetch_add(2, Ordering::SeqCst);
257        let pipe = Pipe::new();
258        self.pipes.lock().insert(base_id, pipe.clone());
259        (base_id, pipe)
260    }
261
262    /// Returns pipe.
263    fn get_pipe(&self, file_id: u64) -> Result<Arc<Pipe>, SyscallError> {
264        let base = file_id & !1; // Even = base
265        self.pipes
266            .lock()
267            .get(&base)
268            .cloned()
269            .ok_or(SyscallError::BadHandle)
270    }
271
272    /// Returns whether read end.
273    fn is_read_end(file_id: u64) -> bool {
274        file_id & 1 == 0
275    }
276}
277
278impl Scheme for PipeScheme {
279    /// Performs the open operation.
280    fn open(&self, _path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
281        Err(SyscallError::NotSupported) // Pipes are created via sys_pipe, not open()
282    }
283
284    /// Performs the read operation.
285    fn read(&self, file_id: u64, _offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
286        if !Self::is_read_end(file_id) {
287            return Err(SyscallError::PermissionDenied);
288        }
289        let pipe = self.get_pipe(file_id)?;
290        pipe.read(buf)
291    }
292
293    /// Performs the write operation.
294    fn write(&self, file_id: u64, _offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
295        if Self::is_read_end(file_id) {
296            return Err(SyscallError::PermissionDenied);
297        }
298        let pipe = self.get_pipe(file_id)?;
299        pipe.write(buf)
300    }
301
302    /// Performs the close operation.
303    fn close(&self, file_id: u64) -> Result<(), SyscallError> {
304        let pipe = self.get_pipe(file_id)?;
305        if Self::is_read_end(file_id) {
306            pipe.close_read();
307        } else {
308            pipe.close_write();
309        }
310
311        // Remove the shared Pipe entry only when both ends are fully closed
312        // (both refcounts have reached zero).
313        let base = file_id & !1;
314        let inner = pipe.inner.lock();
315        if inner.read_closed && inner.write_closed {
316            drop(inner);
317            self.pipes.lock().remove(&base);
318        }
319        Ok(())
320    }
321
322    /// Performs the stat operation.
323    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
324        let pipe = self.get_pipe(file_id)?;
325        let inner = pipe.inner.lock();
326        Ok(finalize_pseudo_stat(
327            FileStat {
328                st_ino: file_id,
329                st_mode: 0o010600, // S_IFIFO | rw-------
330                st_nlink: 1,
331                st_size: inner.len as u64,
332                st_blksize: PIPE_BUF_SIZE as u64,
333                st_blocks: 0,
334                ..FileStat::zeroed()
335            },
336            DEV_PIPEFS,
337            0,
338        ))
339    }
340
341    /// Performs the readdir operation.
342    fn readdir(&self, _file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
343        Err(SyscallError::InvalidArgument)
344    }
345
346    /// Performs the size operation.
347    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
348        let pipe = self.get_pipe(file_id)?;
349        let len = pipe.inner.lock().len;
350        Ok(len as u64)
351    }
352}