Initial commit for fern
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
target
|
||||
**/*.rs.bk
|
||||
.idea
|
||||
*.iml
|
||||
/result*
|
||||
*.log
|
||||
default.nix
|
||||
shell.nix
|
||||
*~
|
||||
TODO.md
|
||||
rust-toolchain.toml
|
||||
*snapshot*
|
||||
|
||||
# cachix tmp file
|
||||
store-path-pre-build
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
|
||||
template/flake.lock
|
||||
ideas.md
|
||||
roadmap.md
|
||||
README.md
|
||||
file.*
|
||||
324
Cargo.lock
generated
Normal file
324
Cargo.lock
generated
Normal file
@@ -0,0 +1,324 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"nix",
|
||||
"rustyline",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "15.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"clipboard-win",
|
||||
"fd-lock",
|
||||
"home",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"radix_trie",
|
||||
"rustyline-derive",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline-derive"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327e9d075f6df7e25fbf594f1be7ef55cf0d567a6cb5112eeccbbd51ceb48e0d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "fern"
|
||||
description = "A linux shell written in rust"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.8.0"
|
||||
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl"] }
|
||||
rustyline = { version = "15.0.0", features = [ "derive" ] }
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1737746512,
|
||||
"narHash": "sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "825479c345a7f806485b7f00dbe3abb50641b083",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
60
flake.nix
Normal file
60
flake.nix
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux"; # Replace with your target system if necessary
|
||||
};
|
||||
slashBuild = pkgs.rustPlatform.buildRustPackage rec {
|
||||
pname = "slash";
|
||||
version = "v0.2.0";
|
||||
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "pagedMov";
|
||||
repo = "slash";
|
||||
rev = "65a7a713a954c0f3fba668c6d7e0cdd023f705f7";
|
||||
hash = "sha256-AVBDv0HQn7hAGo0tW1ZFCdfO4+3VJQ0mCDkow8skD7U=";
|
||||
};
|
||||
|
||||
doCheck = false; # TODO: Find a way to make tests work
|
||||
|
||||
cargoHash = "sha256-lekH6AESWpKjp6mCW7KgN6ACNcG8bHAg4Pu4OXhGJ3Y=";
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.openssl
|
||||
pkgs.openssl.dev
|
||||
pkgs.pkg-config
|
||||
];
|
||||
|
||||
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
|
||||
passthru = {
|
||||
shellPath = "/bin/slash";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.${pkgs.system}.default = slashBuild;
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
pkgs.rust-bin.stable.latest.default
|
||||
pkgs.gcc
|
||||
pkgs.clang
|
||||
pkgs.pkg-config
|
||||
pkgs.libgit2
|
||||
pkgs.libssh2
|
||||
pkgs.libssh2.dev
|
||||
pkgs.openssl
|
||||
pkgs.openssl.dev
|
||||
pkgs.llvm
|
||||
pkgs.libclang
|
||||
pkgs.pam
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
exec slash
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
25
nix/devshell/flake-module.nix
Normal file
25
nix/devshell/flake-module.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
{ inputs, lib, ... }: {
|
||||
imports = [
|
||||
inputs.devshell.flakeModule
|
||||
];
|
||||
|
||||
config.perSystem =
|
||||
{ pkgs
|
||||
, ...
|
||||
}: {
|
||||
config.devshells.default = {
|
||||
imports = [
|
||||
"${inputs.devshell}/extra/language/c.nix"
|
||||
# "${inputs.devshell}/extra/language/rust.nix"
|
||||
];
|
||||
|
||||
commands = with pkgs; [
|
||||
{ package = rust-toolchain; category = "rust"; }
|
||||
];
|
||||
|
||||
language.c = {
|
||||
libraries = lib.optional pkgs.stdenv.isDarwin pkgs.libiconv;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
18
nix/rust-overlay/flake-module.nix
Normal file
18
nix/rust-overlay/flake-module.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{ inputs, ... }:
|
||||
let
|
||||
overlays = [
|
||||
(import inputs.rust-overlay)
|
||||
(self: super: assert !(super ? rust-toolchain); rec {
|
||||
rust-toolchain = super.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml;
|
||||
|
||||
# buildRustCrate/crate2nix depend on this.
|
||||
rustc = rust-toolchain;
|
||||
cargo = rust-toolchain;
|
||||
})
|
||||
];
|
||||
in
|
||||
{
|
||||
perSystem = { system, ... }: {
|
||||
_module.args.pkgs = import inputs.nixpkgs { inherit system overlays; config = { }; };
|
||||
};
|
||||
}
|
||||
15
src/builtin/cd.rs
Normal file
15
src/builtin/cd.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use crate::{parse::parse::{Node, NdRule}, prelude::*};
|
||||
|
||||
pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
argv_iter.next(); // Ignore 'cd'
|
||||
let dir_raw = argv_iter.next().map(|arg| arg.to_string()).unwrap_or(std::env::var("HOME")?);
|
||||
let dir = PathBuf::from(&dir_raw);
|
||||
std::env::set_current_dir(dir)?;
|
||||
shenv.vars_mut().export("PWD",&dir_raw);
|
||||
shenv.set_code(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
53
src/builtin/echo.rs
Normal file
53
src/builtin/echo.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
use crate::{libsh::utils::ArgVec, parse::parse::{Node, NdRule}, prelude::*};
|
||||
|
||||
pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let argv = argv.drop_first().as_strings(shenv);
|
||||
let mut formatted = argv.join(" ");
|
||||
formatted.push('\n');
|
||||
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(e) = write_out(formatted) {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
} else {
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(e) = write_out(formatted) {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
Parent { child } => {
|
||||
shenv.reset_io()?;
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some("echo"), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
18
src/builtin/export.rs
Normal file
18
src/builtin/export.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
argv_iter.next(); // Ignore 'export'
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let arg_raw = arg.to_string();
|
||||
if let Some((var,val)) = arg_raw.split_once('=') {
|
||||
shenv.vars_mut().export(var, val);
|
||||
} else {
|
||||
eprintln!("Expected an assignment in export args, found this: {}", arg_raw)
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
127
src/builtin/jobctl.rs
Normal file
127
src/builtin/jobctl.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use shellenv::jobs::JobCmdFlags;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> {
|
||||
let blame = node.span();
|
||||
let cmd = if fg { "fg" } else { "bg" };
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv_s = argv.drop_first().as_strings(shenv).into_iter();
|
||||
|
||||
if read_jobs(|j| j.get_fg().is_some()) {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
format!("Somehow called {} with an existing foreground job",cmd),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found".into(), blame))
|
||||
};
|
||||
|
||||
let tabid = match argv_s.next() {
|
||||
Some(arg) => parse_job_id(&arg, blame.clone())?,
|
||||
None => curr_job_id
|
||||
};
|
||||
|
||||
let mut job = write_jobs(|j| {
|
||||
let id = JobID::TableID(tabid);
|
||||
let query_result = j.query(id.clone());
|
||||
if query_result.is_some() {
|
||||
Ok(j.remove_job(id.clone()).unwrap())
|
||||
} else {
|
||||
Err(ShErr::full(ShErrKind::ExecFail, format!("Job id `{}' not found", tabid), blame))
|
||||
}
|
||||
})?;
|
||||
|
||||
job.killpg(Signal::SIGCONT)?;
|
||||
|
||||
if fg {
|
||||
write_jobs(|j| j.new_fg(job))?;
|
||||
} else {
|
||||
let job_order = read_jobs(|j| j.order().to_vec());
|
||||
write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?;
|
||||
write_jobs(|j| j.insert_job(job, true))?;
|
||||
}
|
||||
shenv.set_code(0);
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
||||
if arg.starts_with('%') {
|
||||
let arg = arg.strip_prefix('%').unwrap();
|
||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
Ok(arg.parse::<usize>().unwrap())
|
||||
} else {
|
||||
let result = write_jobs(|j| {
|
||||
let query_result = j.query(JobID::Command(arg.into()));
|
||||
query_result.map(|job| job.tabid().unwrap())
|
||||
});
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
|
||||
}
|
||||
}
|
||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
let result = write_jobs(|j| {
|
||||
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
|
||||
if let Some(job) = pgid_query_result {
|
||||
return Some(job.tabid().unwrap())
|
||||
}
|
||||
|
||||
if arg.parse::<i32>().unwrap() > 0 {
|
||||
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
|
||||
return table_id_query_result.map(|job| job.tabid().unwrap());
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
|
||||
}
|
||||
} else {
|
||||
Err(ShErr::full(ShErrKind::SyntaxErr,format!("Invalid fd arg: {}", arg),blame))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv = argv.drop_first().into_iter();
|
||||
|
||||
let mut flags = JobCmdFlags::empty();
|
||||
while let Some(arg) = argv.next() {
|
||||
let arg_s = arg.to_string();
|
||||
let mut chars = arg_s.chars().peekable();
|
||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||
return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
|
||||
}
|
||||
chars.next();
|
||||
while let Some(ch) = chars.next() {
|
||||
let flag = match ch {
|
||||
'l' => JobCmdFlags::LONG,
|
||||
'p' => JobCmdFlags::PIDS,
|
||||
'n' => JobCmdFlags::NEW_ONLY,
|
||||
'r' => JobCmdFlags::RUNNING,
|
||||
's' => JobCmdFlags::STOPPED,
|
||||
_ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
|
||||
|
||||
};
|
||||
flags |= flag
|
||||
}
|
||||
}
|
||||
read_jobs(|j| j.print_jobs(flags))?;
|
||||
shenv.set_code(0);
|
||||
} else { unreachable!() }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
17
src/builtin/mod.rs
Normal file
17
src/builtin/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
pub mod echo;
|
||||
pub mod cd;
|
||||
pub mod pwd;
|
||||
pub mod export;
|
||||
pub mod jobctl;
|
||||
pub mod read;
|
||||
|
||||
pub const BUILTINS: [&str;8] = [
|
||||
"echo",
|
||||
"cd",
|
||||
"pwd",
|
||||
"export",
|
||||
"fg",
|
||||
"bg",
|
||||
"jobs",
|
||||
"read"
|
||||
];
|
||||
50
src/builtin/pwd.rs
Normal file
50
src/builtin/pwd.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
use crate::{parse::parse::{NdFlag, Node, NdRule}, prelude::*};
|
||||
|
||||
pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv: _, redirs } = rule {
|
||||
let mut pwd = shenv.vars().get_var("PWD").to_string();
|
||||
pwd.push('\n');
|
||||
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(e) = write_out(pwd) {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
} else {
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(e) = write_out(pwd) {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
Parent { child } => {
|
||||
shenv.reset_io()?;
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some("echo"), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
39
src/builtin/read.rs
Normal file
39
src/builtin/read.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn read_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let argv = argv.drop_first();
|
||||
let mut argv_iter = argv.iter();
|
||||
// TODO: properly implement redirections
|
||||
// using activate_redirs() was causing issues, may require manual handling
|
||||
|
||||
let mut buf = vec![0u8; 1024];
|
||||
let bytes_read = read(0, &mut buf)?;
|
||||
buf.truncate(bytes_read);
|
||||
|
||||
let read_input = String::from_utf8_lossy(&buf).trim_end().to_string();
|
||||
|
||||
if let Some(var) = argv_iter.next() {
|
||||
/*
|
||||
let words: Vec<&str> = read_input.split_whitespace().collect();
|
||||
|
||||
for (var, value) in argv_iter.zip(words.iter().chain(std::iter::repeat(&""))) {
|
||||
shenv.vars_mut().set_var(&var.to_string(), value);
|
||||
}
|
||||
|
||||
// Assign the rest of the string to the first variable if there's only one
|
||||
if argv.len() == 1 {
|
||||
shenv.vars_mut().set_var(&first_var.to_string(), &read_input);
|
||||
}
|
||||
*/
|
||||
shenv.vars_mut().set_var(&var.to_string(), &read_input);
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
log!(TRACE, "leaving read");
|
||||
shenv.set_code(0);
|
||||
Ok(())
|
||||
}
|
||||
344
src/execute.rs
Normal file
344
src/execute.rs
Normal file
@@ -0,0 +1,344 @@
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
use crate::{builtin::export::export, libsh::{error::Blame, sys::{execvpe, get_bin_path}, utils::{ArgVec, StrOps}}, parse::{lex::Token, parse::{CmdGuard, NdFlag, Node, NdRule, SynTree}}, prelude::*};
|
||||
|
||||
pub struct Executor<'a> {
|
||||
ast: SynTree,
|
||||
shenv: &'a mut ShEnv
|
||||
}
|
||||
|
||||
impl<'a> Executor<'a> {
|
||||
pub fn new(ast: SynTree, shenv: &'a mut ShEnv) -> Self {
|
||||
Self { ast, shenv }
|
||||
}
|
||||
pub fn walk(&mut self) -> ShResult<()> {
|
||||
log!(DEBUG, "Starting walk");
|
||||
while let Some(node) = self.ast.next_node() {
|
||||
let span = node.span();
|
||||
if let NdRule::CmdList { cmds } = node.clone().into_rule() {
|
||||
log!(TRACE, "{:?}", cmds);
|
||||
exec_list(cmds, self.shenv).try_blame(span)?
|
||||
} else { unreachable!() }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(DEBUG, "Executing list");
|
||||
let mut list = VecDeque::from(list);
|
||||
while let Some(cmd_info) = list.fpop() {
|
||||
let guard = cmd_info.0;
|
||||
let cmd = cmd_info.1;
|
||||
let span = cmd.span();
|
||||
|
||||
if let Some(guard) = guard {
|
||||
let code = shenv.get_code();
|
||||
match guard {
|
||||
CmdGuard::And => {
|
||||
if code != 0 { break; }
|
||||
}
|
||||
CmdGuard::Or => {
|
||||
if code == 0 { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
log!(TRACE, "{:?}", *cmd.rule());
|
||||
match *cmd.rule() {
|
||||
NdRule::Command {..} if cmd.flags().contains(NdFlag::BUILTIN) => exec_builtin(cmd,shenv).try_blame(span)?,
|
||||
NdRule::Command {..} => exec_cmd(cmd,shenv).try_blame(span)?,
|
||||
NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(span)?,
|
||||
NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(span)?,
|
||||
NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(span)?,
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let snapshot = shenv.clone();
|
||||
shenv.vars_mut().reset_params();
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Subshell { body, argv, redirs } = rule {
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); // Allow sub-forks in this case
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
for arg in argv {
|
||||
shenv.vars_mut().bpush_arg(&arg.to_string());
|
||||
}
|
||||
let body_raw = body.to_string();
|
||||
let lexer_input = Rc::new(
|
||||
body_raw[1..body_raw.len() - 1].to_string()
|
||||
);
|
||||
let token_stream = Lexer::new(lexer_input).lex();
|
||||
match Parser::new(token_stream).parse() {
|
||||
Ok(syn_tree) => {
|
||||
if let Err(e) = Executor::new(syn_tree, shenv).walk() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
} else {
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
for arg in argv {
|
||||
shenv.vars_mut().bpush_arg(&arg.to_string());
|
||||
}
|
||||
let body_raw = body.to_string();
|
||||
let lexer_input = Rc::new(
|
||||
body_raw[1..body_raw.len() - 1].to_string()
|
||||
);
|
||||
let token_stream = Lexer::new(lexer_input).lex();
|
||||
match Parser::new(token_stream).parse() {
|
||||
Ok(syn_tree) => {
|
||||
if let Err(e) = Executor::new(syn_tree, shenv).walk() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
Parent { child } => {
|
||||
*shenv = snapshot;
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some("anonymous subshell"), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(DEBUG, "Executing builtin");
|
||||
let command = if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
||||
argv.first().unwrap().to_string()
|
||||
} else { unreachable!() };
|
||||
|
||||
log!(TRACE, "{}", command.as_str());
|
||||
match command.as_str() {
|
||||
"echo" => echo(node, shenv)?,
|
||||
"cd" => cd(node,shenv)?,
|
||||
"pwd" => pwd(node, shenv)?,
|
||||
"export" => export(node, shenv)?,
|
||||
"jobs" => jobs(node, shenv)?,
|
||||
"fg" => continue_job(node, shenv, true)?,
|
||||
"bg" => continue_job(node, shenv, false)?,
|
||||
"read" => read_builtin(node, shenv)?,
|
||||
_ => unimplemented!("Have not yet implemented support for builtin `{}'",command)
|
||||
}
|
||||
log!(TRACE, "done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(DEBUG, "Executing assignment");
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Assignment { assignments, cmd } = rule {
|
||||
log!(TRACE, "Assignments: {:?}", assignments);
|
||||
log!(TRACE, "Command: {:?}", cmd);
|
||||
let mut assigns = assignments.into_iter();
|
||||
if let Some(cmd) = cmd {
|
||||
while let Some(assign) = assigns.next() {
|
||||
let assign_raw = assign.to_string();
|
||||
if let Some((var,val)) = assign_raw.split_once('=') {
|
||||
shenv.vars_mut().export(var, val);
|
||||
}
|
||||
}
|
||||
if cmd.flags().contains(NdFlag::BUILTIN) {
|
||||
exec_builtin(*cmd, shenv)?;
|
||||
} else {
|
||||
exec_cmd(*cmd, shenv)?;
|
||||
}
|
||||
} else {
|
||||
while let Some(assign) = assigns.next() {
|
||||
let assign_raw = assign.to_string();
|
||||
if let Some((var,val)) = assign_raw.split_once('=') {
|
||||
shenv.vars_mut().set_var(var, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(DEBUG, "Executing pipeline");
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Pipeline { cmds } = rule {
|
||||
let mut prev_rpipe: Option<i32> = None;
|
||||
let mut cmds = VecDeque::from(cmds);
|
||||
let mut pgid = None;
|
||||
let mut cmd_names = vec![];
|
||||
let mut pids = vec![];
|
||||
|
||||
while let Some(cmd) = cmds.pop_front() {
|
||||
let (r_pipe, w_pipe) = if cmds.is_empty() {
|
||||
// If we are on the last command, don't make new pipes
|
||||
(None,None)
|
||||
} else {
|
||||
let (r_pipe, w_pipe) = c_pipe()?;
|
||||
(Some(r_pipe),Some(w_pipe))
|
||||
};
|
||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
||||
let cmd_name = argv.first().unwrap().span().get_slice().to_string();
|
||||
cmd_names.push(cmd_name);
|
||||
} else { unimplemented!() }
|
||||
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
// Set NO_FORK since we are already in a fork, to prevent unnecessarily forking again
|
||||
shenv.ctx_mut().set_flag(ExecFlags::NO_FORK);
|
||||
// We close this r_pipe since it's the one the next command will use, so not useful here
|
||||
if let Some(r_pipe) = r_pipe {
|
||||
close(r_pipe.as_raw_fd())?;
|
||||
}
|
||||
|
||||
// Create some redirections
|
||||
if let Some(w_pipe) = w_pipe {
|
||||
let wpipe_redir = Redir::new(1, RedirType::Output, RedirTarget::Fd(w_pipe.as_raw_fd()));
|
||||
shenv.ctx_mut().push_rdr(wpipe_redir);
|
||||
}
|
||||
// Use the r_pipe created in the last iteration
|
||||
if let Some(prev_rpipe) = prev_rpipe {
|
||||
let rpipe_redir = Redir::new(0, RedirType::Input, RedirTarget::Fd(prev_rpipe.as_raw_fd()));
|
||||
shenv.ctx_mut().push_rdr(rpipe_redir);
|
||||
}
|
||||
|
||||
if cmd.flags().contains(NdFlag::BUILTIN) {
|
||||
exec_builtin(cmd, shenv).unwrap();
|
||||
} else {
|
||||
exec_cmd(cmd, shenv).unwrap();
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
Parent { child } => {
|
||||
// Close the write pipe out here to signal EOF
|
||||
if let Some(w_pipe) = w_pipe {
|
||||
close(w_pipe.as_raw_fd())?;
|
||||
}
|
||||
if pgid.is_none() {
|
||||
pgid = Some(child);
|
||||
}
|
||||
pids.push(child);
|
||||
prev_rpipe = r_pipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i,pid) in pids.iter().enumerate() {
|
||||
let command = cmd_names.get(i).unwrap();
|
||||
let children = vec![
|
||||
ChildProc::new(*pid, Some(&command), pgid)?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(pgid.unwrap())
|
||||
.build();
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(DEBUG, "Executing command");
|
||||
let blame = node.span();
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let (argv,envp) = prep_execve(argv, shenv);
|
||||
let command = argv.first().unwrap().to_string();
|
||||
if get_bin_path(&command, shenv).is_some() {
|
||||
|
||||
shenv.save_io()?;
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
log!(TRACE, "Not forking");
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(errno) = execvpe(command, argv, envp) {
|
||||
if errno != Errno::EFAULT {
|
||||
exit(errno as i32);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log!(TRACE, "Forking");
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
log!(DEBUG, redirs);
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
execvpe(command, argv, envp)?;
|
||||
exit(1);
|
||||
}
|
||||
Parent { child } => {
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some(&command), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
log!(TRACE, "New job: {:?}", job);
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::CmdNotFound, format!("{}", command), blame))
|
||||
}
|
||||
} else { unreachable!("Found this rule in exec_cmd: {:?}", rule) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prep_execve(argv: Vec<Token>, shenv: &mut ShEnv) -> (Vec<String>, Vec<String>) {
|
||||
log!(DEBUG, "Preparing execvpe args");
|
||||
let argv_s = argv.as_strings(shenv);
|
||||
log!(DEBUG, argv_s);
|
||||
|
||||
let mut envp = vec![];
|
||||
let env_vars = shenv.vars().env().clone();
|
||||
let mut entries = env_vars.iter().collect::<VecDeque<(&String,&String)>>();
|
||||
while let Some(entry) = entries.fpop() {
|
||||
let key = entry.0;
|
||||
let val = entry.1;
|
||||
let formatted = format!("{}={}",key,val);
|
||||
envp.push(formatted);
|
||||
}
|
||||
log!(TRACE, argv_s);
|
||||
(argv_s, envp)
|
||||
}
|
||||
63
src/expand/expand_vars.rs
Normal file
63
src/expand/expand_vars.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use crate::{parse::lex::Token, prelude::*};
|
||||
|
||||
pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
let var_name = var_sub.to_string();
|
||||
let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']);
|
||||
let value = Rc::new(
|
||||
shenv.vars()
|
||||
.get_var(var_name)
|
||||
.to_string()
|
||||
);
|
||||
Lexer::new(value).lex() // Automatically handles word splitting for us
|
||||
}
|
||||
|
||||
pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String {
|
||||
let dquote_raw = dquote.to_string();
|
||||
let mut result = String::new();
|
||||
let mut var_name = String::new();
|
||||
let mut chars = dquote_raw.chars();
|
||||
let mut in_brace = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
if let Some(next_ch) = chars.next() {
|
||||
result.push(next_ch)
|
||||
}
|
||||
}
|
||||
'"' => continue,
|
||||
'$' => {
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'"' => continue,
|
||||
'{' => {
|
||||
in_brace = true;
|
||||
}
|
||||
'}' if in_brace => {
|
||||
break
|
||||
}
|
||||
_ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => {
|
||||
var_name.push(ch);
|
||||
break
|
||||
}
|
||||
'@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => {
|
||||
var_name.push(ch);
|
||||
break
|
||||
}
|
||||
' ' | '\t' => {
|
||||
break
|
||||
}
|
||||
_ => var_name.push(ch)
|
||||
}
|
||||
}
|
||||
log!(TRACE, var_name);
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
log!(TRACE, value);
|
||||
result.push_str(value);
|
||||
}
|
||||
_ => result.push(ch)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
2
src/expand/mod.rs
Normal file
2
src/expand/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod expand_vars;
|
||||
pub mod tilde;
|
||||
11
src/expand/tilde.rs
Normal file
11
src/expand/tilde.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn expand_tilde(tilde_sub: Token) -> String {
|
||||
let tilde_sub_raw = tilde_sub.to_string();
|
||||
if tilde_sub_raw.starts_with('~') {
|
||||
let home = std::env::var("HOME").unwrap_or_default();
|
||||
tilde_sub_raw.replacen('~', &home, 1)
|
||||
} else {
|
||||
tilde_sub_raw
|
||||
}
|
||||
}
|
||||
36
src/libsh/collections.rs
Normal file
36
src/libsh/collections.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub trait VecDequeAliases<T> {
|
||||
fn fpop(&mut self) -> Option<T>;
|
||||
fn fpush(&mut self, value: T);
|
||||
fn bpop(&mut self) -> Option<T>;
|
||||
fn bpush(&mut self, value: T);
|
||||
fn to_vec(self) -> Vec<T>;
|
||||
}
|
||||
|
||||
impl<T> VecDequeAliases<T> for VecDeque<T> {
|
||||
/// Alias for pop_front()
|
||||
fn fpop(&mut self) -> Option<T> {
|
||||
self.pop_front()
|
||||
}
|
||||
/// Alias for push_front()
|
||||
fn fpush(&mut self, value: T) {
|
||||
self.push_front(value);
|
||||
}
|
||||
/// Alias for pop_back()
|
||||
fn bpop(&mut self) -> Option<T> {
|
||||
self.pop_back()
|
||||
}
|
||||
/// Alias for push_back()
|
||||
fn bpush(&mut self, value: T) {
|
||||
self.push_back(value);
|
||||
}
|
||||
/// Just turns the deque into a vector
|
||||
fn to_vec(mut self) -> Vec<T> {
|
||||
let mut vec = vec![];
|
||||
while let Some(item) = self.fpop() {
|
||||
vec.push(item)
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
195
src/libsh/error.rs
Normal file
195
src/libsh/error.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::parse::lex::Span;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub type ShResult<T> = Result<T,ShErr>;
|
||||
|
||||
|
||||
pub trait Blame {
|
||||
/// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full
|
||||
/// This will also set the span on a ShErr::Builder
|
||||
fn blame(self, span: Span) -> Self;
|
||||
|
||||
/// If an error is propagated to this point, then attempt to blame a span.
|
||||
/// If the error in question has already blamed a span, don't overwrite it.
|
||||
/// Used as a last resort in higher level contexts in case an error somehow goes unblamed
|
||||
fn try_blame(self, span: Span) -> Self;
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ShErr {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
ShErr::io()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::env::VarError> for ShErr {
|
||||
fn from(value: std::env::VarError) -> Self {
|
||||
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rustyline::error::ReadlineError> for ShErr {
|
||||
fn from(value: rustyline::error::ReadlineError) -> Self {
|
||||
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Errno> for ShErr {
|
||||
fn from(value: Errno) -> Self {
|
||||
ShErr::simple(ShErrKind::Errno, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Blame for Result<T,ShErr> {
|
||||
fn blame(self, span: Span) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.blame(span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
fn try_blame(self, span: Span) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.try_blame(span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum ShErrKind {
|
||||
IoErr,
|
||||
SyntaxErr,
|
||||
ParseErr,
|
||||
InternalErr,
|
||||
ExecFail,
|
||||
Errno,
|
||||
CmdNotFound,
|
||||
CleanExit,
|
||||
FuncReturn,
|
||||
LoopContinue,
|
||||
LoopBreak,
|
||||
Null
|
||||
}
|
||||
|
||||
impl Default for ShErrKind {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ShErr {
|
||||
Simple { kind: ShErrKind, message: String },
|
||||
Full { kind: ShErrKind, message: String, span: Span },
|
||||
}
|
||||
|
||||
impl ShErr {
|
||||
pub fn simple(kind: ShErrKind, message: &str) -> Self {
|
||||
Self::Simple { kind, message: message.to_string() }
|
||||
}
|
||||
pub fn io() -> Self {
|
||||
io::Error::last_os_error().into()
|
||||
}
|
||||
pub fn full(kind: ShErrKind, message: String, span: Span) -> Self {
|
||||
Self::Full { kind, message, span }
|
||||
}
|
||||
pub fn try_blame(&mut self, blame: Span) {
|
||||
match self {
|
||||
Self::Full {..} => {
|
||||
/* Do not overwrite */
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), span: blame }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn blame(&mut self, blame: Span) {
|
||||
match self {
|
||||
Self::Full { kind: _, message: _, span } => {
|
||||
*span = blame;
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), span: blame }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_msg(&mut self, new_message: String) {
|
||||
match self {
|
||||
Self::Full { kind: _, message, span: _ } => {
|
||||
*message = new_message
|
||||
}
|
||||
Self::Simple { kind: _, message } => {
|
||||
*message = new_message
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_kind(&mut self, new_kind: ShErrKind) {
|
||||
match self {
|
||||
Self::Full { kind, message: _, span: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
Self::Simple { kind, message: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn display_kind(&self) -> String {
|
||||
match self {
|
||||
ShErr::Simple { kind, message: _ } |
|
||||
ShErr::Full { kind, message: _, span: _ } => {
|
||||
match kind {
|
||||
ShErrKind::IoErr => "I/O Error: ".into(),
|
||||
ShErrKind::SyntaxErr => "Syntax Error: ".into(),
|
||||
ShErrKind::ParseErr => "Parse Error: ".into(),
|
||||
ShErrKind::InternalErr => "Internal Error: ".into(),
|
||||
ShErrKind::ExecFail => "Execution Failed: ".into(),
|
||||
ShErrKind::Errno => "ERRNO: ".into(),
|
||||
ShErrKind::CmdNotFound => "Command not found: ".into(),
|
||||
ShErrKind::CleanExit |
|
||||
ShErrKind::FuncReturn |
|
||||
ShErrKind::LoopContinue |
|
||||
ShErrKind::LoopBreak |
|
||||
ShErrKind::Null => "".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_window(&self) -> String {
|
||||
if let ShErr::Full { kind: _, message: _, span } = self {
|
||||
let window = span.get_slice();
|
||||
window.split_once('\n').unwrap_or((&window,"")).0.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShErr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_display = match self {
|
||||
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
|
||||
ShErr::Full { kind: _, message, span } => {
|
||||
let (offset,line_no,line_text) = span.get_line();
|
||||
let dist = span.end() - span.start();
|
||||
let padding = " ".repeat(offset);
|
||||
let line_inner = "~".repeat(dist.saturating_sub(2));
|
||||
let err_kind = style_text(&self.display_kind(), Style::Red | Style::Bold);
|
||||
let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message);
|
||||
let indicator_line = if dist == 1 {
|
||||
format!("{}^",padding)
|
||||
} else {
|
||||
format!("{}^{}^",padding,line_inner)
|
||||
};
|
||||
let error_full = format!("\n{}\n{}\n{}\n",stat_line,line_text,indicator_line);
|
||||
|
||||
error_full
|
||||
}
|
||||
};
|
||||
write!(f,"{}",error_display)
|
||||
}
|
||||
}
|
||||
6
src/libsh/mod.rs
Normal file
6
src/libsh/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod sys;
|
||||
#[macro_use]
|
||||
pub mod utils;
|
||||
pub mod collections;
|
||||
pub mod error;
|
||||
pub mod term;
|
||||
51
src/libsh/sys.rs
Normal file
51
src/libsh/sys.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||
|
||||
pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option<PathBuf> {
|
||||
let env = shenv.vars().env();
|
||||
let path_var = env.get("PATH")?;
|
||||
let mut paths = path_var.split(':');
|
||||
while let Some(raw_path) = paths.next() {
|
||||
let mut path = PathBuf::from(raw_path);
|
||||
path.push(command);
|
||||
//TODO: handle this unwrap
|
||||
if path.exists() {
|
||||
return Some(path)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn write_out(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(1), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_err(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(2), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return is `readpipe`, `writepipe`
|
||||
/// Contains all of the necessary boilerplate for grabbing two pipe fds using libc::pipe()
|
||||
pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> {
|
||||
let mut pipes: [i32;2] = [0;2];
|
||||
let ret = unsafe { libc::pipe(pipes.as_mut_ptr()) };
|
||||
if ret < 0 {
|
||||
return Err(Errno::from_raw(ret))
|
||||
}
|
||||
Ok((pipes[0],pipes[1]))
|
||||
}
|
||||
|
||||
pub fn execvpe(cmd: String, argv: Vec<String>, envp: Vec<String>) -> Result<(),Errno> {
|
||||
let cmd_raw = CString::new(cmd).unwrap();
|
||||
|
||||
let argv = argv.into_iter().map(|arg| CString::new(arg).unwrap()).collect::<Vec<CString>>();
|
||||
let envp = envp.into_iter().map(|var| CString::new(var).unwrap()).collect::<Vec<CString>>();
|
||||
|
||||
nix::unistd::execvpe(&cmd_raw, &argv, &envp).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
108
src/libsh/term.rs
Normal file
108
src/libsh/term.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use std::{fmt::Display, ops::BitOr};
|
||||
|
||||
/// Enum representing a single ANSI style
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
Reset,
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
BrightBlack,
|
||||
BrightRed,
|
||||
BrightGreen,
|
||||
BrightYellow,
|
||||
BrightBlue,
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Reversed,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Style::Reset => "\x1b[0m",
|
||||
Style::Black => "\x1b[30m",
|
||||
Style::Red => "\x1b[31m",
|
||||
Style::Green => "\x1b[32m",
|
||||
Style::Yellow => "\x1b[33m",
|
||||
Style::Blue => "\x1b[34m",
|
||||
Style::Magenta => "\x1b[35m",
|
||||
Style::Cyan => "\x1b[36m",
|
||||
Style::White => "\x1b[37m",
|
||||
Style::BrightBlack => "\x1b[90m",
|
||||
Style::BrightRed => "\x1b[91m",
|
||||
Style::BrightGreen => "\x1b[92m",
|
||||
Style::BrightYellow => "\x1b[93m",
|
||||
Style::BrightBlue => "\x1b[94m",
|
||||
Style::BrightMagenta => "\x1b[95m",
|
||||
Style::BrightCyan => "\x1b[96m",
|
||||
Style::BrightWhite => "\x1b[97m",
|
||||
Style::Bold => "\x1b[1m",
|
||||
Style::Italic => "\x1b[3m",
|
||||
Style::Underline => "\x1b[4m",
|
||||
Style::Reversed => "\x1b[7m",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a **set** of styles
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StyleSet {
|
||||
styles: Vec<Style>,
|
||||
}
|
||||
|
||||
impl StyleSet {
|
||||
pub fn new() -> Self {
|
||||
Self { styles: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add(mut self, style: Style) -> Self {
|
||||
if !self.styles.contains(&style) {
|
||||
self.styles.push(style);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> String {
|
||||
self.styles.iter().map(|s| s.as_str()).collect::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow OR (`|`) operator to combine multiple `Style` values into a `StyleSet`
|
||||
impl BitOr for Style {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> StyleSet {
|
||||
StyleSet::new().add(self).add(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow OR (`|`) operator to combine `StyleSet` with `Style`
|
||||
impl BitOr<Style> for StyleSet {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Style) -> StyleSet {
|
||||
self.add(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Style> for StyleSet {
|
||||
fn from(style: Style) -> Self {
|
||||
StyleSet::new().add(style)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply styles to a string
|
||||
pub fn style_text<Str: Display, Sty: Into<StyleSet>>(text: Str, styles: Sty) -> String {
|
||||
let styles = styles.into();
|
||||
format!("{}{}{}", styles.as_str(), text, Style::Reset.as_str())
|
||||
}
|
||||
340
src/libsh/utils.rs
Normal file
340
src/libsh/utils.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
use core::{arch::asm, fmt::{self, Debug, Display, Write}, ops::Deref};
|
||||
use std::{os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, str::FromStr};
|
||||
|
||||
use nix::libc::getpgrp;
|
||||
|
||||
use crate::{expand::{expand_vars::{expand_dquote, expand_var}, tilde::expand_tilde}, prelude::*};
|
||||
|
||||
pub trait StrOps {
|
||||
fn trim_quotes(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait ArgVec {
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String>;
|
||||
fn drop_first(self) -> Vec<Token>;
|
||||
}
|
||||
|
||||
impl ArgVec for Vec<Token> {
|
||||
/// Converts the contained tokens into strings.
|
||||
/// This function also performs token expansion.
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String> {
|
||||
let mut argv_iter = self.into_iter();
|
||||
let mut argv_processed = vec![];
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
match arg.rule() {
|
||||
TkRule::VarSub => {
|
||||
let mut tokens = expand_var(arg, shenv).into_iter();
|
||||
while let Some(token) = tokens.next() {
|
||||
argv_processed.push(token.to_string())
|
||||
}
|
||||
}
|
||||
TkRule::TildeSub => {
|
||||
let expanded = expand_tilde(arg);
|
||||
argv_processed.push(expanded);
|
||||
}
|
||||
TkRule::DQuote => {
|
||||
let expanded = expand_dquote(arg, shenv);
|
||||
argv_processed.push(expanded)
|
||||
}
|
||||
_ => {
|
||||
argv_processed.push(arg.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
argv_processed
|
||||
}
|
||||
/// This is used to ignore the first argument
|
||||
/// Most commonly used in builtins where execvpe is not used
|
||||
fn drop_first(self) -> Vec<Token> {
|
||||
self[1..].to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test {
|
||||
($test:block) => {
|
||||
$test
|
||||
exit(1)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum LogLevel {
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4,
|
||||
TRACE = 5,
|
||||
NULL = 0
|
||||
}
|
||||
|
||||
impl Display for LogLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ERROR => write!(f,"{}",style_text("ERROR", Style::Red | Style::Bold)),
|
||||
WARN => write!(f,"{}",style_text("WARN", Style::Yellow | Style::Bold)),
|
||||
INFO => write!(f,"{}",style_text("INFO", Style::Green | Style::Bold)),
|
||||
DEBUG => write!(f,"{}",style_text("DEBUG", Style::Magenta | Style::Bold)),
|
||||
TRACE => write!(f,"{}",style_text("TRACE", Style::Blue | Style::Bold)),
|
||||
NULL => write!(f,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $($var:ident),+) => {{
|
||||
$(
|
||||
let var_name = stringify!($var);
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = style_text(file,Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = style_text(line,Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {} = {:#?}",$level, file_styled,line_styled,var_name, &$var);
|
||||
|
||||
write(borrow_fd(2),format!("{}\n",logged).as_bytes()).unwrap();
|
||||
}
|
||||
)+
|
||||
}};
|
||||
|
||||
($level:expr, $lit:literal) => {{
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = style_text(file, Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = style_text(line, Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, $lit);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
if $level <= log_level() {
|
||||
let formatted = format!($($arg)*);
|
||||
let file = file!();
|
||||
let file_styled = style_text(file, Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = style_text(line, Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, formatted);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bp {
|
||||
($var:expr) => {
|
||||
log!($var);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
($($arg:tt)*) => {
|
||||
log!($(arg)*);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn borrow_fd<'a>(fd: i32) -> BorrowedFd<'a> {
|
||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||
}
|
||||
|
||||
// TODO: add more of these
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub enum RedirType {
|
||||
Input,
|
||||
Output,
|
||||
Append,
|
||||
HereDoc,
|
||||
HereString
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum RedirTarget {
|
||||
Fd(i32),
|
||||
File(PathBuf),
|
||||
}
|
||||
|
||||
pub struct RedirBldr {
|
||||
src: Option<i32>,
|
||||
op: Option<RedirType>,
|
||||
tgt: Option<RedirTarget>,
|
||||
}
|
||||
|
||||
impl RedirBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { src: None, op: None, tgt: None }
|
||||
}
|
||||
pub fn with_src(self, src: i32) -> Self {
|
||||
Self { src: Some(src), op: self.op, tgt: self.tgt }
|
||||
}
|
||||
pub fn with_op(self, op: RedirType) -> Self {
|
||||
Self { src: self.src, op: Some(op), tgt: self.tgt }
|
||||
}
|
||||
pub fn with_tgt(self, tgt: RedirTarget) -> Self {
|
||||
Self { src: self.src, op: self.op, tgt: Some(tgt) }
|
||||
}
|
||||
pub fn src(&self) -> Option<i32> {
|
||||
self.src
|
||||
}
|
||||
pub fn op(&self) -> Option<RedirType> {
|
||||
self.op
|
||||
}
|
||||
pub fn tgt(&self) -> Option<&RedirTarget> {
|
||||
self.tgt.as_ref()
|
||||
}
|
||||
pub fn build(self) -> Redir {
|
||||
Redir::new(self.src.unwrap(), self.op.unwrap(), self.tgt.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RedirBldr {
|
||||
type Err = ShErr;
|
||||
fn from_str(raw: &str) -> ShResult<Self> {
|
||||
let mut redir_bldr = RedirBldr::new().with_src(1);
|
||||
let mut chars = raw.chars().peekable();
|
||||
|
||||
let mut raw_src = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_src.push(chars.next().unwrap())
|
||||
}
|
||||
if !raw_src.is_empty() {
|
||||
let src = raw_src.parse::<i32>().unwrap();
|
||||
redir_bldr = redir_bldr.with_src(src);
|
||||
}
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'<' => {
|
||||
redir_bldr = redir_bldr.with_src(0);
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereString);
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereDoc);
|
||||
}
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Input);
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if chars.peek() == Some(&'>') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Append);
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Output);
|
||||
}
|
||||
}
|
||||
'&' => {
|
||||
let mut raw_tgt = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_tgt.push(chars.next().unwrap())
|
||||
}
|
||||
let redir_target = RedirTarget::Fd(raw_tgt.parse::<i32>().unwrap());
|
||||
redir_bldr = redir_bldr.with_tgt(redir_target);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
Ok(redir_bldr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Redir {
|
||||
pub src: i32,
|
||||
pub op: RedirType,
|
||||
pub tgt: RedirTarget
|
||||
}
|
||||
|
||||
impl Redir {
|
||||
pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self {
|
||||
Self { src, op, tgt }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct CmdRedirs {
|
||||
open: Vec<RawFd>,
|
||||
targets_fd: Vec<Redir>,
|
||||
targets_file: Vec<Redir>
|
||||
}
|
||||
|
||||
impl CmdRedirs {
|
||||
pub fn new(mut redirs: Vec<Redir>) -> Self {
|
||||
let mut targets_fd = vec![];
|
||||
let mut targets_file = vec![];
|
||||
while let Some(redir) = redirs.pop() {
|
||||
let Redir { src: _, op: _, tgt } = &redir;
|
||||
match tgt {
|
||||
RedirTarget::Fd(_) => targets_fd.push(redir),
|
||||
RedirTarget::File(_) => targets_file.push(redir)
|
||||
}
|
||||
}
|
||||
Self { open: vec![], targets_fd, targets_file }
|
||||
}
|
||||
pub fn close_all(&mut self) -> ShResult<()> {
|
||||
while let Some(fd) = self.open.pop() {
|
||||
if let Err(e) = close(fd) {
|
||||
self.open.push(fd);
|
||||
return Err(e.into())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn activate(&mut self) -> ShResult<()> {
|
||||
self.open_file_tgts()?;
|
||||
self.open_fd_tgts()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_file_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_file.pop() {
|
||||
let Redir { src, op, tgt } = redir;
|
||||
let src = borrow_fd(src);
|
||||
|
||||
let mut file_fd = if let RedirTarget::File(path) = tgt {
|
||||
let flags = match op {
|
||||
RedirType::Input => OFlag::O_RDONLY,
|
||||
RedirType::Output => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
|
||||
RedirType::Append => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_APPEND,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
let mode = Mode::from_bits(0o644).unwrap();
|
||||
open(&path,flags,mode)?
|
||||
} else { unreachable!() };
|
||||
|
||||
dup2(file_fd.as_raw_fd(),src.as_raw_fd())?;
|
||||
close(file_fd.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_fd_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_fd.pop() {
|
||||
let Redir { src, op: _, tgt } = redir;
|
||||
let mut tgt = if let RedirTarget::Fd(fd) = tgt {
|
||||
borrow_fd(fd)
|
||||
} else { unreachable!() };
|
||||
let src = borrow_fd(src);
|
||||
dup2(tgt.as_raw_fd(), src.as_raw_fd())?;
|
||||
close(tgt.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim_quotes(s: impl ToString) -> String {
|
||||
let s = s.to_string();
|
||||
if s.starts_with('"') && s.ends_with('"') {
|
||||
s.trim_matches('"').to_string()
|
||||
} else if s.starts_with('\'') && s.ends_with('\'') {
|
||||
s.trim_matches('\'').to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
48
src/main.rs
Normal file
48
src/main.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
#![allow(unused_unsafe)]
|
||||
|
||||
pub mod libsh;
|
||||
pub mod shellenv;
|
||||
pub mod parse;
|
||||
pub mod prelude;
|
||||
pub mod execute;
|
||||
pub mod signal;
|
||||
pub mod prompt;
|
||||
pub mod builtin;
|
||||
pub mod expand;
|
||||
|
||||
use libc::PIPE_BUF;
|
||||
use nix::unistd::setpgid;
|
||||
use signal::sig_setup;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn main() {
|
||||
sig_setup();
|
||||
let mut shenv = ShEnv::new();
|
||||
|
||||
loop {
|
||||
log!(TRACE, "Entered loop");
|
||||
let line = match prompt::read_line(&mut shenv) {
|
||||
Ok(line) => line,
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let input = Rc::new(line);
|
||||
log!(INFO, "New input: {:?}", input);
|
||||
let token_stream = Lexer::new(input).lex();
|
||||
log!(TRACE, "Token stream: {:?}", token_stream);
|
||||
match Parser::new(token_stream).parse() {
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
}
|
||||
Ok(syn_tree) => {
|
||||
if let Err(e) = Executor::new(syn_tree, &mut shenv).walk() {
|
||||
eprintln!("{}",e);
|
||||
}
|
||||
}
|
||||
}
|
||||
log!(TRACE, "Finished iteration");
|
||||
}
|
||||
}
|
||||
1064
src/parse/lex.rs
Normal file
1064
src/parse/lex.rs
Normal file
File diff suppressed because it is too large
Load Diff
2
src/parse/mod.rs
Normal file
2
src/parse/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod lex;
|
||||
pub mod parse;
|
||||
537
src/parse/parse.rs
Normal file
537
src/parse/parse.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
use core::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::lex::{TkRule, Span, Token};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
||||
pub struct NdFlag: u32 {
|
||||
const BACKGROUND = 0b00000000000000000000000000000001;
|
||||
const FUNCTION = 0b00000000000000000000000000000010;
|
||||
const BUILTIN = 0b00000000000000000000000000000100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait ParseRule {
|
||||
/// Used for cases where a rule is optional
|
||||
fn try_match(input: &[Token]) -> ShResult<Option<Node>>;
|
||||
/// Used for cases where a rule is assumed based on context
|
||||
/// For instance, if the "for" keyword is encountered, then it *must* be a for loop
|
||||
/// And if it isn't, return a parse error
|
||||
fn assert_match(input: &[Token]) -> ShResult<Node> {
|
||||
Self::try_match(input)?.ok_or_else(||
|
||||
ShErr::simple(ShErrKind::ParseErr, "Parse Error")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum CmdGuard {
|
||||
And,
|
||||
Or
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Node {
|
||||
node_rule: NdRule,
|
||||
tokens: Vec<Token>,
|
||||
span: Span,
|
||||
flags: NdFlag,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn len(&self) -> usize {
|
||||
self.tokens.len()
|
||||
}
|
||||
pub fn tokens(&self) -> &Vec<Token> {
|
||||
&self.tokens
|
||||
}
|
||||
pub fn rule(&self) -> &NdRule {
|
||||
&self.node_rule
|
||||
}
|
||||
pub fn rule_mut(&mut self) -> &mut NdRule {
|
||||
&mut self.node_rule
|
||||
}
|
||||
pub fn into_rule(self) -> NdRule {
|
||||
self.node_rule
|
||||
}
|
||||
pub fn span(&self) -> Span {
|
||||
self.span.clone()
|
||||
}
|
||||
pub fn flags(&self) -> NdFlag {
|
||||
self.flags
|
||||
}
|
||||
pub fn flags_mut(&mut self) -> &mut NdFlag {
|
||||
&mut self.flags
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Node {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let raw = self.span().get_slice();
|
||||
write!(f, "{}", raw)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum NdRule {
|
||||
Main { cmd_lists: Vec<Node> },
|
||||
Command { argv: Vec<Token>, redirs: Vec<Redir> },
|
||||
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
|
||||
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
|
||||
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
|
||||
Pipeline { cmds: Vec<Node> }
|
||||
}
|
||||
|
||||
/// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule.
|
||||
macro_rules! ndrule_def {
|
||||
($name:ident,$try:expr) => {
|
||||
#[derive(Debug)]
|
||||
pub struct $name;
|
||||
impl ParseRule for $name {
|
||||
fn try_match(input: &[Token]) -> ShResult<Option<Node>> {
|
||||
$try(input)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// This macro attempts to match all of the given Rules. It returns upon finding the first match, so the order matters
|
||||
/// Place the most specialized/specific rules first, and the most general rules last
|
||||
macro_rules! try_rules {
|
||||
($tokens:expr, $($name:ident),+) => {
|
||||
$(
|
||||
let result = $name::try_match($tokens)?;
|
||||
if let Some(node) = result {
|
||||
return Ok(Some(node))
|
||||
}
|
||||
)+
|
||||
return Ok(None)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SynTree {
|
||||
tree: VecDeque<Node>
|
||||
}
|
||||
|
||||
impl SynTree {
|
||||
pub fn new() -> Self {
|
||||
Self { tree: VecDeque::new() }
|
||||
}
|
||||
pub fn push_node(&mut self, node: Node) {
|
||||
self.tree.bpush(node)
|
||||
}
|
||||
pub fn next_node(&mut self) -> Option<Node> {
|
||||
self.tree.fpop()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
token_stream: Vec<Token>,
|
||||
ast: SynTree
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(mut token_stream: Vec<Token>) -> Self {
|
||||
log!(TRACE, "New parser");
|
||||
token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment));
|
||||
Self { token_stream, ast: SynTree::new() }
|
||||
}
|
||||
|
||||
pub fn parse(mut self) -> ShResult<SynTree> {
|
||||
log!(TRACE, "Starting parse");
|
||||
let mut lists = VecDeque::new();
|
||||
let token_slice = &*self.token_stream;
|
||||
// Get the Main rule
|
||||
if let Some(mut node) = Main::try_match(token_slice)? {
|
||||
// Extract the inner lists
|
||||
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
|
||||
while let Some(node) = cmd_lists.pop() {
|
||||
log!(DEBUG, node);
|
||||
lists.bpush(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(node) = lists.bpop() {
|
||||
// Push inner command lists to self.ast
|
||||
self.ast.push_node(node);
|
||||
}
|
||||
Ok(self.ast)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_span(toks: &Vec<Token>) -> ShResult<Span> {
|
||||
if toks.is_empty() {
|
||||
Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list"))
|
||||
} else {
|
||||
let start = toks.first().unwrap().span().start();
|
||||
let end = toks.iter().last().unwrap().span().end();
|
||||
let input = toks.iter().last().unwrap().span().get_input();
|
||||
Ok(Span::new(input,start,end))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Redirs with FD sources appear to be looping endlessly for some reason
|
||||
|
||||
ndrule_def!(Main, |tokens: &[Token]| {
|
||||
log!(TRACE, "Parsing main");
|
||||
let mut cmd_lists = vec![];
|
||||
let mut node_toks = vec![];
|
||||
let mut token_slice = &*tokens;
|
||||
|
||||
while let Some(node) = CmdList::try_match(token_slice)? {
|
||||
node_toks.extend(node.tokens().clone());
|
||||
token_slice = &token_slice[node.len()..];
|
||||
cmd_lists.push(node);
|
||||
}
|
||||
|
||||
if cmd_lists.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::Main { cmd_lists },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
Ok(Some(node))
|
||||
});
|
||||
|
||||
ndrule_def!(CmdList, |tokens: &[Token]| {
|
||||
log!(TRACE, "Parsing cmdlist");
|
||||
let mut commands: Vec<(Option<CmdGuard>,Node)> = vec![];
|
||||
let mut node_toks = vec![];
|
||||
let mut token_slice = &*tokens;
|
||||
let mut cmd_guard = None; // Operators like '&&' and '||'
|
||||
|
||||
while let Some(mut node) = Expr::try_match(token_slice)? {
|
||||
// Add sub-node tokens to our tokens
|
||||
node_toks.extend(node.tokens().clone());
|
||||
// Reflect changes in the token slice
|
||||
log!(DEBUG, token_slice);
|
||||
token_slice = &token_slice[node.len()..];
|
||||
log!(DEBUG, token_slice);
|
||||
// Push sub-node
|
||||
if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
||||
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
|
||||
*node.flags_mut() |= NdFlag::BUILTIN;
|
||||
}
|
||||
}
|
||||
commands.push((cmd_guard.take(),node));
|
||||
|
||||
// If the next token is '&&' or '||' then we set cmd_guard and go again
|
||||
if token_slice.first().is_some_and(|tk| matches!(tk.rule(),TkRule::AndOp | TkRule::OrOp)) {
|
||||
let token = token_slice.first().unwrap();
|
||||
node_toks.push(token.clone());
|
||||
match token.rule() {
|
||||
TkRule::AndOp => cmd_guard = Some(CmdGuard::And),
|
||||
TkRule::OrOp => cmd_guard = Some(CmdGuard::Or),
|
||||
_ => unreachable!()
|
||||
}
|
||||
token_slice = &token_slice[1..];
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node_toks.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::CmdList { cmds: commands },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
Ok(Some(node))
|
||||
});
|
||||
|
||||
ndrule_def!(Expr, |tokens: &[Token]| {
|
||||
try_rules!(tokens,
|
||||
Pipeline,
|
||||
Subshell,
|
||||
Assignment,
|
||||
Command
|
||||
);
|
||||
});
|
||||
// Used in pipelines to avoid recursion
|
||||
ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
|
||||
try_rules!(tokens,
|
||||
Subshell,
|
||||
Assignment,
|
||||
Command
|
||||
);
|
||||
});
|
||||
|
||||
ndrule_def!(Subshell, |tokens: &[Token]| {
|
||||
let mut tokens_iter = tokens.iter();
|
||||
let mut node_toks = vec![];
|
||||
let mut argv = vec![];
|
||||
let mut redirs = vec![];
|
||||
if let Some(token) = tokens_iter.next() {
|
||||
if let TkRule::Subshell = token.rule() {
|
||||
node_toks.push(token.clone());
|
||||
let body = token.clone();
|
||||
while let Some(token) = tokens_iter.next() {
|
||||
match token.rule() {
|
||||
TkRule::AndOp |
|
||||
TkRule::OrOp |
|
||||
TkRule::PipeOp |
|
||||
TkRule::ErrPipeOp => {
|
||||
break
|
||||
}
|
||||
TkRule::Sep => {
|
||||
node_toks.push(token.clone());
|
||||
break;
|
||||
}
|
||||
TkRule::Ident |
|
||||
TkRule::SQuote |
|
||||
TkRule::DQuote |
|
||||
TkRule::Assign |
|
||||
TkRule::TildeSub |
|
||||
TkRule::VarSub => {
|
||||
node_toks.push(token.clone());
|
||||
argv.push(token.clone());
|
||||
}
|
||||
TkRule::RedirOp => {
|
||||
node_toks.push(token.clone());
|
||||
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
|
||||
let redir_raw = token.span().get_slice();
|
||||
let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap();
|
||||
// If there isn't an FD target, get the next token and use it as the filename
|
||||
if redir_bldr.tgt().is_none() {
|
||||
if let Some(filename) = tokens_iter.next() {
|
||||
// Make sure it's a word and not an operator or something
|
||||
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) {
|
||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
||||
err.blame(token.span().clone());
|
||||
return Err(err)
|
||||
}
|
||||
node_toks.push(filename.clone());
|
||||
// Construct the Path object
|
||||
let filename_raw = filename.span().get_slice();
|
||||
let filename_path = PathBuf::from(filename_raw);
|
||||
let tgt = RedirTarget::File(filename_path);
|
||||
// Update the builder
|
||||
redir_bldr = redir_bldr.with_tgt(tgt);
|
||||
} else {
|
||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
||||
err.blame(token.span().clone());
|
||||
return Err(err)
|
||||
}
|
||||
}
|
||||
redirs.push(redir_bldr.build());
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::Subshell { body, argv, redirs },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
return Ok(Some(node))
|
||||
} else {
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
});
|
||||
|
||||
ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
||||
log!(TRACE, "Parsing pipeline");
|
||||
let mut tokens_iter = tokens.iter().peekable();
|
||||
let mut node_toks = vec![];
|
||||
let mut cmds = vec![];
|
||||
|
||||
while let Some(token) = tokens_iter.peek() {
|
||||
match token.rule() {
|
||||
TkRule::AndOp | TkRule::OrOp => {
|
||||
// If there are no commands or only one, this isn't a pipeline
|
||||
match cmds.len() {
|
||||
0 | 1 => return Ok(None),
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
_ => { /* Keep going */ }
|
||||
}
|
||||
if let Some(mut cmd) = ExprNoPipeline::try_match(tokens)? {
|
||||
// Add sub-node's tokens to our tokens
|
||||
node_toks.extend(cmd.tokens().clone());
|
||||
|
||||
// Reflect changes in tokens and tokens_iter
|
||||
tokens = &tokens[cmd.len()..];
|
||||
for _ in 0..cmd.len() {
|
||||
tokens_iter.next();
|
||||
}
|
||||
|
||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
||||
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
|
||||
*cmd.flags_mut() |= NdFlag::BUILTIN;
|
||||
}
|
||||
}
|
||||
// Push sub-node
|
||||
cmds.push(cmd);
|
||||
|
||||
if tokens_iter.peek().is_none_or(|tk| !matches!(tk.rule(),TkRule::PipeOp | TkRule::ErrPipeOp)) {
|
||||
match cmds.len() {
|
||||
0 | 1 => {
|
||||
return Ok(None)
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
} else {
|
||||
if tokens_iter.peek().is_some() {
|
||||
node_toks.push(tokens_iter.next().unwrap().clone());
|
||||
tokens = &tokens[1..];
|
||||
continue
|
||||
} else {
|
||||
match cmds.len() {
|
||||
0 | 1 => return Ok(None),
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match cmds.len() {
|
||||
0 | 1 => return Ok(None),
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
}
|
||||
if node_toks.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::Pipeline { cmds },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
Ok(Some(node))
|
||||
});
|
||||
|
||||
ndrule_def!(Command, |tokens: &[Token]| {
|
||||
log!(TRACE, "Parsing command");
|
||||
let mut tokens = tokens.iter().peekable();
|
||||
let mut node_toks = vec![];
|
||||
let mut argv = vec![];
|
||||
let mut redirs = vec![];
|
||||
|
||||
while let Some(token) = tokens.peek() {
|
||||
match token.rule() {
|
||||
TkRule::AndOp | TkRule::OrOp | TkRule::PipeOp | TkRule::ErrPipeOp => {
|
||||
break
|
||||
}
|
||||
_ => { /* Keep going */ }
|
||||
}
|
||||
let token = tokens.next().unwrap();
|
||||
node_toks.push(token.clone());
|
||||
match token.rule() {
|
||||
TkRule::Ident |
|
||||
TkRule::SQuote |
|
||||
TkRule::DQuote |
|
||||
TkRule::Assign |
|
||||
TkRule::TildeSub |
|
||||
TkRule::VarSub => {
|
||||
argv.push(token.clone());
|
||||
}
|
||||
TkRule::RedirOp => {
|
||||
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
|
||||
let redir_raw = token.span().get_slice();
|
||||
let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap();
|
||||
// If there isn't an FD target, get the next token and use it as the filename
|
||||
if redir_bldr.tgt().is_none() {
|
||||
if let Some(filename) = tokens.next() {
|
||||
// Make sure it's a word and not an operator or something
|
||||
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) {
|
||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
||||
err.blame(token.span().clone());
|
||||
return Err(err)
|
||||
}
|
||||
node_toks.push(filename.clone());
|
||||
// Construct the Path object
|
||||
let filename_raw = filename.span().get_slice();
|
||||
let filename_path = PathBuf::from(filename_raw);
|
||||
let tgt = RedirTarget::File(filename_path);
|
||||
// Update the builder
|
||||
redir_bldr = redir_bldr.with_tgt(tgt);
|
||||
} else {
|
||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
||||
err.blame(token.span().clone());
|
||||
return Err(err)
|
||||
}
|
||||
}
|
||||
redirs.push(redir_bldr.build());
|
||||
}
|
||||
TkRule::Sep => break,
|
||||
_ => unreachable!("Found this rule: {:?}", token.rule())
|
||||
}
|
||||
}
|
||||
if node_toks.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
if !argv.is_empty() {
|
||||
let node = Node {
|
||||
node_rule: NdRule::Command { argv, redirs },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
Ok(Some(node))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
|
||||
ndrule_def!(Assignment, |tokens: &[Token]| {
|
||||
log!(TRACE, "Parsing assignment");
|
||||
let mut tokens = tokens.into_iter().peekable();
|
||||
let mut node_toks = vec![];
|
||||
let mut assignments = vec![];
|
||||
while tokens.peek().is_some_and(|tk| tk.rule() == TkRule::Assign) {
|
||||
let token = tokens.next().unwrap();
|
||||
node_toks.push(token.clone());
|
||||
assignments.push(token.clone());
|
||||
}
|
||||
if assignments.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
if tokens.peek().is_some() {
|
||||
let tokens_vec: Vec<Token> = tokens.into_iter().map(|token| token.clone()).collect();
|
||||
let tokens_slice = &tokens_vec;
|
||||
let cmd = Command::try_match(tokens_slice)?.map(|cmd| Box::new(cmd));
|
||||
if let Some(ref cmd) = cmd {
|
||||
node_toks.extend(cmd.tokens().clone());
|
||||
}
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::Assignment { assignments, cmd },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
return Ok(Some(node))
|
||||
} else {
|
||||
let span = get_span(&node_toks)?;
|
||||
let node = Node {
|
||||
node_rule: NdRule::Assignment { assignments, cmd: None },
|
||||
tokens: node_toks,
|
||||
span,
|
||||
flags: NdFlag::empty()
|
||||
};
|
||||
Ok(Some(node))
|
||||
}
|
||||
});
|
||||
156
src/prelude.rs
Normal file
156
src/prelude.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
pub use std::{
|
||||
io::{
|
||||
self,
|
||||
Read,
|
||||
Write
|
||||
},
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
os::fd::{
|
||||
OwnedFd,
|
||||
BorrowedFd,
|
||||
RawFd,
|
||||
FromRawFd
|
||||
},
|
||||
collections::{
|
||||
VecDeque,
|
||||
HashMap,
|
||||
},
|
||||
ffi::{
|
||||
CStr,
|
||||
CString
|
||||
},
|
||||
path::{
|
||||
Path,
|
||||
PathBuf,
|
||||
},
|
||||
process::{
|
||||
exit
|
||||
},
|
||||
};
|
||||
pub use bitflags::bitflags;
|
||||
pub use nix::{
|
||||
fcntl::{
|
||||
open,
|
||||
OFlag,
|
||||
},
|
||||
sys::{
|
||||
signal::{
|
||||
killpg,
|
||||
kill,
|
||||
signal,
|
||||
pthread_sigmask,
|
||||
SigmaskHow,
|
||||
SigSet,
|
||||
SigHandler,
|
||||
Signal
|
||||
},
|
||||
wait::{
|
||||
waitpid,
|
||||
WaitStatus as WtStat,
|
||||
WaitPidFlag as WtFlag
|
||||
},
|
||||
stat::Mode,
|
||||
memfd::memfd_create,
|
||||
},
|
||||
errno::Errno,
|
||||
unistd::{
|
||||
Pid,
|
||||
ForkResult::*,
|
||||
fork,
|
||||
getppid,
|
||||
getpid,
|
||||
getpgid,
|
||||
getpgrp,
|
||||
geteuid,
|
||||
read,
|
||||
write,
|
||||
isatty,
|
||||
tcgetpgrp,
|
||||
tcsetpgrp,
|
||||
dup,
|
||||
dup2,
|
||||
close,
|
||||
},
|
||||
libc,
|
||||
};
|
||||
pub use crate::{
|
||||
libsh::{
|
||||
term::{
|
||||
Style,
|
||||
style_text
|
||||
},
|
||||
utils::{
|
||||
LogLevel::*,
|
||||
ArgVec,
|
||||
Redir,
|
||||
RedirType,
|
||||
RedirBldr,
|
||||
RedirTarget,
|
||||
CmdRedirs,
|
||||
borrow_fd,
|
||||
trim_quotes
|
||||
},
|
||||
collections::{
|
||||
VecDequeAliases
|
||||
},
|
||||
sys::{
|
||||
self,
|
||||
write_err,
|
||||
write_out,
|
||||
c_pipe,
|
||||
execvpe
|
||||
},
|
||||
error::{
|
||||
ShErrKind,
|
||||
ShErr,
|
||||
ShResult
|
||||
},
|
||||
},
|
||||
builtin::{
|
||||
echo::echo,
|
||||
cd::cd,
|
||||
pwd::pwd,
|
||||
read::read_builtin,
|
||||
jobctl::{
|
||||
continue_job,
|
||||
jobs
|
||||
},
|
||||
BUILTINS
|
||||
},
|
||||
shellenv::{
|
||||
self,
|
||||
wait_fg,
|
||||
log_level,
|
||||
attach_tty,
|
||||
term_ctlr,
|
||||
take_term,
|
||||
jobs::{
|
||||
JobTab,
|
||||
JobID,
|
||||
write_jobs,
|
||||
read_jobs
|
||||
},
|
||||
exec_ctx::ExecFlags,
|
||||
shenv::ShEnv
|
||||
},
|
||||
execute::Executor,
|
||||
parse::{
|
||||
parse::{
|
||||
Node,
|
||||
NdRule,
|
||||
Parser,
|
||||
ParseRule
|
||||
},
|
||||
lex::{
|
||||
Span,
|
||||
Token,
|
||||
TkRule,
|
||||
Lexer,
|
||||
LexRule
|
||||
},
|
||||
},
|
||||
log,
|
||||
test,
|
||||
bp,
|
||||
};
|
||||
113
src/prompt/highlight.rs
Normal file
113
src/prompt/highlight.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use rustyline::highlight::Highlighter;
|
||||
use sys::get_bin_path;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::readline::SynHelper;
|
||||
|
||||
impl<'a> Highlighter for SynHelper<'a> {
|
||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
|
||||
let mut result = String::new();
|
||||
let mut tokens = Lexer::new(Rc::new(line.to_string())).lex().into_iter();
|
||||
let mut is_command = true;
|
||||
let mut in_array = false;
|
||||
|
||||
while let Some(token) = tokens.next() {
|
||||
let raw = token.to_string();
|
||||
match token.rule() {
|
||||
TkRule::Comment => {
|
||||
let styled = style_text(&raw, Style::BrightBlack);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::ErrPipeOp |
|
||||
TkRule::OrOp |
|
||||
TkRule::AndOp |
|
||||
TkRule::PipeOp |
|
||||
TkRule::RedirOp |
|
||||
TkRule::BgOp => {
|
||||
is_command = true;
|
||||
let styled = style_text(&raw, Style::Cyan);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::Keyword => {
|
||||
if &raw == "for" {
|
||||
in_array = true;
|
||||
}
|
||||
let styled = style_text(&raw, Style::Yellow);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::Subshell => {
|
||||
let body = &raw[1..raw.len() - 1];
|
||||
let highlighted = self.highlight(body, 0).to_string();
|
||||
let styled_o_paren = style_text("(", Style::BrightBlue);
|
||||
let styled_c_paren = style_text(")", Style::BrightBlue);
|
||||
let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}");
|
||||
is_command = false;
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::Ident => {
|
||||
if in_array {
|
||||
if &raw == "in" {
|
||||
let styled = style_text(&raw, Style::Yellow);
|
||||
result.push_str(&styled);
|
||||
} else {
|
||||
let styled = style_text(&raw, Style::Magenta);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
} else if is_command {
|
||||
if get_bin_path(&token.to_string(), self.shenv).is_some() ||
|
||||
self.shenv.logic().get_alias(&raw).is_some() ||
|
||||
self.shenv.logic().get_function(&raw).is_some() ||
|
||||
BUILTINS.contains(&raw.as_str()) {
|
||||
let styled = style_text(&raw, Style::Green);
|
||||
result.push_str(&styled);
|
||||
} else {
|
||||
let styled = style_text(&raw, Style::Red | Style::Bold);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
is_command = false;
|
||||
} else {
|
||||
result.push_str(&raw);
|
||||
}
|
||||
}
|
||||
TkRule::Sep => {
|
||||
is_command = true;
|
||||
in_array = false;
|
||||
result.push_str(&raw);
|
||||
}
|
||||
_ => {
|
||||
result.push_str(&raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::borrow::Cow::Owned(result)
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> std::borrow::Cow<'b, str> {
|
||||
let _ = default;
|
||||
std::borrow::Cow::Borrowed(prompt)
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
|
||||
std::borrow::Cow::Borrowed(hint)
|
||||
}
|
||||
|
||||
fn highlight_candidate<'c>(
|
||||
&self,
|
||||
candidate: &'c str, // FIXME should be Completer::Candidate
|
||||
completion: rustyline::CompletionType,
|
||||
) -> std::borrow::Cow<'c, str> {
|
||||
let _ = completion;
|
||||
std::borrow::Cow::Borrowed(candidate)
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, pos: usize, kind: rustyline::highlight::CmdKind) -> bool {
|
||||
let _ = (line, pos, kind);
|
||||
true
|
||||
}
|
||||
}
|
||||
46
src/prompt/mod.rs
Normal file
46
src/prompt/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::prelude::*;
|
||||
use readline::SynHelper;
|
||||
use rustyline::{config::Configurer, history::DefaultHistory, ColorMode, CompletionType, Config, DefaultEditor, EditMode, Editor};
|
||||
|
||||
pub mod readline;
|
||||
pub mod highlight;
|
||||
|
||||
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
||||
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
||||
let mut config = Config::builder()
|
||||
.max_history_size(1000).unwrap()
|
||||
.history_ignore_dups(true).unwrap()
|
||||
.completion_prompt_limit(100)
|
||||
.edit_mode(EditMode::Vi)
|
||||
.color_mode(ColorMode::Enabled)
|
||||
.tab_stop(2)
|
||||
.build();
|
||||
|
||||
let mut editor = Editor::with_config(config).unwrap();
|
||||
editor.set_completion_type(CompletionType::List);
|
||||
editor.set_helper(Some(SynHelper::new(shenv)));
|
||||
if !hist_path.is_empty() {
|
||||
editor.load_history(&PathBuf::from(hist_path)).unwrap();
|
||||
}
|
||||
editor
|
||||
}
|
||||
|
||||
pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult<String> {
|
||||
log!(TRACE, "Entering prompt");
|
||||
let prompt = &style_text("$ ", Style::Green | Style::Bold);
|
||||
let mut editor = init_rl(shenv);
|
||||
match editor.readline(prompt) {
|
||||
Ok(line) => Ok(line),
|
||||
Err(rustyline::error::ReadlineError::Eof) => {
|
||||
kill(Pid::this(), Signal::SIGQUIT)?;
|
||||
Ok(String::new())
|
||||
}
|
||||
Err(rustyline::error::ReadlineError::Interrupted) => {
|
||||
Ok(String::new())
|
||||
}
|
||||
Err(e) => {
|
||||
log!(ERROR, e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/prompt/readline.rs
Normal file
90
src/prompt/readline.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use rustyline::{completion::{Completer, FilenameCompleter}, highlight::Highlighter, hint::{Hint, Hinter}, history::{History, SearchDirection}, validate::{ValidationResult, Validator}, Helper};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct SynHelper<'a> {
|
||||
file_comp: FilenameCompleter,
|
||||
pub shenv: &'a mut ShEnv,
|
||||
pub commands: Vec<String>
|
||||
}
|
||||
|
||||
impl<'a> Helper for SynHelper<'a> {}
|
||||
|
||||
impl<'a> SynHelper<'a> {
|
||||
pub fn new(shenv: &'a mut ShEnv) -> Self {
|
||||
Self {
|
||||
file_comp: FilenameCompleter::new(),
|
||||
shenv,
|
||||
commands: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hist_search(&self, term: &str, hist: &dyn History) -> Option<String> {
|
||||
let limit = hist.len();
|
||||
let mut latest_match = None;
|
||||
for i in 0..limit {
|
||||
if let Some(hist_entry) = hist.get(i, SearchDirection::Forward).ok()? {
|
||||
if hist_entry.entry.starts_with(term) {
|
||||
latest_match = Some(hist_entry.entry.into_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
latest_match
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Validator for SynHelper<'a> {
|
||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
Ok(ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Completer for SynHelper<'a> {
|
||||
type Candidate = String;
|
||||
fn complete( &self, line: &str, pos: usize, ctx: &rustyline::Context<'_>,) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
|
||||
Ok((0,vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SynHint {
|
||||
text: String,
|
||||
styled: String
|
||||
}
|
||||
|
||||
impl SynHint {
|
||||
pub fn new(text: String) -> Self {
|
||||
let styled = style_text(&text, Style::BrightBlack);
|
||||
Self { text, styled }
|
||||
}
|
||||
pub fn empty() -> Self {
|
||||
Self { text: String::new(), styled: String::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hint for SynHint {
|
||||
fn display(&self) -> &str {
|
||||
&self.styled
|
||||
}
|
||||
fn completion(&self) -> Option<&str> {
|
||||
if !self.text.is_empty() {
|
||||
Some(&self.text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hinter for SynHelper<'a> {
|
||||
type Hint = SynHint;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
|
||||
return Some(SynHint::empty());
|
||||
if line.is_empty() {
|
||||
return None
|
||||
}
|
||||
let history = ctx.history();
|
||||
let result = self.hist_search(line, history)?;
|
||||
let window = result[line.len()..].to_string();
|
||||
Some(SynHint::new(window))
|
||||
}
|
||||
}
|
||||
116
src/shellenv/exec_ctx.rs
Normal file
116
src/shellenv/exec_ctx.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd)]
|
||||
pub struct ExecFlags: u32 {
|
||||
const NO_FORK = 0x00000001;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ExecCtx {
|
||||
redirs: Vec<Redir>,
|
||||
flags: ExecFlags,
|
||||
io_masks: IoMasks,
|
||||
saved_io: Option<SavedIo>
|
||||
}
|
||||
|
||||
impl ExecCtx {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
redirs: vec![],
|
||||
flags: ExecFlags::empty(),
|
||||
io_masks: IoMasks::new(),
|
||||
saved_io: None
|
||||
}
|
||||
}
|
||||
pub fn masks(&self) -> &IoMasks {
|
||||
&self.io_masks
|
||||
}
|
||||
pub fn push_rdr(&mut self, redir: Redir) {
|
||||
self.redirs.push(redir)
|
||||
}
|
||||
pub fn saved_io(&mut self) -> &mut Option<SavedIo> {
|
||||
&mut self.saved_io
|
||||
}
|
||||
pub fn activate_rdrs(&mut self) -> ShResult<()> {
|
||||
let mut redirs = CmdRedirs::new(core::mem::take(&mut self.redirs));
|
||||
self.redirs = vec![];
|
||||
redirs.activate()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn flags(&self) -> ExecFlags {
|
||||
self.flags
|
||||
}
|
||||
pub fn set_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags |= flag
|
||||
}
|
||||
pub fn unset_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags &= !flag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct SavedIo {
|
||||
pub stdin: RawFd,
|
||||
pub stdout: RawFd,
|
||||
pub stderr: RawFd
|
||||
}
|
||||
|
||||
impl SavedIo {
|
||||
pub fn save(stdin: RawFd, stdout: RawFd, stderr: RawFd) -> Self {
|
||||
Self { stdin, stdout, stderr }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct IoMask {
|
||||
default: RawFd,
|
||||
mask: Option<RawFd>
|
||||
}
|
||||
|
||||
impl IoMask {
|
||||
pub fn new(default: RawFd) -> Self {
|
||||
Self { default, mask: None }
|
||||
}
|
||||
pub fn new_mask(&mut self, mask: RawFd) {
|
||||
self.mask = Some(mask)
|
||||
}
|
||||
pub fn unmask(&mut self) {
|
||||
self.mask = None
|
||||
}
|
||||
pub fn get_fd(&self) -> RawFd {
|
||||
if let Some(fd) = self.mask {
|
||||
fd
|
||||
} else {
|
||||
self.default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
/// Necessary for when process file descriptors are permanently redirected using `exec`
|
||||
pub struct IoMasks {
|
||||
stdin: IoMask,
|
||||
stdout: IoMask,
|
||||
stderr: IoMask
|
||||
}
|
||||
|
||||
impl IoMasks {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stdin: IoMask::new(0),
|
||||
stdout: IoMask::new(1),
|
||||
stderr: IoMask::new(2),
|
||||
}
|
||||
}
|
||||
pub fn stdin(&self) -> &IoMask {
|
||||
&self.stdin
|
||||
}
|
||||
pub fn stdout(&self) -> &IoMask {
|
||||
&self.stdout
|
||||
}
|
||||
pub fn stderr(&self) -> &IoMask {
|
||||
&self.stderr
|
||||
}
|
||||
}
|
||||
573
src/shellenv/jobs.rs
Normal file
573
src/shellenv/jobs.rs
Normal file
@@ -0,0 +1,573 @@
|
||||
use std::{fmt, sync::{Arc, LazyLock, RwLock}};
|
||||
|
||||
use nix::unistd::setpgid;
|
||||
use shellenv::{disable_reaping, enable_reaping};
|
||||
use sys::SIG_EXIT_OFFSET;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct JobCmdFlags: u8 {
|
||||
const LONG = 0b0000_0001; // 0x01
|
||||
const PIDS = 0b0000_0010; // 0x02
|
||||
const NEW_ONLY = 0b0000_0100; // 0x04
|
||||
const RUNNING = 0b0000_1000; // 0x08
|
||||
const STOPPED = 0b0001_0000; // 0x10
|
||||
const INIT = 0b0010_0000; // 0x20
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayWaitStatus(pub WtStat);
|
||||
|
||||
impl fmt::Display for DisplayWaitStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => write!(f, "done"),
|
||||
_ => write!(f, "failed: {}", code),
|
||||
}
|
||||
}
|
||||
WtStat::Signaled(_, signal, _) => {
|
||||
write!(f, "signaled: {:?}", signal)
|
||||
}
|
||||
WtStat::Stopped(_, signal) => {
|
||||
write!(f, "stopped: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceEvent(_, signal, _) => {
|
||||
write!(f, "ptrace event: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceSyscall(_) => {
|
||||
write!(f, "ptrace syscall")
|
||||
}
|
||||
WtStat::Continued(_) => {
|
||||
write!(f, "continued")
|
||||
}
|
||||
WtStat::StillAlive => {
|
||||
write!(f, "running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The job table
|
||||
pub static JOBS: LazyLock<Arc<RwLock<JobTab>>> = LazyLock::new(|| {
|
||||
Arc::new(
|
||||
RwLock::new(
|
||||
JobTab::new()
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
pub fn write_jobs<'a,T,F: FnOnce(&mut JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let mut jobs = JOBS.write().unwrap();
|
||||
operation(&mut jobs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_jobs<'a,T,F: FnOnce(&JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let jobs = JOBS.read().unwrap();
|
||||
operation(&jobs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JobID {
|
||||
Pgid(Pid),
|
||||
Pid(Pid),
|
||||
TableID(usize),
|
||||
Command(String)
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct ChildProc {
|
||||
pgid: Pid,
|
||||
pid: Pid,
|
||||
command: Option<String>,
|
||||
stat: WtStat
|
||||
}
|
||||
|
||||
impl<'a> ChildProc {
|
||||
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
||||
let command = command.map(|str| str.to_string());
|
||||
let stat = if kill(pid,None).is_ok() {
|
||||
WtStat::StillAlive
|
||||
} else {
|
||||
WtStat::Exited(pid, 0)
|
||||
};
|
||||
let mut child = Self { pgid: pid, pid, command, stat };
|
||||
if let Some(pgid) = pgid {
|
||||
child.set_pgid(pgid)?;
|
||||
}
|
||||
Ok(child)
|
||||
}
|
||||
pub fn pid(&self) -> Pid {
|
||||
self.pid
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn cmd(&self) -> Option<&str> {
|
||||
self.command.as_ref().map(|cmd| cmd.as_str())
|
||||
}
|
||||
pub fn stat(&self) -> WtStat {
|
||||
self.stat
|
||||
}
|
||||
pub fn wait(&mut self, flags: Option<WtFlag>) -> Result<WtStat,Errno> {
|
||||
let result = waitpid(self.pid, flags);
|
||||
if let Ok(stat) = result {
|
||||
self.stat = stat
|
||||
}
|
||||
result
|
||||
}
|
||||
pub fn kill<T: Into<Option<Signal>>>(&self, sig: T) -> ShResult<()> {
|
||||
Ok(kill(self.pid, sig)?)
|
||||
}
|
||||
pub fn set_pgid(&mut self, pgid: Pid) -> ShResult<()> {
|
||||
unsafe { setpgid(self.pid, pgid)? };
|
||||
self.pgid = pgid;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_stat(&mut self, stat: WtStat) {
|
||||
self.stat = stat
|
||||
}
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.stat == WtStat::StillAlive
|
||||
}
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Stopped(..))
|
||||
}
|
||||
pub fn exited(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Exited(..))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Default for JobBldr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl JobBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { table_id: None, pgid: None, children: vec![] }
|
||||
}
|
||||
pub fn with_id(self, id: usize) -> Self {
|
||||
Self {
|
||||
table_id: Some(id),
|
||||
pgid: self.pgid,
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: Some(pgid),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid,
|
||||
children
|
||||
}
|
||||
}
|
||||
pub fn build(self) -> Job {
|
||||
Job {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Job {
|
||||
table_id: Option<usize>,
|
||||
pgid: Pid,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn set_tabid(&mut self, id: usize) {
|
||||
self.table_id = Some(id)
|
||||
}
|
||||
pub fn running(&self) -> bool {
|
||||
!self.children.iter().all(|chld| chld.exited())
|
||||
}
|
||||
pub fn tabid(&self) -> Option<usize> {
|
||||
self.table_id
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn get_cmds(&self) -> Vec<&str> {
|
||||
let mut cmds = vec![];
|
||||
for child in &self.children {
|
||||
cmds.push(child.cmd().unwrap_or_default())
|
||||
}
|
||||
cmds
|
||||
}
|
||||
pub fn set_stats(&mut self, stat: WtStat) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
pub fn get_stats(&self) -> Vec<WtStat> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.stat())
|
||||
.collect::<Vec<WtStat>>()
|
||||
}
|
||||
pub fn get_pids(&self) -> Vec<Pid> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.pid())
|
||||
.collect::<Vec<Pid>>()
|
||||
}
|
||||
pub fn children(&self) -> &[ChildProc] {
|
||||
&self.children
|
||||
}
|
||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||
&mut self.children
|
||||
}
|
||||
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
||||
let stat = match sig {
|
||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||
Signal::SIGCONT => WtStat::Continued(self.pgid),
|
||||
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
|
||||
_ => unimplemented!("{}",sig)
|
||||
};
|
||||
self.set_stats(stat);
|
||||
Ok(killpg(self.pgid, sig)?)
|
||||
}
|
||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
||||
let mut stats = vec![];
|
||||
for child in self.children.iter_mut() {
|
||||
let result = child.wait(Some(WtFlag::WUNTRACED));
|
||||
match result {
|
||||
Ok(stat) => {
|
||||
stats.push(stat);
|
||||
}
|
||||
Err(Errno::ECHILD) => break,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
}
|
||||
Ok(stats)
|
||||
}
|
||||
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
|
||||
match id {
|
||||
JobID::Pid(pid) => {
|
||||
let query_result = self.children.iter_mut().find(|chld| chld.pid == pid);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::Command(cmd) => {
|
||||
let query_result = self.children
|
||||
.iter_mut()
|
||||
.find(|chld| chld
|
||||
.cmd()
|
||||
.is_some_and(|chld_cmd| chld_cmd.contains(&cmd))
|
||||
);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::TableID(tid) => {
|
||||
if self.table_id.is_some_and(|tblid| tblid == tid) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
JobID::Pgid(pgid) => {
|
||||
if pgid == self.pgid {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn display(&self, job_order: &[usize], flags: JobCmdFlags) -> String {
|
||||
let long = flags.contains(JobCmdFlags::LONG);
|
||||
let init = flags.contains(JobCmdFlags::INIT);
|
||||
let pids = flags.contains(JobCmdFlags::PIDS);
|
||||
|
||||
let current = job_order.last();
|
||||
let prev = if job_order.len() > 2 {
|
||||
job_order.get(job_order.len() - 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let id = self.table_id.unwrap();
|
||||
let symbol = if current == self.table_id.as_ref() {
|
||||
"+"
|
||||
} else if prev == self.table_id.as_ref() {
|
||||
"-"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let padding_count = symbol.len() + id.to_string().len() + 3;
|
||||
let padding = " ".repeat(padding_count);
|
||||
|
||||
let mut output = format!("[{}]{}\t", id + 1, symbol);
|
||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||
let pid = if pids || init {
|
||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||
pid.push(' ');
|
||||
pid
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let job_stat = *self.get_stats().get(i).unwrap();
|
||||
let fmt_stat = DisplayWaitStatus(job_stat).to_string();
|
||||
|
||||
let mut stat_line = if init {
|
||||
"".to_string()
|
||||
} else {
|
||||
fmt_stat.clone()
|
||||
};
|
||||
stat_line = format!("{}{} ",pid,stat_line);
|
||||
stat_line = format!("{} {}", stat_line, cmd);
|
||||
stat_line = match job_stat {
|
||||
WtStat::Stopped(..) | WtStat::Signaled(..) => style_text(stat_line, Style::Magenta),
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => style_text(stat_line, Style::Green),
|
||||
_ => style_text(stat_line, Style::Red),
|
||||
}
|
||||
}
|
||||
_ => style_text(stat_line, Style::Cyan)
|
||||
};
|
||||
if i != self.get_cmds().len() - 1 {
|
||||
stat_line = format!("{} |",stat_line);
|
||||
}
|
||||
|
||||
let stat_final = if long {
|
||||
format!(
|
||||
"{}{} {}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
self.get_pids().get(i).unwrap(),
|
||||
stat_line
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
stat_line
|
||||
)
|
||||
};
|
||||
output.push_str(&stat_final);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
||||
}
|
||||
pub fn take_fg(&mut self) -> Option<Job> {
|
||||
self.fg.take()
|
||||
}
|
||||
fn next_open_pos(&self) -> usize {
|
||||
if let Some(position) = self.jobs.iter().position(|slot| slot.is_none()) {
|
||||
position
|
||||
} else {
|
||||
self.jobs.len()
|
||||
}
|
||||
}
|
||||
pub fn jobs(&self) -> &Vec<Option<Job>> {
|
||||
&self.jobs
|
||||
}
|
||||
pub fn jobs_mut(&mut self) -> &mut Vec<Option<Job>> {
|
||||
&mut self.jobs
|
||||
}
|
||||
pub fn curr_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
fn prune_jobs(&mut self) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
self.jobs.pop();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert_job(&mut self, mut job: Job, silent: bool) -> ShResult<usize> {
|
||||
self.prune_jobs();
|
||||
let tab_pos = if let Some(id) = job.tabid() { id } else { self.next_open_pos() };
|
||||
job.set_tabid(tab_pos);
|
||||
self.order.push(tab_pos);
|
||||
if !silent {
|
||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
||||
}
|
||||
if tab_pos == self.jobs.len() {
|
||||
self.jobs.push(Some(job))
|
||||
} else {
|
||||
self.jobs[tab_pos] = Some(job);
|
||||
}
|
||||
Ok(tab_pos)
|
||||
}
|
||||
pub fn order(&self) -> &[usize] {
|
||||
&self.order
|
||||
}
|
||||
pub fn query(&self, identifier: JobID) -> Option<&Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get(id).and_then(|job| job.as_ref())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get_mut(id).and_then(|job| job.as_mut())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_fg(&self) -> Option<&Job> {
|
||||
self.fg.as_ref()
|
||||
}
|
||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||
self.fg.as_mut()
|
||||
}
|
||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||
log!(DEBUG, "New fg job: {:?}", job);
|
||||
let pgid = job.pgid();
|
||||
self.fg = Some(job);
|
||||
attach_tty(pgid)?;
|
||||
let statuses = self.fg.as_mut().unwrap().wait_pgrp()?;
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(statuses)
|
||||
}
|
||||
pub fn fg_to_bg(&mut self, stat: WtStat) -> ShResult<()> {
|
||||
if self.fg.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
take_term()?;
|
||||
let fg = std::mem::take(&mut self.fg);
|
||||
log!(DEBUG, "Moving foreground job to background");
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, shenv: &mut ShEnv, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
super::wait_fg(job, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||
if let Some(tabid) = tabid {
|
||||
self.jobs.get_mut(tabid).and_then(Option::take)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn print_jobs(&self, flags: JobCmdFlags) -> ShResult<()> {
|
||||
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.filter(|job| job.as_ref().is_some_and(|job| self.new_updates.contains(&job.tabid().unwrap())))
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
} else {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
};
|
||||
for job in jobs.iter().flatten() {
|
||||
// Skip foreground job
|
||||
let id = job.tabid().unwrap();
|
||||
// Filter jobs based on flags
|
||||
if flags.contains(JobCmdFlags::RUNNING) && !matches!(job.get_stats().get(id).unwrap(), WtStat::StillAlive | WtStat::Continued(_)) {
|
||||
continue;
|
||||
}
|
||||
if flags.contains(JobCmdFlags::STOPPED) && !matches!(job.get_stats().get(id).unwrap(), WtStat::Stopped(_,_)) {
|
||||
continue;
|
||||
}
|
||||
// Print the job in the selected format
|
||||
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
22
src/shellenv/logic.rs
Normal file
22
src/shellenv/logic.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct LogTab {
|
||||
aliases: HashMap<String,String>,
|
||||
functions: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl LogTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
aliases: HashMap::new(),
|
||||
functions: HashMap::new()
|
||||
}
|
||||
}
|
||||
pub fn get_alias(&self,name: &str) -> Option<&str> {
|
||||
self.aliases.get(name).map(|a| a.as_str())
|
||||
}
|
||||
pub fn get_function(&self,name: &str) -> Option<&str> {
|
||||
self.functions.get(name).map(|a| a.as_str())
|
||||
}
|
||||
}
|
||||
18
src/shellenv/meta.rs
Normal file
18
src/shellenv/meta.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MetaTab {
|
||||
last_status: i32
|
||||
}
|
||||
|
||||
impl MetaTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_status: 0
|
||||
}
|
||||
}
|
||||
pub fn set_status(&mut self, code: i32) {
|
||||
self.last_status = code
|
||||
}
|
||||
pub fn last_status(&self) -> i32 {
|
||||
self.last_status
|
||||
}
|
||||
}
|
||||
111
src/shellenv/mod.rs
Normal file
111
src/shellenv/mod.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::env;
|
||||
|
||||
use jobs::Job;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub mod jobs;
|
||||
pub mod logic;
|
||||
pub mod exec_ctx;
|
||||
pub mod meta;
|
||||
pub mod shenv;
|
||||
pub mod vars;
|
||||
|
||||
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
||||
pub fn take_term() -> ShResult<()> {
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Disabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::ignore_sigchld)) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits on the current foreground job and updates the shell's last status code
|
||||
pub fn wait_fg(job: Job, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Waiting on foreground job");
|
||||
let mut code = 0;
|
||||
attach_tty(job.pgid())?;
|
||||
disable_reaping()?;
|
||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||
for status in statuses {
|
||||
match status {
|
||||
WtStat::Exited(_, exit_code) => {
|
||||
code = exit_code;
|
||||
}
|
||||
WtStat::Stopped(pid, sig) => {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
WtStat::Signaled(pid, sig, _) => {
|
||||
if sig == Signal::SIGTSTP {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
}
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
_ => { /* Do nothing */ }
|
||||
}
|
||||
}
|
||||
take_term()?;
|
||||
shenv.set_code(code);
|
||||
log!(TRACE, "exit code: {}", code);
|
||||
enable_reaping()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_level() -> crate::libsh::utils::LogLevel {
|
||||
let level = env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||
match level.to_lowercase().as_str() {
|
||||
"error" => ERROR,
|
||||
"warn" => WARN,
|
||||
"info" => INFO,
|
||||
"debug" => DEBUG,
|
||||
"trace" => TRACE,
|
||||
_ => NULL
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Enabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::handle_sigchld)) }.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() {
|
||||
return Ok(())
|
||||
}
|
||||
log!(DEBUG, "Attaching tty to pgid: {}",pgid);
|
||||
|
||||
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
||||
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
||||
}
|
||||
|
||||
let mut new_mask = SigSet::empty();
|
||||
let mut mask_bkup = SigSet::empty();
|
||||
|
||||
new_mask.add(Signal::SIGTSTP);
|
||||
new_mask.add(Signal::SIGTTIN);
|
||||
new_mask.add(Signal::SIGTTOU);
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
|
||||
|
||||
let result = unsafe { tcsetpgrp(borrow_fd(0), pgid) };
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
|
||||
|
||||
match result {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
log!(ERROR, "error while switching term control: {}",e);
|
||||
unsafe { tcsetpgrp(borrow_fd(0), getpgrp())? };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn term_ctlr() -> Pid {
|
||||
unsafe { tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp()) }
|
||||
}
|
||||
85
src/shellenv/shenv.rs
Normal file
85
src/shellenv/shenv.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ShEnv {
|
||||
vars: shellenv::vars::VarTab,
|
||||
logic: shellenv::logic::LogTab,
|
||||
meta: shellenv::meta::MetaTab,
|
||||
ctx: shellenv::exec_ctx::ExecCtx
|
||||
}
|
||||
|
||||
impl ShEnv {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vars: shellenv::vars::VarTab::new(),
|
||||
logic: shellenv::logic::LogTab::new(),
|
||||
meta: shellenv::meta::MetaTab::new(),
|
||||
ctx: shellenv::exec_ctx::ExecCtx::new(),
|
||||
}
|
||||
}
|
||||
pub fn vars(&self) -> &shellenv::vars::VarTab {
|
||||
&self.vars
|
||||
}
|
||||
pub fn vars_mut(&mut self) -> &mut shellenv::vars::VarTab {
|
||||
&mut self.vars
|
||||
}
|
||||
pub fn meta(&self) -> &shellenv::meta::MetaTab {
|
||||
&self.meta
|
||||
}
|
||||
pub fn meta_mut(&mut self) -> &mut shellenv::meta::MetaTab {
|
||||
&mut self.meta
|
||||
}
|
||||
pub fn logic(&self) -> &shellenv::logic::LogTab {
|
||||
&self.logic
|
||||
}
|
||||
pub fn logic_mut(&mut self) -> &mut shellenv::logic::LogTab {
|
||||
&mut self.logic
|
||||
}
|
||||
pub fn save_io(&mut self) -> ShResult<()> {
|
||||
let ctx = self.ctx_mut();
|
||||
let stdin = ctx.masks().stdin().get_fd();
|
||||
let stdout = ctx.masks().stdout().get_fd();
|
||||
let stderr = ctx.masks().stderr().get_fd();
|
||||
|
||||
let saved_in = dup(stdin)?;
|
||||
let saved_out = dup(stdout)?;
|
||||
let saved_err = dup(stderr)?;
|
||||
|
||||
let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err);
|
||||
*ctx.saved_io() = Some(saved_io);
|
||||
Ok(())
|
||||
}
|
||||
pub fn reset_io(&mut self) -> ShResult<()> {
|
||||
let ctx = self.ctx_mut();
|
||||
if let Some(saved) = ctx.saved_io().take() {
|
||||
let saved_in = saved.stdin;
|
||||
let saved_out = saved.stdout;
|
||||
let saved_err = saved.stderr;
|
||||
dup2(0,saved_in)?;
|
||||
close(saved_in)?;
|
||||
dup2(1,saved_out)?;
|
||||
close(saved_out)?;
|
||||
dup2(2,saved_err)?;
|
||||
close(saved_err)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn collect_redirs(&mut self, mut redirs: Vec<Redir>) {
|
||||
let ctx = self.ctx_mut();
|
||||
while let Some(redir) = redirs.pop() {
|
||||
ctx.push_rdr(redir);
|
||||
}
|
||||
}
|
||||
pub fn set_code(&mut self, code: i32) {
|
||||
self.vars_mut().set_param("?", &code.to_string());
|
||||
}
|
||||
pub fn get_code(&self) -> i32 {
|
||||
self.vars().get_param("?").parse::<i32>().unwrap_or(0)
|
||||
}
|
||||
pub fn ctx(&self) -> &shellenv::exec_ctx::ExecCtx {
|
||||
&self.ctx
|
||||
}
|
||||
pub fn ctx_mut(&mut self) -> &mut shellenv::exec_ctx::ExecCtx {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
162
src/shellenv/vars.rs
Normal file
162
src/shellenv/vars.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use std::env;
|
||||
|
||||
use nix::unistd::{gethostname, User};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct VarTab {
|
||||
env: HashMap<String,String>,
|
||||
params: HashMap<String,String>,
|
||||
pos_params: VecDeque<String>,
|
||||
vars: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl VarTab {
|
||||
pub fn new() -> Self {
|
||||
let (params,pos_params) = Self::init_params();
|
||||
Self {
|
||||
env: Self::init_env(),
|
||||
params,
|
||||
pos_params,
|
||||
vars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn init_params() -> (HashMap<String,String>, VecDeque<String>) {
|
||||
let mut args = std::env::args().collect::<Vec<String>>();
|
||||
let mut params = HashMap::new();
|
||||
let mut pos_params = VecDeque::new();
|
||||
|
||||
params.insert("@".to_string(), args.join(" "));
|
||||
params.insert("#".to_string(), args.len().to_string());
|
||||
|
||||
while let Some(arg) = args.pop() {
|
||||
pos_params.fpush(arg);
|
||||
}
|
||||
|
||||
(params,pos_params)
|
||||
}
|
||||
pub fn init_env() -> HashMap<String,String> {
|
||||
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
||||
// First, inherit any env vars from the parent process
|
||||
let mut env_vars = std::env::vars().collect::<HashMap<String,String>>();
|
||||
let term = {
|
||||
if isatty(1).unwrap() {
|
||||
if let Ok(term) = std::env::var("TERM") {
|
||||
term
|
||||
} else {
|
||||
"linux".to_string()
|
||||
}
|
||||
} else {
|
||||
"xterm-256color".to_string()
|
||||
}
|
||||
};
|
||||
let home;
|
||||
let username;
|
||||
let uid;
|
||||
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
||||
home = user.dir;
|
||||
username = user.name;
|
||||
uid = user.uid;
|
||||
} else {
|
||||
home = PathBuf::new();
|
||||
username = "unknown".into();
|
||||
uid = 0.into();
|
||||
}
|
||||
let home = pathbuf_to_string(Ok(home));
|
||||
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
||||
|
||||
env_vars.insert("IFS".into(), " \t\n".into());
|
||||
env::set_var("IFS", " \t\n");
|
||||
env_vars.insert("HOSTNAME".into(), hostname.clone());
|
||||
env::set_var("HOSTNAME", hostname);
|
||||
env_vars.insert("UID".into(), uid.to_string());
|
||||
env::set_var("UID", uid.to_string());
|
||||
env_vars.insert("PPID".into(), getppid().to_string());
|
||||
env::set_var("PPID", getppid().to_string());
|
||||
env_vars.insert("TMPDIR".into(), "/tmp".into());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
env_vars.insert("TERM".into(), term.clone());
|
||||
env::set_var("TERM", term);
|
||||
env_vars.insert("LANG".into(), "en_US.UTF-8".into());
|
||||
env::set_var("LANG", "en_US.UTF-8");
|
||||
env_vars.insert("USER".into(), username.clone());
|
||||
env::set_var("USER", username.clone());
|
||||
env_vars.insert("LOGNAME".into(), username.clone());
|
||||
env::set_var("LOGNAME", username);
|
||||
env_vars.insert("PWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("OLDPWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("HOME".into(), home.clone());
|
||||
env::set_var("HOME", home.clone());
|
||||
env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe()));
|
||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||
env_vars.insert("HIST_FILE".into(),format!("{}/.fern_hist",home));
|
||||
env::set_var("HIST_FILE",format!("{}/.fern_hist",home));
|
||||
|
||||
env_vars
|
||||
}
|
||||
pub fn env(&self) -> &HashMap<String,String> {
|
||||
&self.env
|
||||
}
|
||||
pub fn env_mut(&mut self) -> &mut HashMap<String,String> {
|
||||
&mut self.env
|
||||
}
|
||||
pub fn reset_params(&mut self) {
|
||||
self.params.clear();
|
||||
}
|
||||
pub fn unset_param(&mut self, key: &str) {
|
||||
self.params.remove(key);
|
||||
}
|
||||
pub fn set_param(&mut self, key: &str, val: &str) {
|
||||
self.params.insert(key.to_string(), val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, key: &str) -> &str {
|
||||
self.params.get(key).map(|s| s.as_str()).unwrap_or_default()
|
||||
}
|
||||
/// Push an arg to the back of the positional parameter deque
|
||||
pub fn bpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.bpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the back of the positional parameter deque
|
||||
pub fn bpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.bpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
/// Push an arg to the front of the positional parameter deque
|
||||
pub fn fpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.fpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the front of the positional parameter deque
|
||||
pub fn fpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.fpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
pub fn get_var(&self, var: &str) -> &str {
|
||||
if let Ok(idx) = var.parse::<usize>() {
|
||||
self.pos_params.get(idx).map(|p| p.as_str()).unwrap_or_default()
|
||||
} else if let Some(var) = self.env.get(var) {
|
||||
var.as_str()
|
||||
} else if let Some(param) = self.params.get(var) {
|
||||
param.as_str()
|
||||
} else {
|
||||
self.vars.get(var).map(|v| v.as_str()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
pub fn set_var(&mut self, var: &str, val: &str) {
|
||||
self.vars.insert(var.to_string(), val.to_string());
|
||||
}
|
||||
pub fn export(&mut self, var: &str, val: &str) {
|
||||
self.env.insert(var.to_string(),val.to_string());
|
||||
std::env::set_var(var, val);
|
||||
}
|
||||
}
|
||||
163
src/signal.rs
Normal file
163
src/signal.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn sig_setup() {
|
||||
unsafe {
|
||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGTSTP).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGINT).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
||||
/*
|
||||
Do nothing
|
||||
|
||||
This function exists because using SIGIGN to ignore SIGCHLD
|
||||
will cause the kernel to automatically reap the child process, which is not what we want.
|
||||
This handler will leave the signaling process as a zombie, allowing us
|
||||
to handle it somewhere else.
|
||||
|
||||
This handler is used when we want to handle SIGCHLD explicitly,
|
||||
like in the case of handling foreground jobs
|
||||
*/
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigquit(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
exit(0);
|
||||
}
|
||||
|
||||
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
||||
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||
if let Err(e) = match status {
|
||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
||||
WtStat::Continued(pid) => child_continued(pid),
|
||||
WtStat::StillAlive => break,
|
||||
_ => unimplemented!()
|
||||
} {
|
||||
eprintln!("{}",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let stat = WtStat::Signaled(pid, sig, false);
|
||||
child.set_stat(stat);
|
||||
}
|
||||
});
|
||||
if sig == Signal::SIGINT {
|
||||
take_term().unwrap()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_stopped(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let status = WtStat::Stopped(pid, sig);
|
||||
child.set_stat(status);
|
||||
} else if j.get_fg_mut().is_some_and(|fg| fg.pgid() == pgid) {
|
||||
j.fg_to_bg(WtStat::Stopped(pid, sig)).unwrap();
|
||||
}
|
||||
});
|
||||
take_term()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_continued(pid: Pid) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
job.killpg(Signal::SIGCONT).ok();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||
/*
|
||||
* Here we are going to get metadata on the exited process by querying the job table with the pid.
|
||||
* Then if the discovered job is the fg task, return terminal control to rsh
|
||||
* If it is not the fg task, print the display info for the job in the job table
|
||||
* We can reasonably assume that if it is not a foreground job, then it exists in the job table
|
||||
* If this assumption is incorrect, the code has gone wrong somewhere.
|
||||
*/
|
||||
let (
|
||||
pgid,
|
||||
is_fg,
|
||||
is_finished
|
||||
) = write_jobs(|j| {
|
||||
let fg_pgid = j.get_fg().map(|job| job.pgid());
|
||||
if let Some(job) = j.query_mut(JobID::Pid(pid)) {
|
||||
let pgid = job.pgid();
|
||||
let is_fg = fg_pgid.is_some_and(|fg| fg == pgid);
|
||||
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
||||
let is_finished = !job.running();
|
||||
|
||||
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
||||
child.set_stat(status);
|
||||
}
|
||||
|
||||
Ok((pgid, is_fg, is_finished))
|
||||
} else {
|
||||
Err(ShErr::simple(ShErrKind::InternalErr, "Job not found"))
|
||||
}
|
||||
})?;
|
||||
|
||||
if is_finished {
|
||||
if is_fg {
|
||||
take_term()?;
|
||||
} else {
|
||||
println!();
|
||||
let job_order = read_jobs(|j| j.order().to_vec());
|
||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||
if let Some(job) = result {
|
||||
println!("{}",job.display(&job_order,shellenv::jobs::JobCmdFlags::PIDS))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user