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