1use super::Command;
20use crate::Runtime;
21use crate::trap::run_exit_trap;
22use enumset::EnumSet;
23use itertools::Itertools as _;
24use std::ops::ControlFlow::{Break, Continue};
25use std::rc::Rc;
26use yash_env::Env;
27use yash_env::io::Fd;
28use yash_env::job::Pid;
29use yash_env::job::add_job_if_suspended;
30use yash_env::option::Option::{Exec, Interactive, PipeFail};
31use yash_env::option::State::{Off, On};
32use yash_env::semantics::Divert;
33use yash_env::semantics::ExitStatus;
34use yash_env::semantics::Result;
35use yash_env::stack::Frame;
36use yash_env::subshell::Config;
37use yash_env::subshell::JobControl;
38use yash_env::system::concurrency::WriteAll;
39use yash_env::system::{Close, Dup, Errno, Isatty, Pipe};
40use yash_syntax::syntax;
41
42impl<S: Runtime + 'static> Command<S> for syntax::Pipeline {
80 async fn execute(&self, env: &mut Env<S>) -> Result {
81 if env.options.get(Exec) == Off && env.options.get(Interactive) == Off {
82 return Continue(());
83 }
84
85 if !self.negation {
86 return execute_commands_in_pipeline(env, &self.commands).await;
87 }
88
89 let mut env = env.push_frame(Frame::Condition);
90 execute_commands_in_pipeline(&mut env, &self.commands).await?;
91 env.exit_status = if env.exit_status.is_successful() {
92 ExitStatus::FAILURE
93 } else {
94 ExitStatus::SUCCESS
95 };
96 Continue(())
97 }
98}
99
100async fn execute_commands_in_pipeline<S: Runtime + 'static>(
101 env: &mut Env<S>,
102 commands: &[Rc<syntax::Command>],
103) -> Result {
104 match commands.len() {
105 0 => {
106 env.exit_status = ExitStatus::SUCCESS;
107 Continue(())
108 }
109
110 1 => commands[0].execute(env).await,
111
112 _ => {
113 if env.controls_jobs() {
114 execute_job_controlled_pipeline(env, commands).await?
115 } else {
116 execute_multi_command_pipeline(env, commands).await?
117 }
118 env.apply_errexit()
119 }
120 }
121}
122
123async fn execute_job_controlled_pipeline<S: Runtime + 'static>(
124 env: &mut Env<S>,
125 commands: &[Rc<syntax::Command>],
126) -> Result {
127 let commands_2 = commands.to_vec();
128 let subshell = Config::foreground().start_and_wait(env, async move |sub_env, _job_control| {
129 let result = execute_multi_command_pipeline(sub_env, &commands_2).await;
130 sub_env.apply_result(result);
131 run_exit_trap(sub_env).await;
132 });
133 match subshell.await {
134 Ok((pid, result)) => {
135 env.exit_status = add_job_if_suspended(env, pid, result, || to_job_name(commands))?;
136 Continue(())
137 }
138 Err(errno) => {
139 let message = format!("cannot start a subshell in the pipeline: {errno}\n");
141 env.system.print_error(&message).await;
142 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
143 }
144 }
145}
146
147fn to_job_name(commands: &[Rc<syntax::Command>]) -> String {
148 commands
149 .iter()
150 .format_with(" | ", |cmd, f| f(&format_args!("{cmd}")))
151 .to_string()
152}
153
154async fn execute_multi_command_pipeline<S: Runtime + 'static>(
155 env: &mut Env<S>,
156 commands: &[Rc<syntax::Command>],
157) -> Result {
158 let mut commands = commands.iter().cloned();
160 let mut pipes = PipeSet::new();
161 let mut pids = Vec::new();
162 while let Some(command) = commands.next() {
163 let has_next = commands.len() > 0; shift_or_fail(env, &mut pipes, has_next).await?;
165
166 let pipes = pipes;
167 let start_result = Config::new()
168 .start(env, async move |env, _job_control| {
169 let result = connect_pipe_and_execute_command(env, pipes, command).await;
170 env.apply_result(result);
171 run_exit_trap(env).await;
172 })
173 .await;
174 pids.push(pid_or_fail(env, start_result).await?);
175 }
176
177 shift_or_fail(env, &mut pipes, false).await?;
178
179 let mut final_exit_status = ExitStatus::SUCCESS;
181 let pipefail = env.options.get(PipeFail) == On;
182 for pid in pids {
183 let exit_status = env
184 .wait_for_subshell_to_finish(pid)
185 .await
186 .expect("cannot receive exit status of child process")
187 .1;
188 if !exit_status.is_successful() || !pipefail {
189 final_exit_status = exit_status;
190 }
191 }
192 env.exit_status = final_exit_status;
193
194 Continue(())
195}
196
197async fn shift_or_fail<S>(env: &mut Env<S>, pipes: &mut PipeSet, has_next: bool) -> Result
198where
199 S: Close + Isatty + Pipe + WriteAll,
200{
201 match pipes.shift(env, has_next) {
202 Ok(()) => Continue(()),
203 Err(errno) => {
204 let message = format!("cannot connect pipes in the pipeline: {errno}\n");
206 env.system.print_error(&message).await;
207 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
208 }
209 }
210}
211
212async fn connect_pipe_and_execute_command<S: Runtime + 'static>(
213 env: &mut Env<S>,
214 pipes: PipeSet,
215 command: Rc<syntax::Command>,
216) -> Result {
217 match pipes.move_to_stdin_stdout(env) {
218 Ok(()) => (),
219 Err(errno) => {
220 let message = format!("cannot connect pipes in the pipeline: {errno}\n");
222 env.system.print_error(&message).await;
223 return Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)));
224 }
225 }
226
227 command.execute(env).await
228}
229
230async fn pid_or_fail<S>(
231 env: &mut Env<S>,
232 start_result: std::result::Result<(Pid, Option<JobControl>), Errno>,
233) -> Result<Pid>
234where
235 S: Isatty + WriteAll,
236{
237 match start_result {
238 Ok((pid, job_control)) => {
239 debug_assert_eq!(job_control, None);
240 Continue(pid)
241 }
242 Err(errno) => {
243 env.system
245 .print_error(&format!(
246 "cannot start a subshell in the pipeline: {errno}\n"
247 ))
248 .await;
249 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
250 }
251 }
252}
253
254#[derive(Clone, Copy, Default)]
256struct PipeSet {
257 read_previous: Option<Fd>,
258 next: Option<(Fd, Fd)>,
260}
261
262impl PipeSet {
263 fn new() -> Self {
264 Self::default()
265 }
266
267 fn shift<S: Pipe + Close>(
272 &mut self,
273 env: &mut Env<S>,
274 has_next: bool,
275 ) -> std::result::Result<(), Errno> {
276 if let Some(fd) = self.read_previous {
277 let _ = env.system.close(fd);
278 }
279
280 if let Some((reader, writer)) = self.next {
281 let _ = env.system.close(writer);
282 self.read_previous = Some(reader);
283 } else {
284 self.read_previous = None;
285 }
286
287 self.next = None;
288 if has_next {
289 self.next = Some(env.system.pipe()?);
290 }
291
292 Ok(())
293 }
294
295 fn move_to_stdin_stdout<S: Dup + Close>(
298 mut self,
299 env: &mut Env<S>,
300 ) -> std::result::Result<(), Errno> {
301 if let Some((reader, writer)) = self.next {
302 assert_ne!(reader, writer);
303 assert_ne!(self.read_previous, Some(reader));
304 assert_ne!(self.read_previous, Some(writer));
305
306 env.system.close(reader)?;
307 if writer != Fd::STDOUT {
308 if self.read_previous == Some(Fd::STDOUT) {
309 self.read_previous =
310 Some(env.system.dup(Fd::STDOUT, Fd(0), EnumSet::empty())?);
311 }
312 env.system.dup2(writer, Fd::STDOUT)?;
313 env.system.close(writer)?;
314 }
315 }
316 if let Some(reader) = self.read_previous {
317 if reader != Fd::STDIN {
318 env.system.dup2(reader, Fd::STDIN)?;
319 env.system.close(reader)?;
320 }
321 }
322 Ok(())
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use crate::tests::cat_builtin;
330 use crate::tests::return_builtin;
331 use crate::tests::suspend_builtin;
332 use assert_matches::assert_matches;
333 use futures_util::FutureExt as _;
334 use std::pin::Pin;
335 use std::rc::Rc;
336 use yash_env::VirtualSystem;
337 use yash_env::builtin::Builtin;
338 use yash_env::builtin::Type::Special;
339 use yash_env::job::ProcessResult;
340 use yash_env::job::ProcessState;
341 use yash_env::option::Option::{ErrExit, Monitor};
342 use yash_env::semantics::Field;
343 use yash_env::system::Concurrent;
344 use yash_env::system::GetPid as _;
345 use yash_env::system::r#virtual::FileBody;
346 use yash_env::system::r#virtual::SIGSTOP;
347 use yash_env::test_helper::assert_stdout;
348 use yash_env::test_helper::in_virtual_system;
349 use yash_env::test_helper::stub_tty;
350
351 #[test]
352 fn empty_pipeline() {
353 let mut env = Env::new_virtual();
354 let pipeline = syntax::Pipeline {
355 commands: vec![],
356 negation: false,
357 };
358 let result = pipeline.execute(&mut env).now_or_never().unwrap();
359 assert_eq!(result, Continue(()));
360 assert_eq!(env.exit_status, ExitStatus(0));
361 }
362
363 #[test]
364 fn single_command_pipeline_returns_exit_status_intact_without_divert() {
365 let mut env = Env::new_virtual();
366 env.builtins.insert("return", return_builtin());
367 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
368 let result = pipeline.execute(&mut env).now_or_never().unwrap();
369 assert_eq!(result, Continue(()));
370 assert_eq!(env.exit_status, ExitStatus(93));
371 }
372
373 #[test]
374 fn single_command_pipeline_returns_exit_status_intact_with_divert() {
375 let mut env = Env::new_virtual();
376 env.builtins.insert("return", return_builtin());
377 env.exit_status = ExitStatus(17);
378 let pipeline: syntax::Pipeline = "return 37".parse().unwrap();
379 let result = pipeline.execute(&mut env).now_or_never().unwrap();
380 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(37)))));
381 assert_eq!(env.exit_status, ExitStatus(17));
382 }
383
384 #[test]
385 fn multi_command_pipeline_without_pipefail_returns_last_command_exit_status() {
386 in_virtual_system(|mut env, _state| async move {
387 env.builtins.insert("return", return_builtin());
388 env.options.set(PipeFail, Off);
389
390 let pipeline: syntax::Pipeline = "return -n 0 | return -n 0".parse().unwrap();
391 let result = pipeline.execute(&mut env).await;
392 assert_eq!(result, Continue(()));
393 assert_eq!(env.exit_status, ExitStatus(0));
394
395 let pipeline: syntax::Pipeline = "return -n 10 | return -n 20".parse().unwrap();
396 let result = pipeline.execute(&mut env).await;
397 assert_eq!(result, Continue(()));
398 assert_eq!(env.exit_status, ExitStatus(20));
399
400 let pipeline: syntax::Pipeline = "return -n 0 | return -n 20 | return -n 0 |\
401 return -n 30 | return -n 0 | return -n 0"
402 .parse()
403 .unwrap();
404 let result = pipeline.execute(&mut env).await;
405 assert_eq!(result, Continue(()));
406 assert_eq!(env.exit_status, ExitStatus(0));
407 });
408 }
409
410 #[test]
411 fn multi_command_pipeline_with_pipefail_returns_last_failed_command_exit_status() {
412 in_virtual_system(|mut env, _state| async move {
413 env.builtins.insert("return", return_builtin());
414 env.options.set(PipeFail, On);
415
416 let pipeline: syntax::Pipeline = "return -n 0 | return -n 0".parse().unwrap();
417 let result = pipeline.execute(&mut env).await;
418 assert_eq!(result, Continue(()));
419 assert_eq!(env.exit_status, ExitStatus(0));
420
421 let pipeline: syntax::Pipeline = "return -n 10 | return -n 20".parse().unwrap();
422 let result = pipeline.execute(&mut env).await;
423 assert_eq!(result, Continue(()));
424 assert_eq!(env.exit_status, ExitStatus(20));
425
426 let pipeline: syntax::Pipeline = "return -n 0 | return -n 20 | return -n 0 |\
427 return -n 30 | return -n 0 | return -n 0"
428 .parse()
429 .unwrap();
430 let result = pipeline.execute(&mut env).await;
431 assert_eq!(result, Continue(()));
432 assert_eq!(env.exit_status, ExitStatus(30));
433 });
434 }
435
436 #[test]
437 fn multi_command_pipeline_waits_for_all_child_commands() {
438 in_virtual_system(|mut env, state| async move {
439 env.builtins.insert("return", return_builtin());
440 let pipeline: syntax::Pipeline =
441 "return -n 1 | return -n 2 | return -n 3".parse().unwrap();
442 _ = pipeline.execute(&mut env).await;
443
444 for (pid, process) in &state.borrow().processes {
446 if *pid == env.main_pid {
447 assert_eq!(process.state(), ProcessState::Running);
448 } else {
449 assert_matches!(
450 process.state(),
451 ProcessState::Halted(ProcessResult::Exited(_))
452 );
453 }
454 }
455 });
456 }
457
458 #[test]
459 fn multi_command_pipeline_does_not_wait_for_unrelated_child() {
460 in_virtual_system(|mut env, state| async move {
461 env.builtins.insert("return", return_builtin());
462
463 let list: syntax::List = "return -n 7&".parse().unwrap();
464 _ = list.execute(&mut env).await;
465 let async_pid = {
466 let state = state.borrow();
467 let mut iter = state.processes.keys();
468 assert_eq!(iter.next(), Some(&env.main_pid));
469 let async_pid = *iter.next().unwrap();
470 assert_eq!(iter.next(), None);
471 async_pid
472 };
473
474 let pipeline: syntax::Pipeline =
475 "return -n 1 | return -n 2 | return -n 3".parse().unwrap();
476 _ = pipeline.execute(&mut env).await;
477
478 let state = state.borrow();
479 let process = &state.processes[&async_pid];
480 assert_eq!(process.state(), ProcessState::exited(7));
481 assert!(process.state_has_changed());
482 });
483 }
484
485 #[test]
486 fn pipe_connects_commands_in_pipeline() {
487 in_virtual_system(|mut env, state| async move {
488 {
489 let file = state.borrow().file_system.get("/dev/stdin").unwrap();
490 let mut file = file.borrow_mut();
491 file.body = FileBody::new(*b"ok\n");
492 }
493
494 env.builtins.insert("cat", cat_builtin());
495
496 let pipeline: syntax::Pipeline = "cat | cat | cat".parse().unwrap();
497 let result = pipeline.execute(&mut env).await;
498 assert_eq!(result, Continue(()));
499 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
500 assert_stdout(&state, |stdout| assert_eq!(stdout, "ok\n"));
501 });
502 }
503
504 #[test]
505 fn pipeline_leaves_no_pipe_fds_leftover() {
506 in_virtual_system(|mut env, state| async move {
507 env.builtins.insert("cat", cat_builtin());
508 let pipeline: syntax::Pipeline = "cat | cat".parse().unwrap();
509 let _ = pipeline.execute(&mut env).await;
510
511 let state = state.borrow();
512 let fds = state.processes[&env.main_pid].fds();
513 for fd in 3..10 {
514 assert!(!fds.contains_key(&Fd(fd)), "fd={fd}");
515 }
516 });
517 }
518
519 #[test]
520 fn inverting_exit_status_to_0_without_divert() {
521 let mut env = Env::new_virtual();
522 env.builtins.insert("return", return_builtin());
523 let pipeline: syntax::Pipeline = "! return -n 42".parse().unwrap();
524 let result = pipeline.execute(&mut env).now_or_never().unwrap();
525 assert_eq!(result, Continue(()));
526 assert_eq!(env.exit_status, ExitStatus(0));
527 }
528
529 #[test]
530 fn inverting_exit_status_to_1_without_divert() {
531 let mut env = Env::new_virtual();
532 env.builtins.insert("return", return_builtin());
533 let pipeline: syntax::Pipeline = "! return -n 0".parse().unwrap();
534 let result = pipeline.execute(&mut env).now_or_never().unwrap();
535 assert_eq!(result, Continue(()));
536 assert_eq!(env.exit_status, ExitStatus(1));
537 }
538
539 #[test]
540 fn not_inverting_exit_status_with_divert() {
541 let mut env = Env::new_virtual();
542 env.builtins.insert("return", return_builtin());
543 env.exit_status = ExitStatus(3);
544 let pipeline: syntax::Pipeline = "! return 15".parse().unwrap();
545 let result = pipeline.execute(&mut env).now_or_never().unwrap();
546 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(15)))));
547 assert_eq!(env.exit_status, ExitStatus(3));
548 }
549
550 #[test]
551 fn noexec_option() {
552 let mut env = Env::new_virtual();
553 env.builtins.insert("return", return_builtin());
554 env.options.set(Exec, Off);
555 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
556 let result = pipeline.execute(&mut env).now_or_never().unwrap();
557 assert_eq!(result, Continue(()));
558 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
559 }
560
561 #[test]
562 fn noexec_option_interactive() {
563 let mut env = Env::new_virtual();
564 env.builtins.insert("return", return_builtin());
565 env.options.set(Exec, Off);
566 env.options.set(Interactive, On);
567 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
568 let result = pipeline.execute(&mut env).now_or_never().unwrap();
569 assert_eq!(result, Continue(()));
570 assert_eq!(env.exit_status, ExitStatus(93));
571 }
572
573 #[test]
574 fn errexit_option() {
575 in_virtual_system(|mut env, _state| async move {
576 env.builtins.insert("return", return_builtin());
577 env.options.set(ErrExit, On);
578
579 let pipeline: syntax::Pipeline = "return -n 0 | return -n 93".parse().unwrap();
580 let result = pipeline.execute(&mut env).await;
581
582 assert_eq!(result, Break(Divert::Exit(None)));
583 assert_eq!(env.exit_status, ExitStatus(93));
584 });
585 }
586
587 #[test]
588 fn stack_without_inversion() {
589 fn stub_builtin(
590 env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
591 _args: Vec<Field>,
592 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
593 Box::pin(async move {
594 assert!(!env.stack.contains(&Frame::Condition), "{:?}", env.stack);
595 Default::default()
596 })
597 }
598
599 let mut env = Env::new_virtual();
600 env.builtins
601 .insert("foo", Builtin::new(Special, stub_builtin));
602 let pipeline: syntax::Pipeline = "foo".parse().unwrap();
603 let result = pipeline.execute(&mut env).now_or_never().unwrap();
604 assert_eq!(result, Continue(()));
605 }
606
607 #[test]
608 fn stack_with_inversion() {
609 fn stub_builtin(
610 env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
611 _args: Vec<Field>,
612 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
613 Box::pin(async move {
614 assert_matches!(
615 env.stack.as_slice(),
616 [Frame::Condition, Frame::Builtin { .. }]
617 );
618 Default::default()
619 })
620 }
621
622 let mut env = Env::new_virtual();
623 env.builtins
624 .insert("foo", Builtin::new(Special, stub_builtin));
625 let pipeline: syntax::Pipeline = "! foo".parse().unwrap();
626 let result = pipeline.execute(&mut env).now_or_never().unwrap();
627 assert_eq!(result, Continue(()));
628 }
629
630 #[test]
631 fn process_group_id_of_job_controlled_pipeline() {
632 fn stub_builtin(
633 env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
634 _args: Vec<Field>,
635 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
636 let pgid = env.system.getpgrp().0 as _;
637 Box::pin(async move { yash_env::builtin::Result::new(ExitStatus(pgid)) })
638 }
639
640 in_virtual_system(|mut env, state| async move {
641 env.builtins
642 .insert("foo", Builtin::new(Special, stub_builtin));
643 env.options.set(Monitor, On);
644 stub_tty(&state);
645
646 let pipeline: syntax::Pipeline = "foo | foo".parse().unwrap();
648 let result = pipeline.execute(&mut env).await;
649 assert_eq!(result, Continue(()));
650 assert_ne!(env.exit_status, ExitStatus(env.main_pgid.0 as _));
651
652 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
654 })
655 }
656
657 #[test]
658 fn job_controlled_suspended_pipeline_in_job_list() {
659 in_virtual_system(|mut env, state| async move {
660 env.builtins.insert("return", return_builtin());
661 env.builtins.insert("suspend", suspend_builtin());
662 env.options.set(Monitor, On);
663 stub_tty(&state);
664
665 let pipeline: syntax::Pipeline = "return -n 0 | suspend x".parse().unwrap();
666 let result = pipeline.execute(&mut env).await;
667 assert_eq!(result, Continue(()));
668 assert_eq!(env.exit_status, ExitStatus::from(SIGSTOP));
669
670 assert_eq!(env.jobs.len(), 1);
671 let job = env.jobs.iter().next().unwrap().1;
672 assert!(job.job_controlled);
673 assert_eq!(job.state, ProcessState::stopped(SIGSTOP));
674 assert!(job.state_changed);
675 assert_eq!(job.name, "return -n 0 | suspend x");
676 })
677 }
678
679 #[test]
680 fn pipe_set_shift_to_first_command() {
681 let system = VirtualSystem::new();
682 let process_id = system.process_id;
683 let state = Rc::clone(&system.state);
684 let mut env = Env::with_system(system);
685 let mut pipes = PipeSet::new();
686
687 let result = pipes.shift(&mut env, true);
688 assert_eq!(result, Ok(()));
689 assert_eq!(pipes.read_previous, None);
690 assert_eq!(pipes.next, Some((Fd(3), Fd(4))));
691 let state = state.borrow();
692 let process = &state.processes[&process_id];
693 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
694 assert_eq!(process.fds().get(&Fd(4)).unwrap().flags, EnumSet::empty());
695 }
696
697 #[test]
698 fn pipe_set_shift_to_middle_command() {
699 let system = VirtualSystem::new();
700 let process_id = system.process_id;
701 let state = Rc::clone(&system.state);
702 let mut env = Env::with_system(system);
703 let mut pipes = PipeSet::new();
704
705 let _ = pipes.shift(&mut env, true);
706 let result = pipes.shift(&mut env, true);
707 assert_eq!(result, Ok(()));
708 assert_eq!(pipes.read_previous, Some(Fd(3)));
709 assert_eq!(pipes.next, Some((Fd(4), Fd(5))));
710 let state = state.borrow();
711 let process = &state.processes[&process_id];
712 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
713 assert_eq!(process.fds().get(&Fd(4)).unwrap().flags, EnumSet::empty());
714 assert_eq!(process.fds().get(&Fd(5)).unwrap().flags, EnumSet::empty());
715 }
716
717 #[test]
718 fn pipe_set_shift_to_last_command() {
719 let system = VirtualSystem::new();
720 let process_id = system.process_id;
721 let state = Rc::clone(&system.state);
722 let mut env = Env::with_system(system);
723 let mut pipes = PipeSet::new();
724
725 let _ = pipes.shift(&mut env, true);
726 let result = pipes.shift(&mut env, false);
727 assert_eq!(result, Ok(()));
728 assert_eq!(pipes.read_previous, Some(Fd(3)));
729 assert_eq!(pipes.next, None);
730 let state = state.borrow();
731 let process = &state.processes[&process_id];
732 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
733 }
734
735 }