Support schemes that require opening multiple asymmetric file descriptors
Created by: Yoric
Redox offers scheme pipe
but this scheme needs a special syscall pipe2
for opening. This is due to the fact that when we open a pipe, we actually open two file descriptors, while syscall open
will only let us open a single file descriptor.
This has the following consequences:
- a syscall that can only be used to access a single scheme is a bad smell;
- we cannot move
pipe
to userland; - if we want to implement a new scheme similar to
pipe
(e.g.cable
), we'll need a new syscall just for that scheme; - further schemes that require opening several asymmetric file descriptors cannot be implemented in userland.
I can think of several manners to solve the issue.
Scheme::open
1. Modifying We could modify Scheme::open
roughly as follows:
/// `out_descriptors` is the set of descriptors created by this call to `open`.
/// Actual number of descriptors depends on the scheme used (and possibly the path).
fn open(&self, path: &[u8], flag: usize, ..., out_descriptors: &mut [usize]) -> Result<usize>
This has the following advantages, in addition to fixing the issues mentioned initially:
- we remove one special-case syscall (
pipe2
) and fold it into something existing and more powerful (open
); - implementing
pipe2
still requires performing a single syscall, which is good for performance.
Drawbacks:
-
open
becomes a bit more complicated (this can be almost entirely mitigated); - we need to port all existing schemes (this can be almost entirely mitigated).
pipe2
& Scheme
2. Extending Currently, pipe2
accepts as arguments:
- a mutable pair of file descriptors;
- a flag.
This could be extended to accept:
- a mutable buffer of file descriptors (actual size depends on the scheme used);
- a flag;
- a path (used to determine the scheme used).
To make this work, we would need to complete it with a new method of Scheme
, tentatively named pipe
and defined as follows:
fn pipe(&self, flag: usize, path: &[u8], out_descriptors: &mut [usize]) -> Result<usize>
Note that name pipe
/pipe2
may be misleading at this stage, so we may want to find a better name. Maybe open_batch
.
This has the following advantages, in addition to fixing the issues mentioned initially:
- it requires very few changes to the kernel;
- it would let us keep
pipe2
as a single syscall, which is good for performance and atomicity.
Drawbacks:
- that's one syscall that we could remove entirely.
openat
3. Using Issue #791 (closed) calls for implementing a syscall openat
, which may be used to derive a file descriptor from another one. In this setting, a userland implementation of pipe2
would look like:
fun pipe2() -> Result<(usize, usize)> {
let pipe = open("pipe:", ...)?;
let read = openat(pipe, "read")?;
let write = openat(pipe, "write")?;
let _ = close(pipe);
Ok((read, write))
}
This has the following advantages, in addition to fixing the issues mentioned initially:
- we remove one special-case syscall (
pipe2
) and fold it into something existing and more powerful (openat
); - read/write ends of a pipe are pretty much capabilities, so this uses a mechanism designed for capabilities anyway;
- it requires no further changes to the kernel than what has already been planned;
-
openat
is a very generic mechanism, which can be used both for security reasons (as part of capabilities) and for performance reasons (to minimize I/O, as demonstrated on Linux and BSD).
Drawbacks:
- executing
pipe2
requires performing 4 syscalls instead of 1 (or 3 syscalls ifopenat
supports opening several paths at once), so that's a regression for performance and atomicity.