Giter Club home page Giter Club logo

rusty-doors's Introduction

Rusty Doors

The goal of this crate is to expose the illumos Doors API in Rust. It exposes the native doors API verbatim, and also provides some moderately safer abstractions.

Example

A server procedure that simply doubles its input might look like this:

use doors::server::Door;
use doors::server::Request;
use doors::server::Response;

#[doors::server_procedure]
fn double(x: Request) -> Response<[u8; 1]> {
  if x.data.len() > 0 {
    return Response::new([x.data[0] * 2]);
  } else {
    // We were given nothing, and 2 times nothing is zero...
    return Response::new([0]);
  }
}

let door = Door::create(double).unwrap();
door.force_install("/tmp/double.door").unwrap();

A client program which invokes that server procedure might look something like this:

use doors::Client;

let client = Client::open("/tmp/double.door").unwrap();

let result = client.call_with_data(&[111]).unwrap();
assert_eq!(result.data()[0], 222);

Tests

Run make tests to run the unit tests, and run make all to run the full build pipeline.

Acknowledgements

rusty-doors's People

Contributors

robertdfrench avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

rusty-doors's Issues

Mask signals during Ergonomic door call

In DOOR_CALL(3C) it says (emphasis mine):

The door_call() function is not a restartable system call. It returns EINTR if a signal was caught and handled by this thread. If the door invocation is not idempotent the caller should mask any signals that may be generated during a door_call() operation.

Or maybe the default door_call should say in the docs that it expects the server procedure to be idempotent, and we could have a separate door_call_nonidempotent call that masks signals to make sure that signals received by the client do not cause the server to be interrupted.

Set up SLSA Provenance

Let's go ahead and get in front of that. We don't have very many dependencies in this crate, so it will not be hard to get them in line.

Handle door_return errors

According to DOOR_RETURN(3C), door_return can actually fail and return to the calling process in certain situations:

   Upon successful completion, door_return() does not return to the calling
   process. Otherwise, door_return() returns -1 to the calling process and
   sets errno to indicate the error.

   ...

   The door_return() function fails and returns to the calling process if:

   E2BIG
             Arguments were too big for client.


   EFAULT
             The address of data_ptr or desc_ptr is invalid.


   EINVAL
             Invalid door_return() arguments were passed or a thread is
             bound to a door that no longer exists.


   EMFILE
             The client has too many open descriptors.

So! It would be neat to be able to turn this into a function that produces a Result type

Remove implementations from native types

Don't add logic to anything that we don't own in this crate. The types that are defined in doors.h, etc have to be imported literally, but should only be accessed using the foreign type pattern. This will keep us from needing to have references to door_desc_t in all parts of the code.

Let the user create door_desc_t types

They should own (and therefore manage) the Vec<door_desc_t> that is passed to door_Call or door_return. Creating this vector for them leads to memory leaks, I think. No new heap allocations should happen in the door_call / door_return wrappers

Align all the servers, tests, and door paths

There are lots of tests that have one name which rely on server with a slightly different name, and rendezvous at door paths that have a third variant. All of these should be renamed something like this:

  • $test_name <- integration test client
  • "$test_name_test_server" <- integration test server
  • "$test_name_test.door" <- rendezvous path

Provide Generic forms of door_call

Like https://github.com/oxidecomputer/rusty-doors/blob/main/lib/src/lib.rs#L77

Although this raises some problems of its own -- namely, how to ensure that clients do not cause runtime errors just because they can pass arbitrary types to a door call? The server procedure will certainly be expecting a well-defined type. Maybe something roughly along these lines would be more appropriate:

In Server

#[door]
fn greet(name: CString) -> CString {
  // construct a greeting response
}

In Client

let door = Door::open<CString, CString>("/path/to/door")?;
let greeting = door.call(CString::from("robert"))?;

That way, instead of door_call being generic (and thus seeming to compile against more or less whatever type), you have to explicitly state the types you expect when opening a file descriptor for the door client. That would at least ensure that putting the wrong variable into door.call results in a compile error. I am assuming that people will be less likely to get the type names wrong since those will come from the documentation for the server procedure.

Split illumos Error type up into call-specific Error types

The errors mean different things depending on what function you just invoked. They don't all need to be in a big category, because they already are (in libc namespace). Further, each man page for each door function says what kinds of errors it can set to errno if it fails, so that list needs to be the enumeration.

Fix current door_call interface

The current door_call interface is sortof busted -- it doesn't handle rbuf correctly, or cleanly.

I think we need a DoorArgumentsBuffer type which has a to_door_args() method that will emit the current type. That way we still can have a single thing to own each of these buffers -- if folks don't want or need to use that, they can construct a door_args_t themselves.

If rbuf.ptr() doesn't match the storage allocated for rbuf by the DoorArgumentsBuffer, then somebody will need to deallocate the old rbuf (since it wasn't used) and munmap the new rbuf (since it was mapped into the client's address space by the kernel) once it is no longer needed.

Originally posted by @robertdfrench in #6 (comment)

Implement door_info calls

This is another packed structure, so there will be some unaligned access issues. But this can be leveraged in tests to assert that door_create calls were made with the correct arguments.

Replace trait-based server procedures with proc-macros

Currently we use a system of trait-based server procedures, where every server procedure needs to be associated with a type, and that type needs to implement a trait called ServerProcedure. This requires consumers of this crate to cook up an empty type just to hold on to a function, which isn't terrible, but it isn't great.

Incorporating proc-macro-based server procedures will make for less boilerplate for the user, but it will require the proc-macro definition to be extended to cover file descriptors. Also, the ServerProcedure trait is aware of the lifetime of the return data, so the proc-macro will need to account for that as well (to avoid memory leaks, or at least force the developers to admit that they are leaking data).

Specify data type in door function

Somehow we'll need to specify what type the door data is. Something like:

doorfn!(Func, i32, {
   // data is available as a vec<i32> now
})

Create a tutorial

https://github.com/robertdfrench/revolving-doors is a good tutorial, but it doesn't apply to Rust, and won't leverage the Rust-friendly abstractions in this crate. A separate tutorial (separate from the documentation, which will read more like a reference manual) will be useful for folks who might be approaching doors for the first time in Rust.

Draw up every possible ownership scenario for door_call

Here are the memory layout scenarios if we ignore descriptors:

  1. Door call is empty and return is empty
  2. Door call is empty and return maps $M$ bytes into the address space (since rbuf is NULL)
  3. Door call references $D$ bytes of data, $0$ bytes of rbuf, and return is empty
  4. Door call references $D$ bytes of data, $0$ bytes of rbuf, and return maps $M$ bytes into the address space
  5. Door call references $D$ bytes of data, $R$ bytes of rbuf, and return copies $C &lt; R$ bytes into rbuf
  6. Door call references $D$ bytes of data within rbuf. rbuf has length $R &gt; D$, and return copies $C &lt; R$ bytes into rbuf
  7. Door call references $D$ bytes of data within rbuf. rbuf has length $R &gt; D$, but return data has length $M &gt; R$, so return maps $M$ bytes into the address space.
scenario data rbuf rbuf' Drop rbuf' ?
1 $(0,0)$ $(0,0)$ $(0,0)$
2 $(0,0)$ $(0,0)$ $(rbuf',rbuf'+M)$ Yes
3 $(data,data+D)$ $(0,0)$ $(0,0)$
4 $(data,data+D)$ $(0,0)$ $(rbuf',rbuf'+M)$ Yes
5 $(data,data+D)$ $(rbuf,rbuf+R)$ $(rbuf,rbuf+R)$
6 $(rbuf,rbuf+D)$ $(rbuf,rbuf+R)$ $(rbuf,rbuf+R)$
7 $(rbuf,rbuf+D)$ $(rbuf,rbuf+R)$ $(rbuf',rbuf'+M)$ Yes

Proposal: Lambda-like syntax for door procedure functions

Door procedures could look like this:

door::server_procedure!(|UserType data, Vec<RawFd> descriptors| {
    println!("I received {}", data);
    println!("I also got {} file descriptors", descriptors.len());
})

but that leaves open the question of where to name the server procedure (and the resulting type). Maybe this:

door::server_procedure!(|UserType data, Vec<RawFd> descriptors| {
    println!("I received {}", data);
    println!("I also got {} file descriptors", descriptors.len());
}, DoorName)

or

door::fn!(DoorName = |UserType data, Vec<RawFd> descriptors| {
    println!("I received {}", data);
    println!("I also got {} file descriptors", descriptors.len());
})

which has the advantage of being most reminiscent of the lambda syntax, but doesn't strictly stand out clearly in the code as an identifier.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.