Git Product home page Git Product logo

command-fds's Introduction

command-fds

crates.io page docs.rs page

A library for passing arbitrary file descriptors when spawning child processes.

Example

use command_fds::{CommandFdExt, FdMapping};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::process::Command;

// Open a file.
let file = File::open("Cargo.toml").unwrap();

// Prepare to run `ls -l /proc/self/fd` with some FDs mapped.
let mut command = Command::new("ls");
command.arg("-l").arg("/proc/self/fd");
command
    .fd_mappings(vec![
        // Map `file` as FD 3 in the child process.
        FdMapping {
            parent_fd: file.as_raw_fd(),
            child_fd: 3,
        },
        // Map this process's stdin as FD 5 in the child process.
        FdMapping {
            parent_fd: 0,
            child_fd: 5,
        },
    ])
    .unwrap();

// Spawn the child process.
let mut child = command.spawn().unwrap();
child.wait().unwrap();

License

Licensed under the Apache License, Version 2.0.

Contributing

If you want to contribute to the project, see details of how we accept contributions.

command-fds's People

Contributors

dependabot[bot] avatar jgalenson avatar notpeelz avatar qwandor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

command-fds's Issues

Improper use could cause undefined behaviour

There are two places I can think of in this library where UB can occur in safe code:

1. CommandFdExt::fd_mappings

The fd_mappings function has the following warning in the documentation

Warning: Calling this more than once on the same command, or attempting to run the same command more than once after calling this, may result in unexpected behaviour.

This clearly violates the rule put forward in The Rustonomicon:

No matter what, Safe Rust can't cause Undefined Behavior.

This means that the library should either:

  • Have runtime checks to prevent calling the fd_mappings or spawn function more than once. Panicking is not considered unsafe
    This is analogous to the indexing ([]) operator of slices - trying to access an out of bounds index causes a panic
  • Mark the fd_mappings function as unsafe - this makes it clear that there is a contract that needs to be fulfilled in order to call the function and the user must assert they understand it by using the unsafe {} block
    This is analogous to the unsafe get_unchecked method of a slice

In both cases the documentation should be updated accordingly (replacing the warning with a Panics or Safety section)

2. FdMapping

The other potential source of UB is the FdMapping struct. As per the documentation for FdMapping:

The parent_fd must be kept open until after the child is spawned.

This is clearly another contract that must be fulfilled by the crate user and improper usage can and will lead to UB. While getting the RawFd from an OwnedFd (or any other type - using the IntoRawFd trait) is not unsafe, using a RawFd is. The logic is essentially the same as with raw pointers:

  • Getting a raw pointer is safe (see Vec::as_ptr)
  • Dereferencing a raw pointer is unsafe

Rust's standard library already provides a way to use file descriptors safely: the OwnedFd and BorrowedFd types. See the I/O safety section in std::io docs for more details. They can be used to ensure that parent_fd will in fact be opened while we have a reference to a OwnedFd or BorrowedFd.

Fixing this problem is a bit more nuanced. The simple option would be to make either the fd_mappings function or FdMapping constructor unsafe (well, currently there is no FdMapping::new function, so the interface would need to be changed - thread) and document the safety contract (namely: the user must ensure parent_fd is not closed until the child is spawned).

A more sound solution could be making it so that fd_mappings has a safe variant which takes either BorrowedFds or OwnedFds for the parent_fd. (I'm not sure how should child_fd be implemented, as it doesn't refer to an existing fs - leaving it as a RawFd might be a valid solution).

The usage could look like this (modified example from the docs):

With BorrowFd:

let file = File::open("Cargo.toml").unwrap();

let mut command = Command::new("ls");
command.arg("-l").arg("/proc/self/fd");

let fd: BorrowedFd = file.as_fd();

command
    .fd_mappings(vec![
        FdMapping::from_borrowed_fd(
            /* parent_fd: */ fd, // or &fd ?
            /* child_fd: */ 3,
        ),
    ])
    .unwrap();

// We must use lifetime annotations to make sure the borrowed fd is still valid at this point
// This might require creating a wrapper over `Command`
let mut child = command.spawn().unwrap();

// The file is still valid at this point, but two processes have file descriptors to it
// Are there any valid use cases for this?
// The behaviour could be unexpected, as two processes have access to the same file
file.write(/*...*/);

// We can drop the file - this closes the file descriptor owned by file,
// but no the duplicated one, used by the new thread
drop(file);

child.wait().unwrap();

With OwnedFd:

let file = File::open("Cargo.toml").unwrap();

let mut command = Command::new("ls");
command.arg("-l").arg("/proc/self/fd");

let owned_fd = OwnedFd::from(file);
// OwnedFd::from<File> consumes `file`
// so the variable `file` is no longer valid at this point

command
    .fd_mappings(vec![
        FdMapping::from_owned_fd(
            /* parent_fd: */ owned_fd, // We transfer ownership of owned_fd
            /* child_fd: */ 3,
        ),
    ])
    .unwrap();

let mut child = command.spawn().unwrap();
// OwnedFd (just as File) implements Drop, which closes the owned stream
// The internal implementation should consider when the OwnedFd is dropped
// Of course it should still be valid when we make the `dup2` syscall
// We should probably examine how it is implemented for standard streams in std::process
// 
// The documentation should make it clear when the original file descriptor will be closed
// This is especially important when using pipes, where we expect all the descriptors on the writeable end to be closed,
// so that the reading end can end with an EOF

child.wait().unwrap();

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.