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