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_if::cfg_if! {
19 if #[cfg(windows)] {
20 #[path = "windows.rs"]
21 mod sys;
22 } else if #[cfg(target_os = "linux")] {
23 #[path = "linux.rs"]
24 mod sys;
25 } else {
26 #[path = "unix.rs"]
27 mod sys;
28 }
29}
30
31#[cfg(unix)]
32use std::os::unix::process::CommandExt;
33#[cfg(windows)]
34use std::os::windows::process::CommandExt;
35use std::{ffi::OsStr, io, path::Path, process};
36
37use compio_buf::{BufResult, IntoInner};
38use compio_driver::{AsFd, AsRawFd, BorrowedFd, RawFd, SharedFd, ToSharedFd};
39use compio_io::AsyncReadExt;
40use compio_runtime::Attacher;
41use futures_util::future::Either;
42
43#[derive(Debug)]
117pub struct Command(process::Command);
118
119impl Command {
120 pub fn new(program: impl AsRef<OsStr>) -> Self {
122 Self(process::Command::new(program))
123 }
124
125 pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
127 self.0.arg(arg);
128 self
129 }
130
131 pub fn args<I, S>(&mut self, args: I) -> &mut Self
133 where
134 I: IntoIterator<Item = S>,
135 S: AsRef<OsStr>,
136 {
137 self.0.args(args);
138 self
139 }
140
141 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
143 where
144 K: AsRef<OsStr>,
145 V: AsRef<OsStr>,
146 {
147 self.0.env(key, val);
148 self
149 }
150
151 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
153 where
154 I: IntoIterator<Item = (K, V)>,
155 K: AsRef<OsStr>,
156 V: AsRef<OsStr>,
157 {
158 self.0.envs(vars);
159 self
160 }
161
162 pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
165 self.0.env_remove(key);
166 self
167 }
168
169 pub fn env_clear(&mut self) -> &mut Self {
172 self.0.env_clear();
173 self
174 }
175
176 pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
178 self.0.current_dir(dir);
179 self
180 }
181
182 pub fn stdin<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
184 self.0.stdin(cfg.try_into()?);
185 Ok(self)
186 }
187
188 pub fn stdout<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
190 self.0.stdout(cfg.try_into()?);
191 Ok(self)
192 }
193
194 pub fn stderr<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
196 self.0.stderr(cfg.try_into()?);
197 Ok(self)
198 }
199
200 pub fn get_program(&self) -> &OsStr {
202 self.0.get_program()
203 }
204
205 pub fn get_args(&self) -> process::CommandArgs<'_> {
207 self.0.get_args()
208 }
209
210 pub fn get_envs(&self) -> process::CommandEnvs<'_> {
213 self.0.get_envs()
214 }
215
216 pub fn get_current_dir(&self) -> Option<&Path> {
218 self.0.get_current_dir()
219 }
220
221 pub fn spawn(&mut self) -> io::Result<Child> {
223 #[cfg(all(target_os = "linux", feature = "linux_pidfd"))]
224 {
225 use std::os::linux::process::CommandExt;
226 self.0.create_pidfd(true);
227 }
228 let mut child = self.0.spawn()?;
229 let stdin = if let Some(stdin) = child.stdin.take() {
230 Some(ChildStdin::new(stdin)?)
231 } else {
232 None
233 };
234 let stdout = if let Some(stdout) = child.stdout.take() {
235 Some(ChildStdout::new(stdout)?)
236 } else {
237 None
238 };
239 let stderr = if let Some(stderr) = child.stderr.take() {
240 Some(ChildStderr::new(stderr)?)
241 } else {
242 None
243 };
244 Ok(Child {
245 child,
246 stdin,
247 stdout,
248 stderr,
249 })
250 }
251
252 pub async fn status(&mut self) -> io::Result<process::ExitStatus> {
256 let child = self.spawn()?;
257 child.wait().await
258 }
259
260 pub async fn output(&mut self) -> io::Result<process::Output> {
263 let child = self.spawn()?;
264 child.wait_with_output().await
265 }
266}
267
268#[cfg(windows)]
269impl Command {
270 pub fn creation_flags(&mut self, flags: u32) -> &mut Self {
276 self.0.creation_flags(flags);
277 self
278 }
279
280 pub fn raw_arg(&mut self, text_to_append_as_is: impl AsRef<OsStr>) -> &mut Self {
282 self.0.raw_arg(text_to_append_as_is);
283 self
284 }
285}
286
287#[cfg(unix)]
288impl Command {
289 pub fn uid(&mut self, id: u32) -> &mut Self {
293 self.0.uid(id);
294 self
295 }
296
297 pub fn gid(&mut self, id: u32) -> &mut Self {
300 self.0.gid(id);
301 self
302 }
303
304 pub unsafe fn pre_exec(
311 &mut self,
312 f: impl FnMut() -> io::Result<()> + Send + Sync + 'static,
313 ) -> &mut Self {
314 unsafe { self.0.pre_exec(f) };
315 self
316 }
317
318 pub fn exec(&mut self) -> io::Error {
320 self.0.exec()
321 }
322
323 pub fn arg0(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
326 self.0.arg0(arg);
327 self
328 }
329
330 pub fn process_group(&mut self, pgroup: i32) -> &mut Self {
332 self.0.process_group(pgroup);
333 self
334 }
335}
336
337pub struct Child {
355 child: process::Child,
356 pub stdin: Option<ChildStdin>,
358 pub stdout: Option<ChildStdout>,
360 pub stderr: Option<ChildStderr>,
362}
363
364impl Child {
365 pub fn kill(&mut self) -> io::Result<()> {
368 self.child.kill()
369 }
370
371 pub fn id(&self) -> u32 {
373 self.child.id()
374 }
375
376 pub async fn wait(self) -> io::Result<process::ExitStatus> {
381 sys::child_wait(self.child).await
382 }
383
384 pub async fn wait_with_output(mut self) -> io::Result<process::Output> {
387 let status = sys::child_wait(self.child);
388 let stdout = if let Some(stdout) = &mut self.stdout {
389 Either::Left(stdout.read_to_end(vec![]))
390 } else {
391 Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
392 };
393 let stderr = if let Some(stderr) = &mut self.stderr {
394 Either::Left(stderr.read_to_end(vec![]))
395 } else {
396 Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
397 };
398 let (status, BufResult(out_res, stdout), BufResult(err_res, stderr)) =
399 futures_util::future::join3(status, stdout, stderr).await;
400 let status = status?;
401 out_res?;
402 err_res?;
403 Ok(process::Output {
404 status,
405 stdout,
406 stderr,
407 })
408 }
409}
410
411pub struct ChildStdout(Attacher<process::ChildStdout>);
414
415impl ChildStdout {
416 fn new(stdout: process::ChildStdout) -> io::Result<Self> {
417 Attacher::new(stdout).map(Self)
418 }
419}
420
421impl TryFrom<ChildStdout> for process::Stdio {
422 type Error = ChildStdout;
423
424 fn try_from(value: ChildStdout) -> Result<Self, ChildStdout> {
425 value
426 .0
427 .into_inner()
428 .try_unwrap()
429 .map(Self::from)
430 .map_err(|fd| ChildStdout(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
431 }
432}
433
434impl AsFd for ChildStdout {
435 fn as_fd(&self) -> BorrowedFd<'_> {
436 self.0.as_fd()
437 }
438}
439
440impl AsRawFd for ChildStdout {
441 fn as_raw_fd(&self) -> RawFd {
442 self.0.as_raw_fd()
443 }
444}
445
446impl ToSharedFd<process::ChildStdout> for ChildStdout {
447 fn to_shared_fd(&self) -> SharedFd<process::ChildStdout> {
448 self.0.to_shared_fd()
449 }
450}
451
452pub struct ChildStderr(Attacher<process::ChildStderr>);
454
455impl ChildStderr {
456 fn new(stderr: process::ChildStderr) -> io::Result<Self> {
457 Attacher::new(stderr).map(Self)
458 }
459}
460
461impl TryFrom<ChildStderr> for process::Stdio {
462 type Error = ChildStderr;
463
464 fn try_from(value: ChildStderr) -> Result<Self, ChildStderr> {
465 value
466 .0
467 .into_inner()
468 .try_unwrap()
469 .map(Self::from)
470 .map_err(|fd| ChildStderr(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
471 }
472}
473
474impl AsFd for ChildStderr {
475 fn as_fd(&self) -> BorrowedFd<'_> {
476 self.0.as_fd()
477 }
478}
479
480impl AsRawFd for ChildStderr {
481 fn as_raw_fd(&self) -> RawFd {
482 self.0.as_raw_fd()
483 }
484}
485
486impl ToSharedFd<process::ChildStderr> for ChildStderr {
487 fn to_shared_fd(&self) -> SharedFd<process::ChildStderr> {
488 self.0.to_shared_fd()
489 }
490}
491
492pub struct ChildStdin(Attacher<process::ChildStdin>);
495
496impl ChildStdin {
497 fn new(stdin: process::ChildStdin) -> io::Result<Self> {
498 Attacher::new(stdin).map(Self)
499 }
500}
501
502impl TryFrom<ChildStdin> for process::Stdio {
503 type Error = ChildStdin;
504
505 fn try_from(value: ChildStdin) -> Result<Self, ChildStdin> {
506 value
507 .0
508 .into_inner()
509 .try_unwrap()
510 .map(Self::from)
511 .map_err(|fd| ChildStdin(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
512 }
513}
514
515impl AsFd for ChildStdin {
516 fn as_fd(&self) -> BorrowedFd<'_> {
517 self.0.as_fd()
518 }
519}
520
521impl AsRawFd for ChildStdin {
522 fn as_raw_fd(&self) -> RawFd {
523 self.0.as_raw_fd()
524 }
525}
526
527impl ToSharedFd<process::ChildStdin> for ChildStdin {
528 fn to_shared_fd(&self) -> SharedFd<process::ChildStdin> {
529 self.0.to_shared_fd()
530 }
531}