ph-init mostly rewritten

Is more complex now and reads various kernel command line options to
configure behavior.

  * optionally set up tmpfs overlay over read-only rootfs
  * find and mount rootfs
  * mount /proc /sys /tmp /dev
  * mount 9p home directory
  * launch shell as root or as user
This commit is contained in:
Bruce Leidl 2019-09-20 16:10:30 -04:00
parent f71e3adb79
commit 4fc4c05155
5 changed files with 451 additions and 185 deletions

49
rust/src/init/cmdline.rs Normal file
View File

@ -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<String, Option<String>>,
}
impl CmdLine {
pub fn load() -> Result<Self> {
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<String> {
if let Some(val) = self.vars.get(name) {
val.as_ref().cloned()
} else {
None
}
}
}

64
rust/src/init/error.rs Normal file
View File

@ -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<T> = result::Result<T, Error>;

View File

@ -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<Child>{
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
"#;

232
rust/src/init/setup.rs Normal file
View File

@ -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<Option<String>>,
}
impl Setup {
pub fn create(hostname: &str) -> Result<Self> {
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<Child> {
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<String>,
readonly: bool,
}
impl RootFS {
fn load(cmdline: &CmdLine) -> Result<Self> {
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))
}
}

View File

@ -2,100 +2,109 @@ use std::io;
use std::ptr;
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<P: AsRef<Path>>(directories: &[P]) -> Result<()> {
for dir in directories {
mkdir(dir)?;
}
Ok(())
}
pub fn mkdir<P: AsRef<Path>>(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<S: AsRef<OsStr>>(name: S) -> io::Result<()> {
pub fn sethostname<S: AsRef<OsStr>>(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<u32> {
pub fn setsid() -> Result<u32> {
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<i32> {
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 {