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