RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / rust / sys / src / bin / getty.rs
2803 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//! 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
    }
}