]>
Commit | Line | Data |
---|---|---|
2a1ef81f | 1 | use anyhow::{bail, Error}; |
3b259e7a SR |
2 | use std::ffi::CStr; |
3 | use std::fs; | |
2a1ef81f SR |
4 | use std::path::PathBuf; |
5 | use std::process::Command; | |
3b259e7a SR |
6 | |
7 | const URANDOM_MAJ: u64 = 1; | |
8 | const URANDOM_MIN: u64 = 9; | |
9 | ||
10 | /// Set up a somewhat normal linux userspace environment before starting the restore daemon, and | |
11 | /// provide error messages to the user if doing so fails. | |
12 | /// | |
13 | /// This is supposed to run as /init in an initramfs image. | |
14 | fn main() { | |
15 | println!("[init-shim] beginning user space setup"); | |
16 | ||
17 | // /dev is mounted automatically | |
18 | wrap_err("mount /sys", || do_mount("/sys", "sysfs")); | |
19 | wrap_err("mount /proc", || do_mount("/proc", "proc")); | |
20 | ||
21 | // make device nodes required by daemon | |
22 | wrap_err("mknod /dev/urandom", || { | |
23 | do_mknod("/dev/urandom", URANDOM_MAJ, URANDOM_MIN) | |
24 | }); | |
25 | ||
2a1ef81f SR |
26 | if let Err(err) = run_agetty() { |
27 | // not fatal | |
28 | println!("[init-shim] debug: agetty start failed: {}", err); | |
29 | } | |
30 | ||
3b259e7a SR |
31 | let uptime = read_uptime(); |
32 | println!("[init-shim] reached daemon start after {:.2}s", uptime); | |
33 | ||
34 | do_run("/proxmox-restore-daemon"); | |
35 | } | |
36 | ||
2a1ef81f SR |
37 | fn run_agetty() -> Result<(), Error> { |
38 | use nix::unistd::{fork, ForkResult}; | |
39 | ||
40 | if !PathBuf::from("/sbin/agetty").exists() { | |
41 | bail!("/sbin/agetty not found, probably not running debug mode and safe to ignore"); | |
42 | } | |
43 | ||
44 | if !PathBuf::from("/sys/class/tty/ttyS1/device/driver/serial8250").exists() { | |
45 | bail!("ttyS1 device does not exist or is not a 8250"); | |
46 | } | |
47 | ||
48 | let dev = fs::read_to_string("/sys/class/tty/ttyS1/dev")?; | |
49 | let (tty_maj, tty_min) = dev.trim().split_at(dev.find(':').unwrap_or(1)); | |
50 | do_mknod("/dev/ttyS1", tty_maj.parse()?, tty_min[1..].parse()?)?; | |
51 | ||
52 | match unsafe { fork() } { | |
53 | Ok(ForkResult::Parent { .. }) => {} | |
54 | Ok(ForkResult::Child) => loop { | |
55 | // continue to restart agetty if it exits, this runs in a forked process | |
56 | println!("[init-shim] Spawning new agetty"); | |
57 | let res = Command::new("/sbin/agetty") | |
58 | .args(&["-a", "root", "-l", "/bin/busybox", "-o", "sh", "115200", "ttyS1"]) | |
59 | .spawn() | |
60 | .unwrap() | |
61 | .wait() | |
62 | .unwrap(); | |
63 | println!("[init-shim] agetty exited: {}", res.code().unwrap_or(-1)); | |
64 | }, | |
65 | Err(err) => println!("fork failed: {}", err), | |
66 | } | |
67 | ||
68 | Ok(()) | |
69 | } | |
70 | ||
3b259e7a SR |
71 | fn do_mount(target: &str, fstype: &str) -> Result<(), Error> { |
72 | use nix::mount::{mount, MsFlags}; | |
2a1ef81f | 73 | fs::create_dir_all(target)?; |
3b259e7a SR |
74 | let none_type: Option<&CStr> = None; |
75 | mount( | |
76 | none_type, | |
77 | target, | |
78 | Some(fstype), | |
79 | MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC, | |
80 | none_type, | |
81 | )?; | |
82 | Ok(()) | |
83 | } | |
84 | ||
85 | fn do_mknod(path: &str, maj: u64, min: u64) -> Result<(), Error> { | |
86 | use nix::sys::stat; | |
87 | let dev = stat::makedev(maj, min); | |
88 | stat::mknod(path, stat::SFlag::S_IFCHR, stat::Mode::S_IRWXU, dev)?; | |
89 | Ok(()) | |
90 | } | |
91 | ||
92 | fn read_uptime() -> f32 { | |
93 | let uptime = wrap_err("read /proc/uptime", || { | |
94 | fs::read_to_string("/proc/uptime").map_err(|e| e.into()) | |
95 | }); | |
96 | // this can never fail on a sane kernel, so just unwrap | |
97 | uptime | |
98 | .split_ascii_whitespace() | |
99 | .next() | |
100 | .unwrap() | |
101 | .parse() | |
102 | .unwrap() | |
103 | } | |
104 | ||
105 | fn do_run(cmd: &str) -> ! { | |
106 | use std::io::ErrorKind; | |
3b259e7a SR |
107 | |
108 | let spawn_res = Command::new(cmd).env("RUST_BACKTRACE", "1").spawn(); | |
109 | ||
110 | match spawn_res { | |
111 | Ok(mut child) => { | |
112 | let res = wrap_err("wait failed", || child.wait().map_err(|e| e.into())); | |
113 | error(&format!( | |
114 | "child process {} (pid={} exitcode={}) exited unexpectedly, check log for more info", | |
115 | cmd, | |
116 | child.id(), | |
117 | res.code().unwrap_or(-1), | |
118 | )); | |
119 | } | |
120 | Err(err) if err.kind() == ErrorKind::NotFound => { | |
121 | error(&format!( | |
122 | concat!( | |
123 | "{} missing from image.\n", | |
124 | "This initramfs should only be run with proxmox-file-restore!" | |
125 | ), | |
126 | cmd | |
127 | )); | |
128 | } | |
129 | Err(err) => { | |
130 | error(&format!( | |
131 | "unexpected error during start of {}: {}", | |
132 | cmd, err | |
133 | )); | |
134 | } | |
135 | } | |
136 | } | |
137 | ||
138 | fn wrap_err<R, F: FnOnce() -> Result<R, Error>>(op: &str, f: F) -> R { | |
139 | match f() { | |
140 | Ok(r) => r, | |
141 | Err(e) => error(&format!("operation '{}' failed: {}", op, e)), | |
142 | } | |
143 | } | |
144 | ||
145 | fn error(msg: &str) -> ! { | |
146 | use nix::sys::reboot; | |
147 | ||
148 | println!("\n--------"); | |
149 | println!("ERROR: Init shim failed\n"); | |
150 | println!("{}", msg); | |
151 | println!("--------\n"); | |
152 | ||
153 | // in case a fatal error occurs we shut down the VM, there's no sense in continuing and this | |
154 | // will certainly alert whoever started us up in the first place | |
155 | let err = reboot::reboot(reboot::RebootMode::RB_POWER_OFF).unwrap_err(); | |
156 | println!("'reboot' syscall failed: {} - cannot continue", err); | |
157 | ||
158 | // in case 'reboot' fails just loop forever | |
159 | loop { | |
160 | std::thread::sleep(std::time::Duration::from_secs(600)); | |
161 | } | |
162 | } |