Initial commit for fern

This commit is contained in:
2025-03-02 16:26:28 -05:00
parent 56917524c3
commit a9a9642a2a
40 changed files with 5281 additions and 0 deletions

116
src/shellenv/exec_ctx.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}