use std::{ ffi::{OsStr, OsString}, fs::File, io::{self, BufRead, Result}, os::unix::ffi::*, path::Path, process, }; use nix::unistd::{self, Gid, Uid}; // use crate::prepared_command::PreparedCommand; use crate::util::Checkable; type ID = u32; #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] struct SubIDRange { start: ID, count: ID, } fn parse_uid(s: &OsStr) -> Option { s.to_str().and_then(|s| s.parse().ok()) } fn parse_id_range(line: Vec, uid: &OsStr, username: Option<&OsStr>) -> Option { let parts: Vec<_> = line.split(|c| *c == b':').map(OsStr::from_bytes).collect(); if parts.len() != 3 { return None; } if parts[0] != uid && Some(parts[0]) != username { return None; } let start = parse_uid(parts[1])?; let count = parse_uid(parts[2])?; Some(SubIDRange { start, count }) } fn read_id_ranges(filename: &Path) -> Result> { let uid = users::get_effective_uid(); let username = users::get_user_by_uid(uid).map(|user| user.name().to_os_string()); let uidstr = OsString::from(uid.to_string()); let usernamestr = username.as_ref().map(OsString::as_os_str); let file = File::open(filename)?; let lines = io::BufReader::new(file).split(b'\n'); lines .map(|line| Ok(parse_id_range(line?, &uidstr, usernamestr))) .filter_map(Result::transpose) .collect() } #[derive(Debug)] struct SubIDMap { lower: ID, upper: ID, count: ID, } pub const BUILD_UID: Uid = Uid::from_raw(800); pub const BUILD_GID: Gid = Gid::from_raw(800); fn generate_idmap(id: ID, mapped_id: ID, mut ranges: Vec) -> Vec { let mut map = Vec::new(); map.push(SubIDMap { lower: mapped_id, upper: id, count: 1, }); let mut add_map = |lower, upper, count| { if count > 0 { map.push(SubIDMap { lower, upper, count, }); } count }; let mut lower = 0; ranges.sort(); for range in &ranges { if mapped_id >= lower && mapped_id < lower + range.count { let offset = mapped_id - lower; lower += add_map(lower, range.start, offset) + 1; lower += add_map(lower, range.start + offset, range.count - offset); } else { lower += add_map(lower, range.start, range.count); } } map } fn get_uid_map() -> Result> { let uid = users::get_effective_uid(); let uid_ranges = read_id_ranges(Path::new("/etc/subuid"))?; Ok(generate_idmap(uid, BUILD_UID.as_raw(), uid_ranges)) } fn get_gid_map() -> Result> { let gid = users::get_effective_gid(); let gid_ranges = read_id_ranges(Path::new("/etc/subgid"))?; Ok(generate_idmap(gid, BUILD_GID.as_raw(), gid_ranges)) } fn run_idmap_cmd(cmd: &str, pid: &str, map: &Vec) -> Result<()> { let mut builder = process::Command::new(cmd); builder.arg(pid); for uids in map { builder.arg(uids.lower.to_string()); builder.arg(uids.upper.to_string()); builder.arg(uids.count.to_string()); } builder.status().and_then(|status| status.check()) } pub fn idmap(pid: unistd::Pid) -> Result<()> { let pid_string = pid.to_string(); run_idmap_cmd("newuidmap", pid_string.as_str(), &get_uid_map()?)?; run_idmap_cmd("newgidmap", pid_string.as_str(), &get_gid_map()?)?; Ok(()) }