asterinas/kernel/src/fs/pipe.rs

365 lines
10 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use core::sync::atomic::{AtomicU32, Ordering};
use super::{
file_handle::FileLike,
utils::{AccessMode, Channel, Consumer, InodeMode, InodeType, Metadata, Producer, StatusFlags},
};
use crate::{
events::{IoEvents, Observer},
prelude::*,
process::{
signal::{Pollable, Poller},
Gid, Uid,
},
time::clocks::RealTimeCoarseClock,
};
const DEFAULT_PIPE_BUF_SIZE: usize = 65536;
pub fn new_pair() -> Result<(Arc<PipeReader>, Arc<PipeWriter>)> {
let (producer, consumer) = Channel::with_capacity(DEFAULT_PIPE_BUF_SIZE).split();
Ok((
PipeReader::new(consumer, StatusFlags::empty())?,
PipeWriter::new(producer, StatusFlags::empty())?,
))
}
pub fn new_pair_with_capacity(capacity: usize) -> Result<(Arc<PipeReader>, Arc<PipeWriter>)> {
let (producer, consumer) = Channel::with_capacity(capacity).split();
Ok((
PipeReader::new(consumer, StatusFlags::empty())?,
PipeWriter::new(producer, StatusFlags::empty())?,
))
}
pub struct PipeReader {
consumer: Consumer<u8>,
status_flags: AtomicU32,
}
impl PipeReader {
pub fn new(consumer: Consumer<u8>, status_flags: StatusFlags) -> Result<Arc<Self>> {
check_status_flags(status_flags)?;
Ok(Arc::new(Self {
consumer,
status_flags: AtomicU32::new(status_flags.bits()),
}))
}
}
impl Pollable for PipeReader {
fn poll(&self, mask: IoEvents, poller: Option<&mut Poller>) -> IoEvents {
self.consumer.poll(mask, poller)
}
}
impl FileLike for PipeReader {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
let read_len = if self.status_flags().contains(StatusFlags::O_NONBLOCK) {
self.consumer.try_read(writer)?
} else {
self.wait_events(IoEvents::IN, None, || self.consumer.try_read(writer))?
};
Ok(read_len)
}
fn status_flags(&self) -> StatusFlags {
StatusFlags::from_bits_truncate(self.status_flags.load(Ordering::Relaxed))
}
fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> {
check_status_flags(new_flags)?;
self.status_flags.store(new_flags.bits(), Ordering::Relaxed);
Ok(())
}
fn access_mode(&self) -> AccessMode {
AccessMode::O_RDONLY
}
fn metadata(&self) -> Metadata {
// This is a dummy implementation.
// TODO: Add "PipeFS" and link `PipeReader` to it.
let now = RealTimeCoarseClock::get().read_time();
Metadata {
dev: 0,
ino: 0,
size: 0,
blk_size: 0,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
type_: InodeType::NamedPipe,
mode: InodeMode::from_bits_truncate(0o400),
nlinks: 1,
uid: Uid::new_root(),
gid: Gid::new_root(),
rdev: 0,
}
}
fn register_observer(
&self,
observer: Weak<dyn Observer<IoEvents>>,
mask: IoEvents,
) -> Result<()> {
self.consumer.register_observer(observer, mask)
}
fn unregister_observer(
&self,
observer: &Weak<dyn Observer<IoEvents>>,
) -> Option<Weak<dyn Observer<IoEvents>>> {
self.consumer.unregister_observer(observer)
}
}
pub struct PipeWriter {
producer: Producer<u8>,
status_flags: AtomicU32,
}
impl PipeWriter {
pub fn new(producer: Producer<u8>, status_flags: StatusFlags) -> Result<Arc<Self>> {
check_status_flags(status_flags)?;
Ok(Arc::new(Self {
producer,
status_flags: AtomicU32::new(status_flags.bits()),
}))
}
}
impl Pollable for PipeWriter {
fn poll(&self, mask: IoEvents, poller: Option<&mut Poller>) -> IoEvents {
self.producer.poll(mask, poller)
}
}
impl FileLike for PipeWriter {
fn write(&self, reader: &mut VmReader) -> Result<usize> {
if self.status_flags().contains(StatusFlags::O_NONBLOCK) {
self.producer.try_write(reader)
} else {
self.wait_events(IoEvents::OUT, None, || self.producer.try_write(reader))
}
}
fn status_flags(&self) -> StatusFlags {
StatusFlags::from_bits_truncate(self.status_flags.load(Ordering::Relaxed))
}
fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> {
check_status_flags(new_flags)?;
self.status_flags.store(new_flags.bits(), Ordering::Relaxed);
Ok(())
}
fn access_mode(&self) -> AccessMode {
AccessMode::O_WRONLY
}
fn metadata(&self) -> Metadata {
// This is a dummy implementation.
// TODO: Add "PipeFS" and link `PipeWriter` to it.
let now = RealTimeCoarseClock::get().read_time();
Metadata {
dev: 0,
ino: 0,
size: 0,
blk_size: 0,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
type_: InodeType::NamedPipe,
mode: InodeMode::from_bits_truncate(0o200),
nlinks: 1,
uid: Uid::new_root(),
gid: Gid::new_root(),
rdev: 0,
}
}
fn register_observer(
&self,
observer: Weak<dyn Observer<IoEvents>>,
mask: IoEvents,
) -> Result<()> {
self.producer.register_observer(observer, mask)
}
fn unregister_observer(
&self,
observer: &Weak<dyn Observer<IoEvents>>,
) -> Option<Weak<dyn Observer<IoEvents>>> {
self.producer.unregister_observer(observer)
}
}
fn check_status_flags(status_flags: StatusFlags) -> Result<()> {
if status_flags.contains(StatusFlags::O_DIRECT) {
// "O_DIRECT .. Older kernels that do not support this flag will indicate this via an
// EINVAL error."
//
// See <https://man7.org/linux/man-pages/man2/pipe.2.html>.
return_errno_with_message!(Errno::EINVAL, "the `O_DIRECT` flag is not supported");
}
// TODO: Setting most of the other flags will succeed on Linux, but their effects need to be
// validated.
Ok(())
}
#[cfg(ktest)]
mod test {
use alloc::sync::Arc;
use core::sync::atomic::{self, AtomicBool};
use ostd::prelude::*;
use super::*;
use crate::{
fs::utils::Channel,
thread::{
kernel_thread::{KernelThreadExt, ThreadOptions},
Thread,
},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Ordering {
WriteThenRead,
ReadThenWrite,
}
fn test_blocking<W, R>(write: W, read: R, ordering: Ordering)
where
W: Fn(Arc<PipeWriter>) + Sync + Send + 'static,
R: Fn(Arc<PipeReader>) + Sync + Send + 'static,
{
let channel = Channel::with_capacity(1);
let (writer, readr) = channel.split();
let writer = PipeWriter::new(writer, StatusFlags::empty()).unwrap();
let reader = PipeReader::new(readr, StatusFlags::empty()).unwrap();
// FIXME: `ThreadOptions::new` currently accepts `Fn`, forcing us to use `SpinLock` to gain
// internal mutability. We should avoid this `SpinLock` by making `ThreadOptions::new`
// accept `FnOnce`.
let writer_with_lock: SpinLock<_> = SpinLock::new(Some(writer));
let reader_with_lock: SpinLock<_> = SpinLock::new(Some(reader));
let signal_writer = Arc::new(AtomicBool::new(false));
let signal_reader = signal_writer.clone();
let writer = Thread::spawn_kernel_thread(ThreadOptions::new(move || {
let writer = writer_with_lock.lock().take().unwrap();
if ordering == Ordering::ReadThenWrite {
while !signal_writer.load(atomic::Ordering::Relaxed) {
Thread::yield_now();
}
} else {
signal_writer.store(true, atomic::Ordering::Relaxed);
}
write(writer);
}));
let reader = Thread::spawn_kernel_thread(ThreadOptions::new(move || {
let reader = reader_with_lock.lock().take().unwrap();
if ordering == Ordering::WriteThenRead {
while !signal_reader.load(atomic::Ordering::Relaxed) {
Thread::yield_now();
}
} else {
signal_reader.store(true, atomic::Ordering::Relaxed);
}
read(reader);
}));
writer.join();
reader.join();
}
#[ktest]
fn test_read_empty() {
test_blocking(
|writer| {
assert_eq!(writer.write(&mut reader_from(&[1])).unwrap(), 1);
},
|reader| {
let mut buf = [0; 1];
assert_eq!(reader.read(&mut writer_from(&mut buf)).unwrap(), 1);
assert_eq!(&buf, &[1]);
},
Ordering::ReadThenWrite,
);
}
#[ktest]
fn test_write_full() {
test_blocking(
|writer| {
assert_eq!(writer.write(&mut reader_from(&[1, 2])).unwrap(), 1);
assert_eq!(writer.write(&mut reader_from(&[2])).unwrap(), 1);
},
|reader| {
let mut buf = [0; 2];
assert_eq!(reader.read(&mut writer_from(&mut buf)).unwrap(), 1);
assert_eq!(&buf[..1], &[1]);
assert_eq!(reader.read(&mut writer_from(&mut buf)).unwrap(), 1);
assert_eq!(&buf[..1], &[2]);
},
Ordering::WriteThenRead,
);
}
#[ktest]
fn test_read_closed() {
test_blocking(
drop,
|reader| {
let mut buf = [0; 1];
assert_eq!(reader.read(&mut writer_from(&mut buf)).unwrap(), 0);
},
Ordering::ReadThenWrite,
);
}
#[ktest]
fn test_write_closed() {
test_blocking(
|writer| {
assert_eq!(writer.write(&mut reader_from(&[1, 2])).unwrap(), 1);
assert_eq!(
writer.write(&mut reader_from(&[2])).unwrap_err().error(),
Errno::EPIPE
);
},
drop,
Ordering::WriteThenRead,
);
}
fn reader_from(buf: &[u8]) -> VmReader {
VmReader::from(buf).to_fallible()
}
fn writer_from(buf: &mut [u8]) -> VmWriter {
VmWriter::from(buf).to_fallible()
}
}