Skip to main content

yash_semantics/
redir.rs

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