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;
108#[allow(deprecated)]
109use yash_syntax::source::pretty::{Annotation, AnnotationType, MessageBase};
110use yash_syntax::source::pretty::{Report, ReportType, Snippet};
111use yash_syntax::syntax::HereDoc;
112use yash_syntax::syntax::Redir;
113use yash_syntax::syntax::RedirBody;
114use yash_syntax::syntax::RedirOp;
115use yash_syntax::syntax::Unquote;
116
117/// Record of saving an open file description in another file descriptor.
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119struct SavedFd {
120    /// File descriptor by which the original open file description was
121    /// previously accessible.
122    original: Fd,
123    /// Temporary file descriptor that remembers the original open file
124    /// description.
125    save: Option<Fd>,
126}
127
128/// Types of errors that may occur in the redirection.
129#[derive(Clone, Debug, Eq, Error, PartialEq)]
130#[non_exhaustive]
131pub enum ErrorCause {
132    /// Expansion error.
133    #[error(transparent)]
134    Expansion(#[from] crate::expansion::ErrorCause),
135
136    /// Pathname containing a nul byte.
137    #[error(transparent)]
138    NulByte(#[from] NulError),
139
140    /// The target file descriptor could not be modified for the redirection.
141    #[error("{1}")]
142    FdNotOverwritten(Fd, Errno),
143
144    /// Use of an FD reserved by the shell
145    ///
146    /// This error occurs when a redirection tries to modify an existing FD with
147    /// the CLOEXEC flag set. See the [module documentation](self) for details.
148    #[error("file descriptor {0} is reserved by the shell")]
149    ReservedFd(Fd),
150
151    /// Error while opening a file.
152    ///
153    /// The `CString` is the pathname of the file that could not be opened.
154    #[error("cannot open file '{}': {}", .0.to_string_lossy(), .1)]
155    OpenFile(CString, Errno),
156
157    /// Operand of `<&` or `>&` that cannot be parsed as an integer.
158    #[error("{0} is not a valid file descriptor: {1}")]
159    MalformedFd(String, ParseIntError),
160
161    /// `<&` applied to an unreadable file descriptor
162    #[error("{0} is not a readable file descriptor")]
163    UnreadableFd(Fd),
164
165    /// `>&` applied to an unwritable file descriptor
166    #[error("{0} is not a writable file descriptor")]
167    UnwritableFd(Fd),
168
169    /// Error preparing a temporary file to save here-document content
170    #[error("cannot prepare temporary file for here-document: {0}")]
171    TemporaryFileUnavailable(Errno),
172
173    /// Pipe redirection is used, which is not yet implemented.
174    #[error("pipe redirection is not yet implemented")]
175    UnsupportedPipeRedirection,
176
177    /// Here-string redirection is used, which is not yet implemented.
178    #[error("here-string redirection is not yet implemented")]
179    UnsupportedHereString,
180}
181
182impl ErrorCause {
183    /// Returns an error message describing the error.
184    #[must_use]
185    pub fn message(&self) -> &str {
186        // TODO Localize
187        use ErrorCause::*;
188        match self {
189            Expansion(e) => e.message(),
190            NulByte(_) => "nul byte found in the pathname",
191            FdNotOverwritten(_, _) | ReservedFd(_) => "cannot redirect the file descriptor",
192            OpenFile(_, _) => "cannot open the file",
193            MalformedFd(_, _) => "not a valid file descriptor",
194            UnreadableFd(_) | UnwritableFd(_) => "cannot copy file descriptor",
195            TemporaryFileUnavailable(_) => "cannot prepare here-document",
196            UnsupportedPipeRedirection | UnsupportedHereString => "unsupported redirection",
197        }
198    }
199
200    /// Returns a label for annotating the error location.
201    #[must_use]
202    pub fn label(&self) -> Cow<'_, str> {
203        // TODO Localize
204        use ErrorCause::*;
205        match self {
206            Expansion(e) => e.label(),
207            NulByte(_) => "pathname should not contain a nul byte".into(),
208            FdNotOverwritten(_, errno) => errno.to_string().into(),
209            ReservedFd(fd) => format!("file descriptor {fd} reserved by shell").into(),
210            OpenFile(path, errno) => format!("{}: {}", path.to_string_lossy(), errno).into(),
211            MalformedFd(value, error) => format!("{value}: {error}").into(),
212            UnreadableFd(fd) => format!("{fd}: not a readable file descriptor").into(),
213            UnwritableFd(fd) => format!("{fd}: not a writable file descriptor").into(),
214            TemporaryFileUnavailable(errno) => errno.to_string().into(),
215            UnsupportedPipeRedirection => "pipe redirection is not yet implemented".into(),
216            UnsupportedHereString => "here-string redirection is not yet implemented".into(),
217        }
218    }
219}
220
221/// Explanation of a redirection error.
222#[derive(Clone, Debug, Eq, Error, PartialEq)]
223#[error("{cause}")]
224pub struct Error {
225    pub cause: ErrorCause,
226    pub location: Location,
227}
228
229impl From<crate::expansion::Error> for Error {
230    fn from(e: crate::expansion::Error) -> Self {
231        Error {
232            cause: e.cause.into(),
233            location: e.location,
234        }
235    }
236}
237
238impl Error {
239    /// Returns a report for the error.
240    #[must_use]
241    pub fn to_report(&self) -> Report<'_> {
242        let mut report = Report::new();
243        report.r#type = ReportType::Error;
244        report.title = self.cause.message().into();
245        report.snippets = Snippet::with_primary_span(&self.location, self.cause.label());
246        report
247    }
248}
249
250/// Converts the error into a report by calling [`Error::to_report`].
251impl<'a> From<&'a Error> for Report<'a> {
252    #[inline(always)]
253    fn from(error: &'a Error) -> Self {
254        error.to_report()
255    }
256}
257
258#[allow(deprecated)]
259impl MessageBase for Error {
260    fn message_title(&self) -> Cow<'_, str> {
261        self.cause.message().into()
262    }
263
264    fn main_annotation(&self) -> Annotation<'_> {
265        Annotation::new(AnnotationType::Error, self.cause.label(), &self.location)
266    }
267}
268
269/// Intermediate state of a redirected file descriptor
270#[derive(Debug)]
271enum FdSpec {
272    /// File descriptor specifically opened for redirection
273    Owned(Fd),
274    /// Existing file descriptor
275    Borrowed(Fd),
276    /// Closed file descriptor
277    Closed,
278}
279
280impl FdSpec {
281    fn as_fd(&self) -> Option<Fd> {
282        match self {
283            &FdSpec::Owned(fd) | &FdSpec::Borrowed(fd) => Some(fd),
284            &FdSpec::Closed => None,
285        }
286    }
287
288    fn close<S: System>(self, system: &mut S) {
289        match self {
290            FdSpec::Owned(fd) => {
291                let _ = system.close(fd);
292            }
293            FdSpec::Borrowed(_) | FdSpec::Closed => (),
294        }
295    }
296}
297
298const MODE: Mode = Mode::ALL_READ.union(Mode::ALL_WRITE);
299
300fn is_cloexec(env: &Env, fd: Fd) -> bool {
301    matches!(env.system.fcntl_getfd(fd), Ok(flags) if flags.contains(FdFlag::CloseOnExec))
302}
303
304fn into_c_string_value_and_origin(field: Field) -> Result<(CString, Location), Error> {
305    match CString::new(field.value) {
306        Ok(value) => Ok((value, field.origin)),
307        Err(e) => Err(Error {
308            cause: ErrorCause::NulByte(e),
309            location: field.origin,
310        }),
311    }
312}
313
314/// Opens a file for redirection.
315fn open_file(
316    env: &mut Env,
317    access: OfdAccess,
318    flags: EnumSet<OpenFlag>,
319    path: Field,
320) -> Result<(FdSpec, Location), Error> {
321    let system = &mut env.system;
322    let (path, origin) = into_c_string_value_and_origin(path)?;
323    match system.open(&path, access, flags, MODE) {
324        Ok(fd) => Ok((FdSpec::Owned(fd), origin)),
325        Err(errno) => Err(Error {
326            cause: ErrorCause::OpenFile(path, errno),
327            location: origin,
328        }),
329    }
330}
331
332/// Opens a file for writing with the `noclobber` option.
333fn open_file_noclobber(env: &mut Env, path: Field) -> Result<(FdSpec, Location), Error> {
334    let system = &mut env.system;
335    let (path, origin) = into_c_string_value_and_origin(path)?;
336
337    const FLAGS_EXCL: EnumSet<OpenFlag> = enum_set!(OpenFlag::Create | OpenFlag::Exclusive);
338    match system.open(&path, OfdAccess::WriteOnly, FLAGS_EXCL, MODE) {
339        Ok(fd) => return Ok((FdSpec::Owned(fd), origin)),
340        Err(Errno::EEXIST) => (),
341        Err(errno) => {
342            return Err(Error {
343                cause: ErrorCause::OpenFile(path, errno),
344                location: origin,
345            });
346        }
347    }
348
349    // Okay, it seems there is an existing file. Try opening it.
350    match system.open(&path, OfdAccess::WriteOnly, EnumSet::empty(), MODE) {
351        Ok(fd) => {
352            let is_regular = system
353                .fstat(fd)
354                .is_ok_and(|stat| stat.r#type == FileType::Regular);
355            if is_regular {
356                // We opened the FD without the O_CREAT flag, so somebody else
357                // must have created this file. Failure.
358                let _: Result<_, _> = system.close(fd);
359                Err(Error {
360                    cause: ErrorCause::OpenFile(path, Errno::EEXIST),
361                    location: origin,
362                })
363            } else {
364                Ok((FdSpec::Owned(fd), origin))
365            }
366        }
367        Err(Errno::ENOENT) => {
368            // A file existed on the first open but not on the second. There are
369            // two possibilities: One is that a file existed on the first open
370            // call and had been removed before the second. In this case, we
371            // might be able to create another if we start over. The other is
372            // that there is a symbolic link pointing to nothing, in which case
373            // retrying would only lead to the same result. Since there is no
374            // reliable way to tell the situations apart atomically, we give up
375            // and return the initial error.
376            Err(Error {
377                cause: ErrorCause::OpenFile(path, Errno::EEXIST),
378                location: origin,
379            })
380        }
381        Err(errno) => Err(Error {
382            cause: ErrorCause::OpenFile(path, errno),
383            location: origin,
384        }),
385    }
386}
387
388/// Parses the target of `<&` and `>&`.
389fn copy_fd(
390    env: &mut Env,
391    target: Field,
392    expected_access: OfdAccess,
393) -> Result<(FdSpec, Location), Error> {
394    if target.value == "-" {
395        return Ok((FdSpec::Closed, target.origin));
396    }
397
398    // Parse the string as an integer
399    let fd = match target.value.parse() {
400        Ok(number) => Fd(number),
401        Err(error) => {
402            return Err(Error {
403                cause: ErrorCause::MalformedFd(target.value, error),
404                location: target.origin,
405            });
406        }
407    };
408
409    // Check if the FD is really readable or writable
410    fn is_fd_valid<S: System>(system: &S, fd: Fd, expected_access: OfdAccess) -> bool {
411        system
412            .ofd_access(fd)
413            .is_ok_and(|access| access == expected_access || access == OfdAccess::ReadWrite)
414    }
415    fn fd_mode_error(
416        fd: Fd,
417        expected_access: OfdAccess,
418        target: Field,
419    ) -> Result<(FdSpec, Location), Error> {
420        let cause = match expected_access {
421            OfdAccess::ReadOnly => ErrorCause::UnreadableFd(fd),
422            OfdAccess::WriteOnly => ErrorCause::UnwritableFd(fd),
423            _ => unreachable!("unexpected expected access {expected_access:?}"),
424        };
425        let location = target.origin;
426        Err(Error { cause, location })
427    }
428    if !is_fd_valid(&env.system, fd, expected_access) {
429        return fd_mode_error(fd, expected_access, target);
430    }
431
432    // Ensure the FD has no CLOEXEC flag
433    if is_cloexec(env, fd) {
434        return Err(Error {
435            cause: ErrorCause::ReservedFd(fd),
436            location: target.origin,
437        });
438    }
439
440    Ok((FdSpec::Borrowed(fd), target.origin))
441}
442
443/// Opens the file for a normal redirection.
444async fn open_normal(
445    env: &mut Env,
446    operator: RedirOp,
447    operand: Field,
448) -> Result<(FdSpec, Location), Error> {
449    use RedirOp::*;
450    match operator {
451        FileIn => open_file(env, OfdAccess::ReadOnly, EnumSet::empty(), operand),
452        FileOut if env.options.get(Clobber) == Off => open_file_noclobber(env, operand),
453        FileOut | FileClobber => open_file(
454            env,
455            OfdAccess::WriteOnly,
456            OpenFlag::Create | OpenFlag::Truncate,
457            operand,
458        ),
459        FileAppend => open_file(
460            env,
461            OfdAccess::WriteOnly,
462            OpenFlag::Create | OpenFlag::Append,
463            operand,
464        ),
465        FileInOut => open_file(env, OfdAccess::ReadWrite, OpenFlag::Create.into(), operand),
466        FdIn => copy_fd(env, operand, OfdAccess::ReadOnly),
467        FdOut => copy_fd(env, operand, OfdAccess::WriteOnly),
468        Pipe => Err(Error {
469            cause: ErrorCause::UnsupportedPipeRedirection,
470            location: operand.origin,
471        }),
472        String => Err(Error {
473            cause: ErrorCause::UnsupportedHereString,
474            location: operand.origin,
475        }),
476    }
477}
478
479/// Prepares xtrace for a normal redirection.
480fn trace_normal(xtrace: Option<&mut XTrace>, target_fd: Fd, operator: RedirOp, operand: &Field) {
481    if let Some(xtrace) = xtrace {
482        write!(
483            xtrace.redirs(),
484            "{}{}{} ",
485            target_fd,
486            operator,
487            quoted(&operand.value)
488        )
489        .unwrap();
490    }
491}
492
493/// Prepares xtrace for a here-document.
494fn trace_here_doc(xtrace: Option<&mut XTrace>, target_fd: Fd, here_doc: &HereDoc, content: &str) {
495    if let Some(xtrace) = xtrace {
496        write!(xtrace.redirs(), "{target_fd}{here_doc} ").unwrap();
497        let (delimiter, _is_quoted) = here_doc.delimiter.unquote();
498        writeln!(xtrace.here_doc_contents(), "{content}{delimiter}").unwrap();
499    }
500}
501
502mod here_doc;
503
504/// Performs a redirection.
505#[allow(clippy::await_holding_refcell_ref)]
506async fn perform(
507    env: &mut Env,
508    redir: &Redir,
509    xtrace: Option<&mut XTrace>,
510) -> Result<(SavedFd, Option<ExitStatus>), Error> {
511    let target_fd = redir.fd_or_default();
512
513    // Make sure target_fd doesn't have the CLOEXEC flag
514    if is_cloexec(env, target_fd) {
515        return Err(Error {
516            cause: ErrorCause::ReservedFd(target_fd),
517            location: redir.body.operand().location.clone(),
518        });
519    }
520
521    // Save the current open file description at target_fd to a new FD
522    let save = match env
523        .system
524        .dup(target_fd, MIN_INTERNAL_FD, FdFlag::CloseOnExec.into())
525    {
526        Ok(save_fd) => Some(save_fd),
527        Err(Errno::EBADF) => None,
528        Err(errno) => {
529            return Err(Error {
530                cause: ErrorCause::FdNotOverwritten(target_fd, errno),
531                location: redir.body.operand().location.clone(),
532            });
533        }
534    };
535
536    // Prepare an FD from the redirection body
537    let (fd_spec, location, exit_status) = match &redir.body {
538        RedirBody::Normal { operator, operand } => {
539            // TODO perform pathname expansion if applicable
540            let (expansion, exit_status) = expand_word(env, operand).await?;
541            trace_normal(xtrace, target_fd, *operator, &expansion);
542            let (fd, location) = open_normal(env, *operator, expansion).await?;
543            (fd, location, exit_status)
544        }
545        RedirBody::HereDoc(here_doc) => {
546            let content_ref = here_doc.content.get();
547            let content = content_ref.map(Cow::Borrowed).unwrap_or_default();
548            let (content, exit_status) = expand_text(env, &content).await?;
549            trace_here_doc(xtrace, target_fd, here_doc, &content);
550            let location = here_doc.delimiter.location.clone();
551            match here_doc::open_fd(env, content).await {
552                Ok(fd) => (FdSpec::Owned(fd), location, exit_status),
553                Err(cause) => return Err(Error { cause, location }),
554            }
555        }
556    };
557
558    if let Some(fd) = fd_spec.as_fd() {
559        if fd != target_fd {
560            let dup_result = env.system.dup2(fd, target_fd);
561            fd_spec.close(&mut env.system);
562            match dup_result {
563                Ok(new_fd) => assert_eq!(new_fd, target_fd),
564                Err(errno) => {
565                    return Err(Error {
566                        cause: ErrorCause::FdNotOverwritten(target_fd, errno),
567                        location,
568                    });
569                }
570            }
571        }
572    } else {
573        let _: Result<(), Errno> = env.system.close(target_fd);
574    }
575
576    let original = target_fd;
577    Ok((SavedFd { original, save }, exit_status))
578}
579
580/// `Env` wrapper for performing redirections.
581///
582/// This is an RAII-style wrapper of [`Env`] in which redirections are
583/// performed. A `RedirGuard` keeps track of file descriptors affected by
584/// redirections so that we can restore the file descriptors to the state before
585/// performing the redirections.
586///
587/// There are two ways to clear file descriptors saved in the `RedirGuard`.  One
588/// is [`undo_redirs`](Self::undo_redirs), which restores the file descriptors
589/// to the original state, and the other is
590/// [`preserve_redirs`](Self::preserve_redirs), which removes the saved file
591/// descriptors without restoring the state and thus makes the effect of the
592/// redirections permanent.
593///
594/// When an instance of `RedirGuard` is dropped, `undo_redirs` is implicitly
595/// called. That means you need to call `preserve_redirs` explicitly to preserve
596/// the redirections' effect.
597#[derive(Debug)]
598pub struct RedirGuard<'e> {
599    /// Environment in which redirections are performed.
600    env: &'e mut yash_env::Env,
601    /// Records of file descriptors that have been modified by redirections.
602    saved_fds: Vec<SavedFd>,
603}
604
605impl Deref for RedirGuard<'_> {
606    type Target = yash_env::Env;
607    fn deref(&self) -> &yash_env::Env {
608        self.env
609    }
610}
611
612impl DerefMut for RedirGuard<'_> {
613    fn deref_mut(&mut self) -> &mut yash_env::Env {
614        self.env
615    }
616}
617
618impl std::ops::Drop for RedirGuard<'_> {
619    fn drop(&mut self) {
620        self.undo_redirs()
621    }
622}
623
624impl<'e> RedirGuard<'e> {
625    /// Creates a new `RedirGuard`.
626    pub fn new(env: &'e mut yash_env::Env) -> Self {
627        let saved_fds = Vec::new();
628        RedirGuard { env, saved_fds }
629    }
630
631    /// Performs a redirection.
632    ///
633    /// If successful, this function saves internally a backing copy of the file
634    /// descriptor affected by the redirection, and returns the exit status of
635    /// the last command substitution performed during the redirection, if any.
636    ///
637    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
638    /// and the expanded operands are written to it.
639    pub async fn perform_redir(
640        &mut self,
641        redir: &Redir,
642        xtrace: Option<&mut XTrace>,
643    ) -> Result<Option<ExitStatus>, Error> {
644        let (saved_fd, exit_status) = perform(self, redir, xtrace).await?;
645        self.saved_fds.push(saved_fd);
646        Ok(exit_status)
647    }
648
649    /// Performs redirections.
650    ///
651    /// This is a convenience function for [performing
652    /// redirection](Self::perform_redir) for each iterator item.
653    ///
654    /// If the redirection fails for an item, the remainders are ignored, but
655    /// the effects of the preceding items are not canceled.
656    ///
657    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
658    /// and the expanded operands are written to it.
659    pub async fn perform_redirs<'a, I>(
660        &mut self,
661        redirs: I,
662        mut xtrace: Option<&mut XTrace>,
663    ) -> Result<Option<ExitStatus>, Error>
664    where
665        I: IntoIterator<Item = &'a Redir>,
666    {
667        let mut exit_status = None;
668        for redir in redirs {
669            let new_exit_status = self.perform_redir(redir, xtrace.as_deref_mut()).await?;
670            exit_status = new_exit_status.or(exit_status);
671        }
672        Ok(exit_status)
673    }
674
675    /// Undoes the effect of the redirections.
676    ///
677    /// This function restores the file descriptors affected by redirections to
678    /// the original state and closes internal backing file descriptors, which
679    /// were used for restoration and are no longer needed.
680    pub fn undo_redirs(&mut self) {
681        for SavedFd { original, save } in self.saved_fds.drain(..).rev() {
682            if let Some(save) = save {
683                assert_ne!(save, original);
684                let _: Result<_, _> = self.env.system.dup2(save, original);
685                let _: Result<_, _> = self.env.system.close(save);
686            } else {
687                let _: Result<_, _> = self.env.system.close(original);
688            }
689        }
690    }
691
692    /// Makes the redirections permanent.
693    ///
694    /// This function closes internal backing file descriptors without restoring
695    /// the original file descriptor state.
696    pub fn preserve_redirs(&mut self) {
697        for SavedFd { original: _, save } in self.saved_fds.drain(..) {
698            if let Some(save) = save {
699                let _: Result<_, _> = self.env.system.close(save);
700            }
701        }
702    }
703}
704
705#[cfg(test)]
706mod tests {
707    use super::*;
708    use crate::tests::echo_builtin;
709    use crate::tests::return_builtin;
710    use assert_matches::assert_matches;
711    use futures_util::FutureExt;
712    use std::cell::RefCell;
713    use std::rc::Rc;
714    use yash_env::Env;
715    use yash_env::VirtualSystem;
716    use yash_env::system::resource::LimitPair;
717    use yash_env::system::resource::Resource;
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 mut 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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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(Box::new(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}