From 2bba6b5341fd341de2b282e43f9b62f281ccf40e Mon Sep 17 00:00:00 2001
From: Rutger Broekhoff
Date: Wed, 24 Jan 2024 16:17:20 +0100
Subject: An attempt at implementing a shell

---
 rs/shell/Cargo.toml  |   6 +++
 rs/shell/src/main.rs | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 144 insertions(+)
 create mode 100644 rs/shell/Cargo.toml
 create mode 100644 rs/shell/src/main.rs

(limited to 'rs/shell')

diff --git a/rs/shell/Cargo.toml b/rs/shell/Cargo.toml
new file mode 100644
index 0000000..0dcb6d6
--- /dev/null
+++ b/rs/shell/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "shell"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/rs/shell/src/main.rs b/rs/shell/src/main.rs
new file mode 100644
index 0000000..cbe01e7
--- /dev/null
+++ b/rs/shell/src/main.rs
@@ -0,0 +1,138 @@
+use std::{process::ExitCode, os::unix::process::CommandExt};
+
+fn parse_sq(s: &str) -> Option<(String, &str)> {
+    #[derive(PartialEq, Eq)]
+    enum SqState {
+        Quoted,
+        Unquoted { may_escape: bool },
+        UnquotedEscaped,
+    }
+    
+    let mut result = String::new();
+    let mut state = SqState::Unquoted { may_escape: false };
+    let mut remaining = "";
+    for (i, c) in s.char_indices() {
+        match state {
+            SqState::Unquoted { may_escape: false} => {
+                if c != '\'' {
+                    return None;
+                }
+                state = SqState::Quoted
+            }
+            SqState::Quoted => {
+                if c == '\'' {
+                    state = SqState::Unquoted { may_escape: true };
+                    continue;
+                }
+                result.push(c);
+            }
+            SqState::Unquoted { may_escape: true }=> {
+                if is_posix_space(c) {
+                    remaining = &s[i..];
+                    break;
+                }
+                if c != '\\' {
+                    return None;
+                }
+                state = SqState::UnquotedEscaped;
+            }
+            SqState::UnquotedEscaped => {
+                if c != '\\' && c != '!' {
+                    return None;
+                }
+                result.push(c);
+                state = SqState::Unquoted { may_escape: false };
+            }
+        }
+    }
+
+    if state != (SqState::Unquoted { may_escape: true }) {
+        return None
+    }
+    Some((result, remaining))
+}
+
+fn parse_cmd(mut cmd: &str) -> Option<Vec<String>> {
+    let mut args = Vec::<String>::new();
+
+    cmd = cmd.trim_matches(is_posix_space);
+    while cmd != "" {
+        if cmd.starts_with('\'') {
+            let (arg, remaining) = parse_sq(cmd)?;
+            args.push(arg);
+            cmd = remaining.trim_start_matches(is_posix_space);
+        } else if let Some((arg, remaining)) = cmd.split_once(is_posix_space) {
+            args.push(arg.to_owned());
+            cmd = remaining.trim_start_matches(is_posix_space);
+        } else {
+            args.push(cmd.to_owned());
+            cmd = "";
+        }
+    }
+
+    Some(args)
+}
+
+fn is_posix_space(c: char) -> bool {
+    // Form feed: 0x0c
+    // Vertical tab: 0x0b
+    c == ' ' || c == '\x0c' || c == '\n' || c == '\r' || c == '\t' || c == '\x0b'
+}
+
+fn main() -> ExitCode {
+    let mut args = std::env::args().skip(1);
+    if args.next() != Some("-c".to_string()) {
+        eprintln!("Expected usage: shell -c <argument>");
+        return ExitCode::FAILURE;
+    }
+    let Some(cmd) = args.next() else {
+        eprintln!("Missing argument for argument '-c'");
+        return ExitCode::FAILURE;
+    };
+    if args.next() != None {
+        eprintln!("Too many arguments passed");
+        return ExitCode::FAILURE;
+    }
+
+    let Some(mut cmd) = parse_cmd(&cmd) else {
+        eprintln!("Bad command");
+        return ExitCode::FAILURE;
+    };
+
+    let Some(mut program) = cmd.drain(0..1).next() else {
+        eprintln!("Bad command");
+        return ExitCode::FAILURE;
+    };
+    if program == "git" {
+        let Some(subcommand) = cmd.drain(0..1).next() else {
+            eprintln!("Bad command");
+            return ExitCode::FAILURE;
+        };
+        program.push('-');
+        program.push_str(&subcommand);
+    }
+
+    let mut args = Vec::new();
+
+    let git_cmds = ["receive-pack", "upload-archive", "upload-pack"];
+    if git_cmds.contains(&program.as_str()) {
+        if cmd.len() != 1 {
+            eprintln!("Bad command");
+            return ExitCode::FAILURE;
+        }
+        let repository = cmd[0].trim_start_matches('/');
+        args.push(repository);
+    } else if program == "git-lfs-authenticate" {
+        if cmd.len() != 2 {
+            eprintln!("Bad command");
+            return ExitCode::FAILURE;
+        }
+        let repository = cmd[0].trim_start_matches('/');
+        args.push(repository);
+        args.push(&cmd[1]);
+    }
+
+    let e = std::process::Command::new(program).args(args).exec();
+    eprintln!("Error: {e}");
+    ExitCode::FAILURE
+}
-- 
cgit v1.2.3