//! getty [-1] DEVICE -- run a shell on another terminal, e.g. /dev/hvc0. //! //! Real Unix systems run one "getty" per terminal line: it opens the line //! and starts a login shell on it. Ours skips the login. Since stage 15 it //! does it the classic Unix way, with fork + exec: //! //! 1. fork: the child will become the shell. //! 2. setsid (in the child): a new *session* with no terminal yet. //! 3. open DEVICE: a session leader without a terminal that opens one //! gets it as its *controlling terminal*. That's what lets ^C on //! that terminal send SIGINT to the right programs. //! 4. dup2 it onto fds 0, 1 and 2 (stdin, stdout, stderr). //! 5. execve the shell: it keeps the fds and the session. //! //! getty waits for the shell; when it exits (`exit`) a new one starts, //! unless `-1` was given. //! //! Try it: `getty /dev/hvc0 &` here, then on the host //! `socat - UNIX-CONNECT:target/hvc0.sock` and type commands. #![no_std] #![no_main] extern crate ulib; ulib::entry!(main); use ulib::*; /// The shell to start, first one that exists: rbsh on the root partition /// (boot contract v2), else the FAT32 disk's /bin/sh (contract v1, where /// that is rbsh). const SHELLS: [(&str, &str); 2] = [("/bin/rbsh", "rbsh"), ("/mnt/disk0/bin/sh", "sh")]; /// The child's side (steps 2-5). Only returns on failure. fn become_shell(dev: &str) -> i64 { setsid(); let fd = open(dev, O_RDWR); // becomes our controlling terminal if fd < 0 { eprintln!("getty: {}: {}", dev, strerror(fd)); return 126; } for target in 0..3 { dup2(fd, target); } if fd > 2 { close(fd); } let greeting = "\nRustbelt getty: shell on this terminal (type `exit` to end it)\n"; write(1, greeting.as_bytes()); let mut st = Stat::default(); let (path, argv0) = SHELLS.iter().copied().find(|(p, _)| stat(p, &mut st) == 0).unwrap_or(SHELLS[1]); let err = execve(path, &[argv0], &environ()); eprintln!("getty: {}: {}", path, strerror(err)); 127 } fn main() -> i64 { let (once, dev) = match (arg(1), arg(2)) { (Some("-1"), Some(dev)) => (true, dev), (Some(dev), None) if dev != "-1" => (false, dev), _ => { eprintln!("usage: getty [-1] DEVICE (e.g. getty /dev/hvc0 &)"); return 2; } }; loop { let pid = fork(); if pid == 0 { exit(become_shell(dev)); } if pid < 0 { eprintln!("getty: fork: {}", strerror(pid)); return 1; } let status = wait(pid); if once || status == 126 || status == 127 { return status; // done, or the terminal/shell can't be opened } sleep_ms(500); // don't spin if the shell keeps dying at once } }