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