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 as _;
89use std::num::ParseIntError;
90use std::ops::Deref;
91use std::ops::DerefMut;
92use thiserror::Error;
93use yash_env::Env;
94use yash_env::io::Fd;
95use yash_env::io::MIN_INTERNAL_FD;
96use yash_env::option::Option::Clobber;
97use yash_env::option::State::Off;
98use yash_env::semantics::ExitStatus;
99use yash_env::semantics::Field;
100use yash_env::system::Stat as _;
101use yash_env::system::{Close, Dup, Errno, Fcntl, FdFlag, Fstat, Mode, OfdAccess, Open, OpenFlag};
102use yash_quote::quoted;
103use yash_syntax::source::Location;
104use yash_syntax::source::pretty::{Report, ReportType, Snippet};
105use yash_syntax::syntax::HereDoc;
106use yash_syntax::syntax::Redir;
107use yash_syntax::syntax::RedirBody;
108use yash_syntax::syntax::RedirOp;
109use yash_syntax::syntax::Unquote as _;
110
111#[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
503async fn perform<S>(
505 env: &mut Env<S>,
506 redir: &Redir,
507 xtrace: Option<&mut XTrace>,
508) -> Result<(SavedFd, Option<ExitStatus>), Error>
509where
510 S: Runtime + 'static,
511{
512 let target_fd = redir.fd_or_default();
513
514 if is_cloexec(env, target_fd) {
516 return Err(Error {
517 cause: ErrorCause::ReservedFd(target_fd),
518 location: redir.body.operand().location.clone(),
519 });
520 }
521
522 let save = match env
524 .system
525 .dup(target_fd, MIN_INTERNAL_FD, FdFlag::CloseOnExec.into())
526 {
527 Ok(save_fd) => Some(save_fd),
528 Err(Errno::EBADF) => None,
529 Err(errno) => {
530 return Err(Error {
531 cause: ErrorCause::FdNotOverwritten(target_fd, errno),
532 location: redir.body.operand().location.clone(),
533 });
534 }
535 };
536
537 let (fd_spec, location, exit_status) = match &redir.body {
539 RedirBody::Normal { operator, operand } => {
540 let (expansion, exit_status) = expand_word(env, operand).await?;
542 trace_normal(xtrace, target_fd, *operator, &expansion);
543 let (fd, location) = open_normal(env, *operator, expansion).await?;
544 (fd, location, exit_status)
545 }
546 RedirBody::HereDoc(here_doc) => {
547 let content_ref = here_doc.content.get();
548 let content = content_ref.map(Cow::Borrowed).unwrap_or_default();
549 let (content, exit_status) = expand_text(env, &content).await?;
550 trace_here_doc(xtrace, target_fd, here_doc, &content);
551 let location = here_doc.delimiter.location.clone();
552 match here_doc::open_fd(env, content).await {
553 Ok(fd) => (FdSpec::Owned(fd), location, exit_status),
554 Err(cause) => return Err(Error { cause, location }),
555 }
556 }
557 };
558
559 if let Some(fd) = fd_spec.as_fd() {
560 if fd != target_fd {
561 let dup_result = env.system.dup2(fd, target_fd);
562 fd_spec.close(&env.system);
563 match dup_result {
564 Ok(new_fd) => assert_eq!(new_fd, target_fd),
565 Err(errno) => {
566 return Err(Error {
567 cause: ErrorCause::FdNotOverwritten(target_fd, errno),
568 location,
569 });
570 }
571 }
572 }
573 } else {
574 let _: Result<(), Errno> = env.system.close(target_fd);
575 }
576
577 let original = target_fd;
578 Ok((SavedFd { original, save }, exit_status))
579}
580
581#[derive(Debug)]
599pub struct RedirGuard<'e, S: Close + Dup> {
600 env: &'e mut yash_env::Env<S>,
602 saved_fds: Vec<SavedFd>,
604}
605
606impl<S: Close + Dup> Deref for RedirGuard<'_, S> {
607 type Target = yash_env::Env<S>;
608 fn deref(&self) -> &yash_env::Env<S> {
609 self.env
610 }
611}
612
613impl<S: Close + Dup> DerefMut for RedirGuard<'_, S> {
614 fn deref_mut(&mut self) -> &mut yash_env::Env<S> {
615 self.env
616 }
617}
618
619impl<S: Close + Dup> std::ops::Drop for RedirGuard<'_, S> {
620 fn drop(&mut self) {
621 self.undo_redirs()
622 }
623}
624
625impl<'e, S: Close + Dup> RedirGuard<'e, S> {
626 pub fn new(env: &'e mut yash_env::Env<S>) -> Self {
628 let saved_fds = Vec::new();
629 RedirGuard { env, saved_fds }
630 }
631
632 pub async fn perform_redir(
641 &mut self,
642 redir: &Redir,
643 xtrace: Option<&mut XTrace>,
644 ) -> Result<Option<ExitStatus>, Error>
645 where
646 S: Runtime + 'static,
647 {
648 let (saved_fd, exit_status) = perform(self, redir, xtrace).await?;
649 self.saved_fds.push(saved_fd);
650 Ok(exit_status)
651 }
652
653 pub async fn perform_redirs<'a, I>(
664 &mut self,
665 redirs: I,
666 mut xtrace: Option<&mut XTrace>,
667 ) -> Result<Option<ExitStatus>, Error>
668 where
669 S: Runtime + 'static,
670 I: IntoIterator<Item = &'a Redir>,
671 {
672 let mut exit_status = None;
673 for redir in redirs {
674 let new_exit_status = self.perform_redir(redir, xtrace.as_deref_mut()).await?;
675 exit_status = new_exit_status.or(exit_status);
676 }
677 Ok(exit_status)
678 }
679
680 pub fn undo_redirs(&mut self) {
686 for SavedFd { original, save } in self.saved_fds.drain(..).rev() {
687 if let Some(save) = save {
688 assert_ne!(save, original);
689 let _: Result<_, _> = self.env.system.dup2(save, original);
690 let _: Result<_, _> = self.env.system.close(save);
691 } else {
692 let _: Result<_, _> = self.env.system.close(original);
693 }
694 }
695 }
696
697 pub fn preserve_redirs(&mut self) {
702 for SavedFd { original: _, save } in self.saved_fds.drain(..) {
703 if let Some(save) = save {
704 let _: Result<_, _> = self.env.system.close(save);
705 }
706 }
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use crate::tests::echo_builtin;
714 use crate::tests::return_builtin;
715 use assert_matches::assert_matches;
716 use futures_util::FutureExt as _;
717 use std::cell::RefCell;
718 use std::rc::Rc;
719 use yash_env::Env;
720 use yash_env::VirtualSystem;
721 use yash_env::system::Concurrent;
722 use yash_env::system::Read as _;
723 use yash_env::system::Write as _;
724 use yash_env::system::resource::LimitPair;
725 use yash_env::system::resource::Resource;
726 use yash_env::system::resource::SetRlimit as _;
727 use yash_env::system::r#virtual::FileBody;
728 use yash_env::system::r#virtual::Inode;
729 use yash_env::system::r#virtual::SystemState;
730 use yash_env::test_helper::in_virtual_system;
731 use yash_env::waker::WakerSet;
732 use yash_syntax::syntax::Text;
733
734 fn env_with_nofile_limit() -> (Env<Rc<Concurrent<VirtualSystem>>>, Rc<RefCell<SystemState>>) {
736 let system = VirtualSystem::new();
737 system
738 .setrlimit(
739 Resource::NOFILE,
740 LimitPair {
741 soft: 1024,
742 hard: 1024,
743 },
744 )
745 .unwrap();
746 let state = Rc::clone(&system.state);
747 let env = Env::with_system(Rc::new(Concurrent::new(system)));
748 (env, state)
749 }
750
751 #[test]
752 fn basic_file_in_redirection() {
753 let (mut env, state) = env_with_nofile_limit();
754 let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
755 let mut state = state.borrow_mut();
756 state.file_system.save("foo", file).unwrap();
757 drop(state);
758 let mut env = RedirGuard::new(&mut env);
759 let redir = "3< foo".parse().unwrap();
760 let result = env
761 .perform_redir(&redir, None)
762 .now_or_never()
763 .unwrap()
764 .unwrap();
765 assert_eq!(result, None);
766
767 let mut buffer = [0; 4];
768 let read_count = env
769 .system
770 .read(Fd(3), &mut buffer)
771 .now_or_never()
772 .unwrap()
773 .unwrap();
774 assert_eq!(read_count, 3);
775 assert_eq!(buffer, [42, 123, 254, 0]);
776 }
777
778 #[test]
779 fn moving_fd() {
780 let (mut env, state) = env_with_nofile_limit();
781 let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
782 let mut state = state.borrow_mut();
783 state.file_system.save("foo", file).unwrap();
784 drop(state);
785 let mut env = RedirGuard::new(&mut env);
786 let redir = "< foo".parse().unwrap();
787 env.perform_redir(&redir, None)
788 .now_or_never()
789 .unwrap()
790 .unwrap();
791
792 let mut buffer = [0; 4];
793 let read_count = env
794 .system
795 .read(Fd::STDIN, &mut buffer)
796 .now_or_never()
797 .unwrap()
798 .unwrap();
799 assert_eq!(read_count, 3);
800 assert_eq!(buffer, [42, 123, 254, 0]);
801
802 let e = env
803 .system
804 .read(Fd(3), &mut buffer)
805 .now_or_never()
806 .unwrap()
807 .unwrap_err();
808 assert_eq!(e, Errno::EBADF);
809 }
810
811 #[test]
812 fn saving_and_undoing_fd() {
813 let (mut env, state) = env_with_nofile_limit();
814 let mut state = state.borrow_mut();
815 state.file_system.save("file", Rc::default()).unwrap();
816 state
817 .file_system
818 .get("/dev/stdin")
819 .unwrap()
820 .borrow_mut()
821 .body = FileBody::new([17]);
822 drop(state);
823 let mut redir_env = RedirGuard::new(&mut env);
824 let redir = "< file".parse().unwrap();
825 redir_env
826 .perform_redir(&redir, None)
827 .now_or_never()
828 .unwrap()
829 .unwrap();
830 redir_env.undo_redirs();
831 drop(redir_env);
832
833 let mut buffer = [0; 2];
834 let read_count = env
835 .system
836 .read(Fd::STDIN, &mut buffer)
837 .now_or_never()
838 .unwrap()
839 .unwrap();
840 assert_eq!(read_count, 1);
841 assert_eq!(buffer[0], 17);
842 }
843
844 #[test]
845 fn preserving_fd() {
846 let (mut env, state) = env_with_nofile_limit();
847 let mut state = state.borrow_mut();
848 state.file_system.save("file", Rc::default()).unwrap();
849 state
850 .file_system
851 .get("/dev/stdin")
852 .unwrap()
853 .borrow_mut()
854 .body = FileBody::new([17]);
855 drop(state);
856 let mut redir_env = RedirGuard::new(&mut env);
857 let redir = "< file".parse().unwrap();
858 redir_env
859 .perform_redir(&redir, None)
860 .now_or_never()
861 .unwrap()
862 .unwrap();
863 redir_env.preserve_redirs();
864 drop(redir_env);
865
866 let mut buffer = [0; 2];
867 let read_count = env
868 .system
869 .read(Fd::STDIN, &mut buffer)
870 .now_or_never()
871 .unwrap()
872 .unwrap();
873 assert_eq!(read_count, 0);
874 let e = env
875 .system
876 .read(MIN_INTERNAL_FD, &mut buffer)
877 .now_or_never()
878 .unwrap()
879 .unwrap_err();
880 assert_eq!(e, Errno::EBADF);
881 }
882
883 #[test]
884 fn undoing_without_initial_fd() {
885 let (mut env, state) = env_with_nofile_limit();
886 let mut state = state.borrow_mut();
887 state.file_system.save("input", Rc::default()).unwrap();
888 drop(state);
889 let mut redir_env = RedirGuard::new(&mut env);
890 let redir = "4< input".parse().unwrap();
891 redir_env
892 .perform_redir(&redir, None)
893 .now_or_never()
894 .unwrap()
895 .unwrap();
896 redir_env.undo_redirs();
897 drop(redir_env);
898
899 let mut buffer = [0; 1];
900 let e = env
901 .system
902 .read(Fd(4), &mut buffer)
903 .now_or_never()
904 .unwrap()
905 .unwrap_err();
906 assert_eq!(e, Errno::EBADF);
907 }
908
909 #[test]
910 fn unreadable_file() {
911 let mut env = env_with_nofile_limit().0;
912 let mut env = RedirGuard::new(&mut env);
913 let redir = "< no_such_file".parse().unwrap();
914 let e = env
915 .perform_redir(&redir, None)
916 .now_or_never()
917 .unwrap()
918 .unwrap_err();
919 assert_eq!(
920 e.cause,
921 ErrorCause::OpenFile(c"no_such_file".to_owned(), Errno::ENOENT)
922 );
923 assert_eq!(e.location, redir.body.operand().location);
924 }
925
926 #[test]
927 fn multiple_redirections() {
928 let (mut env, state) = env_with_nofile_limit();
929 let mut state = state.borrow_mut();
930 let file = Rc::new(RefCell::new(Inode::new([100])));
931 state.file_system.save("foo", file).unwrap();
932 let file = Rc::new(RefCell::new(Inode::new([200])));
933 state.file_system.save("bar", file).unwrap();
934 drop(state);
935 let mut env = RedirGuard::new(&mut env);
936 env.perform_redir(&"< foo".parse().unwrap(), None)
937 .now_or_never()
938 .unwrap()
939 .unwrap();
940 env.perform_redir(&"3< bar".parse().unwrap(), None)
941 .now_or_never()
942 .unwrap()
943 .unwrap();
944
945 let mut buffer = [0; 1];
946 let read_count = env
947 .system
948 .read(Fd::STDIN, &mut buffer)
949 .now_or_never()
950 .unwrap()
951 .unwrap();
952 assert_eq!(read_count, 1);
953 assert_eq!(buffer, [100]);
954 let read_count = env
955 .system
956 .read(Fd(3), &mut buffer)
957 .now_or_never()
958 .unwrap()
959 .unwrap();
960 assert_eq!(read_count, 1);
961 assert_eq!(buffer, [200]);
962 }
963
964 #[test]
965 fn later_redirection_wins() {
966 let (mut env, state) = env_with_nofile_limit();
967 let mut state = state.borrow_mut();
968 let file = Rc::new(RefCell::new(Inode::new([100])));
969 state.file_system.save("foo", file).unwrap();
970 let file = Rc::new(RefCell::new(Inode::new([200])));
971 state.file_system.save("bar", file).unwrap();
972 drop(state);
973
974 let mut env = RedirGuard::new(&mut env);
975 env.perform_redir(&"< foo".parse().unwrap(), None)
976 .now_or_never()
977 .unwrap()
978 .unwrap();
979 env.perform_redir(&"< bar".parse().unwrap(), None)
980 .now_or_never()
981 .unwrap()
982 .unwrap();
983
984 let mut buffer = [0; 1];
985 let read_count = env
986 .system
987 .read(Fd::STDIN, &mut buffer)
988 .now_or_never()
989 .unwrap()
990 .unwrap();
991 assert_eq!(read_count, 1);
992 assert_eq!(buffer, [200]);
993 }
994
995 #[test]
996 fn target_with_cloexec() {
997 let mut env = env_with_nofile_limit().0;
998 let fd = env
999 .system
1000 .open(
1001 c"foo",
1002 OfdAccess::WriteOnly,
1003 OpenFlag::Create.into(),
1004 Mode::ALL_9,
1005 )
1006 .now_or_never()
1007 .unwrap()
1008 .unwrap();
1009 env.system
1010 .fcntl_setfd(fd, FdFlag::CloseOnExec.into())
1011 .unwrap();
1012
1013 let mut env = RedirGuard::new(&mut env);
1014 let redir = format!("{fd}> bar").parse().unwrap();
1015 let e = env
1016 .perform_redir(&redir, None)
1017 .now_or_never()
1018 .unwrap()
1019 .unwrap_err();
1020 assert_eq!(e.cause, ErrorCause::ReservedFd(fd));
1021 assert_eq!(e.location, redir.body.operand().location);
1022 }
1023
1024 #[test]
1025 fn exit_status_of_command_substitution_in_normal() {
1026 in_virtual_system(|mut env, state| async move {
1027 env.builtins.insert("echo", echo_builtin());
1028 env.builtins.insert("return", return_builtin());
1029 let mut env = RedirGuard::new(&mut env);
1030 let redir = "3> $(echo foo; return -n 79)".parse().unwrap();
1031 let result = env.perform_redir(&redir, None).await.unwrap();
1032 assert_eq!(result, Some(ExitStatus(79)));
1033 let file = state.borrow().file_system.get("foo");
1034 assert!(file.is_ok(), "{file:?}");
1035 })
1036 }
1037
1038 #[test]
1039 fn exit_status_of_command_substitution_in_here_doc() {
1040 in_virtual_system(|mut env, _state| async move {
1041 env.builtins.insert("echo", echo_builtin());
1042 env.builtins.insert("return", return_builtin());
1043 let mut env = RedirGuard::new(&mut env);
1044 let redir = Redir {
1045 fd: Some(Fd(4)),
1046 body: RedirBody::HereDoc(Rc::new(HereDoc {
1047 delimiter: "-END".parse().unwrap(),
1048 remove_tabs: false,
1049 content: "$(echo foo)$(echo bar; return -n 42)\n"
1050 .parse::<Text>()
1051 .unwrap()
1052 .into(),
1053 })),
1054 };
1055 let result = env.perform_redir(&redir, None).await.unwrap();
1056 assert_eq!(result, Some(ExitStatus(42)));
1057
1058 let mut buffer = [0; 10];
1059 let count = env
1060 .system
1061 .read(Fd(4), &mut buffer)
1062 .now_or_never()
1063 .unwrap()
1064 .unwrap();
1065 assert_eq!(count, 7);
1066 assert_eq!(&buffer[..7], b"foobar\n");
1067 })
1068 }
1069
1070 #[test]
1071 fn xtrace_normal() {
1072 let mut xtrace = XTrace::new();
1073 let mut env = env_with_nofile_limit().0;
1074 let mut env = RedirGuard::new(&mut env);
1075 env.perform_redir(&"> foo${unset-&}".parse().unwrap(), Some(&mut xtrace))
1076 .now_or_never()
1077 .unwrap()
1078 .unwrap();
1079 env.perform_redir(&"3>> bar".parse().unwrap(), Some(&mut xtrace))
1080 .now_or_never()
1081 .unwrap()
1082 .unwrap();
1083 let result = xtrace.finish(&mut env).now_or_never().unwrap();
1084 assert_eq!(result, "1>'foo&' 3>>bar\n");
1085 }
1086
1087 #[test]
1088 fn xtrace_here_doc() {
1089 let mut xtrace = XTrace::new();
1090 let mut env = env_with_nofile_limit().0;
1091 let mut env = RedirGuard::new(&mut env);
1092
1093 let redir = Redir {
1094 fd: Some(Fd(4)),
1095 body: RedirBody::HereDoc(Rc::new(HereDoc {
1096 delimiter: r"-\END".parse().unwrap(),
1097 remove_tabs: false,
1098 content: "foo\n".parse::<Text>().unwrap().into(),
1099 })),
1100 };
1101 env.perform_redir(&redir, Some(&mut xtrace))
1102 .now_or_never()
1103 .unwrap()
1104 .unwrap();
1105
1106 let redir = Redir {
1107 fd: Some(Fd(5)),
1108 body: RedirBody::HereDoc(Rc::new(HereDoc {
1109 delimiter: r"EOF".parse().unwrap(),
1110 remove_tabs: false,
1111 content: "bar${unset-}\n".parse::<Text>().unwrap().into(),
1112 })),
1113 };
1114 env.perform_redir(&redir, Some(&mut xtrace))
1115 .now_or_never()
1116 .unwrap()
1117 .unwrap();
1118
1119 let result = xtrace.finish(&mut env).now_or_never().unwrap();
1120 assert_eq!(result, "4<< -\\END 5<<EOF\nfoo\n-END\nbar\nEOF\n");
1121 }
1122
1123 #[test]
1124 fn file_in_closes_opened_file_on_error() {
1125 let mut env = env_with_nofile_limit().0;
1126 let mut env = RedirGuard::new(&mut env);
1127 let redir = "999999999</dev/stdin".parse().unwrap();
1128 let e = env
1129 .perform_redir(&redir, None)
1130 .now_or_never()
1131 .unwrap()
1132 .unwrap_err();
1133
1134 assert_eq!(
1135 e.cause,
1136 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1137 );
1138 assert_eq!(e.location, redir.body.operand().location);
1139 let mut buffer = [0; 1];
1140 let e = env
1141 .system
1142 .read(Fd(3), &mut buffer)
1143 .now_or_never()
1144 .unwrap()
1145 .unwrap_err();
1146 assert_eq!(e, Errno::EBADF);
1147 }
1148
1149 #[test]
1150 fn file_out_creates_empty_file() {
1151 let (mut env, state) = env_with_nofile_limit();
1152 let mut env = RedirGuard::new(&mut env);
1153 let redir = "3> foo".parse().unwrap();
1154 env.perform_redir(&redir, None)
1155 .now_or_never()
1156 .unwrap()
1157 .unwrap();
1158 env.system
1159 .write(Fd(3), &[42, 123, 57])
1160 .now_or_never()
1161 .unwrap()
1162 .unwrap();
1163
1164 let file = state.borrow().file_system.get("foo").unwrap();
1165 let file = file.borrow();
1166 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1167 assert_eq!(content[..], [42, 123, 57]);
1168 });
1169 }
1170
1171 #[test]
1172 fn file_out_truncates_existing_file() {
1173 let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1174 let (mut env, state) = env_with_nofile_limit();
1175 let mut state = state.borrow_mut();
1176 state.file_system.save("foo", Rc::clone(&file)).unwrap();
1177 drop(state);
1178 let mut env = RedirGuard::new(&mut env);
1179
1180 let redir = "3> foo".parse().unwrap();
1181 env.perform_redir(&redir, None)
1182 .now_or_never()
1183 .unwrap()
1184 .unwrap();
1185
1186 let file = file.borrow();
1187 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1188 assert_eq!(content[..], []);
1189 });
1190 }
1191
1192 #[test]
1193 fn file_out_noclobber_with_regular_file() {
1194 let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1195 let (mut env, state) = env_with_nofile_limit();
1196 let mut state = state.borrow_mut();
1197 state.file_system.save("foo", Rc::clone(&file)).unwrap();
1198 drop(state);
1199 env.options.set(Clobber, Off);
1200 let mut env = RedirGuard::new(&mut env);
1201
1202 let redir = "3> foo".parse().unwrap();
1203 let e = env
1204 .perform_redir(&redir, None)
1205 .now_or_never()
1206 .unwrap()
1207 .unwrap_err();
1208
1209 assert_eq!(
1210 e.cause,
1211 ErrorCause::OpenFile(c"foo".to_owned(), Errno::EEXIST)
1212 );
1213 assert_eq!(e.location, redir.body.operand().location);
1214 let file = file.borrow();
1215 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1216 assert_eq!(content[..], [42, 123, 254]);
1217 });
1218 }
1219
1220 #[test]
1221 fn file_out_noclobber_with_non_regular_file() {
1222 let inode = Inode {
1223 body: FileBody::Fifo {
1224 content: Default::default(),
1225 readers: 1,
1226 writers: 0,
1227 pending_open_wakers: WakerSet::new(),
1228 pending_read_wakers: WakerSet::new(),
1229 pending_write_wakers: WakerSet::new(),
1230 },
1231 permissions: Default::default(),
1232 };
1233 let file = Rc::new(RefCell::new(inode));
1234 let (mut env, state) = env_with_nofile_limit();
1235 let mut state = state.borrow_mut();
1236 state.file_system.save("foo", Rc::clone(&file)).unwrap();
1237 drop(state);
1238 env.options.set(Clobber, Off);
1239 let mut env = RedirGuard::new(&mut env);
1240
1241 let redir = "3> foo".parse().unwrap();
1242 let result = env.perform_redir(&redir, None).now_or_never().unwrap();
1243 assert_eq!(result, Ok(None));
1244 }
1245
1246 #[test]
1247 fn file_out_closes_opened_file_on_error() {
1248 let mut env = env_with_nofile_limit().0;
1249 let mut env = RedirGuard::new(&mut env);
1250 let redir = "999999999>foo".parse().unwrap();
1251 let e = env
1252 .perform_redir(&redir, None)
1253 .now_or_never()
1254 .unwrap()
1255 .unwrap_err();
1256
1257 assert_eq!(
1258 e.cause,
1259 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1260 );
1261 assert_eq!(e.location, redir.body.operand().location);
1262 let e = env
1263 .system
1264 .write(Fd(3), &[0x20])
1265 .now_or_never()
1266 .unwrap()
1267 .unwrap_err();
1268 assert_eq!(e, Errno::EBADF);
1269 }
1270
1271 #[test]
1272 fn file_clobber_creates_empty_file() {
1273 let (mut env, state) = env_with_nofile_limit();
1274 let mut env = RedirGuard::new(&mut env);
1275
1276 let redir = "3>| foo".parse().unwrap();
1277 env.perform_redir(&redir, None)
1278 .now_or_never()
1279 .unwrap()
1280 .unwrap();
1281 env.system
1282 .write(Fd(3), &[42, 123, 57])
1283 .now_or_never()
1284 .unwrap()
1285 .unwrap();
1286
1287 let file = state.borrow().file_system.get("foo").unwrap();
1288 let file = file.borrow();
1289 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1290 assert_eq!(content[..], [42, 123, 57]);
1291 });
1292 }
1293
1294 #[test]
1295 fn file_clobber_by_default_truncates_existing_file() {
1296 let file = Rc::new(RefCell::new(Inode::new([42, 123, 254])));
1297 let (mut env, state) = env_with_nofile_limit();
1298 let mut state = state.borrow_mut();
1299 state.file_system.save("foo", Rc::clone(&file)).unwrap();
1300 drop(state);
1301 let mut env = RedirGuard::new(&mut env);
1302
1303 let redir = "3>| foo".parse().unwrap();
1304 env.perform_redir(&redir, None)
1305 .now_or_never()
1306 .unwrap()
1307 .unwrap();
1308
1309 let file = file.borrow();
1310 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1311 assert_eq!(content[..], []);
1312 });
1313 }
1314
1315 #[test]
1318 fn file_clobber_closes_opened_file_on_error() {
1319 let mut env = env_with_nofile_limit().0;
1320 let mut env = RedirGuard::new(&mut env);
1321 let redir = "999999999>|foo".parse().unwrap();
1322 let e = env
1323 .perform_redir(&redir, None)
1324 .now_or_never()
1325 .unwrap()
1326 .unwrap_err();
1327
1328 assert_eq!(
1329 e.cause,
1330 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1331 );
1332 assert_eq!(e.location, redir.body.operand().location);
1333 let e = env
1334 .system
1335 .write(Fd(3), &[0x20])
1336 .now_or_never()
1337 .unwrap()
1338 .unwrap_err();
1339 assert_eq!(e, Errno::EBADF);
1340 }
1341
1342 #[test]
1343 fn file_append_creates_empty_file() {
1344 let (mut env, state) = env_with_nofile_limit();
1345 let mut env = RedirGuard::new(&mut env);
1346
1347 let redir = "3>> foo".parse().unwrap();
1348 env.perform_redir(&redir, None)
1349 .now_or_never()
1350 .unwrap()
1351 .unwrap();
1352 env.system
1353 .write(Fd(3), &[42, 123, 57])
1354 .now_or_never()
1355 .unwrap()
1356 .unwrap();
1357
1358 let file = state.borrow().file_system.get("foo").unwrap();
1359 let file = file.borrow();
1360 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1361 assert_eq!(content[..], [42, 123, 57]);
1362 });
1363 }
1364
1365 #[test]
1366 fn file_append_appends_to_existing_file() {
1367 let file = Rc::new(RefCell::new(Inode::new(*b"one\n")));
1368 let (mut env, state) = env_with_nofile_limit();
1369 let mut state = state.borrow_mut();
1370 state.file_system.save("foo", Rc::clone(&file)).unwrap();
1371 drop(state);
1372 let mut env = RedirGuard::new(&mut env);
1373
1374 let redir = ">> foo".parse().unwrap();
1375 env.perform_redir(&redir, None)
1376 .now_or_never()
1377 .unwrap()
1378 .unwrap();
1379 env.system
1380 .write(Fd::STDOUT, "two\n".as_bytes())
1381 .now_or_never()
1382 .unwrap()
1383 .unwrap();
1384
1385 let file = file.borrow();
1386 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1387 assert_eq!(std::str::from_utf8(content), Ok("one\ntwo\n"));
1388 });
1389 }
1390
1391 #[test]
1392 fn file_append_closes_opened_file_on_error() {
1393 let mut env = env_with_nofile_limit().0;
1394 let mut env = RedirGuard::new(&mut env);
1395 let redir = "999999999>>foo".parse().unwrap();
1396 let e = env
1397 .perform_redir(&redir, None)
1398 .now_or_never()
1399 .unwrap()
1400 .unwrap_err();
1401
1402 assert_eq!(
1403 e.cause,
1404 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1405 );
1406 assert_eq!(e.location, redir.body.operand().location);
1407 let e = env
1408 .system
1409 .write(Fd(3), &[0x20])
1410 .now_or_never()
1411 .unwrap()
1412 .unwrap_err();
1413 assert_eq!(e, Errno::EBADF);
1414 }
1415
1416 #[test]
1417 fn file_in_out_creates_empty_file() {
1418 let (mut env, state) = env_with_nofile_limit();
1419 let mut env = RedirGuard::new(&mut env);
1420 let redir = "3<> foo".parse().unwrap();
1421 env.perform_redir(&redir, None)
1422 .now_or_never()
1423 .unwrap()
1424 .unwrap();
1425 env.system
1426 .write(Fd(3), &[230, 175, 26])
1427 .now_or_never()
1428 .unwrap()
1429 .unwrap();
1430
1431 let file = state.borrow().file_system.get("foo").unwrap();
1432 let file = file.borrow();
1433 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1434 assert_eq!(content[..], [230, 175, 26]);
1435 });
1436 }
1437
1438 #[test]
1439 fn file_in_out_leaves_existing_file_content() {
1440 let (mut env, state) = env_with_nofile_limit();
1441 let file = Rc::new(RefCell::new(Inode::new([132, 79, 210])));
1442 let mut state = state.borrow_mut();
1443 state.file_system.save("foo", file).unwrap();
1444 drop(state);
1445 let mut env = RedirGuard::new(&mut env);
1446 let redir = "3<> foo".parse().unwrap();
1447 env.perform_redir(&redir, None)
1448 .now_or_never()
1449 .unwrap()
1450 .unwrap();
1451
1452 let mut buffer = [0; 4];
1453 let read_count = env
1454 .system
1455 .read(Fd(3), &mut buffer)
1456 .now_or_never()
1457 .unwrap()
1458 .unwrap();
1459 assert_eq!(read_count, 3);
1460 assert_eq!(buffer, [132, 79, 210, 0]);
1461 }
1462
1463 #[test]
1464 fn file_in_out_closes_opened_file_on_error() {
1465 let mut env = env_with_nofile_limit().0;
1466 let mut env = RedirGuard::new(&mut env);
1467 let redir = "999999999<>foo".parse().unwrap();
1468 let e = env
1469 .perform_redir(&redir, None)
1470 .now_or_never()
1471 .unwrap()
1472 .unwrap_err();
1473
1474 assert_eq!(
1475 e.cause,
1476 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1477 );
1478 assert_eq!(e.location, redir.body.operand().location);
1479 let e = env
1480 .system
1481 .write(Fd(3), &[0x20])
1482 .now_or_never()
1483 .unwrap()
1484 .unwrap_err();
1485 assert_eq!(e, Errno::EBADF);
1486 }
1487
1488 #[test]
1489 fn fd_in_copies_fd() {
1490 for fd in [Fd(0), Fd(3)] {
1491 let (mut env, state) = env_with_nofile_limit();
1492 state
1493 .borrow_mut()
1494 .file_system
1495 .get("/dev/stdin")
1496 .unwrap()
1497 .borrow_mut()
1498 .body = FileBody::new([1, 2, 42]);
1499 let mut env = RedirGuard::new(&mut env);
1500 let redir = "3<& 0".parse().unwrap();
1501 env.perform_redir(&redir, None)
1502 .now_or_never()
1503 .unwrap()
1504 .unwrap();
1505
1506 let mut buffer = [0; 4];
1507 let read_count = env
1508 .system
1509 .read(fd, &mut buffer)
1510 .now_or_never()
1511 .unwrap()
1512 .unwrap();
1513 assert_eq!(read_count, 3);
1514 assert_eq!(buffer, [1, 2, 42, 0]);
1515 }
1516 }
1517
1518 #[test]
1519 fn fd_in_closes_fd() {
1520 let mut env = env_with_nofile_limit().0;
1521 let mut env = RedirGuard::new(&mut env);
1522 let redir = "<& -".parse().unwrap();
1523 env.perform_redir(&redir, None)
1524 .now_or_never()
1525 .unwrap()
1526 .unwrap();
1527
1528 let mut buffer = [0; 1];
1529 let e = env
1530 .system
1531 .read(Fd::STDIN, &mut buffer)
1532 .now_or_never()
1533 .unwrap()
1534 .unwrap_err();
1535 assert_eq!(e, Errno::EBADF);
1536 }
1537
1538 #[test]
1539 fn fd_in_rejects_unreadable_fd() {
1540 let mut env = env_with_nofile_limit().0;
1541 let mut env = RedirGuard::new(&mut env);
1542 let redir = "3>foo".parse().unwrap();
1543 env.perform_redir(&redir, None)
1544 .now_or_never()
1545 .unwrap()
1546 .unwrap();
1547
1548 let redir = "<&3".parse().unwrap();
1549 let e = env
1550 .perform_redir(&redir, None)
1551 .now_or_never()
1552 .unwrap()
1553 .unwrap_err();
1554 assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1555 assert_eq!(e.location, redir.body.operand().location);
1556 }
1557
1558 #[test]
1559 fn fd_in_rejects_unopened_fd() {
1560 let mut env = env_with_nofile_limit().0;
1561 let mut env = RedirGuard::new(&mut env);
1562
1563 let redir = "3<&3".parse().unwrap();
1564 let e = env
1565 .perform_redir(&redir, None)
1566 .now_or_never()
1567 .unwrap()
1568 .unwrap_err();
1569 assert_eq!(e.cause, ErrorCause::UnreadableFd(Fd(3)));
1570 assert_eq!(e.location, redir.body.operand().location);
1571 }
1572
1573 #[test]
1574 fn fd_in_rejects_fd_with_cloexec() {
1575 let mut env = env_with_nofile_limit().0;
1576 env.system
1577 .fcntl_setfd(Fd(0), FdFlag::CloseOnExec.into())
1578 .unwrap();
1579
1580 let mut env = RedirGuard::new(&mut env);
1581 let redir = "3<& 0".parse().unwrap();
1582 let e = env
1583 .perform_redir(&redir, None)
1584 .now_or_never()
1585 .unwrap()
1586 .unwrap_err();
1587 assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(0)));
1588 assert_eq!(e.location, redir.body.operand().location);
1589 }
1590
1591 #[test]
1592 fn keep_target_fd_open_on_error_in_fd_in() {
1593 let mut env = env_with_nofile_limit().0;
1594 let mut env = RedirGuard::new(&mut env);
1595 let redir = "999999999<&0".parse().unwrap();
1596 let e = env
1597 .perform_redir(&redir, None)
1598 .now_or_never()
1599 .unwrap()
1600 .unwrap_err();
1601
1602 assert_eq!(
1603 e.cause,
1604 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1605 );
1606 assert_eq!(e.location, redir.body.operand().location);
1607 let mut buffer = [0; 1];
1608 let read_count = env
1609 .system
1610 .read(Fd(0), &mut buffer)
1611 .now_or_never()
1612 .unwrap()
1613 .unwrap();
1614 assert_eq!(read_count, 0);
1615 }
1616
1617 #[test]
1618 fn fd_out_copies_fd() {
1619 for fd in [Fd(1), Fd(4)] {
1620 let (mut env, state) = env_with_nofile_limit();
1621 let mut env = RedirGuard::new(&mut env);
1622 let redir = "4>& 1".parse().unwrap();
1623 env.perform_redir(&redir, None)
1624 .now_or_never()
1625 .unwrap()
1626 .unwrap();
1627
1628 env.system
1629 .write(fd, &[7, 6, 91])
1630 .now_or_never()
1631 .unwrap()
1632 .unwrap();
1633 let file = state.borrow().file_system.get("/dev/stdout").unwrap();
1634 let file = file.borrow();
1635 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
1636 assert_eq!(content[..], [7, 6, 91]);
1637 });
1638 }
1639 }
1640
1641 #[test]
1642 fn fd_out_closes_fd() {
1643 let mut env = env_with_nofile_limit().0;
1644 let mut env = RedirGuard::new(&mut env);
1645 let redir = ">& -".parse().unwrap();
1646 env.perform_redir(&redir, None)
1647 .now_or_never()
1648 .unwrap()
1649 .unwrap();
1650
1651 let mut buffer = [0; 1];
1652 let e = env
1653 .system
1654 .read(Fd::STDOUT, &mut buffer)
1655 .now_or_never()
1656 .unwrap()
1657 .unwrap_err();
1658 assert_eq!(e, Errno::EBADF);
1659 }
1660
1661 #[test]
1662 fn fd_out_rejects_unwritable_fd() {
1663 let mut env = env_with_nofile_limit().0;
1664 let mut env = RedirGuard::new(&mut env);
1665 let redir = "3</dev/stdin".parse().unwrap();
1666 env.perform_redir(&redir, None)
1667 .now_or_never()
1668 .unwrap()
1669 .unwrap();
1670
1671 let redir = ">&3".parse().unwrap();
1672 let e = env
1673 .perform_redir(&redir, None)
1674 .now_or_never()
1675 .unwrap()
1676 .unwrap_err();
1677 assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1678 assert_eq!(e.location, redir.body.operand().location);
1679 }
1680
1681 #[test]
1682 fn fd_out_rejects_unopened_fd() {
1683 let mut env = env_with_nofile_limit().0;
1684 let mut env = RedirGuard::new(&mut env);
1685
1686 let redir = "3>&3".parse().unwrap();
1687 let e = env
1688 .perform_redir(&redir, None)
1689 .now_or_never()
1690 .unwrap()
1691 .unwrap_err();
1692 assert_eq!(e.cause, ErrorCause::UnwritableFd(Fd(3)));
1693 assert_eq!(e.location, redir.body.operand().location);
1694 }
1695
1696 #[test]
1697 fn fd_out_rejects_fd_with_cloexec() {
1698 let mut env = env_with_nofile_limit().0;
1699 env.system
1700 .fcntl_setfd(Fd(1), FdFlag::CloseOnExec.into())
1701 .unwrap();
1702
1703 let mut env = RedirGuard::new(&mut env);
1704 let redir = "4>& 1".parse().unwrap();
1705 let e = env
1706 .perform_redir(&redir, None)
1707 .now_or_never()
1708 .unwrap()
1709 .unwrap_err();
1710 assert_eq!(e.cause, ErrorCause::ReservedFd(Fd(1)));
1711 assert_eq!(e.location, redir.body.operand().location);
1712 }
1713
1714 #[test]
1715 fn keep_target_fd_open_on_error_in_fd_out() {
1716 let mut env = env_with_nofile_limit().0;
1717 let mut env = RedirGuard::new(&mut env);
1718 let redir = "999999999>&1".parse().unwrap();
1719 let e = env
1720 .perform_redir(&redir, None)
1721 .now_or_never()
1722 .unwrap()
1723 .unwrap_err();
1724
1725 assert_eq!(
1726 e.cause,
1727 ErrorCause::FdNotOverwritten(Fd(999999999), Errno::EBADF)
1728 );
1729 assert_eq!(e.location, redir.body.operand().location);
1730 let write_count = env
1731 .system
1732 .write(Fd(1), &[0x20])
1733 .now_or_never()
1734 .unwrap()
1735 .unwrap();
1736 assert_eq!(write_count, 1);
1737 }
1738
1739 #[test]
1740 fn pipe_redirection_not_yet_implemented() {
1741 let mut env = env_with_nofile_limit().0;
1742 let mut env = RedirGuard::new(&mut env);
1743 let redir = "3>>|4".parse().unwrap();
1744 let e = env
1745 .perform_redir(&redir, None)
1746 .now_or_never()
1747 .unwrap()
1748 .unwrap_err();
1749
1750 assert_eq!(e.cause, ErrorCause::UnsupportedPipeRedirection);
1751 assert_eq!(e.location, redir.body.operand().location);
1752 }
1753
1754 #[test]
1755 fn here_string_not_yet_implemented() {
1756 let mut env = env_with_nofile_limit().0;
1757 let mut env = RedirGuard::new(&mut env);
1758 let redir = "3<<< here".parse().unwrap();
1759 let e = env
1760 .perform_redir(&redir, None)
1761 .now_or_never()
1762 .unwrap()
1763 .unwrap_err();
1764
1765 assert_eq!(e.cause, ErrorCause::UnsupportedHereString);
1766 assert_eq!(e.location, redir.body.operand().location);
1767 }
1768}