Skip to content

Commit

Permalink
Support running a script file
Browse files Browse the repository at this point in the history
This commit implements the missing part of yash::startup::prepare_input
which opens a script file specified on the command line.

This commit also adds a new method SystemEx::move_fd_internal which
moves a file descriptor to MIN_INTERNAL_FD or larger.
  • Loading branch information
magicant committed Sep 2, 2023
1 parent 95a98f7 commit 01c971a
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 12 deletions.
10 changes: 4 additions & 6 deletions yash-env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
use self::builtin::Builtin;
use self::function::FunctionSet;
use self::io::Fd;
use self::io::MIN_INTERNAL_FD;
use self::job::JobSet;
use self::job::Pid;
use self::job::WaitStatus;
Expand Down Expand Up @@ -66,6 +65,7 @@ use std::ops::ControlFlow::{self, Break, Continue};
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use system::SystemEx;
use yash_syntax::alias::AliasSet;

/// Whole shell execution environment.
Expand Down Expand Up @@ -287,13 +287,10 @@ impl Env {

let first_fd = self.system.open(
CStr::from_bytes_with_nul(b"/dev/tty\0").unwrap(),
crate::system::OFlag::O_RDWR,
crate::system::OFlag::O_RDWR | crate::system::OFlag::O_CLOEXEC,
crate::system::Mode::empty(),
)?;
let final_fd =
self.system
.dup(first_fd, MIN_INTERNAL_FD, crate::system::FdFlag::FD_CLOEXEC);
let _ = self.system.close(first_fd);
let final_fd = self.system.move_fd_internal(first_fd);
self.tty = final_fd.ok();
final_fd
}
Expand Down Expand Up @@ -454,6 +451,7 @@ pub mod variable;
#[cfg(test)]
mod tests {
use super::*;
use crate::io::MIN_INTERNAL_FD;
use crate::job::Job;
use crate::subshell::Subshell;
use crate::system::r#virtual::INode;
Expand Down
30 changes: 30 additions & 0 deletions yash-env/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod r#virtual;

use crate::io::Fd;
use crate::io::Stderr;
use crate::io::MIN_INTERNAL_FD;
use crate::job::Pid;
use crate::job::WaitStatus;
#[cfg(doc)]
Expand Down Expand Up @@ -430,6 +431,35 @@ pub trait Dir: Debug {
///
/// This trait provides some extension methods for `System`.
pub trait SystemEx: System {
/// Moves a file descriptor to [`MIN_INTERNAL_FD`] or larger.
///
/// This function can be used to make sure a file descriptor used by the
/// shell does not conflict with file descriptors used by the user.
/// [`MIN_INTERNAL_FD`] is the minimum file descriptor number the shell
/// uses internally. This function moves the file descriptor to a number
/// larger than or equal to [`MIN_INTERNAL_FD`].
///
/// If the given file descriptor is less than [`MIN_INTERNAL_FD`], this
/// function duplicates the file descriptor with [`System::dup`] and closes
/// the original one. Otherwise, this function does nothing.
///
/// The new file descriptor will have the CLOEXEC flag set when it is
/// dupped. Note that, if the original file descriptor has the CLOEXEC flag
/// unset and is already larger than or equal to [`MIN_INTERNAL_FD`], this
/// function will not set the CLOEXEC flag for the returned file descriptor.
///
/// This function returns the new file descriptor on success. On error, it
/// closes the original file descriptor and returns the error.
fn move_fd_internal(&mut self, from: Fd) -> nix::Result<Fd> {
if from >= MIN_INTERNAL_FD {
return Ok(from);
}

let new = self.dup(from, MIN_INTERNAL_FD, FdFlag::FD_CLOEXEC);
self.close(from).ok();
new
}

/// Switches the foreground process group with SIGTTOU blocked.
///
/// This is a convenience function to change the foreground process group
Expand Down
2 changes: 1 addition & 1 deletion yash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async fn parse_and_print(mut env: yash_env::Env) -> i32 {
// TODO run rcfile if interactive

// Prepare the input for the main read-eval loop
let input = match prepare_input(&env.system, &run.source) {
let input = match prepare_input(&mut env.system, &run.source) {
Ok(input) => input,
Err(e) => {
let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
Expand Down
35 changes: 30 additions & 5 deletions yash/src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
use self::args::Run;
use self::args::Source;
use std::cell::Cell;
use std::ffi::CString;
use std::rc::Rc;
use thiserror::Error;
use yash_env::input::FdReader;
use yash_env::io::Fd;
use yash_env::option::Option::Interactive;
use yash_env::option::State;
use yash_env::system::Errno;
use yash_env::system::Mode;
use yash_env::system::OFlag;
use yash_env::system::SystemEx;
use yash_env::SharedSystem;
use yash_env::System;
#[cfg(doc)]
Expand Down Expand Up @@ -67,18 +71,18 @@ pub struct SourceInput<'a> {
/// Error returned by [`prepare_input`].
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot open script file '{path}': {errno}")]
pub struct PrepareInputError {
pub struct PrepareInputError<'a> {
/// Raw error value returned by the underlying system call.
pub errno: Errno,
/// Path of the script file that could not be opened.
pub path: String,
pub path: &'a str,
}

/// Prepares the input for the shell.
pub fn prepare_input<'a>(
system: &SharedSystem,
system: &mut SharedSystem,
source: &'a Source,
) -> Result<SourceInput<'a>, PrepareInputError> {
) -> Result<SourceInput<'a>, PrepareInputError<'a>> {
match source {
Source::Stdin => {
let mut input = Box::new(FdReader::new(Fd::STDIN, system.clone()));
Expand All @@ -90,7 +94,28 @@ pub fn prepare_input<'a>(
verbose: Some(echo),
})
}
Source::File { .. } => todo!(),

Source::File { path } => {
let c_path = CString::new(path.as_str()).map_err(|_| PrepareInputError {
errno: Errno::EILSEQ,
path,
})?;
let fd = system
.open(&c_path, OFlag::O_RDONLY | OFlag::O_CLOEXEC, Mode::empty())
.and_then(|fd| system.move_fd_internal(fd))
.map_err(|errno| PrepareInputError { errno, path })?;
let mut input = Box::new(FdReader::new(fd, system.clone()));
let echo = Rc::new(Cell::new(State::Off));
input.set_echo(Some(Rc::clone(&echo)));
Ok(SourceInput {
input,
source: SyntaxSource::CommandFile {
path: path.to_owned(),
},
verbose: Some(echo),
})
}

Source::String(command) => {
let input = Box::new(Memory::new(command));
Ok(SourceInput {
Expand Down

0 comments on commit 01c971a

Please sign in to comment.