1#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(
5 all(feature = "linux_pidfd", target_os = "linux"),
6 feature(linux_pidfd)
7)]
8#![allow(unused_features)]
9#![warn(missing_docs)]
10#![deny(rustdoc::broken_intra_doc_links)]
11#![doc(
12 html_logo_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
13)]
14#![doc(
15 html_favicon_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
16)]
17
18cfg_select! {
19 windows => {
20 #[path = "windows.rs"]
21 mod sys;
22 }
23 target_os = "linux" => {
24 #[path = "linux.rs"]
25 mod sys;
26 }
27 _ => {
28 #[path = "unix.rs"]
29 mod sys;
30 }
31}
32
33#[cfg(unix)]
34use std::os::unix::process::CommandExt;
35#[cfg(windows)]
36use std::os::windows::process::CommandExt;
37use std::{ffi::OsStr, io, path::Path, process};
38
39use compio_buf::{BufResult, IntoInner};
40use compio_driver::{AsFd, AsRawFd, BorrowedFd, RawFd, SharedFd, ToSharedFd};
41use compio_io::AsyncReadExt;
42use compio_runtime::Attacher;
43use futures_util::future::Either;
44
45#[derive(Debug)]
119pub struct Command(process::Command);
120
121impl Command {
122 pub fn new(program: impl AsRef<OsStr>) -> Self {
124 Self(process::Command::new(program))
125 }
126
127 pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
129 self.0.arg(arg);
130 self
131 }
132
133 pub fn args<I, S>(&mut self, args: I) -> &mut Self
135 where
136 I: IntoIterator<Item = S>,
137 S: AsRef<OsStr>,
138 {
139 self.0.args(args);
140 self
141 }
142
143 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
145 where
146 K: AsRef<OsStr>,
147 V: AsRef<OsStr>,
148 {
149 self.0.env(key, val);
150 self
151 }
152
153 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
155 where
156 I: IntoIterator<Item = (K, V)>,
157 K: AsRef<OsStr>,
158 V: AsRef<OsStr>,
159 {
160 self.0.envs(vars);
161 self
162 }
163
164 pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
167 self.0.env_remove(key);
168 self
169 }
170
171 pub fn env_clear(&mut self) -> &mut Self {
174 self.0.env_clear();
175 self
176 }
177
178 pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
180 self.0.current_dir(dir);
181 self
182 }
183
184 pub fn stdin<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
186 self.0.stdin(cfg.try_into()?);
187 Ok(self)
188 }
189
190 pub fn stdout<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
192 self.0.stdout(cfg.try_into()?);
193 Ok(self)
194 }
195
196 pub fn stderr<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
198 self.0.stderr(cfg.try_into()?);
199 Ok(self)
200 }
201
202 pub fn get_program(&self) -> &OsStr {
204 self.0.get_program()
205 }
206
207 pub fn get_args(&self) -> process::CommandArgs<'_> {
209 self.0.get_args()
210 }
211
212 pub fn get_envs(&self) -> process::CommandEnvs<'_> {
215 self.0.get_envs()
216 }
217
218 pub fn get_current_dir(&self) -> Option<&Path> {
220 self.0.get_current_dir()
221 }
222
223 pub fn spawn(&mut self) -> io::Result<Child> {
225 #[cfg(all(target_os = "linux", feature = "linux_pidfd"))]
226 {
227 use std::os::linux::process::CommandExt;
228 self.0.create_pidfd(true);
229 }
230 let mut child = self.0.spawn()?;
231 let stdin = if let Some(stdin) = child.stdin.take() {
232 Some(ChildStdin::new(stdin)?)
233 } else {
234 None
235 };
236 let stdout = if let Some(stdout) = child.stdout.take() {
237 Some(ChildStdout::new(stdout)?)
238 } else {
239 None
240 };
241 let stderr = if let Some(stderr) = child.stderr.take() {
242 Some(ChildStderr::new(stderr)?)
243 } else {
244 None
245 };
246 Ok(Child {
247 child,
248 stdin,
249 stdout,
250 stderr,
251 })
252 }
253
254 pub async fn status(&mut self) -> io::Result<process::ExitStatus> {
258 let child = self.spawn()?;
259 child.wait().await
260 }
261
262 pub async fn output(&mut self) -> io::Result<process::Output> {
265 let child = self.spawn()?;
266 child.wait_with_output().await
267 }
268}
269
270#[cfg(windows)]
271impl Command {
272 pub fn creation_flags(&mut self, flags: u32) -> &mut Self {
278 self.0.creation_flags(flags);
279 self
280 }
281
282 pub fn raw_arg(&mut self, text_to_append_as_is: impl AsRef<OsStr>) -> &mut Self {
284 self.0.raw_arg(text_to_append_as_is);
285 self
286 }
287}
288
289#[cfg(unix)]
290impl Command {
291 pub fn uid(&mut self, id: u32) -> &mut Self {
295 self.0.uid(id);
296 self
297 }
298
299 pub fn gid(&mut self, id: u32) -> &mut Self {
302 self.0.gid(id);
303 self
304 }
305
306 pub unsafe fn pre_exec(
313 &mut self,
314 f: impl FnMut() -> io::Result<()> + Send + Sync + 'static,
315 ) -> &mut Self {
316 unsafe { self.0.pre_exec(f) };
317 self
318 }
319
320 pub fn exec(&mut self) -> io::Error {
322 self.0.exec()
323 }
324
325 pub fn arg0(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
328 self.0.arg0(arg);
329 self
330 }
331
332 pub fn process_group(&mut self, pgroup: i32) -> &mut Self {
334 self.0.process_group(pgroup);
335 self
336 }
337}
338
339pub struct Child {
357 child: process::Child,
358 pub stdin: Option<ChildStdin>,
360 pub stdout: Option<ChildStdout>,
362 pub stderr: Option<ChildStderr>,
364}
365
366impl Child {
367 pub fn kill(&mut self) -> io::Result<()> {
370 self.child.kill()
371 }
372
373 pub fn id(&self) -> u32 {
375 self.child.id()
376 }
377
378 pub async fn wait(self) -> io::Result<process::ExitStatus> {
383 sys::child_wait(self.child).await
384 }
385
386 pub async fn wait_with_output(mut self) -> io::Result<process::Output> {
389 let status = sys::child_wait(self.child);
390 let stdout = if let Some(stdout) = &mut self.stdout {
391 Either::Left(stdout.read_to_end(vec![]))
392 } else {
393 Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
394 };
395 let stderr = if let Some(stderr) = &mut self.stderr {
396 Either::Left(stderr.read_to_end(vec![]))
397 } else {
398 Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
399 };
400 let (status, BufResult(out_res, stdout), BufResult(err_res, stderr)) =
401 futures_util::future::join3(status, stdout, stderr).await;
402 let status = status?;
403 out_res?;
404 err_res?;
405 Ok(process::Output {
406 status,
407 stdout,
408 stderr,
409 })
410 }
411}
412
413pub struct ChildStdout(Attacher<process::ChildStdout>);
416
417impl ChildStdout {
418 fn new(stdout: process::ChildStdout) -> io::Result<Self> {
419 Attacher::new(stdout).map(Self)
420 }
421}
422
423impl TryFrom<ChildStdout> for process::Stdio {
424 type Error = ChildStdout;
425
426 fn try_from(value: ChildStdout) -> Result<Self, ChildStdout> {
427 value
428 .0
429 .into_inner()
430 .try_unwrap()
431 .map(Self::from)
432 .map_err(|fd| ChildStdout(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
433 }
434}
435
436impl AsFd for ChildStdout {
437 fn as_fd(&self) -> BorrowedFd<'_> {
438 self.0.as_fd()
439 }
440}
441
442impl AsRawFd for ChildStdout {
443 fn as_raw_fd(&self) -> RawFd {
444 self.0.as_raw_fd()
445 }
446}
447
448impl ToSharedFd<process::ChildStdout> for ChildStdout {
449 fn to_shared_fd(&self) -> SharedFd<process::ChildStdout> {
450 self.0.to_shared_fd()
451 }
452}
453
454pub struct ChildStderr(Attacher<process::ChildStderr>);
456
457impl ChildStderr {
458 fn new(stderr: process::ChildStderr) -> io::Result<Self> {
459 Attacher::new(stderr).map(Self)
460 }
461}
462
463impl TryFrom<ChildStderr> for process::Stdio {
464 type Error = ChildStderr;
465
466 fn try_from(value: ChildStderr) -> Result<Self, ChildStderr> {
467 value
468 .0
469 .into_inner()
470 .try_unwrap()
471 .map(Self::from)
472 .map_err(|fd| ChildStderr(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
473 }
474}
475
476impl AsFd for ChildStderr {
477 fn as_fd(&self) -> BorrowedFd<'_> {
478 self.0.as_fd()
479 }
480}
481
482impl AsRawFd for ChildStderr {
483 fn as_raw_fd(&self) -> RawFd {
484 self.0.as_raw_fd()
485 }
486}
487
488impl ToSharedFd<process::ChildStderr> for ChildStderr {
489 fn to_shared_fd(&self) -> SharedFd<process::ChildStderr> {
490 self.0.to_shared_fd()
491 }
492}
493
494pub struct ChildStdin(Attacher<process::ChildStdin>);
497
498impl ChildStdin {
499 fn new(stdin: process::ChildStdin) -> io::Result<Self> {
500 Attacher::new(stdin).map(Self)
501 }
502}
503
504impl TryFrom<ChildStdin> for process::Stdio {
505 type Error = ChildStdin;
506
507 fn try_from(value: ChildStdin) -> Result<Self, ChildStdin> {
508 value
509 .0
510 .into_inner()
511 .try_unwrap()
512 .map(Self::from)
513 .map_err(|fd| ChildStdin(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
514 }
515}
516
517impl AsFd for ChildStdin {
518 fn as_fd(&self) -> BorrowedFd<'_> {
519 self.0.as_fd()
520 }
521}
522
523impl AsRawFd for ChildStdin {
524 fn as_raw_fd(&self) -> RawFd {
525 self.0.as_raw_fd()
526 }
527}
528
529impl ToSharedFd<process::ChildStdin> for ChildStdin {
530 fn to_shared_fd(&self) -> SharedFd<process::ChildStdin> {
531 self.0.to_shared_fd()
532 }
533}