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