diff --git a/rust/src/init/cmdline.rs b/rust/src/init/cmdline.rs new file mode 100644 index 0000000..de1fd91 --- /dev/null +++ b/rust/src/init/cmdline.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; +use std::fs; +use std::path::Path; +use crate::sys::mount_procfs; +use crate::error::{Error,Result}; + +pub struct CmdLine { + vars: HashMap>, +} + +impl CmdLine { + pub fn load() -> Result { + let proc_path = Path::new("/proc/cmdline"); + if !proc_path.exists() { + mount_procfs()?; + } + + let cmdline = fs::read_to_string("/proc/cmdline") + .map_err(Error::KernelCmdLine)?; + Ok(Self::parse(cmdline)) + } + + fn parse(line: String) -> Self { + let mut vars = HashMap::new(); + + for v in line.split_whitespace() { + if let Some(eq) = v.find('=') { + let (key, val) = v.split_at(eq); + let val = val.trim_start_matches('='); + vars.insert(key.to_string(), Some(val.to_string())); + } else { + vars.insert(v.to_string(), None); + } + } + CmdLine{ vars } + } + + pub fn has_var(&self, name: &str) -> bool { + self.vars.contains_key(name) + } + + pub fn lookup(&self, name: &str) -> Option { + if let Some(val) = self.vars.get(name) { + val.as_ref().cloned() + } else { + None + } + } +} diff --git a/rust/src/init/error.rs b/rust/src/init/error.rs new file mode 100644 index 0000000..c8753f1 --- /dev/null +++ b/rust/src/init/error.rs @@ -0,0 +1,64 @@ +use std::{result, io, fmt}; + +pub enum Error { + Pid1, + KernelCmdLine(io::Error), + NoRootVar, + NoRootFsVar, + RootFsMount(String, io::Error), + MountProcFS(io::Error), + MountTmpFS(String, io::Error), + MountSysFS(io::Error), + MountDevTmpFS(io::Error), + MountDevPts(io::Error), + MountOverlay(io::Error), + MoveMount(String, String, io::Error), + Mount9P(String, String, io::Error), + Umount(String, io::Error), + MkDir(String, io::Error), + SetHostname(io::Error), + SetSid(io::Error), + SetControllingTty(io::Error), + PivotRoot(String, String, io::Error), + WaitPid(io::Error), + WriteEtcHosts(io::Error), + RunShell(io::Error), + CStringConv, + ChmodFailed(io::Error), + ChownFailed(io::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Error::*; + match self { + Pid1 => write!(f, "not running as pid 1"), + KernelCmdLine(err) => write!(f, "failed to load kernel command line from /proc/cmdline: {}", err), + NoRootVar => write!(f, "Cannot mount rootfs because no phinit.root is set"), + NoRootFsVar => write!(f, "Cannot mount rootfs because no phinit.rootfs is set"), + RootFsMount(rootfs, err) => write!(f, "Failed to mount rootfs {}: {}", rootfs, err), + MountProcFS(err) => write!(f, "unable to mount procfs: {}", err), + MountTmpFS(target,err) => write!(f, "failed to mount tmpfs at {}: {}", target, err), + MountSysFS(err) => write!(f, "failed to mount sysfs at /sys: {}", err), + MountDevTmpFS(err) => write!(f, "failed to mount devtmpfs at /dev: {}", err), + MountDevPts(err) => write!(f, "failed to mount /dev/pts: {}", err), + MountOverlay(err) => write!(f, "failed to mount overlayfs: {}", err), + MoveMount(from, to, err) => write!(f, "failed to move mount from {} to {}: {}", from, to, err), + Mount9P(tag,target, err) => write!(f, "failed to mount 9p volume {} at {}: {}", tag, target, err), + Umount(target, err) => write!(f, "failed to unmount {}: {}", target, err), + MkDir(target, err) => write!(f, "failed to mkdir {}: {}", target, err), + SetHostname(err) => write!(f, "sethostname() failed: {}", err), + SetSid(err) => write!(f, "call to setsid() failed: {}", err), + SetControllingTty(err) => write!(f, "failed to set controlling terminal: {}", err), + PivotRoot(newroot, putroot, err) => write!(f, "failed to pivot_root({}, {}): {}", newroot, putroot, err), + WaitPid(err) => write!(f, "failed to waitpid(): {}", err), + WriteEtcHosts(err) => write!(f, "failed to write /etc/hosts: {}", err), + RunShell(err) => write!(f, "error launching shell: {}", err), + CStringConv => write!(f, "failed to create CString"), + ChmodFailed(err) => write!(f, "failed to chmod: {}", err), + ChownFailed(err) => write!(f, "failed to chown: {}", err), + } + } +} + +pub type Result = result::Result; \ No newline at end of file diff --git a/rust/src/init/main.rs b/rust/src/init/main.rs index 4e37548..71868a4 100644 --- a/rust/src/init/main.rs +++ b/rust/src/init/main.rs @@ -1,154 +1,37 @@ extern crate libc; -use std::{io, fs}; -use std::process::{self, Child,Command,Stdio}; -use std::os::unix::process::CommandExt; - +mod error; +mod cmdline; +mod setup; mod sys; -use sys::*; +use crate::setup::Setup; -fn setup_overlay() -> io::Result<()> { +pub use error::{Error,Result}; - // Just using /tmp temporarily as a path expected to exist - // pivot_root() call will move this tmpfs mount - mount_tmpfs("/tmp")?; - mkdir("/tmp/ro")?; - pivot_root("/tmp", "/tmp/ro")?; - - // Current layout: - // - // /ro real root fs is now mounted here - // / tmpfs has been swapped as / by pivot_root() - // - mkdir("/rw")?; - mount_tmpfs("/rw")?; - mkdir("/rw/upper")?; - mkdir("/rw/work")?; - - // Add this to current layout: - // - // /rw 2nd tmpfs mounted here - // /rw/upper empty dir on 2nd tmpfs - // /rw/work empty dir on 2nd tmpfs - - mkdir("/overlay")?; - mount_overlay("/overlay", "lowerdir=/ro,upperdir=/rw/upper,workdir=/rw/work")?; - mkdir("/overlay/ro")?; - mkdir("/overlay/rw")?; - mkdir("/overlay/old-root")?; - - // And this: - // - // /overlay overlay fs mounted here - // /overlay/ro empty dir - // /overlay/rw empty dir - // /overlay/old-root empty dir - - // About to pivot_root() to make /overlay new root fs. - // Move /ro and /rw to expected post-pivot location. - move_mount("/ro", "/overlay/ro")?; - move_mount("/rw", "/overlay/rw")?; - - // swap in overlay as rootfs - pivot_root("/overlay", "/overlay/old-root")?; - - // finally throw away 1st tmpfs - umount("/old-root")?; - rmdir("/old-root")?; +fn run_init() -> Result<()> { + Setup::check_pid1()?; + let setup = Setup::create("airwolf")?; + setup.set_splash(SPLASH); + setup.setup_rootfs()?; + setup.mount_home_if_exists()?; + setup.launch_shell()?; Ok(()) } -fn setup_mounts() -> io::Result<()> { - mount_sysfs("/sys")?; - mount_procfs("/proc")?; - mount_devtmpfs("/dev")?; - mkdir("/dev/pts")?; - mount_devpts("/dev/pts")?; - match mount_9p("home", "/home/user") { - Ok(()) => { println!("Home mounted at /home/user") }, - Err(e) => { println!("Mount of home failed: {}", e) } - } - Ok(()) -} - -fn setup() -> io::Result<()> { - setup_overlay()?; - setup_mounts()?; - sethostname("airwolf")?; - setsid()?; - set_controlling_tty(0, true)?; - Ok(()) -} - -unsafe fn run_shell() -> io::Result{ - Command::new("/bin/bash") - .env_clear() - .env("TERM", "xterm-256color") - .env("HOME", "/home/user") - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .pre_exec(|| {println!("{}", SPLASH);Ok(())}) - .spawn() -} - -fn handle_waitpid_err(err: io::Error) -> ! { - if let Some(errno) = err.raw_os_error() { - if errno == libc::ECHILD { - if let Err(err) = reboot(libc::RB_AUTOBOOT) { - println!("reboot() failed: {:?}", err); - process::exit(-1); - } - } - } - println!("error on waitpid: {:?}", err); - process::exit(-1); -} - -fn wait_for_child() -> i32 { - let r = waitpid(-1, 0); - if let Ok(pid) = r { - return pid; - } - handle_waitpid_err(r.err().unwrap()); -} -fn is_run_systemd() -> bool { - let cmdline = match fs::read_to_string("/proc/cmdline") { - Ok(cmdline) => cmdline, - _ => return false, - }; - cmdline.contains("phinit.run_systemd") -} -fn run_systemd() { - let err = Command::new("/usr/lib/systemd/systemd") - .exec(); - println!("failed to launch systemd: {}", err); -} - fn main() { - if let Err(err) = setup() { - println!("Error on setup(): {:?}", err); return; - } - if is_run_systemd() { - run_systemd() - } - let _child = match unsafe { run_shell() } { - Ok(child) => child, - Err(err) => { println!("Error launching shell: {:?}", err); return; } - }; - loop { - let _ = wait_for_child(); + if let Err(err) = run_init() { + println!("ph-init error: {}", err); } } const SPLASH: &str = r#" - ------------------------------||------------------------------- - [##] + ──────────────────────────────||─────────────────────────────── + [▭▭] /~~~~~~\ - |~~\ /~~| - ==][===|___||___|===][== + │~~╲ ╱~~│ + ≡≡][≡≡≡│___||___│≡≡≡][≡≡ [::] ( () ) [::] - ~/~~~~\~ - O' `o + ~╱~~~~╲~ + ○' `o "#; \ No newline at end of file diff --git a/rust/src/init/setup.rs b/rust/src/init/setup.rs new file mode 100644 index 0000000..d5d2047 --- /dev/null +++ b/rust/src/init/setup.rs @@ -0,0 +1,232 @@ +use crate::cmdline::CmdLine; +use crate::error::{Result,Error}; +use std::{io, fs, process, env}; +use crate::sys::{mount_procfs, mount_tmpfs, mkdir, mount_devpts, create_directories, mount_overlay, move_mount, pivot_root, umount, mount_sysfs, mount_devtmpfs, mount, mount_9p, waitpid, reboot, sethostname, setsid, set_controlling_tty, getpid, chmod, chown}; +use std::path::Path; +use std::process::{Child, Command, Stdio}; +use std::cell::RefCell; +use std::os::unix::process::CommandExt; + +pub struct Setup { + hostname: String, + cmdline: CmdLine, + rootfs: RootFS, + splash: RefCell>, +} + +impl Setup { + pub fn create(hostname: &str) -> Result { + let hostname = hostname.to_string(); + let cmdline = CmdLine::load()?; + let rootfs = RootFS::load(&cmdline)?; + let splash = RefCell::new(None); + Ok(Setup{ hostname, cmdline, rootfs, splash }) + } + + pub fn check_pid1() -> Result<()> { + if getpid() == 1 { + Ok(()) + } else { + Err(Error::Pid1) + } + } + + pub fn setup_rootfs(&self) -> Result<()> { + mount_devtmpfs()?; + mount_tmpfs("/tmp")?; + mkdir("/tmp/sysroot")?; + if self.rootfs.read_only() { + self.setup_readonly_root()?; + } else { + self.setup_writeable_root()?; + } + umount("/opt/ph/tmp")?; + umount("/opt/ph/proc")?; + umount("/opt/ph/dev")?; + + mount_sysfs()?; + mount_procfs()?; + mount_devtmpfs()?; + mount_devpts()?; + mount_tmpfs("/run")?; + mkdir("/run/user")?; + mkdir("/run/user/1000")?; + chown("/run/user/1000", 1000,1000)?; + if Path::new("/dev/wl0").exists() { + chmod("/dev/wl0", 0o666)?; + } + Ok(()) + } + + fn setup_readonly_root(&self) -> Result<()> { + create_directories(&[ + "/tmp/ro", + "/tmp/rw", + "/tmp/rw/upper", + "/tmp/rw/work", + ])?; + mount_tmpfs("/tmp/rw")?; + create_directories(&["/tmp/rw/upper", "/tmp/rw/work"])?; + self.rootfs.mount("/tmp/ro")?; + mount_overlay("/tmp/sysroot", + "lowerdir=/tmp/ro,upperdir=/tmp/rw/upper,workdir=/tmp/rw/work")?; + create_directories(&[ + "/tmp/sysroot/ro", + "/tmp/sysroot/rw" + ])?; + move_mount("/tmp/ro", "/tmp/sysroot/ro")?; + move_mount("/tmp/rw", "/tmp/sysroot/rw")?; + + let toolsdir = Path::new("/tmp/sysroot/opt/ph"); + if !toolsdir.exists() { + fs::create_dir_all(toolsdir) + .map_err(|e| Error::MkDir(String::from("/tmp/sysroot/opt/ph"), e))?; + } + pivot_root("/tmp/sysroot", "/tmp/sysroot/opt/ph")?; + fs::write("/etc/hosts", format!("127.0.0.1 {} localhost", self.hostname)) + .map_err(Error::WriteEtcHosts)?; + Ok(()) + } + + fn setup_writeable_root(&self) -> Result<()> { + self.rootfs.mount("/tmp/sysroot")?; + + let toolsdir = Path::new("/tmp/sysroot/opt/ph"); + if !toolsdir.exists() { + fs::create_dir_all(toolsdir) + .map_err(|e| Error::MkDir(String::from("/tmp/sysroot/opt/ph"), e))?; + } + pivot_root("/tmp/sysroot", "/tmp/sysroot/opt/ph")?; + Ok(()) + } + + pub fn has_9p_home(&self) -> bool { + // XXX + // /sys/bus/virtio/drivers/9pnet_virtio/virtio*/mount_tag + true + } + + pub fn mount_home_if_exists(&self) -> Result<()> { + if self.has_9p_home() { + let homedir = Path::new("/home/user"); + if !homedir.exists() { + mkdir(homedir)?; + } + mount_9p("home", "/home/user")?; + } + Ok(()) + } + + fn handle_waitpid_err(err: io::Error) -> ! { + if let Some(errno) = err.raw_os_error() { + if errno == libc::ECHILD { + if let Err(err) = reboot(libc::RB_AUTOBOOT) { + println!("reboot() failed: {:?}", err); + process::exit(-1); + } + } + } + println!("error on waitpid: {:?}", err); + process::exit(-1); + } + + fn wait_for_child(&self) -> i32 { + match waitpid(-1, 0) { + Ok(pid) => pid, + Err(err) => Self::handle_waitpid_err(err) + } + } + + pub fn set_splash(&self, splash: &str) { + self.splash.borrow_mut().replace(splash.to_string()); + } + + fn run_shell(&self, as_root: bool) -> io::Result { + + let home = if as_root { + "/" + } else { + "/home/user" + }; + env::set_current_dir(home)?; + + unsafe { + let mut cmd = Command::new("/bin/bash"); + cmd.env_clear() + .env("XDG_RUNTIME_DIR", "/run/user/1000") + .env("HOME", home) + .env("SHELL", "/bin/bash") + + .env("TERM", "xterm-256color") + .arg("--login") + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + if let Some(s) = self.splash.borrow().as_ref() { + let splash = s.to_string(); + cmd.pre_exec(move || { + println!("{}", splash); + Ok(()) + }); + } + + if let Some(realm) = self.cmdline.lookup("phinit.realm") { + cmd.env("REALM_NAME", realm); + } + + if !as_root { + cmd.uid(1000); + cmd.gid(1000); + } + cmd.spawn() + } + } + + pub fn launch_shell(&self) -> Result<()> { + let as_root = self.cmdline.has_var("phinit.rootshell"); + sethostname("airwolf")?; + setsid()?; + set_controlling_tty(0, true)?; + + let _child = self.run_shell(as_root) + .map_err(Error::RunShell)?; + loop { + let _ = self.wait_for_child(); + } + } +} + +struct RootFS { + root: String, + fstype: String, + rootflags: Option, + readonly: bool, +} + +impl RootFS { + fn load(cmdline: &CmdLine) -> Result { + let root = cmdline.lookup("phinit.root") + .ok_or(Error::NoRootVar)?; + let fstype = cmdline.lookup("phinit.rootfstype") + .ok_or(Error::NoRootFsVar)?; + let rootflags = cmdline.lookup("phinit.rootflags"); + let readonly = !cmdline.has_var("phinit.root_rw"); + + Ok(RootFS { + root, fstype, rootflags, readonly + }) + } + + fn read_only(&self) -> bool { + self.readonly + } + + fn mount(&self, target: &str) -> Result<()> { + let options = self.rootflags.as_ref().map(|s| s.as_str()); + let flags = libc::MS_RDONLY; + + mount(&self.root, target, &self.fstype, flags, options) + .map_err(|e| Error::RootFsMount(self.root.clone(), e)) + } +} diff --git a/rust/src/init/sys.rs b/rust/src/init/sys.rs index 43ad1d2..dff2a89 100644 --- a/rust/src/init/sys.rs +++ b/rust/src/init/sys.rs @@ -1,101 +1,110 @@ use std::io; use std::ptr; -use std::ffi::{CString,OsStr}; +use std::ffi::{CString, OsStr}; use std::os::unix::ffi::OsStrExt; +use crate::error::{Result,Error}; use libc; +use std::path::Path; -pub fn mount_tmpfs(target: &str) -> io::Result<()> { +pub fn mount_tmpfs(target: &str) -> Result<()> { mount("tmpfs", target, "tmpfs", 0, Some("mode=755")) + .map_err(|e| Error::MountTmpFS(target.to_string(), e)) } -pub fn mount_procfs(target: &str) -> io::Result<()> { - mount("proc", target, "proc", 0, None) +pub fn mount_procfs() -> Result<()> { + mount("proc", "/proc", "proc", 0, None) + .map_err(Error::MountProcFS) } -pub fn mount_sysfs(target: &str) -> io::Result<()> { - mount("sysfs", target, "sysfs", 0, None) +pub fn mount_sysfs() -> Result<()> { + mount("sysfs", "/sys", "sysfs", 0, None) + .map_err(Error::MountSysFS) } -pub fn mount_devtmpfs(target: &str) -> io::Result<()> { - mount("devtmpfs", target, "devtmpfs", 0, None) +pub fn mount_devtmpfs() -> Result<()> { + mount("devtmpfs", "/dev", "devtmpfs", 0, None) + .map_err(Error::MountDevTmpFS) } -pub fn mount_devpts(target: &str) -> io::Result<()> { +pub fn mount_devpts() -> Result<()> { + let target = "/dev/pts"; + if !Path::new(target).exists() { + mkdir(target)?; + } mount("devpts", target, "devpts", 0, None) + .map_err(Error::MountDevPts) } -pub fn mount_overlay(target: &str, args: &str) -> io::Result<()> { +pub fn mount_overlay(target: &str, args: &str) -> Result<()> { mount("overlay", target, "overlay", 0, Some(args)) + .map_err(Error::MountOverlay) } -pub fn move_mount(source: &str, target: &str) -> io::Result<()> { +pub fn move_mount(source: &str, target: &str) -> Result<()> { mount(source, target, "", libc::MS_MOVE, None) + .map_err(|e| Error::MoveMount(source.to_string(), target.to_string(), e)) } -pub fn mount_9p(name: &str, target: &str) -> io::Result<()> { +pub fn mount_9p(name: &str, target: &str) -> Result<()> { const MS_LAZYTIME: libc::c_ulong = (1 << 25); - mount(name, target, "9p", libc::MS_NOATIME|MS_LAZYTIME, Some("trans=virtio,version=9p2000.L,cache=loose")) + mount(name, target, "9p", libc::MS_NOATIME|MS_LAZYTIME, Some("trans=virtio,cache=loose")) + .map_err(|e| Error::Mount9P(name.to_string(), target.to_string(), e)) } - fn cstr(s: &str) -> CString { CString::new(s).unwrap() } +pub fn create_directories>(directories: &[P]) -> Result<()> { + for dir in directories { + mkdir(dir)?; + } + Ok(()) +} +pub fn mkdir>(path: P) -> Result<()> { + let path = path.as_ref(); + let path_cstr = CString::new(path.as_os_str().as_bytes()).map_err(|_| Error::CStringConv)?; -pub fn mkdir(path: &str) -> io::Result<()> { - - let path = cstr(path); unsafe { - if libc::mkdir(path.as_ptr(), 0o755) == -1 { - return Err(io::Error::last_os_error()); + if libc::mkdir(path_cstr.as_ptr(), 0o755) == -1 { + return Err(Error::MkDir(path.display().to_string(), io::Error::last_os_error())) } } Ok(()) } -pub fn rmdir(path: &str) -> io::Result<()> { - let path = cstr(path); - unsafe { - if libc::rmdir(path.as_ptr()) == -1 { - return Err(io::Error::last_os_error()); - } - } - Ok(()) - -} - -pub fn sethostname>(name: S) -> io::Result<()> { +pub fn sethostname>(name: S) -> Result<()> { let ptr = name.as_ref().as_bytes().as_ptr() as *const libc::c_char; let len = name.as_ref().len() as libc::size_t; unsafe { if libc::sethostname(ptr, len) < 0 { - return Err(io::Error::last_os_error()); + let last = io::Error::last_os_error(); + return Err(Error::SetHostname(last)) } } Ok(()) } -pub fn setsid() -> io::Result { +pub fn setsid() -> Result { unsafe { let res = libc::setsid(); if res == -1 { - return Err(io::Error::last_os_error()); + let last = io::Error::last_os_error(); + return Err(Error::SetSid(last)) } Ok(res as u32) } } -fn mount(source: &str, target: &str, fstype: &str, flags: u64, data: Option<&str>) +pub fn mount(source: &str, target: &str, fstype: &str, flags: u64, data: Option<&str>) -> io::Result<()> where { let source = cstr(source); let target = cstr(target); let fstype = cstr(fstype); - let data = data.map(|s| cstr(s) ); let data_ptr = match data { Some(ref s) => s.as_ptr(), @@ -114,32 +123,35 @@ fn mount(source: &str, target: &str, fstype: &str, flags: u64, data: Option<&str Ok(()) } -pub fn pivot_root(new_root: &str, put_old: &str) -> io::Result<()> { - let new_root = cstr(new_root); - let put_old = cstr(put_old); +pub fn pivot_root(new_root: &str, put_old: &str) -> Result<()> { + let _new_root = cstr(new_root); + let _put_old = cstr(put_old); unsafe { - if libc::syscall(libc::SYS_pivot_root, new_root.as_ptr(), put_old.as_ptr()) == -1 { - return Err(io::Error::last_os_error()); + if libc::syscall(libc::SYS_pivot_root, _new_root.as_ptr(), _put_old.as_ptr()) == -1 { + let last = io::Error::last_os_error(); + return Err(Error::PivotRoot(new_root.to_string(), put_old.to_string(), last)) } } Ok(()) } -pub fn umount(path: &str) -> io::Result<()> { - let path = cstr(path); +pub fn umount(path: &str) -> Result<()> { + let _path = cstr(path); unsafe { - if libc::umount(path.as_ptr()) == -1 { - return Err(io::Error::last_os_error()); + if libc::umount(_path.as_ptr()) == -1 { + let last = io::Error::last_os_error(); + return Err(Error::Umount(path.to_string(), last)) } } Ok(()) } -pub fn set_controlling_tty(fd: libc::c_int, force: bool) -> io::Result<()> { +pub fn set_controlling_tty(fd: libc::c_int, force: bool) -> Result<()> { let flag: libc::c_int = if force { 1 } else { 0 }; unsafe { if libc::ioctl(fd, libc::TIOCSCTTY, flag) == -1 { - return Err(io::Error::last_os_error()); + let last = io::Error::last_os_error(); + return Err(Error::SetControllingTty(last)) } Ok(()) } @@ -149,12 +161,38 @@ pub fn waitpid(pid: libc::pid_t, options: libc::c_int) -> io::Result { let mut status = 0 as libc::c_int; unsafe { if libc::waitpid(pid, &mut status, options) == -1 { - return Err(io::Error::last_os_error()); + return Err(io::Error::last_os_error()) } } Ok(status) } +pub fn getpid() -> i32 { + unsafe { libc::getpid() } +} + +pub fn chmod(path: &str, mode: u32) -> Result<()> { + let path = cstr(path); + unsafe { + if libc::chmod(path.as_ptr(), mode) == -1 { + let last = io::Error::last_os_error(); + return Err(Error::ChmodFailed(last)); + } + + } + Ok(()) +} + +pub fn chown(path: &str, uid: u32, gid: u32) -> Result<()> { + let path = cstr(path); + unsafe { + if libc::chown(path.as_ptr(), uid, gid) == -1 { + let last = io::Error::last_os_error(); + return Err(Error::ChmodFailed(last)); + } + } + Ok(()) +} pub fn reboot(cmd: libc::c_int) -> io::Result<()> { unsafe { if libc::reboot(cmd) == -1 {