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