Skip to main content

agentos_kernel/
pipe_manager.rs

1use crate::fd_table::{
2    allocate_file_description_id, FdResult, FileDescription, ProcessFdTable, SharedFileDescription,
3    FILETYPE_PIPE, O_NONBLOCK, O_RDONLY, O_RDWR, O_WRONLY,
4};
5use crate::poll::{PollEvents, PollNotifier, POLLERR, POLLHUP, POLLIN, POLLOUT};
6use std::collections::{BTreeMap, VecDeque};
7use std::error::Error;
8use std::fmt;
9use std::sync::{Arc, Condvar, Mutex, MutexGuard};
10use std::time::Duration;
11use web_time::Instant;
12
13pub const MAX_PIPE_BUFFER_BYTES: usize = 65_536;
14pub const PIPE_BUF_BYTES: usize = 4_096;
15
16pub type PipeResult<T> = Result<T, PipeError>;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PipeError {
20    code: &'static str,
21    message: String,
22}
23
24impl PipeError {
25    pub fn code(&self) -> &'static str {
26        self.code
27    }
28
29    fn bad_file_descriptor(message: impl Into<String>) -> Self {
30        Self {
31            code: "EBADF",
32            message: message.into(),
33        }
34    }
35
36    fn broken_pipe(message: impl Into<String>) -> Self {
37        Self {
38            code: "EPIPE",
39            message: message.into(),
40        }
41    }
42
43    fn would_block(message: impl Into<String>) -> Self {
44        Self {
45            code: "EAGAIN",
46            message: message.into(),
47        }
48    }
49
50    fn no_reader(message: impl Into<String>) -> Self {
51        Self {
52            code: "ENXIO",
53            message: message.into(),
54        }
55    }
56}
57
58impl fmt::Display for PipeError {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "{}: {}", self.code, self.message)
61    }
62}
63
64impl Error for PipeError {}
65
66#[derive(Debug, Clone)]
67pub struct PipeEnd {
68    pub description: SharedFileDescription,
69    pub filetype: u8,
70}
71
72#[derive(Debug, Clone)]
73pub struct PipePair {
74    pub read: PipeEnd,
75    pub write: PipeEnd,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79struct PipeRef {
80    pipe_id: u64,
81    end: PipeSide,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85enum PipeSide {
86    Read,
87    Write,
88    ReadWrite,
89}
90
91#[derive(Debug, Default)]
92struct PendingRead {
93    length: usize,
94    result: Option<Option<Vec<u8>>>,
95}
96
97#[derive(Debug)]
98struct PipeState {
99    buffer: VecDeque<Vec<u8>>,
100    readers: usize,
101    writers: usize,
102    waiting_reads: VecDeque<u64>,
103    mode: u32,
104    uid: u32,
105    gid: u32,
106    named_key: Option<(u64, u64)>,
107}
108
109impl Default for PipeState {
110    fn default() -> Self {
111        Self {
112            buffer: VecDeque::new(),
113            readers: 0,
114            writers: 0,
115            waiting_reads: VecDeque::new(),
116            mode: 0o600,
117            uid: 0,
118            gid: 0,
119            named_key: None,
120        }
121    }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub struct PipeMetadata {
126    pub mode: u32,
127    pub uid: u32,
128    pub gid: u32,
129}
130
131#[derive(Debug)]
132struct PipeManagerState {
133    pipes: BTreeMap<u64, PipeState>,
134    desc_to_pipe: BTreeMap<u64, PipeRef>,
135    named_pipes: BTreeMap<(u64, u64), u64>,
136    waiters: BTreeMap<u64, PendingRead>,
137    next_pipe_id: u64,
138    next_waiter_id: u64,
139}
140
141impl Default for PipeManagerState {
142    fn default() -> Self {
143        Self {
144            pipes: BTreeMap::new(),
145            desc_to_pipe: BTreeMap::new(),
146            named_pipes: BTreeMap::new(),
147            waiters: BTreeMap::new(),
148            next_pipe_id: 1,
149            next_waiter_id: 1,
150        }
151    }
152}
153
154#[derive(Debug)]
155struct PipeManagerInner {
156    state: Mutex<PipeManagerState>,
157    waiters: Condvar,
158}
159
160#[derive(Debug, Clone)]
161pub struct PipeManager {
162    inner: Arc<PipeManagerInner>,
163    notifier: Option<PollNotifier>,
164}
165
166impl Default for PipeManager {
167    fn default() -> Self {
168        Self {
169            inner: Arc::new(PipeManagerInner {
170                state: Mutex::new(PipeManagerState::default()),
171                waiters: Condvar::new(),
172            }),
173            notifier: None,
174        }
175    }
176}
177
178impl PipeManager {
179    pub fn is_write_to_read_pair(
180        &self,
181        write_description_id: u64,
182        read_description_id: u64,
183    ) -> bool {
184        let state = lock_or_recover(&self.inner.state);
185        match (
186            state.desc_to_pipe.get(&write_description_id),
187            state.desc_to_pipe.get(&read_description_id),
188        ) {
189            (Some(write), Some(read)) => {
190                write.pipe_id == read.pipe_id
191                    && write.end == PipeSide::Write
192                    && read.end == PipeSide::Read
193            }
194            _ => false,
195        }
196    }
197
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    pub(crate) fn with_notifier(notifier: PollNotifier) -> Self {
203        Self {
204            notifier: Some(notifier),
205            ..Self::default()
206        }
207    }
208
209    pub fn create_pipe(&self) -> PipePair {
210        let mut state = lock_or_recover(&self.inner.state);
211        let pipe_id = state.next_pipe_id;
212        state.next_pipe_id += 1;
213
214        let read_id = allocate_file_description_id();
215        let write_id = allocate_file_description_id();
216
217        state.pipes.insert(
218            pipe_id,
219            PipeState {
220                readers: 1,
221                writers: 1,
222                ..PipeState::default()
223            },
224        );
225        state.desc_to_pipe.insert(
226            read_id,
227            PipeRef {
228                pipe_id,
229                end: PipeSide::Read,
230            },
231        );
232        state.desc_to_pipe.insert(
233            write_id,
234            PipeRef {
235                pipe_id,
236                end: PipeSide::Write,
237            },
238        );
239        drop(state);
240
241        PipePair {
242            read: PipeEnd {
243                description: Arc::new(FileDescription::with_ref_count(
244                    read_id,
245                    format!("pipe:{pipe_id}:read"),
246                    O_RDONLY,
247                    0,
248                )),
249                filetype: FILETYPE_PIPE,
250            },
251            write: PipeEnd {
252                description: Arc::new(FileDescription::with_ref_count(
253                    write_id,
254                    format!("pipe:{pipe_id}:write"),
255                    O_WRONLY,
256                    0,
257                )),
258                filetype: FILETYPE_PIPE,
259            },
260        }
261    }
262
263    pub fn open_named_pipe(
264        &self,
265        key: (u64, u64),
266        path: &str,
267        flags: u32,
268        timeout: Option<Duration>,
269    ) -> PipeResult<PipeEnd> {
270        let access_mode = flags & 0b11;
271        if !matches!(access_mode, O_RDONLY | O_WRONLY | O_RDWR) {
272            return Err(PipeError::bad_file_descriptor("invalid FIFO access mode"));
273        }
274
275        let mut state = lock_or_recover(&self.inner.state);
276        let pipe_id = match state.named_pipes.get(&key).copied() {
277            Some(pipe_id) => pipe_id,
278            None => {
279                let pipe_id = state.next_pipe_id;
280                state.next_pipe_id += 1;
281                state.named_pipes.insert(key, pipe_id);
282                state.pipes.insert(
283                    pipe_id,
284                    PipeState {
285                        named_key: Some(key),
286                        ..PipeState::default()
287                    },
288                );
289                pipe_id
290            }
291        };
292
293        if access_mode == O_WRONLY
294            && flags & O_NONBLOCK != 0
295            && state
296                .pipes
297                .get(&pipe_id)
298                .is_none_or(|pipe| pipe.readers == 0)
299        {
300            return Err(PipeError::no_reader(format!("FIFO has no reader: {path}")));
301        }
302
303        let description_id = allocate_file_description_id();
304        let side = match access_mode {
305            O_RDONLY => PipeSide::Read,
306            O_WRONLY => PipeSide::Write,
307            O_RDWR => PipeSide::ReadWrite,
308            _ => unreachable!(),
309        };
310        state
311            .desc_to_pipe
312            .insert(description_id, PipeRef { pipe_id, end: side });
313        let pipe = state
314            .pipes
315            .get_mut(&pipe_id)
316            .expect("named pipe must exist after allocation");
317        match side {
318            PipeSide::Read => pipe.readers += 1,
319            PipeSide::Write => pipe.writers += 1,
320            PipeSide::ReadWrite => {
321                pipe.readers += 1;
322                pipe.writers += 1;
323            }
324        }
325        self.notify_waiters_and_pollers();
326
327        let should_wait = flags & O_NONBLOCK == 0 && access_mode != O_RDWR;
328        if should_wait {
329            let ready = |state: &PipeManagerState| {
330                state.pipes.get(&pipe_id).is_some_and(|pipe| match side {
331                    PipeSide::Read => pipe.writers > 0,
332                    PipeSide::Write => pipe.readers > 0,
333                    PipeSide::ReadWrite => true,
334                })
335            };
336            if let Some(timeout) = timeout {
337                let (next, result) = self
338                    .inner
339                    .waiters
340                    .wait_timeout_while(state, timeout, |state| !ready(state))
341                    .unwrap_or_else(|poisoned| poisoned.into_inner());
342                state = next;
343                if result.timed_out() && !ready(&state) {
344                    drop(state);
345                    self.close(description_id);
346                    return Err(PipeError::would_block(format!(
347                        "FIFO open timed out: {path}"
348                    )));
349                }
350            } else {
351                state = self
352                    .inner
353                    .waiters
354                    .wait_while(state, |state| !ready(state))
355                    .unwrap_or_else(|poisoned| poisoned.into_inner());
356            }
357        }
358        drop(state);
359
360        Ok(PipeEnd {
361            description: Arc::new(FileDescription::with_ref_count(
362                description_id,
363                path,
364                flags,
365                0,
366            )),
367            filetype: FILETYPE_PIPE,
368        })
369    }
370
371    pub fn poll(&self, description_id: u64, requested: PollEvents) -> PipeResult<PollEvents> {
372        let state = lock_or_recover(&self.inner.state);
373        let pipe_ref = state
374            .desc_to_pipe
375            .get(&description_id)
376            .copied()
377            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe end"))?;
378        let pipe = state
379            .pipes
380            .get(&pipe_ref.pipe_id)
381            .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
382
383        let mut events = PollEvents::empty();
384        match pipe_ref.end {
385            PipeSide::Read => {
386                if requested.intersects(POLLIN) && !pipe.buffer.is_empty() {
387                    events |= POLLIN;
388                }
389                if pipe.writers == 0 {
390                    events |= POLLHUP;
391                }
392            }
393            PipeSide::Write => {
394                if pipe.readers == 0 {
395                    events |= POLLERR;
396                } else if requested.intersects(POLLOUT)
397                    && (available_capacity(pipe) > 0 || !pipe.waiting_reads.is_empty())
398                {
399                    events |= POLLOUT;
400                }
401            }
402            PipeSide::ReadWrite => {
403                if requested.intersects(POLLIN) && !pipe.buffer.is_empty() {
404                    events |= POLLIN;
405                }
406                if requested.intersects(POLLOUT)
407                    && (available_capacity(pipe) > 0 || !pipe.waiting_reads.is_empty())
408                {
409                    events |= POLLOUT;
410                }
411            }
412        }
413
414        Ok(events)
415    }
416
417    pub fn write(&self, description_id: u64, data: impl AsRef<[u8]>) -> PipeResult<usize> {
418        self.write_with_mode(description_id, data, true)
419    }
420
421    pub fn write_blocking(&self, description_id: u64, data: impl AsRef<[u8]>) -> PipeResult<usize> {
422        self.write_with_mode(description_id, data, false)
423    }
424
425    pub fn write_with_mode(
426        &self,
427        description_id: u64,
428        data: impl AsRef<[u8]>,
429        nonblocking: bool,
430    ) -> PipeResult<usize> {
431        let payload = data.as_ref();
432        let mut state = lock_or_recover(&self.inner.state);
433        let pipe_ref = state
434            .desc_to_pipe
435            .get(&description_id)
436            .copied()
437            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe write end"))?;
438        if !matches!(pipe_ref.end, PipeSide::Write | PipeSide::ReadWrite) {
439            return Err(PipeError::bad_file_descriptor("not a pipe write end"));
440        }
441
442        loop {
443            let waiter_id = {
444                let pipe = state
445                    .pipes
446                    .get_mut(&pipe_ref.pipe_id)
447                    .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
448                if pipe.readers == 0 {
449                    return Err(PipeError::broken_pipe("read end closed"));
450                }
451                pipe.waiting_reads.pop_front()
452            };
453
454            if let Some(waiter_id) = waiter_id {
455                let waiter_length = match state.waiters.get(&waiter_id) {
456                    Some(waiter) => waiter.length,
457                    None => continue,
458                };
459                let delivered_len = waiter_length.min(payload.len());
460                let delivered = payload[..delivered_len].to_vec();
461                let remainder = &payload[delivered_len..];
462
463                if !remainder.is_empty() {
464                    let pipe = state
465                        .pipes
466                        .get_mut(&pipe_ref.pipe_id)
467                        .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
468                    pipe.buffer.push_back(remainder.to_vec());
469                }
470
471                if let Some(waiter) = state.waiters.get_mut(&waiter_id) {
472                    waiter.result = Some(Some(delivered));
473                    self.notify_waiters_and_pollers();
474                    return Ok(payload.len());
475                }
476                continue;
477            }
478
479            let current_buffer_size = {
480                let pipe = state
481                    .pipes
482                    .get(&pipe_ref.pipe_id)
483                    .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
484                buffer_size(&pipe.buffer)
485            };
486            let available = MAX_PIPE_BUFFER_BYTES.saturating_sub(current_buffer_size);
487
488            if payload.len() <= PIPE_BUF_BYTES {
489                if available >= payload.len() {
490                    let pipe = state
491                        .pipes
492                        .get_mut(&pipe_ref.pipe_id)
493                        .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
494                    pipe.buffer.push_back(payload.to_vec());
495                    self.notify_waiters_and_pollers();
496                    return Ok(payload.len());
497                }
498            } else if available > 0 {
499                let chunk_len = available.min(payload.len());
500                let pipe = state
501                    .pipes
502                    .get_mut(&pipe_ref.pipe_id)
503                    .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
504                pipe.buffer.push_back(payload[..chunk_len].to_vec());
505                self.notify_waiters_and_pollers();
506                return Ok(chunk_len);
507            }
508
509            if nonblocking {
510                return Err(PipeError::would_block("pipe buffer full"));
511            }
512
513            state = wait_or_recover(&self.inner.waiters, state);
514        }
515    }
516
517    pub fn read(&self, description_id: u64, length: usize) -> PipeResult<Option<Vec<u8>>> {
518        self.read_with_timeout(description_id, length, None)
519    }
520
521    pub fn read_with_timeout(
522        &self,
523        description_id: u64,
524        length: usize,
525        timeout: Option<Duration>,
526    ) -> PipeResult<Option<Vec<u8>>> {
527        let mut state = lock_or_recover(&self.inner.state);
528        let pipe_ref = state
529            .desc_to_pipe
530            .get(&description_id)
531            .copied()
532            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe read end"))?;
533        if !matches!(pipe_ref.end, PipeSide::Read | PipeSide::ReadWrite) {
534            return Err(PipeError::bad_file_descriptor("not a pipe read end"));
535        }
536
537        let mut waiter_id = None;
538        let deadline = timeout.map(|duration| Instant::now() + duration);
539
540        loop {
541            if let Some(id) = waiter_id {
542                if let Some(waiter) = state.waiters.get_mut(&id) {
543                    if let Some(result) = waiter.result.take() {
544                        state.waiters.remove(&id);
545                        return Ok(result);
546                    }
547                }
548            }
549
550            {
551                let pipe = state
552                    .pipes
553                    .get_mut(&pipe_ref.pipe_id)
554                    .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
555
556                if !pipe.buffer.is_empty() {
557                    let result = drain_buffer(&mut pipe.buffer, length);
558                    self.notify_waiters_and_pollers();
559                    return Ok(Some(result));
560                }
561
562                if pipe.writers == 0 {
563                    if let Some(id) = waiter_id {
564                        state.waiters.remove(&id);
565                    }
566                    return Ok(None);
567                }
568            }
569
570            // A zero/expired timeout is a nonblocking readiness probe. Do not
571            // register and immediately remove a waiter: both transitions wake
572            // the process-wide poll notifier and can make a deferred probe
573            // wake itself forever even though no pipe state changed.
574            if waiter_id.is_none() && deadline.is_some_and(|deadline| Instant::now() >= deadline) {
575                return Err(PipeError::would_block("pipe read timed out"));
576            }
577
578            let id = if let Some(id) = waiter_id {
579                id
580            } else {
581                let next = state.next_waiter_id;
582                state.next_waiter_id += 1;
583                state.waiters.insert(
584                    next,
585                    PendingRead {
586                        length,
587                        result: None,
588                    },
589                );
590                let Some(pipe) = state.pipes.get_mut(&pipe_ref.pipe_id) else {
591                    state.waiters.remove(&next);
592                    return Err(PipeError::bad_file_descriptor("pipe not found"));
593                };
594                pipe.waiting_reads.push_back(next);
595                self.notify_waiters_and_pollers();
596                waiter_id = Some(next);
597                next
598            };
599
600            let Some(deadline) = deadline else {
601                state = wait_or_recover(&self.inner.waiters, state);
602                if !state.waiters.contains_key(&id) {
603                    waiter_id = None;
604                }
605                continue;
606            };
607
608            let now = Instant::now();
609            if now >= deadline {
610                if let Some(id) = waiter_id.take() {
611                    state.waiters.remove(&id);
612                    if let Some(pipe) = state.pipes.get_mut(&pipe_ref.pipe_id) {
613                        pipe.waiting_reads.retain(|queued| *queued != id);
614                    }
615                    self.notify_waiters_and_pollers();
616                }
617                return Err(PipeError::would_block("pipe read timed out"));
618            }
619
620            let remaining = deadline.saturating_duration_since(now);
621            let (next_state, wait_result) =
622                wait_timeout_or_recover(&self.inner.waiters, state, remaining);
623            state = next_state;
624            if !state.waiters.contains_key(&id) {
625                waiter_id = None;
626            }
627            if wait_result.timed_out() {
628                if let Some(id) = waiter_id.take() {
629                    state.waiters.remove(&id);
630                    if let Some(pipe) = state.pipes.get_mut(&pipe_ref.pipe_id) {
631                        pipe.waiting_reads.retain(|queued| *queued != id);
632                    }
633                    self.notify_waiters_and_pollers();
634                }
635                return Err(PipeError::would_block("pipe read timed out"));
636            }
637        }
638    }
639
640    pub fn close(&self, description_id: u64) {
641        let mut state = lock_or_recover(&self.inner.state);
642        let Some(pipe_ref) = state.desc_to_pipe.remove(&description_id) else {
643            return;
644        };
645
646        let (waiter_ids, remove_pipe, should_notify) =
647            if let Some(pipe) = state.pipes.get_mut(&pipe_ref.pipe_id) {
648                match pipe_ref.end {
649                    PipeSide::Read => {
650                        pipe.readers = pipe.readers.saturating_sub(1);
651                        (Vec::new(), pipe.readers == 0 && pipe.writers == 0, true)
652                    }
653                    PipeSide::Write => {
654                        pipe.writers = pipe.writers.saturating_sub(1);
655                        let waiter_ids = if pipe.writers == 0 {
656                            pipe.waiting_reads.drain(..).collect::<Vec<_>>()
657                        } else {
658                            Vec::new()
659                        };
660                        (waiter_ids, pipe.readers == 0 && pipe.writers == 0, true)
661                    }
662                    PipeSide::ReadWrite => {
663                        pipe.readers = pipe.readers.saturating_sub(1);
664                        pipe.writers = pipe.writers.saturating_sub(1);
665                        let waiter_ids = if pipe.writers == 0 {
666                            pipe.waiting_reads.drain(..).collect::<Vec<_>>()
667                        } else {
668                            Vec::new()
669                        };
670                        (waiter_ids, pipe.readers == 0 && pipe.writers == 0, true)
671                    }
672                }
673            } else {
674                (Vec::new(), false, false)
675            };
676
677        for waiter_id in waiter_ids {
678            if let Some(waiter) = state.waiters.get_mut(&waiter_id) {
679                waiter.result = Some(None);
680            }
681        }
682
683        if remove_pipe {
684            if let Some(pipe) = state.pipes.remove(&pipe_ref.pipe_id) {
685                if let Some(key) = pipe.named_key {
686                    state.named_pipes.remove(&key);
687                }
688            }
689        }
690        if should_notify {
691            self.notify_waiters_and_pollers();
692        }
693    }
694
695    pub fn is_pipe(&self, description_id: u64) -> bool {
696        lock_or_recover(&self.inner.state)
697            .desc_to_pipe
698            .contains_key(&description_id)
699    }
700
701    pub fn pipe_id_for(&self, description_id: u64) -> Option<u64> {
702        lock_or_recover(&self.inner.state)
703            .desc_to_pipe
704            .get(&description_id)
705            .map(|pipe_ref| pipe_ref.pipe_id)
706    }
707
708    pub fn metadata(&self, description_id: u64) -> Option<PipeMetadata> {
709        let state = lock_or_recover(&self.inner.state);
710        let pipe_id = state.desc_to_pipe.get(&description_id)?.pipe_id;
711        let pipe = state.pipes.get(&pipe_id)?;
712        Some(PipeMetadata {
713            mode: pipe.mode,
714            uid: pipe.uid,
715            gid: pipe.gid,
716        })
717    }
718
719    pub fn set_owner(&self, description_id: u64, uid: u32, gid: u32) -> PipeResult<()> {
720        let mut state = lock_or_recover(&self.inner.state);
721        let pipe_id = state
722            .desc_to_pipe
723            .get(&description_id)
724            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe end"))?
725            .pipe_id;
726        let pipe = state
727            .pipes
728            .get_mut(&pipe_id)
729            .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
730        pipe.uid = uid;
731        pipe.gid = gid;
732        Ok(())
733    }
734
735    pub fn chmod(&self, description_id: u64, mode: u32) -> PipeResult<()> {
736        let mut state = lock_or_recover(&self.inner.state);
737        let pipe_id = state
738            .desc_to_pipe
739            .get(&description_id)
740            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe end"))?
741            .pipe_id;
742        let pipe = state
743            .pipes
744            .get_mut(&pipe_id)
745            .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
746        pipe.mode = mode & 0o7777;
747        Ok(())
748    }
749
750    pub fn pipe_count(&self) -> usize {
751        lock_or_recover(&self.inner.state).pipes.len()
752    }
753
754    pub fn has_named_pipe(&self, key: (u64, u64)) -> bool {
755        lock_or_recover(&self.inner.state)
756            .named_pipes
757            .contains_key(&key)
758    }
759
760    pub fn named_pipe_peer_ready(&self, description_id: u64) -> PipeResult<Option<bool>> {
761        let state = lock_or_recover(&self.inner.state);
762        let pipe_ref = state
763            .desc_to_pipe
764            .get(&description_id)
765            .copied()
766            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe end"))?;
767        let pipe = state
768            .pipes
769            .get(&pipe_ref.pipe_id)
770            .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
771        if pipe.named_key.is_none() {
772            return Ok(None);
773        }
774        Ok(Some(match pipe_ref.end {
775            PipeSide::Read => pipe.writers > 0,
776            PipeSide::Write => pipe.readers > 0,
777            PipeSide::ReadWrite => true,
778        }))
779    }
780
781    pub fn buffered_bytes(&self) -> usize {
782        lock_or_recover(&self.inner.state)
783            .pipes
784            .values()
785            .map(|pipe| buffer_size(&pipe.buffer))
786            .sum()
787    }
788
789    pub fn waiting_reader_count(&self, description_id: u64) -> PipeResult<usize> {
790        let state = lock_or_recover(&self.inner.state);
791        let pipe_ref = state
792            .desc_to_pipe
793            .get(&description_id)
794            .copied()
795            .ok_or_else(|| PipeError::bad_file_descriptor("not a pipe end"))?;
796        let pipe = state
797            .pipes
798            .get(&pipe_ref.pipe_id)
799            .ok_or_else(|| PipeError::bad_file_descriptor("pipe not found"))?;
800        Ok(pipe.waiting_reads.len())
801    }
802
803    pub fn pending_read_waiter_count(&self) -> usize {
804        lock_or_recover(&self.inner.state).waiters.len()
805    }
806
807    pub fn create_pipe_fds(&self, fd_table: &mut ProcessFdTable) -> FdResult<(u32, u32)> {
808        let pipe = self.create_pipe();
809        let read_fd =
810            fd_table.open_with(Arc::clone(&pipe.read.description), FILETYPE_PIPE, None)?;
811        match fd_table.open_with(Arc::clone(&pipe.write.description), FILETYPE_PIPE, None) {
812            Ok(write_fd) => Ok((read_fd, write_fd)),
813            Err(error) => {
814                fd_table.close(read_fd);
815                self.close(pipe.read.description.id());
816                self.close(pipe.write.description.id());
817                Err(error)
818            }
819        }
820    }
821
822    fn notify_waiters_and_pollers(&self) {
823        self.inner.waiters.notify_all();
824        if let Some(notifier) = &self.notifier {
825            notifier.notify();
826        }
827    }
828}
829
830fn buffer_size(buffer: &VecDeque<Vec<u8>>) -> usize {
831    buffer.iter().map(Vec::len).sum()
832}
833
834fn available_capacity(pipe: &PipeState) -> usize {
835    MAX_PIPE_BUFFER_BYTES.saturating_sub(buffer_size(&pipe.buffer))
836}
837
838fn drain_buffer(buffer: &mut VecDeque<Vec<u8>>, length: usize) -> Vec<u8> {
839    let mut chunks = Vec::new();
840    let mut remaining = length;
841
842    while remaining > 0 {
843        let Some(chunk) = buffer.pop_front() else {
844            break;
845        };
846        if chunk.len() <= remaining {
847            remaining -= chunk.len();
848            chunks.push(chunk);
849        } else {
850            let (head, tail) = chunk.split_at(remaining);
851            chunks.push(head.to_vec());
852            buffer.push_front(tail.to_vec());
853            remaining = 0;
854        }
855    }
856
857    if chunks.len() == 1 {
858        return chunks.pop().expect("single chunk should exist");
859    }
860
861    let total = chunks.iter().map(Vec::len).sum();
862    let mut result = Vec::with_capacity(total);
863    for chunk in chunks {
864        result.extend_from_slice(&chunk);
865    }
866    result
867}
868
869fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
870    match mutex.lock() {
871        Ok(guard) => guard,
872        Err(poisoned) => poisoned.into_inner(),
873    }
874}
875
876fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
877    match condvar.wait(guard) {
878        Ok(guard) => guard,
879        Err(poisoned) => poisoned.into_inner(),
880    }
881}
882
883fn wait_timeout_or_recover<'a, T>(
884    condvar: &Condvar,
885    guard: MutexGuard<'a, T>,
886    timeout: Duration,
887) -> (MutexGuard<'a, T>, std::sync::WaitTimeoutResult) {
888    match condvar.wait_timeout(guard, timeout) {
889        Ok(result) => result,
890        Err(poisoned) => poisoned.into_inner(),
891    }
892}
893
894#[cfg(test)]
895mod tests {
896    use super::*;
897
898    #[test]
899    fn zero_timeout_empty_read_does_not_publish_false_readiness() {
900        let notifier = PollNotifier::default();
901        let manager = PipeManager::with_notifier(notifier.clone());
902        let pipe = manager.create_pipe();
903        let observed = notifier.snapshot();
904
905        let error = manager
906            .read_with_timeout(pipe.read.description.id(), 1, Some(Duration::ZERO))
907            .expect_err("empty nonblocking read must return EAGAIN");
908
909        assert_eq!(error.code(), "EAGAIN");
910        assert_eq!(notifier.snapshot(), observed);
911        let state = lock_or_recover(&manager.inner.state);
912        assert!(state.waiters.is_empty());
913        assert_eq!(state.next_waiter_id, 1);
914    }
915}