rustix

repository·main·Indexed 24 days ago

https://github.com/bytecodealliance/rustix

Safe, efficient, and idiomatic Rust bindings to low-level POSIX, Unix, Linux, and Winsock syscalls. rustix focuses on I/O and memory safety by utilizing Rust's type system, providing high-performance wrappers that use native types like references and slices instead of raw pointers. It supports configurable backends, including a high-performance `linux_raw` backend and a portable `libc` backend, and provides specialized APIs for filesystem operations, networking, and event polling.

Tokens
9.1K
Snippets
27
Records
69
Agent score
82%

What's inside rustix

  1. Overview of rustix

    main

    rustix provides efficient, memory-safe, and I/O-safe Rust bindings to POSIX, Unix, Linux, and Winsock syscalls. It is designed to provide high-performance wrappers that use Rust-native types like references, slices, and Results instead of raw pointers and integer error codes.

    Key features include:

    • I/O Safety: Uses OwnedFd and AsFd instead of raw file descriptors.
    • Memory Safety: Uses Rust's ownership and type system to ensure safety.
    • Efficient Argument Handling: Uses an Arg trait to efficiently accept various Rust string types (including non-UTF-8).
    • Configurable Backends: Supports both a high-performance linux_raw backend and a portable libc backend.

    Note: While the net API supports Windows Sockets 2 (Winsock), most other APIs are not intended for Windows. For higher-level portable APIs, consider crates like cap-std, memfd, timerfd, or io-streams.

  2. Configure rustix backends

    main

    rustix uses two primary backends. The linux_raw backend is enabled by default on supported Linux platforms. It is implemented entirely in Rust, avoiding libc and errno for better performance and inlining capabilities.

    To use the libc backend instead of linux_raw, you can:

    1. Enable the use-libc Cargo feature.
    2. Set the RUSTFLAGS environment variable to --cfg=rustix_use_libc during build.

    Additionally, you can use the use-libc-auxv feature to use getauxval instead of PR_GET_AUXV or reading /proc/self/auxv.

  3. What is rustix and how does it improve syscall usage?

    main

    rustix provides efficient, memory-safe, and I/O-safe wrappers to POSIX-like, Unix-like, Linux, and Winsock syscall-like APIs. It abstracts away many of the historical complexities and safety issues associated with raw C-style syscalls.

    Key improvements include:

    • Error Handling: Translates error values into Rust Results.
    • Memory Safety: Buffers are passed as Rust slices, and out-parameters are returned as values.
    • I/O Safety: Uses AsFd and OwnedFd instead of bare integers to manage file descriptors.
    • Path Handling: Uses the Arg type, allowing path arguments to accept various string types.
    • Large File Support (LFS): Automatically uses 64-bit types (e.g., u64, i64) for file sizes and offsets, providing Year 2038 (y2038) compatibility.
    • Type Safety: Uses enums and bitflags for constants and flags.
    • Simplified APIs: De-multiplexes functions like fcntl and ioctl, and presents variadic functions (like openat) as non-variadic.
    • Automatic Memory Management: Functions returning strings automatically allocate sufficient memory and retry syscalls as needed.

    Note: rustix does not hide significant platform differences, detect runtime support (except for y2038/LFS), or provide sandboxing/ambient authority restrictions. For those features, consider cap-std or system-interface.

    # #[cfg(feature = "net")]
    # fn read(sock: std::net::TcpStream, buf: &mut [u8]) -> std::io::Result<()> {
    # use rustix::net::RecvFlags;
    let (nread, _received) = rustix::net::recv(&sock, buf, RecvFlags::PEEK)?;
    # let _ = nread;
    # Ok(())
    # }
  4. Use rustix file descriptor types (AsFd and OwnedFd)

    main

    To ensure I/O safety, rustix uses AsFd and OwnedFd instead of raw integers. The rustix::fd module exports these types and traits, which are either sourced from std::os::fd or provided as polyfills for older Rust versions or Windows.

    On Windows, rustix provides polyfills that alias socket types to OwnedFd and implement AsFd and AsSocket to maintain a consistent API.

  5. Safety considerations for Uid, Gid, and Pid

    main
    The Uid, Gid, and Pid types can be constructed from raw integers. This construction is marked as unsafe because different operating systems assign special meanings to specific integer values. Users should ensure that raw integer values used to construct these types are valid for the target OS.
  6. How the `Buffer` trait determines I/O return types

    main

    The Buffer trait is used by rustix I/O functions (like read) to determine how data is returned to the caller. The type of buffer you pass dictates the return type of the function:

    If you pass a...You get back a...
    &mut [u8]usize, indicating the number of elements initialized.
    &mut [MaybeUninit<u8>](&mut [u8], &mut [MaybeUninit<u8>]), holding the initialized and uninitialized subslices.
    SpareCapacityusize, indicating the number of elements initialized. The Vec is also extended.

    Supported types include mutable slices (&mut [T]), mutable arrays (&mut [T; N]), Vec<T> (with alloc feature), and slices of uninitialized memory (&mut [MaybeUninit<T>]).

    // Passing a &mut [u8]
    let mut buf = [0_u8; 64];
    let nread = read(fd, &mut buf)?;
    
    // Passing a &mut [MaybeUninit<u8>]
    let mut buf = [MaybeUninit::<u8>::uninit(); 64];
    let (init, uninit) = read(fd, &mut buf)?;
    
    // Passing SpareCapacity
    let mut buf = Vec::with_capacity(64);
    let nread = read(fd, spare_capacity(&mut buf))?;
  7. Iterate over directory entries with RawDir

    main

    RawDir is a low-level directory iterator implemented using the getdents system call. It allows you to iterate over directory entries (including . and ..) using a provided buffer.

    Important Constraints

    • Fixed Buffer Size: This implementation does not automatically grow the buffer. If the buffer is too small to hold the next entry (e.g., due to a very long filename), next() may return an error (specifically Errno::INVAL).
    • Resizing Strategy: To handle arbitrarily large filenames, you must catch the error, drop the current iterator, resize your buffer, and create a new RawDir iterator. The iterator is guaranteed to continue where it left off if the file descriptor remains the same.

    Usage Patterns

    Using a Heap-allocated Buffer (Simple)

    This approach is suitable if you can assume a maximum filename length.

    use std::mem::MaybeUninit;
    use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
    use rustix::cstr;
    
    let fd = openat(
        CWD,
        cstr!("."),
        OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
        Mode::empty(),
    )
    .unwrap();
    
    let mut buf = Vec::with_capacity(8192);
    let mut iter = RawDir::new(fd, buf.spare_capacity_mut());
    while let Some(entry) = iter.next() {
        let entry = entry.unwrap();
        dbg!(&entry);
    }

    Using a Portable Growing Buffer

    This pattern handles entries with arbitrarily large filenames by catching Errno::INVAL and resizing the buffer.

    use std::mem::MaybeUninit;
    use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
    use rustix::io::Errno;
    use rustix::cstr;
    
    let fd = openat(
        CWD,
        cstr!("."),
        OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
        Mode::empty(),
    )
    .unwrap();
    
    let mut buf = Vec::with_capacity(8192);
    'read: loop {
        'resize: {
            let mut iter = RawDir::new(&fd, buf.spare_capacity_mut());
            while let Some(entry) = iter.next() {
                let entry = match entry {
                    Err(Errno::INVAL) => break 'resize,
                    r => r.unwrap(),
                };
                dbg!(&entry);
            }
            break 'read;
        }
    
        let new_capacity = buf.capacity() * 2;
        buf.reserve(new_capacity);
    }
  8. rustix Requirements and Compatibility

    main

    Minimum Supported Rust Version (MSRV)

    • Rust 1.65

    Linux Compatibility

    • Minimum Linux Version: 3.2
    • 64-bit Support: rustix automatically uses 64-bit APIs (LFS) and provides Year 2038 (y2038) support by avoiding 32-bit APIs that would cause these issues. For example, rustix::fstatvfs calls fstatvfs64 and returns a 64-bit struct even on 32-bit platforms.
  9. Reference: rustix Cargo features

    main

    The following Cargo features control which API modules are available. The modules rustix::io, rustix::buffer, rustix::fd, rustix::ffi, and rustix::ioctl are enabled by default.

    NameDescription
    eventrustix::event—Polling and event operations.
    fsrustix::fs—Filesystem operations.
    io_uringrustix::io_uring—Linux io_uring.
    mmrustix::mm—Memory map operations.
    mountrustix::mount—Linux mount API.
    netrustix::net—Network-related operations.
    paramrustix::param—Process parameters.
    piperustix::pipe—Pipe operations.
    processrustix::process—Process-associated operations.
    ptyrustix::pty—Pseudoterminal operations.
    randrustix::rand—Random-related operations.
    shmrustix::shm—POSIX shared memory.
    stdiorustix::stdio—Stdio-related operations.
    systemrustix::system—System-related operations.
    termiosrustix::termios—Terminal I/O stream operations.
    threadrustix::thread—Thread-associated operations.
    timerustix::time—Time-related operations.
    use-libcEnable the libc backend.
    linux_4_11Enable optimizations that assume Linux ≥ 4.11
    linux_5_1Enable optimizations that assume Linux ≥ 5.1
    linux_5_11Enable optimizations that assume Linux ≥ 5.11
    linux_latestEnable optimizations that assume the latest Linux release
    use-libc-auxvUse getauxval instead of PR_GET_AUXV or "/proc/self/auxv".
    stdOn by default; disable to activate #![no_std].
    allocOn by default; enables features that depend on alloc.
  10. Troubleshoot common `Buffer` related compiler errors

    main

    When using Buffer types with rustix I/O functions, you may encounter specific compiler errors. Here is how to resolve them:

    Error MessageSolution
    cannot move out of self which is behind a mutable reference or move occurs because x has type &mut [u8], which does not implement the Copy traitReplace x with &mut *x.
    type annotations needed and cannot infer type of the type parameter Buf declared on the function readChange a &mut [] to &mut [0_u8; 0].
    the trait bound [MaybeUninit<u8>; 1]: Buffer<u8> is not satisfiedAdd a &mut to pass the array by reference instead of by value.
    cannot move out of x, a captured variable in an FnMut closureTry replacing x with &mut *x, or move a let into the closure body.
    captured variable cannot escape FnMut closure bodyUse an explicit loop instead of retry_on_intr.
  11. Construct an object from a raw file descriptor with FromRawFd

    main

    The FromRawFd trait allows you to construct a new instance of a type from a raw file descriptor. This is typically used to consume ownership of the file descriptor, meaning the returned object becomes responsible for closing it when it goes out of scope.

    Safety: The fd passed in must be an owned file descriptor and must be open.

    use std::fs::File;
    use std::io;
    #[cfg(unix)]
    use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
    #[cfg(target_os = "wasi")]
    use std::os::wasi::io::{FromRawFd, IntoRawFd, RawFd};
    
    let f = File::open("foo.txt")?;
    # #[cfg(any(unix, target_os = "wasi"))]
    # let raw_fd: RawFd = f.into_raw_fd();
    // SAFETY: no other functions should call `from_raw_fd`, so there is only one owner for the file descriptor.
    # #[cfg(any(unix, target_os = "wasi"))]
    let f = unsafe { File::from_raw_fd(raw_fd) };
    # Ok::<(), io::Error>(())