1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113struct SavedFd {
114 original: Fd,
117 save: Option<Fd>,
120}
121
122#[derive(Clone, Debug, Eq, Error, PartialEq)]
124#[non_exhaustive]
125pub enum ErrorCause {
126 #[error(transparent)]
128 Expansion(#[from] crate::expansion::ErrorCause),
129
130 #[error(transparent)]
132 NulByte(#[from] NulError),
133
134 #[error("{1}")]
136 FdNotOverwritten(Fd, Errno),
137
138 #[error("file descriptor {0} is reserved by the shell")]
143 ReservedFd(Fd),
144
145 #[error("cannot open file '{}': {}", .0.to_string_lossy(), .1)]
149 OpenFile(CString, Errno),
150
151 #[error("{0} is not a valid file descriptor: {1}")]
153 MalformedFd(String, ParseIntError),
154
155 #[error("{0} is not a readable file descriptor")]
157 UnreadableFd(Fd),
158
159 #[error("{0} is not a writable file descriptor")]
161 UnwritableFd(Fd),
162
163 #[error("cannot prepare temporary file for here-document: {0}")]
165 TemporaryFileUnavailable(Errno),
166
167 #[error("pipe redirection is not yet implemented")]
169 UnsupportedPipeRedirection,
170
171 #[error("here-string redirection is not yet implemented")]
173 UnsupportedHereString,
174}
175
176impl ErrorCause {
177 #[must_use]
179 pub fn message(&self) -> &str {
180 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 #[must_use]
196 pub fn label(&self) -> Cow<'_, str> {
197 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#[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 #[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
244impl<'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#[derive(Debug)]
254enum FdSpec {
255 Owned(Fd),
257 Borrowed(Fd),
259 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
297async 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
315async 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 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 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 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
378fn 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 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 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 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
433async 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
478fn 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
492fn 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#[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 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 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 let (fd_spec, location, exit_status) = match &redir.body {
540 RedirBody::Normal { operator, operand } => {
541 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#[derive(Debug)]
600pub struct RedirGuard<'e, S: Close + Dup> {
601 env: &'e mut yash_env::Env<S>,
603 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 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 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 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 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 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 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 #[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}