lisy 0.1.0

Linux specific high and middle level system level API library.
Documentation
use std::io;
use std::os::unix::ffi::OsStrExt;

use anyhow::{Context as _, Error};

use lisy::mount::{Mount, MountSetAttr, MoveMount, OpenTree};
use lisy::userns::{IdMapping, Userns};

fn usage(mut out: impl io::Write, status: i32) -> ! {
    let _ = writeln!(
        out,
        "\
usage: idmapmount [options...] source destination
options:
  -r                    do a recursive mount
  -u host:ns:count      map a user id range
  -g host:ns:count      map a group id range
  -b host:ns:count      map both
"
    );

    std::process::exit(status);
}

fn main() -> Result<(), Error> {
    let mut args = std::env::args_os().skip(1);

    let mut recursive = false;
    let mut source = None;
    let mut uid_mappings = Vec::new();
    let mut gid_mappings = Vec::new();
    while let Some(arg_os) = args.next() {
        let arg = arg_os.as_bytes();

        if arg == b"-h" || arg == b"--help" {
            usage(std::io::stdout(), 0);
        }

        if arg == b"-r" || arg == b"--recursive" {
            recursive = true;
            continue;
        }

        let (kind, arg) = if let Some(arg) = arg.strip_prefix(b"-u") {
            ('u', arg)
        } else if let Some(arg) = arg.strip_prefix(b"-g") {
            ('g', arg)
        } else if let Some(arg) = arg.strip_prefix(b"-b") {
            ('b', arg)
        } else if arg == b"--" {
            break;
        } else if arg.starts_with(b"-") {
            usage(std::io::stderr(), 1);
        } else {
            source = Some(arg_os);
            break;
        };

        let mapping_owned;
        let mapping = if arg.is_empty() {
            let Some(arg) = args.next() else {
                usage(std::io::stderr(), 1)
            };
            mapping_owned = arg;
            mapping_owned.as_bytes()
        } else {
            arg
        };

        let mapping = IdMapping::parse_common(std::str::from_utf8(mapping)?)?;

        if kind == 'u' || kind == 'b' {
            uid_mappings.push(mapping);
        }
        if kind == 'g' || kind == 'b' {
            gid_mappings.push(mapping);
        }
    }

    let source = match source.or_else(|| args.next()) {
        Some(p) => p,
        None => usage(std::io::stderr(), 1),
    };

    let dest = match args.next() {
        Some(p) => p,
        None => usage(std::io::stderr(), 1),
    };

    let userns = Userns::builder().context("failed to prepare user namespace")?;
    userns
        .map_gids(&gid_mappings)
        .context("failed to map group ids")?;
    userns
        .map_uids(&uid_mappings)
        .context("failed to map user ids")?;
    let userns = userns
        .into_fd()
        .context("failed to finish creating user namespace")?;

    let mut otflags = OpenTree::CLOEXEC | OpenTree::CLONE;
    if recursive {
        otflags |= OpenTree::RECURSIVE
    };
    let mount = Mount::open_tree(&source, otflags, 0).context("open_tree failed on mount point")?;

    mount
        .setattr(
            &MountSetAttr::new().idmap(&userns),
            libc::AT_RECURSIVE | libc::AT_NO_AUTOMOUNT,
        )
        .context("failed to apply idmapping to mount tree")?;

    mount
        .move_mount(&dest, MoveMount::empty())
        .context("failed to move mount into place")?;

    Ok(())
}