Skip to main content

proot/exec/
shebang.rs

1//! `#!` line parsing with the kernel's rules.
2
3/// The interpreter named by a shebang line, with its optional argument.
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct Shebang {
6    /// Interpreter path as written (a guest path).
7    pub interp: Vec<u8>,
8    /// Everything after the interpreter, trimmed, passed as one argument.
9    pub arg: Option<Vec<u8>>,
10}
11
12/// Parses the payload of a `#!` line (without the `#!`, without newline).
13///
14/// Leading blanks are skipped, the interpreter runs until the next blank,
15/// and the remainder, trimmed at both ends, becomes a single argument,
16/// exactly like `fs/binfmt_script.c`.
17///
18/// # Arguments
19/// * `line` - Bytes after `#!`, at most 255.
20///
21/// # Returns
22/// The parsed [`Shebang`], or `None` when no interpreter is present.
23pub fn parse(line: &[u8]) -> Option<Shebang> {
24    let is_blank = |b: &u8| *b == b' ' || *b == b'\t';
25    let line = &line[..line.len().min(255)];
26    // The kernel also stops the line at the first NUL or newline.
27    let end = line
28        .iter()
29        .position(|&b| b == 0 || b == b'\n')
30        .unwrap_or(line.len());
31    let line = &line[..end];
32    let start = line.iter().position(|b| !is_blank(b))?;
33    let rest = &line[start..];
34    let ilen = rest.iter().position(is_blank).unwrap_or(rest.len());
35    let interp = rest[..ilen].to_vec();
36    if interp.is_empty() {
37        return None;
38    }
39    let tail = &rest[ilen..];
40    let tstart = tail.iter().position(|b| !is_blank(b)).unwrap_or(tail.len());
41    let tend = tail
42        .iter()
43        .rposition(|b| !is_blank(b))
44        .map(|p| p + 1)
45        .unwrap_or(tstart);
46    let arg = if tend > tstart {
47        Some(tail[tstart..tend].to_vec())
48    } else {
49        None
50    };
51    Some(Shebang { interp, arg })
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn parses_interpreter_and_single_argument() {
60        assert_eq!(
61            parse(b"/bin/sh"),
62            Some(Shebang {
63                interp: b"/bin/sh".to_vec(),
64                arg: None
65            })
66        );
67        assert_eq!(
68            parse(b"  /usr/bin/env   python3 -u  "),
69            Some(Shebang {
70                interp: b"/usr/bin/env".to_vec(),
71                arg: Some(b"python3 -u".to_vec())
72            })
73        );
74        assert_eq!(
75            parse(b"/bin/bash\t-e"),
76            Some(Shebang {
77                interp: b"/bin/bash".to_vec(),
78                arg: Some(b"-e".to_vec())
79            })
80        );
81        assert_eq!(parse(b"   "), None);
82        assert_eq!(parse(b""), None);
83    }
84}