Skip to main content

yash_semantics/
redir.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Redirection semantics.
18//!
19//! # Effect of redirections
20//!
21//! A [redirection](Redir) modifies its [target file
22//! descriptor](Redir::fd_or_default) on the basis of its [body](RedirBody).
23//!
24//! If the body is `Normal`, the operand word is [expanded](crate::expansion)
25//! first. Then, the [operator](RedirOp) defines the next behavior:
26//!
27//! - `FileIn`: Opens a file for reading, regarding the expanded field as a
28//!   pathname.
29//! - `FileInOut`: Likewise, opens a file for reading and writing.
30//! - `FileOut`, `FileClobber`: Likewise, opens a file for writing and clears
31//!   the file content.  Creates an empty regular file if the file does not
32//!   exist.
33//! - `FileAppend`: Likewise, opens a file for appending.
34//!   Creates an empty regular file if the file does not exist.
35//! - `FdIn`: Copies a file descriptor, regarding the expanded field as a
36//!   non-negative decimal integer denoting a readable file descriptor to copy
37//!   from. Closes the target file descriptor if the field is a single hyphen
38//!   (`-`) instead.
39//! - `FdOut`: Likewise, copies or closes a file descriptor, but the source file
40//!   descriptor must be writable instead of readable.
41//! - `Pipe`: Opens a pipe, regarding the expanded field as a
42//!   non-negative decimal integer denoting a file descriptor to become the
43//!   reading end of the pipe. The target file descriptor will be the writing
44//!   end. (TODO: `Pipe` is not yet implemented.)
45//! - `String`: Opens a readable file descriptor from which you can read the
46//!   expanded field followed by a newline character. (TODO: `String` is not yet
47//!   implemented.)
48//!
49//! If the `Clobber` [shell option](yash_env::option::Option) is off and a
50//! regular file exists at the target pathname, then `FileOut` will fail.
51//!
52//! If the body is `HereDoc`, the redirection opens a readable file descriptor
53//! that yields [expansion](crate::expansion) of the content. The current
54//! implementation uses an unnamed temporary file for the file descriptor, but
55//! we may change the behavior in the future.
56//!
57//! # Performing redirections
58//!
59//! To perform redirections, you need to wrap an [`Env`] in a [`RedirGuard`]
60//! first. Then, you call [`RedirGuard::perform_redir`] to affect the target
61//! file descriptor. When you drop the `RedirGuard`, it undoes the effect to the
62//! file descriptor. See the documentation for [`RedirGuard`] for details.
63//!
64//! # The CLOEXEC flag
65//!
66//! The shell may open file descriptors to accomplish its tasks. For example,
67//! the dot built-in opens an FD to read a script file. Such FDs should be
68//! invisible to the user, so the shell should set the CLOEXEC flag on the FDs.
69//!
70//! When the user tries to redirect an FD with the CLOEXEC flag, it fails with a
71//! [`ReservedFd`](ErrorCause::ReservedFd) error to protect the FD from being
72//! overwritten.
73//!
74//! Note that POSIX requires FDs between 0 and 9 (inclusive) to be available for
75//! the user. The shell should move an FD to 10 or above before setting its
76//! CLOEXEC flag. Also note that the above described behavior about the CLOEXEC
77//! flag is specific to this implementation.
78
79use crate::Runtime;
80use crate::expansion::expand_text;
81use crate::expansion::expand_word;
82use crate::xtrace::XTrace;
83use enumset::EnumSet;
84use enumset::enum_set;
85use std::borrow::Cow;
86use std::ffi::CString;
87use std::ffi::NulError;
88use std::fmt::Write;
89use std::num::ParseIntError;
90use std::ops::Deref;
91use std::ops::DerefMut;
92use thiserror::Error;
93use yash_env::Env;
94use yash_env::io::Fd;
95use yash_env::io::MIN_INTERNAL_FD;
96use yash_env::option::Option::Clobber;
97use yash_env::option::State::Off;
98use yash_env::semantics::ExitStatus;
99use yash_env::semantics::Field;
100use yash_env::system::{
101    Close, Dup, Errno, Fcntl, FdFlag, FileType, Fstat, Mode, OfdAccess, Open, OpenFlag,
102};
103use yash_quote::quoted;
104use yash_syntax::source::Location;
105use yash_syntax::source::pretty::{Report, ReportType, Snippet};
106use yash_syntax::syntax::HereDoc;
107use yash_syntax::syntax::Redir;
108use yash_syntax::syntax::RedirBody;
109use yash_syntax::syntax::RedirOp;
110use yash_syntax::syntax::Unquote;
111
112/// Record of saving an open file description in another file descriptor.
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114struct SavedFd {
115    /// File descriptor by which the original open file description was
116    /// previously accessible.
117    original: Fd,
118    /// Temporary file descriptor that remembers the original open file
119    /// description.
120    save: Option<Fd>,
121}
122
123/// Types of errors that may occur in the redirection.
124#[derive(Clone, Debug, Eq, Error, PartialEq)]
125#[non_exhaustive]
126pub enum ErrorCause {
127    /// Expansion error.
128    #[error(transparent)]
129    Expansion(#[from] crate::expansion::ErrorCause),
130
131    /// Pathname containing a nul byte.
132    #[error(transparent)]
133    NulByte(#[from] NulError),
134
135    /// The target file descriptor could not be modified for the redirection.
136    #[error("{1}")]
137    FdNotOverwritten(Fd, Errno),
138
139    /// Use of an FD reserved by the shell
140    ///
141    /// This error occurs when a redirection tries to modify an existing FD with
142    /// the CLOEXEC flag set. See the [module documentation](self) for details.
143    #[error("file descriptor {0} is reserved by the shell")]
144    ReservedFd(Fd),
145
146    /// Error while opening a file.
147    ///
148    /// The `CString` is the pathname of the file that could not be opened.
149    #[error("cannot open file '{}': {}", .0.to_string_lossy(), .1)]
150    OpenFile(CString, Errno),
151
152    /// Operand of `<&` or `>&` that cannot be parsed as an integer.
153    #[error("{0} is not a valid file descriptor: {1}")]
154    MalformedFd(String, ParseIntError),
155
156    /// `<&` applied to an unreadable file descriptor
157    #[error("{0} is not a readable file descriptor")]
158    UnreadableFd(Fd),
159
160    /// `>&` applied to an unwritable file descriptor
161    #[error("{0} is not a writable file descriptor")]
162    UnwritableFd(Fd),
163
164    /// Error preparing a temporary file to save here-document content
165    #[error("cannot prepare temporary file for here-document: {0}")]
166    TemporaryFileUnavailable(Errno),
167
168    /// Pipe redirection is used, which is not yet implemented.
169    #[error("pipe redirection is not yet implemented")]
170    UnsupportedPipeRedirection,
171
172    /// Here-string redirection is used, which is not yet implemented.
173    #[error("here-string redirection is not yet implemented")]
174    UnsupportedHereString,
175}
176
177impl ErrorCause {
178    /// Returns an error message describing the error.
179    #[must_use]
180    pub fn message(&self) -> &str {
181        // TODO Localize
182        use ErrorCause::*;
183        match self {
184            Expansion(e) => e.message(),
185            NulByte(_) => "nul byte found in the pathname",
186            FdNotOverwritten(_, _) | ReservedFd(_) => "cannot redirect the file descriptor",
187            OpenFile(_, _) => "cannot open the file",
188            MalformedFd(_, _) => "not a valid file descriptor",
189            UnreadableFd(_) | UnwritableFd(_) => "cannot copy file descriptor",
190            TemporaryFileUnavailable(_) => "cannot prepare here-document",
191            UnsupportedPipeRedirection | UnsupportedHereString => "unsupported redirection",
192        }
193    }
194
195    /// Returns a label for annotating the error location.
196    #[must_use]
197    pub fn label(&self) -> Cow<'_, str> {
198        // TODO Localize
199        use ErrorCause::*;
200        match self {
201            Expansion(e) => e.label(),
202            NulByte(_) => "pathname should not contain a nul byte".into(),
203            FdNotOverwritten(_, errno) => errno.to_string().into(),
204            ReservedFd(fd) => format!("file descriptor {fd} reserved by shell").into(),
205            OpenFile(path, errno) => format!("{}: {}", path.to_string_lossy(), errno).into(),
206            MalformedFd(value, error) => format!("{value}: {error}").into(),
207            UnreadableFd(fd) => format!("{fd}: not a readable file descriptor").into(),
208            UnwritableFd(fd) => format!("{fd}: not a writable file descriptor").into(),
209            TemporaryFileUnavailable(errno) => errno.to_string().into(),
210            UnsupportedPipeRedirection => "pipe redirection is not yet implemented".into(),
211            UnsupportedHereString => "here-string redirection is not yet implemented".into(),
212        }
213    }
214}
215
216/// Explanation of a redirection error.
217#[derive(Clone, Debug, Eq, Error, PartialEq)]
218#[error("{cause}")]
219pub struct Error {
220    pub cause: ErrorCause,
221    pub location: Location,
222}
223
224impl From<crate::expansion::Error> for Error {
225    fn from(e: crate::expansion::Error) -> Self {
226        Error {
227            cause: e.cause.into(),
228            location: e.location,
229        }
230    }
231}
232
233impl Error {
234    /// Returns a report for the error.
235    #[must_use]
236    pub fn to_report(&self) -> Report<'_> {
237        let mut report = Report::new();
238        report.r#type = ReportType::Error;
239        report.title = self.cause.message().into();
240        report.snippets = Snippet::with_primary_span(&self.location, self.cause.label());
241        report
242    }
243}
244
245/// Converts the error into a report by calling [`Error::to_report`].
246impl<'a> From<&'a Error> for Report<'a> {
247    #[inline(always)]
248    fn from(error: &'a Error) -> Self {
249        error.to_report()
250    }
251}
252
253/// Intermediate state of a redirected file descriptor
254#[derive(Debug)]
255enum FdSpec {
256    /// File descriptor specifically opened for redirection
257    Owned(Fd),
258    /// Existing file descriptor
259    Borrowed(Fd),
260    /// Closed file descriptor
261    Closed,
262}
263
264impl FdSpec {
265    fn as_fd(&self) -> Option<Fd> {
266        match self {
267            &FdSpec::Owned(fd) | &FdSpec::Borrowed(fd) => Some(fd),
268            &FdSpec::Closed => None,
269        }
270    }
271
272    fn close<S: Close>(self, system: &mut S) {
273        match self {
274            FdSpec::Owned(fd) => {
275                let _ = system.close(fd);
276            }
277            FdSpec::Borrowed(_) | FdSpec::Closed => (),
278        }
279    }
280}
281
282const MODE: Mode = Mode::ALL_READ.union(Mode::ALL_WRITE);
283
284fn is_cloexec<S: Fcntl>(env: &Env<S>, fd: Fd) -> bool {
285    matches!(env.system.fcntl_getfd(fd), Ok(flags) if flags.contains(FdFlag::CloseOnExec))
286}
287
288fn into_c_string_value_and_origin(field: Field) -> Result<(CString, Location), Error> {
289    match CString::new(field.value) {
290        Ok(value) => Ok((value, field.origin)),
291        Err(e) => Err(Error {
292            cause: ErrorCause::NulByte(e),
293            location: field.origin,
294        }),
295    }
296}
297
298/// Opens a file for redirection.
299fn open_file<S: Open>(
300    env: &mut Env<S>,
301    access: OfdAccess,
302    flags: EnumSet<OpenFlag>,
303    path: Field,
304) -> Result<(FdSpec, Location), Error> {
305    let system = &mut env.system;
306    let (path, origin) = into_c_string_value_and_origin(path)?;
307    match system.open(&path, access, flags, MODE) {
308        Ok(fd) => Ok((FdSpec::Owned(fd), origin)),
309        Err(errno) => Err(Error {
310            cause: ErrorCause::OpenFile(path, errno),
311            location: origin,
312        }),
313    }
314}
315
316/// Opens a file for writing with the `noclobber` option.
317fn open_file_noclobber<S>(env: &mut Env<S>, path: Field) -> Result<(FdSpec, Location), Error>
318where
319    S: Open + Fstat + Close,
320{
321    let system = &mut env.system;
322    let (path, origin) = into_c_string_value_and_origin(path)?;
323
324    const FLAGS_EXCL: EnumSet<OpenFlag> = enum_set!(OpenFlag::Create | OpenFlag::Exclusive);
325    match system.open(&path, OfdAccess::WriteOnly, FLAGS_EXCL, MODE) {
326        Ok(fd) => return Ok((FdSpec::Owned(fd), origin)),
327        Err(Errno::EEXIST) => (),
328        Err(errno) => {
329            return Err(Error {
330                cause: ErrorCause::OpenFile(path, errno),
331                location: origin,
332            });
333        }
334    }
335
336    // Okay, it seems there is an existing file. Try opening it.
337    match system.open(&path, OfdAccess::WriteOnly, EnumSet::empty(), MODE) {
338        Ok(fd) => {
339            let is_regular = system
340                .fstat(fd)
341                .is_ok_and(|stat| stat.r#type == FileType::Regular);
342            if is_regular {
343                // We opened the FD without the O_CREAT flag, so somebody else
344                // must have created this file. Failure.
345                let _: Result<_, _> = system.close(fd);
346                Err(Error {
347                    cause: ErrorCause::OpenFile(path, Errno::EEXIST),
348                    location: origin,
349                })
350            } else {
351                Ok((FdSpec::Owned(fd), origin))
352            }
353        }
354        Err(Errno::ENOENT) => {
355            // A file existed on the first open but not on the second. There are
356            // two possibilities: One is that a file existed on the first open
357            // call and had been removed before the second. In this case, we
358            // might be able to create another if we start over. The other is
359            // that there is a symbolic link pointing to nothing, in which case
360            // retrying would only lead to the same result. Since there is no
361            // reliable way to tell the situations apart atomically, we give up
362            // and return the initial error.
363            Err(Error {
364                cause: ErrorCause::OpenFile(path, Errno::EEXIST),
365                location: origin,
366            })
367        }
368        Err(errno) => Err(Error {
369            cause: ErrorCause::OpenFile(path, errno),
370            location: origin,
371        }),
372    }
373}
374
375/// Parses the target of `<&` and `>&`.
376fn copy_fd<S: Fcntl>(
377    env: &mut Env<S>,
378    target: Field,
379    expected_access: OfdAccess,
380) -> Result<(FdSpec, Location), Error> {
381    if target.value == "-" {
382        return Ok((FdSpec::Closed, target.origin));
383    }
384
385    // Parse the string as an integer
386    let fd = match target.value.parse() {
387        Ok(number) => Fd(number),
388        Err(error) => {
389            return Err(Error {
390                cause: ErrorCause::MalformedFd(target.value, error),
391                location: target.origin,
392            });
393        }
394    };
395
396    // Check if the FD is really readable or writable
397    fn is_fd_valid<S: Fcntl>(system: &S, fd: Fd, expected_access: OfdAccess) -> bool {
398        system
399            .ofd_access(fd)
400            .is_ok_and(|access| access == expected_access || access == OfdAccess::ReadWrite)
401    }
402    fn fd_mode_error(
403        fd: Fd,
404        expected_access: OfdAccess,
405        target: Field,
406    ) -> Result<(FdSpec, Location), Error> {
407        let cause = match expected_access {
408            OfdAccess::ReadOnly => ErrorCause::UnreadableFd(fd),
409            OfdAccess::WriteOnly => ErrorCause::UnwritableFd(fd),
410            _ => unreachable!("unexpected expected access {expected_access:?}"),
411        };
412        let location = target.origin;
413        Err(Error { cause, location })
414    }
415    if !is_fd_valid(&env.system, fd, expected_access) {
416        return fd_mode_error(fd, expected_access, target);
417    }
418
419    // Ensure the FD has no CLOEXEC flag
420    if is_cloexec(env, fd) {
421        return Err(Error {
422            cause: ErrorCause::ReservedFd(fd),
423            location: target.origin,
424        });
425    }
426
427    Ok((FdSpec::Borrowed(fd), target.origin))
428}
429
430/// Opens the file for a normal redirection.
431async fn open_normal<S>(
432    env: &mut Env<S>,
433    operator: RedirOp,
434    operand: Field,
435) -> Result<(FdSpec, Location), Error>
436where
437    S: Close + Fstat + Fcntl + Open,
438{
439    use RedirOp::*;
440    match operator {
441        FileIn => open_file(env, OfdAccess::ReadOnly, EnumSet::empty(), operand),
442        FileOut if env.options.get(Clobber) == Off => open_file_noclobber(env, operand),
443        FileOut | FileClobber => open_file(
444            env,
445            OfdAccess::WriteOnly,
446            OpenFlag::Create | OpenFlag::Truncate,
447            operand,
448        ),
449        FileAppend => open_file(
450            env,
451            OfdAccess::WriteOnly,
452            OpenFlag::Create | OpenFlag::Append,
453            operand,
454        ),
455        FileInOut => open_file(env, OfdAccess::ReadWrite, OpenFlag::Create.into(), operand),
456        FdIn => copy_fd(env, operand, OfdAccess::ReadOnly),
457        FdOut => copy_fd(env, operand, OfdAccess::WriteOnly),
458        Pipe => Err(Error {
459            cause: ErrorCause::UnsupportedPipeRedirection,
460            location: operand.origin,
461        }),
462        String => Err(Error {
463            cause: ErrorCause::UnsupportedHereString,
464            location: operand.origin,
465        }),
466    }
467}
468
469/// Prepares xtrace for a normal redirection.
470fn trace_normal(xtrace: Option<&mut XTrace>, target_fd: Fd, operator: RedirOp, operand: &Field) {
471    if let Some(xtrace) = xtrace {
472        write!(
473            xtrace.redirs(),
474            "{}{}{} ",
475            target_fd,
476            operator,
477            quoted(&operand.value)
478        )
479        .unwrap();
480    }
481}
482
483/// Prepares xtrace for a here-document.
484fn trace_here_doc(xtrace: Option<&mut XTrace>, target_fd: Fd, here_doc: &HereDoc, content: &str) {
485    if let Some(xtrace) = xtrace {
486        write!(xtrace.redirs(), "{target_fd}{here_doc} ").unwrap();
487        let (delimiter, _is_quoted) = here_doc.delimiter.unquote();
488        writeln!(xtrace.here_doc_contents(), "{content}{delimiter}").unwrap();
489    }
490}
491
492mod here_doc;
493
494/// Performs a redirection.
495#[allow(clippy::await_holding_refcell_ref)]
496async fn perform<S>(
497    env: &mut Env<S>,
498    redir: &Redir,
499    xtrace: Option<&mut XTrace>,
500) -> Result<(SavedFd, Option<ExitStatus>), Error>
501where
502    S: Runtime + 'static,
503{
504    let target_fd = redir.fd_or_default();
505
506    // Make sure target_fd doesn't have the CLOEXEC flag
507    if is_cloexec(env, target_fd) {
508        return Err(Error {
509            cause: ErrorCause::ReservedFd(target_fd),
510            location: redir.body.operand().location.clone(),
511        });
512    }
513
514    // Save the current open file description at target_fd to a new FD
515    let save = match env
516        .system
517        .dup(target_fd, MIN_INTERNAL_FD, FdFlag::CloseOnExec.into())
518    {
519        Ok(save_fd) => Some(save_fd),
520        Err(Errno::EBADF) => None,
521        Err(errno) => {
522            return Err(Error {
523                cause: ErrorCause::FdNotOverwritten(target_fd, errno),
524                location: redir.body.operand().location.clone(),
525            });
526        }
527    };
528
529    // Prepare an FD from the redirection body
530    let (fd_spec, location, exit_status) = match &redir.body {
531        RedirBody::Normal { operator, operand } => {
532            // TODO perform pathname expansion if applicable
533            let (expansion, exit_status) = expand_word(env, operand).await?;
534            trace_normal(xtrace, target_fd, *operator, &expansion);
535            let (fd, location) = open_normal(env, *operator, expansion).await?;
536            (fd, location, exit_status)
537        }
538        RedirBody::HereDoc(here_doc) => {
539            let content_ref = here_doc.content.get();
540            let content = content_ref.map(Cow::Borrowed).unwrap_or_default();
541            let (content, exit_status) = expand_text(env, &content).await?;
542            trace_here_doc(xtrace, target_fd, here_doc, &content);
543            let location = here_doc.delimiter.location.clone();
544            match here_doc::open_fd(env, content).await {
545                Ok(fd) => (FdSpec::Owned(fd), location, exit_status),
546                Err(cause) => return Err(Error { cause, location }),
547            }
548        }
549    };
550
551    if let Some(fd) = fd_spec.as_fd() {
552        if fd != target_fd {
553            let dup_result = env.system.dup2(fd, target_fd);
554            fd_spec.close(&mut env.system);
555            match dup_result {
556                Ok(new_fd) => assert_eq!(new_fd, target_fd),
557                Err(errno) => {
558                    return Err(Error {
559                        cause: ErrorCause::FdNotOverwritten(target_fd, errno),
560                        location,
561                    });
562                }
563            }
564        }
565    } else {
566        let _: Result<(), Errno> = env.system.close(target_fd);
567    }
568
569    let original = target_fd;
570    Ok((SavedFd { original, save }, exit_status))
571}
572
573/// `Env` wrapper for performing redirections.
574///
575/// This is an RAII-style wrapper of [`Env`] in which redirections are
576/// performed. A `RedirGuard` keeps track of file descriptors affected by
577/// redirections so that we can restore the file descriptors to the state before
578/// performing the redirections.
579///
580/// There are two ways to clear file descriptors saved in the `RedirGuard`.  One
581/// is [`undo_redirs`](Self::undo_redirs), which restores the file descriptors
582/// to the original state, and the other is
583/// [`preserve_redirs`](Self::preserve_redirs), which removes the saved file
584/// descriptors without restoring the state and thus makes the effect of the
585/// redirections permanent.
586///
587/// When an instance of `RedirGuard` is dropped, `undo_redirs` is implicitly
588/// called. That means you need to call `preserve_redirs` explicitly to preserve
589/// the redirections' effect.
590#[derive(Debug)]
591pub struct RedirGuard<'e, S: Close + Dup> {
592    /// Environment in which redirections are performed.
593    env: &'e mut yash_env::Env<S>,
594    /// Records of file descriptors that have been modified by redirections.
595    saved_fds: Vec<SavedFd>,
596}
597
598impl<S: Close + Dup> Deref for RedirGuard<'_, S> {
599    type Target = yash_env::Env<S>;
600    fn deref(&self) -> &yash_env::Env<S> {
601        self.env
602    }
603}
604
605impl<S: Close + Dup> DerefMut for RedirGuard<'_, S> {
606    fn deref_mut(&mut self) -> &mut yash_env::Env<S> {
607        self.env
608    }
609}
610
611impl<S: Close + Dup> std::ops::Drop for RedirGuard<'_, S> {
612    fn drop(&mut self) {
613        self.undo_redirs()
614    }
615}
616
617impl<'e, S: Close + Dup> RedirGuard<'e, S> {
618    /// Creates a new `RedirGuard`.
619    pub fn new(env: &'e mut yash_env::Env<S>) -> Self {
620        let saved_fds = Vec::new();
621        RedirGuard { env, saved_fds }
622    }
623
624    /// Performs a redirection.
625    ///
626    /// If successful, this function saves internally a backing copy of the file
627    /// descriptor affected by the redirection, and returns the exit status of
628    /// the last command substitution performed during the redirection, if any.
629    ///
630    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
631    /// and the expanded operands are written to it.
632    pub async fn perform_redir(
633        &mut self,
634        redir: &Redir,
635        xtrace: Option<&mut XTrace>,
636    ) -> Result<Option<ExitStatus>, Error>
637    where
638        S: Runtime + 'static,
639    {
640        let (saved_fd, exit_status) = perform(self, redir, xtrace).await?;
641        self.saved_fds.push(saved_fd);
642        Ok(exit_status)
643    }
644
645    /// Performs redirections.
646    ///
647    /// This is a convenience function for [performing
648    /// redirection](Self::perform_redir) for each iterator item.
649    ///
650    /// If the redirection fails for an item, the remainders are ignored, but
651    /// the effects of the preceding items are not canceled.
652    ///
653    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
654    /// and the expanded operands are written to it.
655    pub async fn perform_redirs<'a, I>(
656        &mut self,
657        redirs: I,
658        mut xtrace: Option<&mut XTrace>,
659    ) -> Result<Option<ExitStatus>, Error>
660    where
661        S: Runtime + 'static,
662        I: IntoIterator<Item = &'a Redir>,
663    {
664        let mut exit_status = None;
665        for redir in redirs {
666            let new_exit_status = self.perform_redir(redir, xtrace.as_deref_mut()).await?;
667            exit_status = new_exit_status.or(exit_status);
668        }
669        Ok(exit_status)
670    }
671
672    /// Undoes the effect of the redirections.
673    ///
674    /// This function restores the file descriptors affected by redirections to
675    /// the original state and closes internal backing file descriptors, which
676    /// were used for restoration and are no longer needed.
677    pub fn undo_redirs(&mut self) {
678        for SavedFd { original, save } in self.saved_fds.drain(..).rev() {
679            if let Some(save) = save {
680                assert_ne!(save, original);
681                let _: Result<_, _> = self.env.system.dup2(save, original);
682                let _: Result<_, _> = self.env.system.close(save);
683            } else {
684                let _: Result<_, _> = self.env.system.close(original);
685            }
686        }
687    }
688
689    /// Makes the redirections permanent.
690    ///
691    /// This function closes internal backing file descriptors without restoring
692    /// the original file descriptor state.
693    pub fn preserve_redirs(&mut self) {
694        for SavedFd { original: _, save } in self.saved_fds.drain(..) {
695            if let Some(save) = save {
696                let _: Result<_, _> = self.env.system.close(save);
697            }
698        }
699    }
700}
701
702#[cfg(test)]
703mod tests {
704    use super::*;
705    use crate::tests::echo_builtin;
706    use crate::tests::return_builtin;
707    use assert_matches::assert_matches;
708    use futures_util::FutureExt;
709    use std::cell::RefCell;
710    use std::rc::Rc;
711    use yash_env::Env;
712    use yash_env::VirtualSystem;
713    use yash_env::system::Read as _;
714    use yash_env::system::Write as _;
715    use yash_env::system::resource::LimitPair;
716    use yash_env::system::resource::Resource;
717    use yash_env::system::resource::SetRlimit as _;
718    use yash_env::system::r#virtual::FileBody;
719    use yash_env::system::r#virtual::Inode;
720    use yash_env_test_helper::in_virtual_system;
721    use yash_syntax::syntax::Text;
722
723    /// Returns a virtual system with a file descriptor limit.
724    fn system_with_nofile_limit() -> VirtualSystem {
725        let system = VirtualSystem::new();
726        system
727            .setrlimit(
728                Resource::NOFILE,
729                LimitPair {
730                    soft: 1024,
731                    hard: 1024,
732                },
733            )
734            .unwrap();
735        system
736    }
737
738    #[test]
739    fn basic_file_in_redirection() {
740        let system = system_with_nofile_limit();
741        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
742        let mut state = system.state.borrow_mut();
743        state.file_system.save("foo", file).unwrap();
744        drop(state);
745        let mut env = Env::with_system(system);
746        let mut env = RedirGuard::new(&mut env);
747        let redir = "3< foo".parse().unwrap();
748        let result = env
749            .perform_redir(&redir, None)
750            .now_or_never()
751            .unwrap()
752            .unwrap();
753        assert_eq!(result, None);
754
755        let mut buffer = [0; 4];
756        let read_count = env.system.read(Fd(3), &mut buffer).unwrap();
757        assert_eq!(read_count, 3);
758        assert_eq!(buffer, [42, 123, 254, 0]);
759    }
760
761    #[test]
762    fn moving_fd() {
763        let system = system_with_nofile_limit();
764        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
765        let mut state = system.state.borrow_mut();
766        state.file_system.save("foo", file).unwrap();
767        drop(state);
768        let mut env = Env::with_system(system);
769        let mut env = RedirGuard::new(&mut env);
770        let redir = "< foo".parse().unwrap();
771        env.perform_redir(&redir, None)
772            .now_or_never()
773            .unwrap()
774            .unwrap();
775
776        let mut buffer = [0; 4];
777        let read_count = env.system.read(Fd::STDIN, &mut buffer).unwrap();
778        assert_eq!(read_count, 3);
779        assert_eq!(buffer, [42, 123, 254, 0]);
780
781        let e = env.system.read(Fd(3), &mut buffer).unwrap_err();
782        assert_eq!(e, Errno::EBADF);
783    }
784
785    #[test]
786    fn saving_and_undoing_fd() {
787        let system = system_with_nofile_limit();
788        let mut state = system.state.borrow_mut();
789        state.file_system.save("file", Rc::default()).unwrap();
790        state
791            .file_system
792            .get("/dev/stdin")
793            .unwrap()
794            .borrow_mut()
795            .body = FileBody::new([17]);
796        drop(state);
797        let mut env = Env::with_system(system);
798        let mut redir_env = RedirGuard::new(&mut env);
799        let redir = "< file".parse().unwrap();
800        redir_env
801            .perform_redir(&redir, None)
802            .now_or_never()
803            .unwrap()
804            .unwrap();
805        redir_env.undo_redirs();
806        drop(redir_env);
807
808        let mut buffer = [0; 2];
809        let read_count = env.system.read(Fd::STDIN, &mut buffer).unwrap();
810        assert_eq!(read_count, 1);
811        assert_eq!(buffer[0], 17);
812    }
813
814    #[test]
815    fn preserving_fd() {
816        let system = system_with_nofile_limit();
817        let mut state = system.state.borrow_mut();
818        state.file_system.save("file", Rc::default()).unwrap();
819        state
820            .file_system
821            .get("/dev/stdin")
822            .unwrap()
823            .borrow_mut()
824            .body = FileBody::new([17]);
825        drop(state);
826        let mut env = Env::with_system(system);
827        let mut redir_env = RedirGuard::new(&mut env);
828        let redir = "< file".parse().unwrap();
829        redir_env
830            .perform_redir(&redir, None)
831            .now_or_never()
832            .unwrap()
833            .unwrap();
834        redir_env.preserve_redirs();
835        drop(redir_env);
836
837        let mut buffer = [0; 2];
838        let read_count = env.system.read(Fd::STDIN, &mut buffer).unwrap();
839        assert_eq!(read_count, 0);
840        let e = env.system.read(MIN_INTERNAL_FD, &mut buffer).unwrap_err();
841        assert_eq!(e, Errno::EBADF);
842    }
843
844    #[test]
845    fn undoing_without_initial_fd() {
846        let system = system_with_nofile_limit();
847        let mut state = system.state.borrow_mut();
848        state.file_system.save("input", Rc::default()).unwrap();
849        drop(state);
850        let mut env = Env::with_system(system);
851        let mut redir_env = RedirGuard::new(&mut env);
852        let redir = "4< input".parse().unwrap();
853        redir_env
854            .perform_redir(&redir, None)
855            .now_or_never()
856            .unwrap()
857            .unwrap();
858        redir_env.undo_redirs();
859        drop(redir_env);
860
861        let mut buffer = [0; 1];
862        let e = env.system.read(Fd(4), &mut buffer).unwrap_err();
863        assert_eq!(e, Errno::EBADF);
864    }
865
866    #[test]
867    fn unreadable_file() {
868        let mut env = Env::with_system(system_with_nofile_limit());
869        let mut env = RedirGuard::new(&mut env);
870        let redir = "< no_such_file".parse().unwrap();
871        let e = env
872            .perform_redir(&redir, None)
873            .now_or_never()
874            .unwrap()
875            .unwrap_err();
876        assert_eq!(
877            e.cause,
878            ErrorCause::OpenFile(c"no_such_file".to_owned(), Errno::ENOENT)
879        );
880        assert_eq!(e.location, redir.body.operand().location);
881    }
882
883    #[test]
884    fn multiple_redirections() {
885        let system = system_with_nofile_limit();
886        let mut state = system.state.borrow_mut();
887        let file = Rc::new(RefCell::new(Inode::new([100])));
888        state.file_system.save("foo", file).unwrap();
889        let file = Rc::new(RefCell::new(Inode::new([200])));
890        state.file_system.save("bar", file).unwrap();
891        drop(state);
892        let mut env = Env::with_system(system);
893        let mut env = RedirGuard::new(&mut env);
894        env.perform_redir(&"< foo".parse().unwrap(), None)
895            .now_or_never()
896            .unwrap()
897            .unwrap();
898        env.perform_redir(&"3< bar".parse().unwrap(), None)
899            .now_or_never()
900            .unwrap()
901            .unwrap();
902
903        let mut buffer = [0; 1];
904        let read_count = env.system.read(Fd::STDIN, &mut buffer).unwrap();
905        assert_eq!(read_count, 1);
906        assert_eq!(buffer, [100]);
907        let read_count = env.system.read(Fd(3), &mut buffer).unwrap();
908        assert_eq!(read_count, 1);
909        assert_eq!(buffer, [200]);
910    }
911
912    #[test]
913    fn later_redirection_wins() {
914        let system = system_with_nofile_limit();
915        let mut state = system.state.borrow_mut();
916        let file = Rc::new(RefCell::new(Inode::new([100])));
917        state.file_system.save("foo", file).unwrap();
918        let file = Rc::new(RefCell::new(Inode::new([200])));
919        state.file_system.save("bar", file).unwrap();
920        drop(state);
921
922        let mut env = Env::with_system(system);
923        let mut env = RedirGuard::new(&mut env);
924        env.perform_redir(&"< foo".parse().unwrap(), None)
925            .now_or_never()
926            .unwrap()
927            .unwrap();
928        env.perform_redir(&"< bar".parse().unwrap(), None)
929            .now_or_never()
930            .unwrap()
931            .unwrap();
932
933        let mut buffer = [0; 1];
934        let read_count = env.system.read(Fd::STDIN, &mut buffer).unwrap();
935        assert_eq!(read_count, 1);
936        assert_eq!(buffer, [200]);
937    }
938
939    #[test]
940    fn target_with_cloexec() {
941        let mut env = Env::with_system(system_with_nofile_limit());
942        let fd = env
943            .system
944            .open(
945                c"foo",
946                OfdAccess::WriteOnly,
947                OpenFlag::Create.into(),
948                Mode::ALL_9,
949            )
950            .unwrap();
951        env.system
952            .fcntl_setfd(fd, FdFlag::CloseOnExec.into())
953            .unwrap();
954
955        let mut env = RedirGuard::new(&mut env);
956        let redir = format!("{fd}> bar").parse().unwrap();
957        let e = env
958            .perform_redir(&redir, None)
959            .now_or_never()
960            .unwrap()
961            .unwrap_err();
962        assert_eq!(e.cause, ErrorCause::ReservedFd(fd));
963        assert_eq!(e.location, redir.body.operand().location);
964    }
965
966    #[test]
967    fn exit_status_of_command_substitution_in_normal() {
968        in_virtual_system(|mut env, state| async move {
969            env.builtins.insert("echo", echo_builtin());
970            env.builtins.insert("return", return_builtin());
971            let mut env = RedirGuard::new(&mut env);
972            let redir = "3> $(echo foo; return -n 79)".parse().unwrap();
973            let result = env.perform_redir(&redir, None).await.unwrap();
974            assert_eq!(result, Some(ExitStatus(79)));
975            let file = state.borrow().file_system.get("foo");
976            assert!(file.is_ok(), "{file:?}");
977        })
978    }
979
980    #[test]
981    fn exit_status_of_command_substitution_in_here_doc() {
982        in_virtual_system(|mut env, _state| async move {
983            env.builtins.insert("echo", echo_builtin());
984            env.builtins.insert("return", return_builtin());
985            let mut env = RedirGuard::new(&mut env);
986            let redir = Redir {
987                fd: Some(Fd(4)),
988                body: RedirBody::HereDoc(Rc::new(HereDoc {
989                    delimiter: "-END".parse().unwrap(),
990                    remove_tabs: false,
991                    content: "$(echo foo)$(echo bar; return -n 42)\n"
992                        .parse::<Text>()
993                        .unwrap()
994                        .into(),
995                })),
996            };
997            let result = env.perform_redir(&redir, None).await.unwrap();
998            assert_eq!(result, Some(ExitStatus(42)));
999
1000            let mut buffer = [0; 10];
1001            let count = env.system.read(Fd(4), &mut buffer).unwrap();
1002            assert_eq!(count, 7);
1003            assert_eq!(&buffer[..7], b"foobar\n");
1004        })
1005    }
1006
1007    #[test]
1008    fn xtrace_normal() {
1009        let mut xtrace = XTrace::new();
1010        let mut env = Env::with_system(system_with_nofile_limit());
1011        let mut env = RedirGuard::new(&mut env);
1012        env.perform_redir(&"> foo${unset-&}".parse().unwrap(), Some(&mut xtrace))
1013            .now_or_never()
1014            .unwrap()
1015            .unwrap();
1016        env.perform_redir(&"3>> bar".parse().unwrap(), Some(&mut xtrace))
1017            .now_or_never()
1018            .unwrap()
1019            .unwrap();
1020        let result = xtrace.finish(&mut env).now_or_never().unwrap();
1021        assert_eq!(result, "1>'foo&' 3>>bar\n");
1022    }
1023
1024    #[test]
1025    fn xtrace_here_doc() {
1026        let mut xtrace = XTrace::new();
1027        let mut env = Env::with_system(system_with_nofile_limit());
1028        let mut env = RedirGuard::new(&mut env);
1029
1030        let redir = Redir {
1031            fd: Some(Fd(4)),
1032            body: RedirBody::HereDoc(Rc::new(HereDoc {
1033                delimiter: r"-\END".parse().unwrap(),
1034                remove_tabs: false,
1035                content: "foo\n".parse::<Text>().unwrap().into(),
1036            })),
1037        };
1038        env.perform_redir(&redir, Some(&mut xtrace))
1039            .now_or_never()
1040            .unwrap()
1041            .unwrap();
1042
1043        let redir = Redir {
1044            fd: Some(Fd(5)),
1045            body: RedirBody::HereDoc(Rc::new(HereDoc {
1046                delimiter: r"EOF".parse().unwrap(),
1047                remove_tabs: false,
1048                content: "bar${unset-}\n".parse::<Text>().unwrap().into(),
1049            })),
1050        };
1051        env.perform_redir(&redir, Some(&mut xtrace))
1052            .now_or_never()
1053            .unwrap()
1054            .unwrap();
1055
1056        let result = xtrace.finish(&mut env).now_or_never().unwrap();
1057        assert_eq!(result, "4<< -\\END 5<<EOF\nfoo\n-END\nbar\nEOF\n");
1058    }
1059
1060    #[test]
1061    fn file_in_closes_opened_file_on_error() {
1062        let mut env = Env::with_system(system_with_nofile_limit());
1063        let mut env = RedirGuard::new(&mut env);
1064        let redir = "999999999</dev/stdin".parse().unwrap();
1065        let e = env
1066            .perform_redir(&redir, None)
1067            .now_or_never()
1068            .unwrap()
1069            .unwrap_err();
1070
1071        assert_eq!(
1072            e.cause,
1073            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1074        );
1075        assert_eq!(e.location, redir.body.operand().location);
1076        let mut buffer = [0; 1];
1077        let e = env.system.read(Fd(3), &mut buffer).unwrap_err();
1078        assert_eq!(e, Errno::EBADF);
1079    }
1080
1081    #[test]
1082    fn file_out_creates_empty_file() {
1083        let system = system_with_nofile_limit();
1084        let state = Rc::clone(&system.state);
1085        let mut env = Env::with_system(system);
1086        let mut env = RedirGuard::new(&mut env);
1087        let redir = "3> foo".parse().unwrap();
1088        env.perform_redir(&redir, None)
1089            .now_or_never()
1090            .unwrap()
1091            .unwrap();
1092        env.system.write(Fd(3), &[42, 123, 57]).unwrap();
1093
1094        let file = state.borrow().file_system.get("foo").unwrap();
1095        let file = file.borrow();
1096        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1097            assert_eq!(content[..], [42, 123, 57]);
1098        });
1099    }
1100
1101    #[test]
1102    fn file_out_truncates_existing_file() {
1103        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1104        let system = system_with_nofile_limit();
1105        let mut state = system.state.borrow_mut();
1106        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1107        drop(state);
1108        let mut env = Env::with_system(system);
1109        let mut env = RedirGuard::new(&mut env);
1110
1111        let redir = "3> foo".parse().unwrap();
1112        env.perform_redir(&redir, None)
1113            .now_or_never()
1114            .unwrap()
1115            .unwrap();
1116
1117        let file = file.borrow();
1118        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1119            assert_eq!(content[..], []);
1120        });
1121    }
1122
1123    #[test]
1124    fn file_out_noclobber_with_regular_file() {
1125        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1126        let system = system_with_nofile_limit();
1127        let mut state = system.state.borrow_mut();
1128        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1129        drop(state);
1130        let mut env = Env::with_system(system);
1131        env.options.set(Clobber, Off);
1132        let mut env = RedirGuard::new(&mut env);
1133
1134        let redir = "3> foo".parse().unwrap();
1135        let e = env
1136            .perform_redir(&redir, None)
1137            .now_or_never()
1138            .unwrap()
1139            .unwrap_err();
1140
1141        assert_eq!(
1142            e.cause,
1143            ErrorCause::OpenFile(c"foo".to_owned(), Errno::EEXIST)
1144        );
1145        assert_eq!(e.location, redir.body.operand().location);
1146        let file = file.borrow();
1147        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1148            assert_eq!(content[..], [42, 123, 254]);
1149        });
1150    }
1151
1152    #[test]
1153    fn file_out_noclobber_with_non_regular_file() {
1154        let inode = Inode {
1155            body: FileBody::Fifo {
1156                content: Default::default(),
1157                readers: 1,
1158                writers: 0,
1159            },
1160            permissions: Default::default(),
1161        };
1162        let file = Rc::new(RefCell::new(inode));
1163        let system = system_with_nofile_limit();
1164        let mut state = system.state.borrow_mut();
1165        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1166        drop(state);
1167        let mut env = Env::with_system(system);
1168        env.options.set(Clobber, Off);
1169        let mut env = RedirGuard::new(&mut env);
1170
1171        let redir = "3> foo".parse().unwrap();
1172        let result = env.perform_redir(&redir, None).now_or_never().unwrap();
1173        assert_eq!(result, Ok(None));
1174    }
1175
1176    #[test]
1177    fn file_out_closes_opened_file_on_error() {
1178        let mut env = Env::with_system(system_with_nofile_limit());
1179        let mut env = RedirGuard::new(&mut env);
1180        let redir = "999999999>foo".parse().unwrap();
1181        let e = env
1182            .perform_redir(&redir, None)
1183            .now_or_never()
1184            .unwrap()
1185            .unwrap_err();
1186
1187        assert_eq!(
1188            e.cause,
1189            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1190        );
1191        assert_eq!(e.location, redir.body.operand().location);
1192        let e = env.system.write(Fd(3), &[0x20]).unwrap_err();
1193        assert_eq!(e, Errno::EBADF);
1194    }
1195
1196    #[test]
1197    fn file_clobber_creates_empty_file() {
1198        let system = system_with_nofile_limit();
1199        let state = Rc::clone(&system.state);
1200        let mut env = Env::with_system(system);
1201        let mut env = RedirGuard::new(&mut env);
1202
1203        let redir = "3>| foo".parse().unwrap();
1204        env.perform_redir(&redir, None)
1205            .now_or_never()
1206            .unwrap()
1207            .unwrap();
1208        env.system.write(Fd(3), &[42, 123, 57]).unwrap();
1209
1210        let file = state.borrow().file_system.get("foo").unwrap();
1211        let file = file.borrow();
1212        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1213            assert_eq!(content[..], [42, 123, 57]);
1214        });
1215    }
1216
1217    #[test]
1218    fn file_clobber_by_default_truncates_existing_file() {
1219        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1220        let system = system_with_nofile_limit();
1221        let mut state = system.state.borrow_mut();
1222        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1223        drop(state);
1224        let mut env = Env::with_system(system);
1225        let mut env = RedirGuard::new(&mut env);
1226
1227        let redir = "3>| foo".parse().unwrap();
1228        env.perform_redir(&redir, None)
1229            .now_or_never()
1230            .unwrap()
1231            .unwrap();
1232
1233        let file = file.borrow();
1234        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1235            assert_eq!(content[..], []);
1236        });
1237    }
1238
1239    // TODO file_clobber_with_noclobber_fails_with_existing_file
1240
1241    #[test]
1242    fn file_clobber_closes_opened_file_on_error() {
1243        let mut env = Env::with_system(system_with_nofile_limit());
1244        let mut env = RedirGuard::new(&mut env);
1245        let redir = "999999999>|foo".parse().unwrap();
1246        let e = env
1247            .perform_redir(&redir, None)
1248            .now_or_never()
1249            .unwrap()
1250            .unwrap_err();
1251
1252        assert_eq!(
1253            e.cause,
1254            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1255        );
1256        assert_eq!(e.location, redir.body.operand().location);
1257        let e = env.system.write(Fd(3), &[0x20]).unwrap_err();
1258        assert_eq!(e, Errno::EBADF);
1259    }
1260
1261    #[test]
1262    fn file_append_creates_empty_file() {
1263        let system = system_with_nofile_limit();
1264        let state = Rc::clone(&system.state);
1265        let mut env = Env::with_system(system);
1266        let mut env = RedirGuard::new(&mut env);
1267
1268        let redir = "3>> foo".parse().unwrap();
1269        env.perform_redir(&redir, None)
1270            .now_or_never()
1271            .unwrap()
1272            .unwrap();
1273        env.system.write(Fd(3), &[42, 123, 57]).unwrap();
1274
1275        let file = state.borrow().file_system.get("foo").unwrap();
1276        let file = file.borrow();
1277        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1278            assert_eq!(content[..], [42, 123, 57]);
1279        });
1280    }
1281
1282    #[test]
1283    fn file_append_appends_to_existing_file() {
1284        let file = Rc::new(RefCell::new(Inode::new(*b"one\n")));
1285        let system = system_with_nofile_limit();
1286        let mut state = system.state.borrow_mut();
1287        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1288        drop(state);
1289        let mut env = Env::with_system(system);
1290        let mut env = RedirGuard::new(&mut env);
1291
1292        let redir = ">> foo".parse().unwrap();
1293        env.perform_redir(&redir, None)
1294            .now_or_never()
1295            .unwrap()
1296            .unwrap();
1297        env.system.write(Fd::STDOUT, "two\n".as_bytes()).unwrap();
1298
1299        let file = file.borrow();
1300        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1301            assert_eq!(std::str::from_utf8(content), Ok("one\ntwo\n"));
1302        });
1303    }
1304
1305    #[test]
1306    fn file_append_closes_opened_file_on_error() {
1307        let mut env = Env::with_system(system_with_nofile_limit());
1308        let mut env = RedirGuard::new(&mut env);
1309        let redir = "999999999>>foo".parse().unwrap();
1310        let e = env
1311            .perform_redir(&redir, None)
1312            .now_or_never()
1313            .unwrap()
1314            .unwrap_err();
1315
1316        assert_eq!(
1317            e.cause,
1318            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1319        );
1320        assert_eq!(e.location, redir.body.operand().location);
1321        let e = env.system.write(Fd(3), &[0x20]).unwrap_err();
1322        assert_eq!(e, Errno::EBADF);
1323    }
1324
1325    #[test]
1326    fn file_in_out_creates_empty_file() {
1327        let system = system_with_nofile_limit();
1328        let state = Rc::clone(&system.state);
1329        let mut env = Env::with_system(system);
1330        let mut env = RedirGuard::new(&mut env);
1331        let redir = "3<> foo".parse().unwrap();
1332        env.perform_redir(&redir, None)
1333            .now_or_never()
1334            .unwrap()
1335            .unwrap();
1336        env.system.write(Fd(3), &[230, 175, 26]).unwrap();
1337
1338        let file = state.borrow().file_system.get("foo").unwrap();
1339        let file = file.borrow();
1340        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1341            assert_eq!(content[..], [230, 175, 26]);
1342        });
1343    }
1344
1345    #[test]
1346    fn file_in_out_leaves_existing_file_content() {
1347        let system = system_with_nofile_limit();
1348        let file = Rc::new(RefCell::new(Inode::new([132, 79, 210])));
1349        let mut state = system.state.borrow_mut();
1350        state.file_system.save("foo", file).unwrap();
1351        drop(state);
1352        let mut env = Env::with_system(system);
1353        let mut env = RedirGuard::new(&mut env);
1354        let redir = "3<> foo".parse().unwrap();
1355        env.perform_redir(&redir, None)
1356            .now_or_never()
1357            .unwrap()
1358            .unwrap();
1359
1360        let mut buffer = [0; 4];
1361        let read_count = env.system.read(Fd(3), &mut buffer).unwrap();
1362        assert_eq!(read_count, 3);
1363        assert_eq!(buffer, [132, 79, 210, 0]);
1364    }
1365
1366    #[test]
1367    fn file_in_out_closes_opened_file_on_error() {
1368        let mut env = Env::with_system(system_with_nofile_limit());
1369        let mut env = RedirGuard::new(&mut env);
1370        let redir = "999999999<>foo".parse().unwrap();
1371        let e = env
1372            .perform_redir(&redir, None)
1373            .now_or_never()
1374            .unwrap()
1375            .unwrap_err();
1376
1377        assert_eq!(
1378            e.cause,
1379            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1380        );
1381        assert_eq!(e.location, redir.body.operand().location);
1382        let e = env.system.write(Fd(3), &[0x20]).unwrap_err();
1383        assert_eq!(e, Errno::EBADF);
1384    }
1385
1386    #[test]
1387    fn fd_in_copies_fd() {
1388        for fd in [Fd(0), Fd(3)] {
1389            let system = system_with_nofile_limit();
1390            let state = Rc::clone(&system.state);
1391            state
1392                .borrow_mut()
1393                .file_system
1394                .get("/dev/stdin")
1395                .unwrap()
1396                .borrow_mut()
1397                .body = FileBody::new([1, 2, 42]);
1398            let mut env = Env::with_system(system);
1399            let mut env = RedirGuard::new(&mut env);
1400            let redir = "3<& 0".parse().unwrap();
1401            env.perform_redir(&redir, None)
1402                .now_or_never()
1403                .unwrap()
1404                .unwrap();
1405
1406            let mut buffer = [0; 4];
1407            let read_count = env.system.read(fd, &mut buffer).unwrap();
1408            assert_eq!(read_count, 3);
1409            assert_eq!(buffer, [1, 2, 42, 0]);
1410        }
1411    }
1412
1413    #[test]
1414    fn fd_in_closes_fd() {
1415        let mut env = Env::with_system(system_with_nofile_limit());
1416        let mut env = RedirGuard::new(&mut env);
1417        let redir = "<& -".parse().unwrap();
1418        env.perform_redir(&redir, None)
1419            .now_or_never()
1420            .unwrap()
1421            .unwrap();
1422
1423        let mut buffer = [0; 1];
1424        let e = env.system.read(Fd::STDIN, &mut buffer).unwrap_err();
1425        assert_eq!(e, Errno::EBADF);
1426    }
1427
1428    #[test]
1429    fn fd_in_rejects_unreadable_fd() {
1430        let mut env = Env::with_system(system_with_nofile_limit());
1431        let mut env = RedirGuard::new(&mut env);
1432        let redir = "3>foo".parse().unwrap();
1433        env.perform_redir(&redir, None)
1434            .now_or_never()
1435            .unwrap()
1436            .unwrap();
1437
1438        let redir = "<&3".parse().unwrap();
1439        let e = env
1440            .perform_redir(&redir, None)
1441            .now_or_never()
1442            .unwrap()
1443            .unwrap_err();
1444        assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1445        assert_eq!(e.location, redir.body.operand().location);
1446    }
1447
1448    #[test]
1449    fn fd_in_rejects_unopened_fd() {
1450        let mut env = Env::with_system(system_with_nofile_limit());
1451        let mut env = RedirGuard::new(&mut env);
1452
1453        let redir = "3<&3".parse().unwrap();
1454        let e = env
1455            .perform_redir(&redir, None)
1456            .now_or_never()
1457            .unwrap()
1458            .unwrap_err();
1459        assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1460        assert_eq!(e.location, redir.body.operand().location);
1461    }
1462
1463    #[test]
1464    fn fd_in_rejects_fd_with_cloexec() {
1465        let mut env = Env::with_system(system_with_nofile_limit());
1466        env.system
1467            .fcntl_setfd(Fd(0), FdFlag::CloseOnExec.into())
1468            .unwrap();
1469
1470        let mut env = RedirGuard::new(&mut env);
1471        let redir = "3<& 0".parse().unwrap();
1472        let e = env
1473            .perform_redir(&redir, None)
1474            .now_or_never()
1475            .unwrap()
1476            .unwrap_err();
1477        assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(0)));
1478        assert_eq!(e.location, redir.body.operand().location);
1479    }
1480
1481    #[test]
1482    fn keep_target_fd_open_on_error_in_fd_in() {
1483        let mut env = Env::with_system(system_with_nofile_limit());
1484        let mut env = RedirGuard::new(&mut env);
1485        let redir = "999999999<&0".parse().unwrap();
1486        let e = env
1487            .perform_redir(&redir, None)
1488            .now_or_never()
1489            .unwrap()
1490            .unwrap_err();
1491
1492        assert_eq!(
1493            e.cause,
1494            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1495        );
1496        assert_eq!(e.location, redir.body.operand().location);
1497        let mut buffer = [0; 1];
1498        let read_count = env.system.read(Fd(0), &mut buffer).unwrap();
1499        assert_eq!(read_count, 0);
1500    }
1501
1502    #[test]
1503    fn fd_out_copies_fd() {
1504        for fd in [Fd(1), Fd(4)] {
1505            let system = system_with_nofile_limit();
1506            let state = Rc::clone(&system.state);
1507            let mut env = Env::with_system(system);
1508            let mut env = RedirGuard::new(&mut env);
1509            let redir = "4>& 1".parse().unwrap();
1510            env.perform_redir(&redir, None)
1511                .now_or_never()
1512                .unwrap()
1513                .unwrap();
1514
1515            env.system.write(fd, &[7, 6, 91]).unwrap();
1516            let file = state.borrow().file_system.get("/dev/stdout").unwrap();
1517            let file = file.borrow();
1518            assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1519                assert_eq!(content[..], [7, 6, 91]);
1520            });
1521        }
1522    }
1523
1524    #[test]
1525    fn fd_out_closes_fd() {
1526        let mut env = Env::with_system(system_with_nofile_limit());
1527        let mut env = RedirGuard::new(&mut env);
1528        let redir = ">& -".parse().unwrap();
1529        env.perform_redir(&redir, None)
1530            .now_or_never()
1531            .unwrap()
1532            .unwrap();
1533
1534        let mut buffer = [0; 1];
1535        let e = env.system.read(Fd::STDOUT, &mut buffer).unwrap_err();
1536        assert_eq!(e, Errno::EBADF);
1537    }
1538
1539    #[test]
1540    fn fd_out_rejects_unwritable_fd() {
1541        let mut env = Env::with_system(system_with_nofile_limit());
1542        let mut env = RedirGuard::new(&mut env);
1543        let redir = "3</dev/stdin".parse().unwrap();
1544        env.perform_redir(&redir, None)
1545            .now_or_never()
1546            .unwrap()
1547            .unwrap();
1548
1549        let redir = ">&3".parse().unwrap();
1550        let e = env
1551            .perform_redir(&redir, None)
1552            .now_or_never()
1553            .unwrap()
1554            .unwrap_err();
1555        assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1556        assert_eq!(e.location, redir.body.operand().location);
1557    }
1558
1559    #[test]
1560    fn fd_out_rejects_unopened_fd() {
1561        let mut env = Env::with_system(system_with_nofile_limit());
1562        let mut env = RedirGuard::new(&mut env);
1563
1564        let redir = "3>&3".parse().unwrap();
1565        let e = env
1566            .perform_redir(&redir, None)
1567            .now_or_never()
1568            .unwrap()
1569            .unwrap_err();
1570        assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1571        assert_eq!(e.location, redir.body.operand().location);
1572    }
1573
1574    #[test]
1575    fn fd_out_rejects_fd_with_cloexec() {
1576        let mut env = Env::with_system(system_with_nofile_limit());
1577        env.system
1578            .fcntl_setfd(Fd(1), FdFlag::CloseOnExec.into())
1579            .unwrap();
1580
1581        let mut env = RedirGuard::new(&mut env);
1582        let redir = "4>& 1".parse().unwrap();
1583        let e = env
1584            .perform_redir(&redir, None)
1585            .now_or_never()
1586            .unwrap()
1587            .unwrap_err();
1588        assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(1)));
1589        assert_eq!(e.location, redir.body.operand().location);
1590    }
1591
1592    #[test]
1593    fn keep_target_fd_open_on_error_in_fd_out() {
1594        let mut env = Env::with_system(system_with_nofile_limit());
1595        let mut env = RedirGuard::new(&mut env);
1596        let redir = "999999999>&1".parse().unwrap();
1597        let e = env
1598            .perform_redir(&redir, None)
1599            .now_or_never()
1600            .unwrap()
1601            .unwrap_err();
1602
1603        assert_eq!(
1604            e.cause,
1605            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1606        );
1607        assert_eq!(e.location, redir.body.operand().location);
1608        let write_count = env.system.write(Fd(1), &[0x20]).unwrap();
1609        assert_eq!(write_count, 1);
1610    }
1611
1612    #[test]
1613    fn pipe_redirection_not_yet_implemented() {
1614        let mut env = Env::with_system(system_with_nofile_limit());
1615        let mut env = RedirGuard::new(&mut env);
1616        let redir = "3>>|4".parse().unwrap();
1617        let e = env
1618            .perform_redir(&redir, None)
1619            .now_or_never()
1620            .unwrap()
1621            .unwrap_err();
1622
1623        assert_eq!(e.cause, ErrorCause::UnsupportedPipeRedirection);
1624        assert_eq!(e.location, redir.body.operand().location);
1625    }
1626
1627    #[test]
1628    fn here_string_not_yet_implemented() {
1629        let mut env = Env::with_system(system_with_nofile_limit());
1630        let mut env = RedirGuard::new(&mut env);
1631        let redir = "3<<< here".parse().unwrap();
1632        let e = env
1633            .perform_redir(&redir, None)
1634            .now_or_never()
1635            .unwrap()
1636            .unwrap_err();
1637
1638        assert_eq!(e.cause, ErrorCause::UnsupportedHereString);
1639        assert_eq!(e.location, redir.body.operand().location);
1640    }
1641}