Skip to main content

yash_semantics/
redir.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Redirection semantics.
18//!
19//! # Effect of redirections
20//!
21//! A [redirection](Redir) modifies its [target file
22//! descriptor](Redir::fd_or_default) on the basis of its [body](RedirBody).
23//!
24//! If the body is `Normal`, the operand word is [expanded](crate::expansion)
25//! first. Then, the [operator](RedirOp) defines the next behavior:
26//!
27//! - `FileIn`: Opens a file for reading, regarding the expanded field as a
28//!   pathname.
29//! - `FileInOut`: Likewise, opens a file for reading and writing.
30//! - `FileOut`, `FileClobber`: Likewise, opens a file for writing and clears
31//!   the file content.  Creates an empty regular file if the file does not
32//!   exist.
33//! - `FileAppend`: Likewise, opens a file for appending.
34//!   Creates an empty regular file if the file does not exist.
35//! - `FdIn`: Copies a file descriptor, regarding the expanded field as a
36//!   non-negative decimal integer denoting a readable file descriptor to copy
37//!   from. Closes the target file descriptor if the field is a single hyphen
38//!   (`-`) instead.
39//! - `FdOut`: Likewise, copies or closes a file descriptor, but the source file
40//!   descriptor must be writable instead of readable.
41//! - `Pipe`: Opens a pipe, regarding the expanded field as a
42//!   non-negative decimal integer denoting a file descriptor to become the
43//!   reading end of the pipe. The target file descriptor will be the writing
44//!   end. (TODO: `Pipe` is not yet implemented.)
45//! - `String`: Opens a readable file descriptor from which you can read the
46//!   expanded field followed by a newline character. (TODO: `String` is not yet
47//!   implemented.)
48//!
49//! If the `Clobber` [shell option](yash_env::option::Option) is off and a
50//! regular file exists at the target pathname, then `FileOut` will fail.
51//!
52//! If the body is `HereDoc`, the redirection opens a readable file descriptor
53//! that yields [expansion](crate::expansion) of the content. The current
54//! implementation uses an unnamed temporary file for the file descriptor, but
55//! we may change the behavior in the future.
56//!
57//! # Performing redirections
58//!
59//! To perform redirections, you need to wrap an [`Env`] in a [`RedirGuard`]
60//! first. Then, you call [`RedirGuard::perform_redir`] to affect the target
61//! file descriptor. When you drop the `RedirGuard`, it undoes the effect to the
62//! file descriptor. See the documentation for [`RedirGuard`] for details.
63//!
64//! # The CLOEXEC flag
65//!
66//! The shell may open file descriptors to accomplish its tasks. For example,
67//! the dot built-in opens an FD to read a script file. Such FDs should be
68//! invisible to the user, so the shell should set the CLOEXEC flag on the FDs.
69//!
70//! When the user tries to redirect an FD with the CLOEXEC flag, it fails with a
71//! [`ReservedFd`](ErrorCause::ReservedFd) error to protect the FD from being
72//! overwritten.
73//!
74//! Note that POSIX requires FDs between 0 and 9 (inclusive) to be available for
75//! the user. The shell should move an FD to 10 or above before setting its
76//! CLOEXEC flag. Also note that the above described behavior about the CLOEXEC
77//! flag is specific to this implementation.
78
79use crate::Runtime;
80use crate::expansion::expand_text;
81use crate::expansion::expand_word;
82use crate::xtrace::XTrace;
83use enumset::EnumSet;
84use enumset::enum_set;
85use std::borrow::Cow;
86use std::ffi::CString;
87use std::ffi::NulError;
88use std::fmt::Write;
89use std::num::ParseIntError;
90use std::ops::Deref;
91use std::ops::DerefMut;
92use thiserror::Error;
93use yash_env::Env;
94use yash_env::io::Fd;
95use yash_env::io::MIN_INTERNAL_FD;
96use yash_env::option::Option::Clobber;
97use yash_env::option::State::Off;
98use yash_env::semantics::ExitStatus;
99use yash_env::semantics::Field;
100use yash_env::system::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;
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.
504#[allow(clippy::await_holding_refcell_ref)]
505async fn perform<S>(
506    env: &mut Env<S>,
507    redir: &Redir,
508    xtrace: Option<&mut XTrace>,
509) -> Result<(SavedFd, Option<ExitStatus>), Error>
510where
511    S: Runtime + 'static,
512{
513    let target_fd = redir.fd_or_default();
514
515    // Make sure target_fd doesn't have the CLOEXEC flag
516    if is_cloexec(env, target_fd) {
517        return Err(Error {
518            cause: ErrorCause::ReservedFd(target_fd),
519            location: redir.body.operand().location.clone(),
520        });
521    }
522
523    // Save the current open file description at target_fd to a new FD
524    let save = match env
525        .system
526        .dup(target_fd, MIN_INTERNAL_FD, FdFlag::CloseOnExec.into())
527    {
528        Ok(save_fd) => Some(save_fd),
529        Err(Errno::EBADF) => None,
530        Err(errno) => {
531            return Err(Error {
532                cause: ErrorCause::FdNotOverwritten(target_fd, errno),
533                location: redir.body.operand().location.clone(),
534            });
535        }
536    };
537
538    // Prepare an FD from the redirection body
539    let (fd_spec, location, exit_status) = match &redir.body {
540        RedirBody::Normal { operator, operand } => {
541            // TODO perform pathname expansion if applicable
542            let (expansion, exit_status) = expand_word(env, operand).await?;
543            trace_normal(xtrace, target_fd, *operator, &expansion);
544            let (fd, location) = open_normal(env, *operator, expansion).await?;
545            (fd, location, exit_status)
546        }
547        RedirBody::HereDoc(here_doc) => {
548            let content_ref = here_doc.content.get();
549            let content = content_ref.map(Cow::Borrowed).unwrap_or_default();
550            let (content, exit_status) = expand_text(env, &content).await?;
551            trace_here_doc(xtrace, target_fd, here_doc, &content);
552            let location = here_doc.delimiter.location.clone();
553            match here_doc::open_fd(env, content).await {
554                Ok(fd) => (FdSpec::Owned(fd), location, exit_status),
555                Err(cause) => return Err(Error { cause, location }),
556            }
557        }
558    };
559
560    if let Some(fd) = fd_spec.as_fd() {
561        if fd != target_fd {
562            let dup_result = env.system.dup2(fd, target_fd);
563            fd_spec.close(&env.system);
564            match dup_result {
565                Ok(new_fd) => assert_eq!(new_fd, target_fd),
566                Err(errno) => {
567                    return Err(Error {
568                        cause: ErrorCause::FdNotOverwritten(target_fd, errno),
569                        location,
570                    });
571                }
572            }
573        }
574    } else {
575        let _: Result<(), Errno> = env.system.close(target_fd);
576    }
577
578    let original = target_fd;
579    Ok((SavedFd { original, save }, exit_status))
580}
581
582/// `Env` wrapper for performing redirections.
583///
584/// This is an RAII-style wrapper of [`Env`] in which redirections are
585/// performed. A `RedirGuard` keeps track of file descriptors affected by
586/// redirections so that we can restore the file descriptors to the state before
587/// performing the redirections.
588///
589/// There are two ways to clear file descriptors saved in the `RedirGuard`.  One
590/// is [`undo_redirs`](Self::undo_redirs), which restores the file descriptors
591/// to the original state, and the other is
592/// [`preserve_redirs`](Self::preserve_redirs), which removes the saved file
593/// descriptors without restoring the state and thus makes the effect of the
594/// redirections permanent.
595///
596/// When an instance of `RedirGuard` is dropped, `undo_redirs` is implicitly
597/// called. That means you need to call `preserve_redirs` explicitly to preserve
598/// the redirections' effect.
599#[derive(Debug)]
600pub struct RedirGuard<'e, S: Close + Dup> {
601    /// Environment in which redirections are performed.
602    env: &'e mut yash_env::Env<S>,
603    /// Records of file descriptors that have been modified by redirections.
604    saved_fds: Vec<SavedFd>,
605}
606
607impl<S: Close + Dup> Deref for RedirGuard<'_, S> {
608    type Target = yash_env::Env<S>;
609    fn deref(&self) -> &yash_env::Env<S> {
610        self.env
611    }
612}
613
614impl<S: Close + Dup> DerefMut for RedirGuard<'_, S> {
615    fn deref_mut(&mut self) -> &mut yash_env::Env<S> {
616        self.env
617    }
618}
619
620impl<S: Close + Dup> std::ops::Drop for RedirGuard<'_, S> {
621    fn drop(&mut self) {
622        self.undo_redirs()
623    }
624}
625
626impl<'e, S: Close + Dup> RedirGuard<'e, S> {
627    /// Creates a new `RedirGuard`.
628    pub fn new(env: &'e mut yash_env::Env<S>) -> Self {
629        let saved_fds = Vec::new();
630        RedirGuard { env, saved_fds }
631    }
632
633    /// Performs a redirection.
634    ///
635    /// If successful, this function saves internally a backing copy of the file
636    /// descriptor affected by the redirection, and returns the exit status of
637    /// the last command substitution performed during the redirection, if any.
638    ///
639    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
640    /// and the expanded operands are written to it.
641    pub async fn perform_redir(
642        &mut self,
643        redir: &Redir,
644        xtrace: Option<&mut XTrace>,
645    ) -> Result<Option<ExitStatus>, Error>
646    where
647        S: Runtime + 'static,
648    {
649        let (saved_fd, exit_status) = perform(self, redir, xtrace).await?;
650        self.saved_fds.push(saved_fd);
651        Ok(exit_status)
652    }
653
654    /// Performs redirections.
655    ///
656    /// This is a convenience function for [performing
657    /// redirection](Self::perform_redir) for each iterator item.
658    ///
659    /// If the redirection fails for an item, the remainders are ignored, but
660    /// the effects of the preceding items are not canceled.
661    ///
662    /// If `xtrace` is `Some` instance of `XTrace`, the redirection operators
663    /// and the expanded operands are written to it.
664    pub async fn perform_redirs<'a, I>(
665        &mut self,
666        redirs: I,
667        mut xtrace: Option<&mut XTrace>,
668    ) -> Result<Option<ExitStatus>, Error>
669    where
670        S: Runtime + 'static,
671        I: IntoIterator<Item = &'a Redir>,
672    {
673        let mut exit_status = None;
674        for redir in redirs {
675            let new_exit_status = self.perform_redir(redir, xtrace.as_deref_mut()).await?;
676            exit_status = new_exit_status.or(exit_status);
677        }
678        Ok(exit_status)
679    }
680
681    /// Undoes the effect of the redirections.
682    ///
683    /// This function restores the file descriptors affected by redirections to
684    /// the original state and closes internal backing file descriptors, which
685    /// were used for restoration and are no longer needed.
686    pub fn undo_redirs(&mut self) {
687        for SavedFd { original, save } in self.saved_fds.drain(..).rev() {
688            if let Some(save) = save {
689                assert_ne!(save, original);
690                let _: Result<_, _> = self.env.system.dup2(save, original);
691                let _: Result<_, _> = self.env.system.close(save);
692            } else {
693                let _: Result<_, _> = self.env.system.close(original);
694            }
695        }
696    }
697
698    /// Makes the redirections permanent.
699    ///
700    /// This function closes internal backing file descriptors without restoring
701    /// the original file descriptor state.
702    pub fn preserve_redirs(&mut self) {
703        for SavedFd { original: _, save } in self.saved_fds.drain(..) {
704            if let Some(save) = save {
705                let _: Result<_, _> = self.env.system.close(save);
706            }
707        }
708    }
709}
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use crate::tests::echo_builtin;
715    use crate::tests::return_builtin;
716    use assert_matches::assert_matches;
717    use futures_util::FutureExt;
718    use std::cell::RefCell;
719    use std::rc::Rc;
720    use yash_env::Env;
721    use yash_env::VirtualSystem;
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::test_helper::in_virtual_system;
730    use yash_env::waker::WakerSet;
731    use yash_syntax::syntax::Text;
732
733    /// Returns a virtual system with a file descriptor limit.
734    fn system_with_nofile_limit() -> VirtualSystem {
735        let system = VirtualSystem::new();
736        system
737            .setrlimit(
738                Resource::NOFILE,
739                LimitPair {
740                    soft: 1024,
741                    hard: 1024,
742                },
743            )
744            .unwrap();
745        system
746    }
747
748    #[test]
749    fn basic_file_in_redirection() {
750        let system = system_with_nofile_limit();
751        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
752        let mut state = system.state.borrow_mut();
753        state.file_system.save("foo", file).unwrap();
754        drop(state);
755        let mut env = Env::with_system(system);
756        let mut env = RedirGuard::new(&mut env);
757        let redir = "3< foo".parse().unwrap();
758        let result = env
759            .perform_redir(&redir, None)
760            .now_or_never()
761            .unwrap()
762            .unwrap();
763        assert_eq!(result, None);
764
765        let mut buffer = [0; 4];
766        let read_count = env
767            .system
768            .read(Fd(3), &mut buffer)
769            .now_or_never()
770            .unwrap()
771            .unwrap();
772        assert_eq!(read_count, 3);
773        assert_eq!(buffer, [42, 123, 254, 0]);
774    }
775
776    #[test]
777    fn moving_fd() {
778        let system = system_with_nofile_limit();
779        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
780        let mut state = system.state.borrow_mut();
781        state.file_system.save("foo", file).unwrap();
782        drop(state);
783        let mut env = Env::with_system(system);
784        let mut env = RedirGuard::new(&mut env);
785        let redir = "< foo".parse().unwrap();
786        env.perform_redir(&redir, None)
787            .now_or_never()
788            .unwrap()
789            .unwrap();
790
791        let mut buffer = [0; 4];
792        let read_count = env
793            .system
794            .read(Fd::STDIN, &mut buffer)
795            .now_or_never()
796            .unwrap()
797            .unwrap();
798        assert_eq!(read_count, 3);
799        assert_eq!(buffer, [42, 123, 254, 0]);
800
801        let e = env
802            .system
803            .read(Fd(3), &mut buffer)
804            .now_or_never()
805            .unwrap()
806            .unwrap_err();
807        assert_eq!(e, Errno::EBADF);
808    }
809
810    #[test]
811    fn saving_and_undoing_fd() {
812        let system = system_with_nofile_limit();
813        let mut state = system.state.borrow_mut();
814        state.file_system.save("file", Rc::default()).unwrap();
815        state
816            .file_system
817            .get("/dev/stdin")
818            .unwrap()
819            .borrow_mut()
820            .body = FileBody::new([17]);
821        drop(state);
822        let mut env = Env::with_system(system);
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 system = system_with_nofile_limit();
847        let mut state = system.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 env = Env::with_system(system);
857        let mut redir_env = RedirGuard::new(&mut env);
858        let redir = "< file".parse().unwrap();
859        redir_env
860            .perform_redir(&redir, None)
861            .now_or_never()
862            .unwrap()
863            .unwrap();
864        redir_env.preserve_redirs();
865        drop(redir_env);
866
867        let mut buffer = [0; 2];
868        let read_count = env
869            .system
870            .read(Fd::STDIN, &mut buffer)
871            .now_or_never()
872            .unwrap()
873            .unwrap();
874        assert_eq!(read_count, 0);
875        let e = env
876            .system
877            .read(MIN_INTERNAL_FD, &mut buffer)
878            .now_or_never()
879            .unwrap()
880            .unwrap_err();
881        assert_eq!(e, Errno::EBADF);
882    }
883
884    #[test]
885    fn undoing_without_initial_fd() {
886        let system = system_with_nofile_limit();
887        let mut state = system.state.borrow_mut();
888        state.file_system.save("input", Rc::default()).unwrap();
889        drop(state);
890        let mut env = Env::with_system(system);
891        let mut redir_env = RedirGuard::new(&mut env);
892        let redir = "4< input".parse().unwrap();
893        redir_env
894            .perform_redir(&redir, None)
895            .now_or_never()
896            .unwrap()
897            .unwrap();
898        redir_env.undo_redirs();
899        drop(redir_env);
900
901        let mut buffer = [0; 1];
902        let e = env
903            .system
904            .read(Fd(4), &mut buffer)
905            .now_or_never()
906            .unwrap()
907            .unwrap_err();
908        assert_eq!(e, Errno::EBADF);
909    }
910
911    #[test]
912    fn unreadable_file() {
913        let mut env = Env::with_system(system_with_nofile_limit());
914        let mut env = RedirGuard::new(&mut env);
915        let redir = "< no_such_file".parse().unwrap();
916        let e = env
917            .perform_redir(&redir, None)
918            .now_or_never()
919            .unwrap()
920            .unwrap_err();
921        assert_eq!(
922            e.cause,
923            ErrorCause::OpenFile(c"no_such_file".to_owned(), Errno::ENOENT)
924        );
925        assert_eq!(e.location, redir.body.operand().location);
926    }
927
928    #[test]
929    fn multiple_redirections() {
930        let system = system_with_nofile_limit();
931        let mut state = system.state.borrow_mut();
932        let file = Rc::new(RefCell::new(Inode::new([100])));
933        state.file_system.save("foo", file).unwrap();
934        let file = Rc::new(RefCell::new(Inode::new([200])));
935        state.file_system.save("bar", file).unwrap();
936        drop(state);
937        let mut env = Env::with_system(system);
938        let mut env = RedirGuard::new(&mut env);
939        env.perform_redir(&"< foo".parse().unwrap(), None)
940            .now_or_never()
941            .unwrap()
942            .unwrap();
943        env.perform_redir(&"3< bar".parse().unwrap(), None)
944            .now_or_never()
945            .unwrap()
946            .unwrap();
947
948        let mut buffer = [0; 1];
949        let read_count = env
950            .system
951            .read(Fd::STDIN, &mut buffer)
952            .now_or_never()
953            .unwrap()
954            .unwrap();
955        assert_eq!(read_count, 1);
956        assert_eq!(buffer, [100]);
957        let read_count = env
958            .system
959            .read(Fd(3), &mut buffer)
960            .now_or_never()
961            .unwrap()
962            .unwrap();
963        assert_eq!(read_count, 1);
964        assert_eq!(buffer, [200]);
965    }
966
967    #[test]
968    fn later_redirection_wins() {
969        let system = system_with_nofile_limit();
970        let mut state = system.state.borrow_mut();
971        let file = Rc::new(RefCell::new(Inode::new([100])));
972        state.file_system.save("foo", file).unwrap();
973        let file = Rc::new(RefCell::new(Inode::new([200])));
974        state.file_system.save("bar", file).unwrap();
975        drop(state);
976
977        let mut env = Env::with_system(system);
978        let mut env = RedirGuard::new(&mut env);
979        env.perform_redir(&"< foo".parse().unwrap(), None)
980            .now_or_never()
981            .unwrap()
982            .unwrap();
983        env.perform_redir(&"< bar".parse().unwrap(), None)
984            .now_or_never()
985            .unwrap()
986            .unwrap();
987
988        let mut buffer = [0; 1];
989        let read_count = env
990            .system
991            .read(Fd::STDIN, &mut buffer)
992            .now_or_never()
993            .unwrap()
994            .unwrap();
995        assert_eq!(read_count, 1);
996        assert_eq!(buffer, [200]);
997    }
998
999    #[test]
1000    fn target_with_cloexec() {
1001        let mut env = Env::with_system(system_with_nofile_limit());
1002        let fd = env
1003            .system
1004            .open(
1005                c"foo",
1006                OfdAccess::WriteOnly,
1007                OpenFlag::Create.into(),
1008                Mode::ALL_9,
1009            )
1010            .now_or_never()
1011            .unwrap()
1012            .unwrap();
1013        env.system
1014            .fcntl_setfd(fd, FdFlag::CloseOnExec.into())
1015            .unwrap();
1016
1017        let mut env = RedirGuard::new(&mut env);
1018        let redir = format!("{fd}> bar").parse().unwrap();
1019        let e = env
1020            .perform_redir(&redir, None)
1021            .now_or_never()
1022            .unwrap()
1023            .unwrap_err();
1024        assert_eq!(e.cause, ErrorCause::ReservedFd(fd));
1025        assert_eq!(e.location, redir.body.operand().location);
1026    }
1027
1028    #[test]
1029    fn exit_status_of_command_substitution_in_normal() {
1030        in_virtual_system(|mut env, state| async move {
1031            env.builtins.insert("echo", echo_builtin());
1032            env.builtins.insert("return", return_builtin());
1033            let mut env = RedirGuard::new(&mut env);
1034            let redir = "3> $(echo foo; return -n 79)".parse().unwrap();
1035            let result = env.perform_redir(&redir, None).await.unwrap();
1036            assert_eq!(result, Some(ExitStatus(79)));
1037            let file = state.borrow().file_system.get("foo");
1038            assert!(file.is_ok(), "{file:?}");
1039        })
1040    }
1041
1042    #[test]
1043    fn exit_status_of_command_substitution_in_here_doc() {
1044        in_virtual_system(|mut env, _state| async move {
1045            env.builtins.insert("echo", echo_builtin());
1046            env.builtins.insert("return", return_builtin());
1047            let mut env = RedirGuard::new(&mut env);
1048            let redir = Redir {
1049                fd: Some(Fd(4)),
1050                body: RedirBody::HereDoc(Rc::new(HereDoc {
1051                    delimiter: "-END".parse().unwrap(),
1052                    remove_tabs: false,
1053                    content: "$(echo foo)$(echo bar; return -n 42)\n"
1054                        .parse::<Text>()
1055                        .unwrap()
1056                        .into(),
1057                })),
1058            };
1059            let result = env.perform_redir(&redir, None).await.unwrap();
1060            assert_eq!(result, Some(ExitStatus(42)));
1061
1062            let mut buffer = [0; 10];
1063            let count = env
1064                .system
1065                .read(Fd(4), &mut buffer)
1066                .now_or_never()
1067                .unwrap()
1068                .unwrap();
1069            assert_eq!(count, 7);
1070            assert_eq!(&buffer[..7], b"foobar\n");
1071        })
1072    }
1073
1074    #[test]
1075    fn xtrace_normal() {
1076        let mut xtrace = XTrace::new();
1077        let mut env = Env::with_system(system_with_nofile_limit());
1078        let mut env = RedirGuard::new(&mut env);
1079        env.perform_redir(&"> foo${unset-&}".parse().unwrap(), Some(&mut xtrace))
1080            .now_or_never()
1081            .unwrap()
1082            .unwrap();
1083        env.perform_redir(&"3>> bar".parse().unwrap(), Some(&mut xtrace))
1084            .now_or_never()
1085            .unwrap()
1086            .unwrap();
1087        let result = xtrace.finish(&mut env).now_or_never().unwrap();
1088        assert_eq!(result, "1>'foo&' 3>>bar\n");
1089    }
1090
1091    #[test]
1092    fn xtrace_here_doc() {
1093        let mut xtrace = XTrace::new();
1094        let mut env = Env::with_system(system_with_nofile_limit());
1095        let mut env = RedirGuard::new(&mut env);
1096
1097        let redir = Redir {
1098            fd: Some(Fd(4)),
1099            body: RedirBody::HereDoc(Rc::new(HereDoc {
1100                delimiter: r"-\END".parse().unwrap(),
1101                remove_tabs: false,
1102                content: "foo\n".parse::<Text>().unwrap().into(),
1103            })),
1104        };
1105        env.perform_redir(&redir, Some(&mut xtrace))
1106            .now_or_never()
1107            .unwrap()
1108            .unwrap();
1109
1110        let redir = Redir {
1111            fd: Some(Fd(5)),
1112            body: RedirBody::HereDoc(Rc::new(HereDoc {
1113                delimiter: r"EOF".parse().unwrap(),
1114                remove_tabs: false,
1115                content: "bar${unset-}\n".parse::<Text>().unwrap().into(),
1116            })),
1117        };
1118        env.perform_redir(&redir, Some(&mut xtrace))
1119            .now_or_never()
1120            .unwrap()
1121            .unwrap();
1122
1123        let result = xtrace.finish(&mut env).now_or_never().unwrap();
1124        assert_eq!(result, "4<< -\\END 5<<EOF\nfoo\n-END\nbar\nEOF\n");
1125    }
1126
1127    #[test]
1128    fn file_in_closes_opened_file_on_error() {
1129        let mut env = Env::with_system(system_with_nofile_limit());
1130        let mut env = RedirGuard::new(&mut env);
1131        let redir = "999999999</dev/stdin".parse().unwrap();
1132        let e = env
1133            .perform_redir(&redir, None)
1134            .now_or_never()
1135            .unwrap()
1136            .unwrap_err();
1137
1138        assert_eq!(
1139            e.cause,
1140            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1141        );
1142        assert_eq!(e.location, redir.body.operand().location);
1143        let mut buffer = [0; 1];
1144        let e = env
1145            .system
1146            .read(Fd(3), &mut buffer)
1147            .now_or_never()
1148            .unwrap()
1149            .unwrap_err();
1150        assert_eq!(e, Errno::EBADF);
1151    }
1152
1153    #[test]
1154    fn file_out_creates_empty_file() {
1155        let system = system_with_nofile_limit();
1156        let state = Rc::clone(&system.state);
1157        let mut env = Env::with_system(system);
1158        let mut env = RedirGuard::new(&mut env);
1159        let redir = "3> foo".parse().unwrap();
1160        env.perform_redir(&redir, None)
1161            .now_or_never()
1162            .unwrap()
1163            .unwrap();
1164        env.system
1165            .write(Fd(3), &[42, 123, 57])
1166            .now_or_never()
1167            .unwrap()
1168            .unwrap();
1169
1170        let file = state.borrow().file_system.get("foo").unwrap();
1171        let file = file.borrow();
1172        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1173            assert_eq!(content[..], [42, 123, 57]);
1174        });
1175    }
1176
1177    #[test]
1178    fn file_out_truncates_existing_file() {
1179        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1180        let system = system_with_nofile_limit();
1181        let mut state = system.state.borrow_mut();
1182        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1183        drop(state);
1184        let mut env = Env::with_system(system);
1185        let mut env = RedirGuard::new(&mut env);
1186
1187        let redir = "3> foo".parse().unwrap();
1188        env.perform_redir(&redir, None)
1189            .now_or_never()
1190            .unwrap()
1191            .unwrap();
1192
1193        let file = file.borrow();
1194        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1195            assert_eq!(content[..], []);
1196        });
1197    }
1198
1199    #[test]
1200    fn file_out_noclobber_with_regular_file() {
1201        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1202        let system = system_with_nofile_limit();
1203        let mut state = system.state.borrow_mut();
1204        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1205        drop(state);
1206        let mut env = Env::with_system(system);
1207        env.options.set(Clobber, Off);
1208        let mut env = RedirGuard::new(&mut env);
1209
1210        let redir = "3> foo".parse().unwrap();
1211        let e = env
1212            .perform_redir(&redir, None)
1213            .now_or_never()
1214            .unwrap()
1215            .unwrap_err();
1216
1217        assert_eq!(
1218            e.cause,
1219            ErrorCause::OpenFile(c"foo".to_owned(), Errno::EEXIST)
1220        );
1221        assert_eq!(e.location, redir.body.operand().location);
1222        let file = file.borrow();
1223        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1224            assert_eq!(content[..], [42, 123, 254]);
1225        });
1226    }
1227
1228    #[test]
1229    fn file_out_noclobber_with_non_regular_file() {
1230        let inode = Inode {
1231            body: FileBody::Fifo {
1232                content: Default::default(),
1233                readers: 1,
1234                writers: 0,
1235                pending_open_wakers: WakerSet::new(),
1236                pending_read_wakers: WakerSet::new(),
1237                pending_write_wakers: WakerSet::new(),
1238            },
1239            permissions: Default::default(),
1240        };
1241        let file = Rc::new(RefCell::new(inode));
1242        let system = system_with_nofile_limit();
1243        let mut state = system.state.borrow_mut();
1244        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1245        drop(state);
1246        let mut env = Env::with_system(system);
1247        env.options.set(Clobber, Off);
1248        let mut env = RedirGuard::new(&mut env);
1249
1250        let redir = "3> foo".parse().unwrap();
1251        let result = env.perform_redir(&redir, None).now_or_never().unwrap();
1252        assert_eq!(result, Ok(None));
1253    }
1254
1255    #[test]
1256    fn file_out_closes_opened_file_on_error() {
1257        let mut env = Env::with_system(system_with_nofile_limit());
1258        let mut env = RedirGuard::new(&mut env);
1259        let redir = "999999999>foo".parse().unwrap();
1260        let e = env
1261            .perform_redir(&redir, None)
1262            .now_or_never()
1263            .unwrap()
1264            .unwrap_err();
1265
1266        assert_eq!(
1267            e.cause,
1268            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1269        );
1270        assert_eq!(e.location, redir.body.operand().location);
1271        let e = env
1272            .system
1273            .write(Fd(3), &[0x20])
1274            .now_or_never()
1275            .unwrap()
1276            .unwrap_err();
1277        assert_eq!(e, Errno::EBADF);
1278    }
1279
1280    #[test]
1281    fn file_clobber_creates_empty_file() {
1282        let system = system_with_nofile_limit();
1283        let state = Rc::clone(&system.state);
1284        let mut env = Env::with_system(system);
1285        let mut env = RedirGuard::new(&mut env);
1286
1287        let redir = "3>| foo".parse().unwrap();
1288        env.perform_redir(&redir, None)
1289            .now_or_never()
1290            .unwrap()
1291            .unwrap();
1292        env.system
1293            .write(Fd(3), &[42, 123, 57])
1294            .now_or_never()
1295            .unwrap()
1296            .unwrap();
1297
1298        let file = state.borrow().file_system.get("foo").unwrap();
1299        let file = file.borrow();
1300        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1301            assert_eq!(content[..], [42, 123, 57]);
1302        });
1303    }
1304
1305    #[test]
1306    fn file_clobber_by_default_truncates_existing_file() {
1307        let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1308        let system = system_with_nofile_limit();
1309        let mut state = system.state.borrow_mut();
1310        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1311        drop(state);
1312        let mut env = Env::with_system(system);
1313        let mut env = RedirGuard::new(&mut env);
1314
1315        let redir = "3>| foo".parse().unwrap();
1316        env.perform_redir(&redir, None)
1317            .now_or_never()
1318            .unwrap()
1319            .unwrap();
1320
1321        let file = file.borrow();
1322        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1323            assert_eq!(content[..], []);
1324        });
1325    }
1326
1327    // TODO file_clobber_with_noclobber_fails_with_existing_file
1328
1329    #[test]
1330    fn file_clobber_closes_opened_file_on_error() {
1331        let mut env = Env::with_system(system_with_nofile_limit());
1332        let mut env = RedirGuard::new(&mut env);
1333        let redir = "999999999>|foo".parse().unwrap();
1334        let e = env
1335            .perform_redir(&redir, None)
1336            .now_or_never()
1337            .unwrap()
1338            .unwrap_err();
1339
1340        assert_eq!(
1341            e.cause,
1342            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1343        );
1344        assert_eq!(e.location, redir.body.operand().location);
1345        let e = env
1346            .system
1347            .write(Fd(3), &[0x20])
1348            .now_or_never()
1349            .unwrap()
1350            .unwrap_err();
1351        assert_eq!(e, Errno::EBADF);
1352    }
1353
1354    #[test]
1355    fn file_append_creates_empty_file() {
1356        let system = system_with_nofile_limit();
1357        let state = Rc::clone(&system.state);
1358        let mut env = Env::with_system(system);
1359        let mut env = RedirGuard::new(&mut env);
1360
1361        let redir = "3>> foo".parse().unwrap();
1362        env.perform_redir(&redir, None)
1363            .now_or_never()
1364            .unwrap()
1365            .unwrap();
1366        env.system
1367            .write(Fd(3), &[42, 123, 57])
1368            .now_or_never()
1369            .unwrap()
1370            .unwrap();
1371
1372        let file = state.borrow().file_system.get("foo").unwrap();
1373        let file = file.borrow();
1374        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1375            assert_eq!(content[..], [42, 123, 57]);
1376        });
1377    }
1378
1379    #[test]
1380    fn file_append_appends_to_existing_file() {
1381        let file = Rc::new(RefCell::new(Inode::new(*b"one\n")));
1382        let system = system_with_nofile_limit();
1383        let mut state = system.state.borrow_mut();
1384        state.file_system.save("foo", Rc::clone(&file)).unwrap();
1385        drop(state);
1386        let mut env = Env::with_system(system);
1387        let mut env = RedirGuard::new(&mut env);
1388
1389        let redir = ">> foo".parse().unwrap();
1390        env.perform_redir(&redir, None)
1391            .now_or_never()
1392            .unwrap()
1393            .unwrap();
1394        env.system
1395            .write(Fd::STDOUT, "two\n".as_bytes())
1396            .now_or_never()
1397            .unwrap()
1398            .unwrap();
1399
1400        let file = file.borrow();
1401        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1402            assert_eq!(std::str::from_utf8(content), Ok("one\ntwo\n"));
1403        });
1404    }
1405
1406    #[test]
1407    fn file_append_closes_opened_file_on_error() {
1408        let mut env = Env::with_system(system_with_nofile_limit());
1409        let mut env = RedirGuard::new(&mut env);
1410        let redir = "999999999>>foo".parse().unwrap();
1411        let e = env
1412            .perform_redir(&redir, None)
1413            .now_or_never()
1414            .unwrap()
1415            .unwrap_err();
1416
1417        assert_eq!(
1418            e.cause,
1419            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1420        );
1421        assert_eq!(e.location, redir.body.operand().location);
1422        let e = env
1423            .system
1424            .write(Fd(3), &[0x20])
1425            .now_or_never()
1426            .unwrap()
1427            .unwrap_err();
1428        assert_eq!(e, Errno::EBADF);
1429    }
1430
1431    #[test]
1432    fn file_in_out_creates_empty_file() {
1433        let system = system_with_nofile_limit();
1434        let state = Rc::clone(&system.state);
1435        let mut env = Env::with_system(system);
1436        let mut env = RedirGuard::new(&mut env);
1437        let redir = "3<> foo".parse().unwrap();
1438        env.perform_redir(&redir, None)
1439            .now_or_never()
1440            .unwrap()
1441            .unwrap();
1442        env.system
1443            .write(Fd(3), &[230, 175, 26])
1444            .now_or_never()
1445            .unwrap()
1446            .unwrap();
1447
1448        let file = state.borrow().file_system.get("foo").unwrap();
1449        let file = file.borrow();
1450        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1451            assert_eq!(content[..], [230, 175, 26]);
1452        });
1453    }
1454
1455    #[test]
1456    fn file_in_out_leaves_existing_file_content() {
1457        let system = system_with_nofile_limit();
1458        let file = Rc::new(RefCell::new(Inode::new([132, 79, 210])));
1459        let mut state = system.state.borrow_mut();
1460        state.file_system.save("foo", file).unwrap();
1461        drop(state);
1462        let mut env = Env::with_system(system);
1463        let mut env = RedirGuard::new(&mut env);
1464        let redir = "3<> foo".parse().unwrap();
1465        env.perform_redir(&redir, None)
1466            .now_or_never()
1467            .unwrap()
1468            .unwrap();
1469
1470        let mut buffer = [0; 4];
1471        let read_count = env
1472            .system
1473            .read(Fd(3), &mut buffer)
1474            .now_or_never()
1475            .unwrap()
1476            .unwrap();
1477        assert_eq!(read_count, 3);
1478        assert_eq!(buffer, [132, 79, 210, 0]);
1479    }
1480
1481    #[test]
1482    fn file_in_out_closes_opened_file_on_error() {
1483        let mut env = Env::with_system(system_with_nofile_limit());
1484        let mut env = RedirGuard::new(&mut env);
1485        let redir = "999999999<>foo".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 e = env
1498            .system
1499            .write(Fd(3), &[0x20])
1500            .now_or_never()
1501            .unwrap()
1502            .unwrap_err();
1503        assert_eq!(e, Errno::EBADF);
1504    }
1505
1506    #[test]
1507    fn fd_in_copies_fd() {
1508        for fd in [Fd(0), Fd(3)] {
1509            let system = system_with_nofile_limit();
1510            let state = Rc::clone(&system.state);
1511            state
1512                .borrow_mut()
1513                .file_system
1514                .get("/dev/stdin")
1515                .unwrap()
1516                .borrow_mut()
1517                .body = FileBody::new([1, 2, 42]);
1518            let mut env = Env::with_system(system);
1519            let mut env = RedirGuard::new(&mut env);
1520            let redir = "3<& 0".parse().unwrap();
1521            env.perform_redir(&redir, None)
1522                .now_or_never()
1523                .unwrap()
1524                .unwrap();
1525
1526            let mut buffer = [0; 4];
1527            let read_count = env
1528                .system
1529                .read(fd, &mut buffer)
1530                .now_or_never()
1531                .unwrap()
1532                .unwrap();
1533            assert_eq!(read_count, 3);
1534            assert_eq!(buffer, [1, 2, 42, 0]);
1535        }
1536    }
1537
1538    #[test]
1539    fn fd_in_closes_fd() {
1540        let mut env = Env::with_system(system_with_nofile_limit());
1541        let mut env = RedirGuard::new(&mut env);
1542        let redir = "<& -".parse().unwrap();
1543        env.perform_redir(&redir, None)
1544            .now_or_never()
1545            .unwrap()
1546            .unwrap();
1547
1548        let mut buffer = [0; 1];
1549        let e = env
1550            .system
1551            .read(Fd::STDIN, &mut buffer)
1552            .now_or_never()
1553            .unwrap()
1554            .unwrap_err();
1555        assert_eq!(e, Errno::EBADF);
1556    }
1557
1558    #[test]
1559    fn fd_in_rejects_unreadable_fd() {
1560        let mut env = Env::with_system(system_with_nofile_limit());
1561        let mut env = RedirGuard::new(&mut env);
1562        let redir = "3>foo".parse().unwrap();
1563        env.perform_redir(&redir, None)
1564            .now_or_never()
1565            .unwrap()
1566            .unwrap();
1567
1568        let redir = "<&3".parse().unwrap();
1569        let e = env
1570            .perform_redir(&redir, None)
1571            .now_or_never()
1572            .unwrap()
1573            .unwrap_err();
1574        assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1575        assert_eq!(e.location, redir.body.operand().location);
1576    }
1577
1578    #[test]
1579    fn fd_in_rejects_unopened_fd() {
1580        let mut env = Env::with_system(system_with_nofile_limit());
1581        let mut env = RedirGuard::new(&mut env);
1582
1583        let redir = "3<&3".parse().unwrap();
1584        let e = env
1585            .perform_redir(&redir, None)
1586            .now_or_never()
1587            .unwrap()
1588            .unwrap_err();
1589        assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1590        assert_eq!(e.location, redir.body.operand().location);
1591    }
1592
1593    #[test]
1594    fn fd_in_rejects_fd_with_cloexec() {
1595        let mut env = Env::with_system(system_with_nofile_limit());
1596        env.system
1597            .fcntl_setfd(Fd(0), FdFlag::CloseOnExec.into())
1598            .unwrap();
1599
1600        let mut env = RedirGuard::new(&mut env);
1601        let redir = "3<& 0".parse().unwrap();
1602        let e = env
1603            .perform_redir(&redir, None)
1604            .now_or_never()
1605            .unwrap()
1606            .unwrap_err();
1607        assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(0)));
1608        assert_eq!(e.location, redir.body.operand().location);
1609    }
1610
1611    #[test]
1612    fn keep_target_fd_open_on_error_in_fd_in() {
1613        let mut env = Env::with_system(system_with_nofile_limit());
1614        let mut env = RedirGuard::new(&mut env);
1615        let redir = "999999999<&0".parse().unwrap();
1616        let e = env
1617            .perform_redir(&redir, None)
1618            .now_or_never()
1619            .unwrap()
1620            .unwrap_err();
1621
1622        assert_eq!(
1623            e.cause,
1624            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1625        );
1626        assert_eq!(e.location, redir.body.operand().location);
1627        let mut buffer = [0; 1];
1628        let read_count = env
1629            .system
1630            .read(Fd(0), &mut buffer)
1631            .now_or_never()
1632            .unwrap()
1633            .unwrap();
1634        assert_eq!(read_count, 0);
1635    }
1636
1637    #[test]
1638    fn fd_out_copies_fd() {
1639        for fd in [Fd(1), Fd(4)] {
1640            let system = system_with_nofile_limit();
1641            let state = Rc::clone(&system.state);
1642            let mut env = Env::with_system(system);
1643            let mut env = RedirGuard::new(&mut env);
1644            let redir = "4>& 1".parse().unwrap();
1645            env.perform_redir(&redir, None)
1646                .now_or_never()
1647                .unwrap()
1648                .unwrap();
1649
1650            env.system
1651                .write(fd, &[7, 6, 91])
1652                .now_or_never()
1653                .unwrap()
1654                .unwrap();
1655            let file = state.borrow().file_system.get("/dev/stdout").unwrap();
1656            let file = file.borrow();
1657            assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1658                assert_eq!(content[..], [7, 6, 91]);
1659            });
1660        }
1661    }
1662
1663    #[test]
1664    fn fd_out_closes_fd() {
1665        let mut env = Env::with_system(system_with_nofile_limit());
1666        let mut env = RedirGuard::new(&mut env);
1667        let redir = ">& -".parse().unwrap();
1668        env.perform_redir(&redir, None)
1669            .now_or_never()
1670            .unwrap()
1671            .unwrap();
1672
1673        let mut buffer = [0; 1];
1674        let e = env
1675            .system
1676            .read(Fd::STDOUT, &mut buffer)
1677            .now_or_never()
1678            .unwrap()
1679            .unwrap_err();
1680        assert_eq!(e, Errno::EBADF);
1681    }
1682
1683    #[test]
1684    fn fd_out_rejects_unwritable_fd() {
1685        let mut env = Env::with_system(system_with_nofile_limit());
1686        let mut env = RedirGuard::new(&mut env);
1687        let redir = "3</dev/stdin".parse().unwrap();
1688        env.perform_redir(&redir, None)
1689            .now_or_never()
1690            .unwrap()
1691            .unwrap();
1692
1693        let redir = ">&3".parse().unwrap();
1694        let e = env
1695            .perform_redir(&redir, None)
1696            .now_or_never()
1697            .unwrap()
1698            .unwrap_err();
1699        assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1700        assert_eq!(e.location, redir.body.operand().location);
1701    }
1702
1703    #[test]
1704    fn fd_out_rejects_unopened_fd() {
1705        let mut env = Env::with_system(system_with_nofile_limit());
1706        let mut env = RedirGuard::new(&mut env);
1707
1708        let redir = "3>&3".parse().unwrap();
1709        let e = env
1710            .perform_redir(&redir, None)
1711            .now_or_never()
1712            .unwrap()
1713            .unwrap_err();
1714        assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1715        assert_eq!(e.location, redir.body.operand().location);
1716    }
1717
1718    #[test]
1719    fn fd_out_rejects_fd_with_cloexec() {
1720        let mut env = Env::with_system(system_with_nofile_limit());
1721        env.system
1722            .fcntl_setfd(Fd(1), FdFlag::CloseOnExec.into())
1723            .unwrap();
1724
1725        let mut env = RedirGuard::new(&mut env);
1726        let redir = "4>& 1".parse().unwrap();
1727        let e = env
1728            .perform_redir(&redir, None)
1729            .now_or_never()
1730            .unwrap()
1731            .unwrap_err();
1732        assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(1)));
1733        assert_eq!(e.location, redir.body.operand().location);
1734    }
1735
1736    #[test]
1737    fn keep_target_fd_open_on_error_in_fd_out() {
1738        let mut env = Env::with_system(system_with_nofile_limit());
1739        let mut env = RedirGuard::new(&mut env);
1740        let redir = "999999999>&1".parse().unwrap();
1741        let e = env
1742            .perform_redir(&redir, None)
1743            .now_or_never()
1744            .unwrap()
1745            .unwrap_err();
1746
1747        assert_eq!(
1748            e.cause,
1749            ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1750        );
1751        assert_eq!(e.location, redir.body.operand().location);
1752        let write_count = env
1753            .system
1754            .write(Fd(1), &[0x20])
1755            .now_or_never()
1756            .unwrap()
1757            .unwrap();
1758        assert_eq!(write_count, 1);
1759    }
1760
1761    #[test]
1762    fn pipe_redirection_not_yet_implemented() {
1763        let mut env = Env::with_system(system_with_nofile_limit());
1764        let mut env = RedirGuard::new(&mut env);
1765        let redir = "3>>|4".parse().unwrap();
1766        let e = env
1767            .perform_redir(&redir, None)
1768            .now_or_never()
1769            .unwrap()
1770            .unwrap_err();
1771
1772        assert_eq!(e.cause, ErrorCause::UnsupportedPipeRedirection);
1773        assert_eq!(e.location, redir.body.operand().location);
1774    }
1775
1776    #[test]
1777    fn here_string_not_yet_implemented() {
1778        let mut env = Env::with_system(system_with_nofile_limit());
1779        let mut env = RedirGuard::new(&mut env);
1780        let redir = "3<<< here".parse().unwrap();
1781        let e = env
1782            .perform_redir(&redir, None)
1783            .now_or_never()
1784            .unwrap()
1785            .unwrap_err();
1786
1787        assert_eq!(e.cause, ErrorCause::UnsupportedHereString);
1788        assert_eq!(e.location, redir.body.operand().location);
1789    }
1790}