pty-process-0.4.0/.cargo_vcs_info.json0000644000000001360000000000100133250ustar { "git": { "sha1": "74c223df6dd285f17108c8f442d71fd5ddb9561e" }, "path_in_vcs": "" }pty-process-0.4.0/CHANGELOG.md000064400000000000000000000010601046102023000137230ustar 00000000000000# Changelog ## [0.4.0] - 2023-08-06 ### Changed * Switch from nix to rustix, for hopefully better portability ### Added * Implemented AsRawFd for the Pty structs ## [0.3.0] - 2023-03-08 ### Changed * Complete rewrite of the API * Tokio is now the only supported backend, enabled via the `async` feature ## [0.2.0] - 2021-12-15 ### Changed * Simplified the `Error` type to remove a bunch of unnecessary distinctions ## [0.1.1] - 2021-11-10 ### Changed * Bumped deps and moved to 2021 edition ## [0.1.0] - 2021-03-06 ### Added * Initial release pty-process-0.4.0/Cargo.lock0000644000000367300000000000100113110ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "errno" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "futures" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gimli" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", "windows-sys", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "pin-project-lite" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "pty-process" version = "0.4.0" dependencies = [ "futures", "libc", "nix", "regex", "rustix", "tokio", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", "itoa", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" pty-process-0.4.0/Cargo.toml0000644000000027330000000000100113300ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "pty-process" version = "0.4.0" authors = ["Jesse Luehrs "] include = [ "src/**/*", "LICENSE", "README.md", "CHANGELOG.md", ] description = "spawn commands attached to a pty" readme = "README.md" keywords = [ "pty", "spawn", "execute", "process", ] categories = [ "asynchronous", "command-line-interface", ] license = "MIT" repository = "https://git.tozt.net/pty-process" [dependencies.libc] version = "0.2.147" [dependencies.rustix] version = "0.38.7" features = [ "pty", "process", "fs", ] [dependencies.tokio] version = "1.29.1" features = [ "fs", "process", "net", ] optional = true [dev-dependencies.futures] version = "0.3.28" [dev-dependencies.nix] version = "0.26.2" features = [ "signal", "fs", "term", "poll", ] default-features = false [dev-dependencies.regex] version = "1.9.3" [dev-dependencies.tokio] version = "1.29.1" features = ["full"] [features] async = ["tokio"] default = [] pty-process-0.4.0/Cargo.toml.orig000064400000000000000000000015231046102023000150050ustar 00000000000000[package] name = "pty-process" version = "0.4.0" authors = ["Jesse Luehrs "] edition = "2021" description = "spawn commands attached to a pty" repository = "https://git.tozt.net/pty-process" readme = "README.md" keywords = ["pty", "spawn", "execute", "process"] categories = ["asynchronous", "command-line-interface"] license = "MIT" include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [dependencies] libc = "0.2.147" rustix = { version = "0.38.7", features = ["pty", "process", "fs"] } tokio = { version = "1.29.1", features = ["fs", "process", "net"], optional = true } [dev-dependencies] futures = "0.3.28" nix = { version = "0.26.2", default-features = false, features = ["signal", "fs", "term", "poll"] } regex = "1.9.3" tokio = { version = "1.29.1", features = ["full"] } [features] default = [] async = ["tokio"] pty-process-0.4.0/LICENSE000064400000000000000000000022071046102023000131230ustar 00000000000000This software is Copyright (c) 2021 by Jesse Luehrs. This is free software, licensed under: The MIT (X11) License The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pty-process-0.4.0/README.md000064400000000000000000000024221046102023000133740ustar 00000000000000# pty-process This crate is a wrapper around [`tokio::process::Command`] or [`std::process::Command`] which provides the ability to allocate a pty and spawn new processes attached to that pty, with the pty as their controlling terminal. This allows for manipulation of interactive programs. The basic functionality looks like this: ```rust let mut pty = pty_process::Pty::new().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut cmd = pty_process::Command::new("nethack"); let child = cmd.spawn(&pty.pts().unwrap()).unwrap(); ``` The returned `child` is a normal instance of [`tokio::process::Child`] (or [`std::process::Child`] for the [`blocking`](crate::blocking) variant), with its `stdin`/`stdout`/`stderr` file descriptors pointing at the given pty. The `pty` instance implements [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`] (or [`std::io::Read`] and [`std::io::Write`] for the [`blocking`] variant), and can be used to communicate with the child process. The child process will also be made a session leader of a new session, and the controlling terminal of that session will be set to the given pty. ## Features By default, only the [`blocking`](crate::blocking) APIs are available. To include the asynchronous APIs, you must enable the `async` feature. pty-process-0.4.0/src/blocking/command.rs000064400000000000000000000130051046102023000164570ustar 00000000000000use std::os::unix::process::CommandExt as _; /// Wrapper around [`std::process::Command`] pub struct Command { inner: std::process::Command, stdin: bool, stdout: bool, stderr: bool, pre_exec_set: bool, pre_exec: Option< Box std::io::Result<()> + Send + Sync + 'static>, >, } impl Command { /// See [`std::process::Command::new`] pub fn new>(program: S) -> Self { Self { inner: std::process::Command::new(program), stdin: false, stdout: false, stderr: false, pre_exec_set: false, pre_exec: None, } } /// See [`std::process::Command::arg`] pub fn arg>(&mut self, arg: S) -> &mut Self { self.inner.arg(arg); self } /// See [`std::process::Command::args`] pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, S: AsRef, { self.inner.args(args); self } /// See [`std::process::Command::env`] pub fn env(&mut self, key: K, val: V) -> &mut Self where K: AsRef, V: AsRef, { self.inner.env(key, val); self } /// See [`std::process::Command::envs`] pub fn envs(&mut self, vars: I) -> &mut Self where I: IntoIterator, K: AsRef, V: AsRef, { self.inner.envs(vars); self } /// See [`std::process::Command::env_remove`] pub fn env_remove>( &mut self, key: K, ) -> &mut Self { self.inner.env_remove(key); self } /// See [`std::process::Command::env_clear`] pub fn env_clear(&mut self) -> &mut Self { self.inner.env_clear(); self } /// See [`std::process::Command::current_dir`] pub fn current_dir>( &mut self, dir: P, ) -> &mut Self { self.inner.current_dir(dir); self } /// See [`std::process::Command::stdin`] pub fn stdin>( &mut self, cfg: T, ) -> &mut Self { self.stdin = true; self.inner.stdin(cfg); self } /// See [`std::process::Command::stdout`] pub fn stdout>( &mut self, cfg: T, ) -> &mut Self { self.stdout = true; self.inner.stdout(cfg); self } /// See [`std::process::Command::stderr`] pub fn stderr>( &mut self, cfg: T, ) -> &mut Self { self.stderr = true; self.inner.stderr(cfg); self } /// Executes the command as a child process via /// [`std::process::Command::spawn`] on the given pty. The pty will be /// attached to all of `stdin`, `stdout`, and `stderr` of the child, /// unless those file descriptors were previously overridden through calls /// to [`stdin`](Self::stdin), [`stdout`](Self::stdout), or /// [`stderr`](Self::stderr). The newly created child process will also be /// made the session leader of a new session, and will have the given /// pty set as its controlling terminal. /// /// # Errors /// Returns an error if we fail to allocate new file descriptors for /// attaching the pty to the child process, or if we fail to spawn the /// child process (see the documentation for /// [`std::process::Command::spawn`]), or if we fail to make the child a /// session leader or set its controlling terminal. pub fn spawn( &mut self, pts: &crate::blocking::Pts, ) -> crate::Result { let (stdin, stdout, stderr) = pts.0.setup_subprocess()?; if !self.stdin { self.inner.stdin(stdin); } if !self.stdout { self.inner.stdout(stdout); } if !self.stderr { self.inner.stderr(stderr); } let mut session_leader = pts.0.session_leader(); // Safety: setsid() is an async-signal-safe function and ioctl() is a // raw syscall (which is inherently async-signal-safe). if let Some(mut custom) = self.pre_exec.take() { unsafe { self.inner.pre_exec(move || { session_leader()?; custom()?; Ok(()) }) }; } else if !self.pre_exec_set { unsafe { self.inner.pre_exec(session_leader) }; } self.pre_exec_set = true; Ok(self.inner.spawn()?) } /// See [`std::os::unix::process::CommandExt::uid`] pub fn uid(&mut self, id: u32) -> &mut Self { self.inner.uid(id); self } /// See [`std::os::unix::process::CommandExt::gid`] pub fn gid(&mut self, id: u32) -> &mut Self { self.inner.gid(id); self } /// See [`std::os::unix::process::CommandExt::pre_exec`] #[allow(clippy::missing_safety_doc)] pub unsafe fn pre_exec(&mut self, f: F) -> &mut Self where F: FnMut() -> std::io::Result<()> + Send + Sync + 'static, { self.pre_exec = Some(Box::new(f)); self } /// See [`std::os::unix::process::CommandExt::arg0`] pub fn arg0(&mut self, arg: S) -> &mut Self where S: AsRef, { self.inner.arg0(arg); self } } pty-process-0.4.0/src/blocking/mod.rs000064400000000000000000000002711046102023000156210ustar 00000000000000//! Blocking equivalents for [`pty_process::Command`](crate::Command) and //! [`pty_process::Pty`](crate::Pty) mod command; pub use command::Command; mod pty; pub use pty::{Pts, Pty}; pty-process-0.4.0/src/blocking/pty.rs000064400000000000000000000041611046102023000156600ustar 00000000000000/// An allocated pty pub struct Pty(crate::sys::Pty); impl Pty { /// Allocate and return a new pty. /// /// # Errors /// Returns an error if the pty failed to be allocated. pub fn new() -> crate::Result { Ok(Self(crate::sys::Pty::open()?)) } /// Change the terminal size associated with the pty. /// /// # Errors /// Returns an error if we were unable to set the terminal size. pub fn resize(&self, size: crate::Size) -> crate::Result<()> { self.0.set_term_size(size) } /// Opens a file descriptor for the other end of the pty, which should be /// attached to the child process running in it. See /// [`Command::spawn`](crate::blocking::Command::spawn). /// /// # Errors /// Returns an error if the device node to open could not be determined, /// or if the device node could not be opened. pub fn pts(&self) -> crate::Result { Ok(Pts(self.0.pts()?)) } } impl From for std::os::fd::OwnedFd { fn from(pty: Pty) -> Self { pty.0.into() } } impl std::os::fd::AsFd for Pty { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { self.0.as_fd() } } impl std::os::fd::AsRawFd for Pty { fn as_raw_fd(&self) -> std::os::fd::RawFd { self.0.as_raw_fd() } } impl std::io::Read for Pty { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.read(buf) } } impl std::io::Write for Pty { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.0.flush() } } impl std::io::Read for &Pty { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { (&self.0).read(buf) } } impl std::io::Write for &Pty { fn write(&mut self, buf: &[u8]) -> std::io::Result { (&self.0).write(buf) } fn flush(&mut self) -> std::io::Result<()> { (&self.0).flush() } } /// The child end of the pty /// /// See [`Pty::pts`] and [`Command::spawn`](crate::blocking::Command::spawn) pub struct Pts(pub(crate) crate::sys::Pts); pty-process-0.4.0/src/command.rs000064400000000000000000000127031046102023000146730ustar 00000000000000/// Wrapper around [`tokio::process::Command`] pub struct Command { inner: tokio::process::Command, stdin: bool, stdout: bool, stderr: bool, pre_exec_set: bool, pre_exec: Option< Box std::io::Result<()> + Send + Sync + 'static>, >, } impl Command { /// See [`tokio::process::Command::new`] pub fn new>(program: S) -> Self { Self { inner: tokio::process::Command::new(program), stdin: false, stdout: false, stderr: false, pre_exec_set: false, pre_exec: None, } } /// See [`tokio::process::Command::arg`] pub fn arg>(&mut self, arg: S) -> &mut Self { self.inner.arg(arg); self } /// See [`tokio::process::Command::args`] pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, S: AsRef, { self.inner.args(args); self } /// See [`tokio::process::Command::env`] pub fn env(&mut self, key: K, val: V) -> &mut Self where K: AsRef, V: AsRef, { self.inner.env(key, val); self } /// See [`tokio::process::Command::envs`] pub fn envs(&mut self, vars: I) -> &mut Self where I: IntoIterator, K: AsRef, V: AsRef, { self.inner.envs(vars); self } /// See [`tokio::process::Command::env_remove`] pub fn env_remove>( &mut self, key: K, ) -> &mut Self { self.inner.env_remove(key); self } /// See [`tokio::process::Command::env_clear`] pub fn env_clear(&mut self) -> &mut Self { self.inner.env_clear(); self } /// See [`tokio::process::Command::current_dir`] pub fn current_dir>( &mut self, dir: P, ) -> &mut Self { self.inner.current_dir(dir); self } /// See [`tokio::process::Command::stdin`] pub fn stdin>( &mut self, cfg: T, ) -> &mut Self { self.stdin = true; self.inner.stdin(cfg); self } /// See [`tokio::process::Command::stdout`] pub fn stdout>( &mut self, cfg: T, ) -> &mut Self { self.stdout = true; self.inner.stdout(cfg); self } /// See [`tokio::process::Command::stderr`] pub fn stderr>( &mut self, cfg: T, ) -> &mut Self { self.stderr = true; self.inner.stderr(cfg); self } /// Executes the command as a child process via /// [`tokio::process::Command::spawn`] on the given pty. The pty will be /// attached to all of `stdin`, `stdout`, and `stderr` of the child, /// unless those file descriptors were previously overridden through calls /// to [`stdin`](Self::stdin), [`stdout`](Self::stdout), or /// [`stderr`](Self::stderr). The newly created child process will also be /// made the session leader of a new session, and will have the given /// pty set as its controlling terminal. /// /// # Errors /// Returns an error if we fail to allocate new file descriptors for /// attaching the pty to the child process, or if we fail to spawn the /// child process (see the documentation for /// [`tokio::process::Command::spawn`]), or if we fail to make the child a /// session leader or set its controlling terminal. pub fn spawn( &mut self, pts: &crate::Pts, ) -> crate::Result { let (stdin, stdout, stderr) = pts.0.setup_subprocess()?; if !self.stdin { self.inner.stdin(stdin); } if !self.stdout { self.inner.stdout(stdout); } if !self.stderr { self.inner.stderr(stderr); } let mut session_leader = pts.0.session_leader(); // Safety: setsid() is an async-signal-safe function and ioctl() is a // raw syscall (which is inherently async-signal-safe). if let Some(mut custom) = self.pre_exec.take() { unsafe { self.inner.pre_exec(move || { session_leader()?; custom()?; Ok(()) }) }; } else if !self.pre_exec_set { unsafe { self.inner.pre_exec(session_leader) }; } self.pre_exec_set = true; Ok(self.inner.spawn()?) } /// See [`tokio::process::Command::uid`] pub fn uid(&mut self, id: u32) -> &mut Self { self.inner.uid(id); self } /// See [`tokio::process::Command::gid`] pub fn gid(&mut self, id: u32) -> &mut Self { self.inner.gid(id); self } /// See [`tokio::process::Command::pre_exec`] #[allow(clippy::missing_safety_doc)] pub unsafe fn pre_exec(&mut self, f: F) -> &mut Self where F: FnMut() -> std::io::Result<()> + Send + Sync + 'static, { self.pre_exec = Some(Box::new(f)); self } /// See [`tokio::process::Command::arg0`] pub fn arg0(&mut self, arg: S) -> &mut Self where S: AsRef, { self.inner.arg0(arg); self } } pty-process-0.4.0/src/error.rs000064400000000000000000000026161046102023000144100ustar 00000000000000/// Error type for errors from this crate #[derive(Debug)] pub enum Error { /// error came from std::io::Error Io(std::io::Error), /// error came from nix::Error Rustix(rustix::io::Errno), /// unsplit was called on halves of two different ptys #[cfg(feature = "async")] Unsplit(crate::OwnedReadPty, crate::OwnedWritePty), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Io(e) => write!(f, "{e}"), Self::Rustix(e) => write!(f, "{e}"), #[cfg(feature = "async")] Self::Unsplit(..) => { write!(f, "unsplit called on halves of two different ptys") } } } } impl std::convert::From for Error { fn from(e: std::io::Error) -> Self { Self::Io(e) } } impl std::convert::From for Error { fn from(e: rustix::io::Errno) -> Self { Self::Rustix(e) } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(e) => Some(e), Self::Rustix(e) => Some(e), #[cfg(feature = "async")] Self::Unsplit(..) => None, } } } /// Convenience wrapper for `Result`s using [`Error`](Error) pub type Result = std::result::Result; pty-process-0.4.0/src/lib.rs000064400000000000000000000047461046102023000140330ustar 00000000000000//! This crate is a wrapper around [`tokio::process::Command`] or //! [`std::process::Command`] which provides the ability to allocate a pty //! and spawn new processes attached to that pty, with the pty as their //! controlling terminal. This allows for manipulation of interactive //! programs. //! //! The basic functionality looks like this: //! //! ```no_run //! # #[cfg(feature = "async")] //! # #[tokio::main] //! # async fn main() -> pty_process::Result<()> { //! let mut pty = pty_process::Pty::new()?; //! pty.resize(pty_process::Size::new(24, 80))?; //! let mut cmd = pty_process::Command::new("nethack"); //! let child = cmd.spawn(&pty.pts()?)?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "async"))] //! # fn main() -> pty_process::Result<()> { //! let mut pty = pty_process::blocking::Pty::new()?; //! pty.resize(pty_process::Size::new(24, 80))?; //! let mut cmd = pty_process::blocking::Command::new("nethack"); //! let child = cmd.spawn(&pty.pts()?)?; //! # Ok(()) //! # } //! ``` //! //! The returned `child` is a normal instance of [`tokio::process::Child`] (or //! [`std::process::Child`] for the [`blocking`](crate::blocking) variant), //! with its `stdin`/`stdout`/`stderr` file descriptors pointing at the given //! pty. The `pty` instance implements [`tokio::io::AsyncRead`] and //! [`tokio::io::AsyncWrite`] (or [`std::io::Read`] and [`std::io::Write`] for //! the [`blocking`] variant), and can be used to communicate with the child //! process. The child process will also be made a session leader of a new //! session, and the controlling terminal of that session will be set to the //! given pty. //! //! # Features //! //! By default, only the [`blocking`](crate::blocking) APIs are available. To //! include the asynchronous APIs, you must enable the `async` feature. #![warn(clippy::cargo)] #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![warn(clippy::as_conversions)] #![warn(clippy::get_unwrap)] #![allow(clippy::cognitive_complexity)] #![allow(clippy::missing_const_for_fn)] #![allow(clippy::similar_names)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_lines)] #![allow(clippy::type_complexity)] mod error; pub use error::{Error, Result}; mod types; pub use types::Size; mod sys; pub mod blocking; #[cfg(feature = "async")] mod command; #[cfg(feature = "async")] pub use command::Command; #[cfg(feature = "async")] mod pty; #[cfg(feature = "async")] pub use pty::{OwnedReadPty, OwnedWritePty, Pts, Pty, ReadPty, WritePty}; pty-process-0.4.0/src/pty.rs000064400000000000000000000273071046102023000140770ustar 00000000000000#![allow(clippy::module_name_repetitions)] use std::io::{Read as _, Write as _}; type AsyncPty = tokio::io::unix::AsyncFd; /// An allocated pty pub struct Pty(AsyncPty); impl Pty { /// Allocate and return a new pty. /// /// # Errors /// Returns an error if the pty failed to be allocated, or if we were /// unable to put it into non-blocking mode. pub fn new() -> crate::Result { let pty = crate::sys::Pty::open()?; pty.set_nonblocking()?; Ok(Self(tokio::io::unix::AsyncFd::new(pty)?)) } /// Change the terminal size associated with the pty. /// /// # Errors /// Returns an error if we were unable to set the terminal size. pub fn resize(&self, size: crate::Size) -> crate::Result<()> { self.0.get_ref().set_term_size(size) } /// Opens a file descriptor for the other end of the pty, which should be /// attached to the child process running in it. See /// [`Command::spawn`](crate::Command::spawn). /// /// # Errors /// Returns an error if the device node to open could not be determined, /// or if the device node could not be opened. pub fn pts(&self) -> crate::Result { Ok(Pts(self.0.get_ref().pts()?)) } /// Splits a `Pty` into a read half and a write half, which can be used to /// read from and write to the pty concurrently. Does not allocate, but /// the returned halves cannot be moved to independent tasks. pub fn split(&mut self) -> (ReadPty<'_>, WritePty<'_>) { (ReadPty(&self.0), WritePty(&self.0)) } /// Splits a `Pty` into a read half and a write half, which can be used to /// read from and write to the pty concurrently. This method requires an /// allocation, but the returned halves can be moved to independent tasks. /// The original `Pty` instance can be recovered via the /// [`OwnedReadPty::unsplit`] method. #[must_use] pub fn into_split(self) -> (OwnedReadPty, OwnedWritePty) { let Self(pt) = self; let read_pt = std::sync::Arc::new(pt); let write_pt = std::sync::Arc::clone(&read_pt); (OwnedReadPty(read_pt), OwnedWritePty(write_pt)) } } impl From for std::os::fd::OwnedFd { fn from(pty: Pty) -> Self { pty.0.into_inner().into() } } impl std::os::fd::AsFd for Pty { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { self.0.get_ref().as_fd() } } impl std::os::fd::AsRawFd for Pty { fn as_raw_fd(&self) -> std::os::fd::RawFd { self.0.get_ref().as_raw_fd() } } impl tokio::io::AsyncRead for Pty { fn poll_read( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_read_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; // XXX should be able to optimize this once read_buf is stabilized // in std let b = buf.initialize_unfilled(); match guard.try_io(|inner| inner.get_ref().read(b)) { Ok(Ok(bytes)) => { buf.advance(bytes); return std::task::Poll::Ready(Ok(())); } Ok(Err(e)) => return std::task::Poll::Ready(Err(e)), Err(_would_block) => continue, } } } } impl tokio::io::AsyncWrite for Pty { fn poll_write( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().write(buf)) { Ok(result) => return std::task::Poll::Ready(result), Err(_would_block) => continue, } } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().flush()) { Ok(_) => return std::task::Poll::Ready(Ok(())), Err(_would_block) => continue, } } } fn poll_shutdown( self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } } /// The child end of the pty /// /// See [`Pty::pts`] and [`Command::spawn`](crate::Command::spawn) pub struct Pts(pub(crate) crate::sys::Pts); /// Borrowed read half of a [`Pty`] pub struct ReadPty<'a>(&'a AsyncPty); impl<'a> tokio::io::AsyncRead for ReadPty<'a> { fn poll_read( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_read_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; // XXX should be able to optimize this once read_buf is stabilized // in std let b = buf.initialize_unfilled(); match guard.try_io(|inner| inner.get_ref().read(b)) { Ok(Ok(bytes)) => { buf.advance(bytes); return std::task::Poll::Ready(Ok(())); } Ok(Err(e)) => return std::task::Poll::Ready(Err(e)), Err(_would_block) => continue, } } } } /// Borrowed write half of a [`Pty`] pub struct WritePty<'a>(&'a AsyncPty); impl<'a> WritePty<'a> { /// Change the terminal size associated with the pty. /// /// # Errors /// Returns an error if we were unable to set the terminal size. pub fn resize(&self, size: crate::Size) -> crate::Result<()> { self.0.get_ref().set_term_size(size) } } impl<'a> tokio::io::AsyncWrite for WritePty<'a> { fn poll_write( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().write(buf)) { Ok(result) => return std::task::Poll::Ready(result), Err(_would_block) => continue, } } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().flush()) { Ok(_) => return std::task::Poll::Ready(Ok(())), Err(_would_block) => continue, } } } fn poll_shutdown( self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } } /// Owned read half of a [`Pty`] #[derive(Debug)] pub struct OwnedReadPty(std::sync::Arc); impl OwnedReadPty { /// Attempt to join the two halves of a `Pty` back into a single instance. /// The two halves must have originated from calling /// [`into_split`](Pty::into_split) on a single instance. /// /// # Errors /// Returns an error if the two halves came from different [`Pty`] /// instances. The mismatched halves are returned as part of the error. pub fn unsplit(self, write_half: OwnedWritePty) -> crate::Result { let Self(read_pt) = self; let OwnedWritePty(write_pt) = write_half; if std::sync::Arc::ptr_eq(&read_pt, &write_pt) { drop(write_pt); Ok(Pty(std::sync::Arc::try_unwrap(read_pt) // it shouldn't be possible for more than two references to // the same pty to exist .unwrap_or_else(|_| unreachable!()))) } else { Err(crate::Error::Unsplit( Self(read_pt), OwnedWritePty(write_pt), )) } } } impl tokio::io::AsyncRead for OwnedReadPty { fn poll_read( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_read_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; // XXX should be able to optimize this once read_buf is stabilized // in std let b = buf.initialize_unfilled(); match guard.try_io(|inner| inner.get_ref().read(b)) { Ok(Ok(bytes)) => { buf.advance(bytes); return std::task::Poll::Ready(Ok(())); } Ok(Err(e)) => return std::task::Poll::Ready(Err(e)), Err(_would_block) => continue, } } } } /// Owned write half of a [`Pty`] #[derive(Debug)] pub struct OwnedWritePty(std::sync::Arc); impl OwnedWritePty { /// Change the terminal size associated with the pty. /// /// # Errors /// Returns an error if we were unable to set the terminal size. pub fn resize(&self, size: crate::Size) -> crate::Result<()> { self.0.get_ref().set_term_size(size) } } impl tokio::io::AsyncWrite for OwnedWritePty { fn poll_write( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().write(buf)) { Ok(result) => return std::task::Poll::Ready(result), Err(_would_block) => continue, } } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { loop { let mut guard = match self.0.poll_write_ready(cx) { std::task::Poll::Ready(guard) => guard, std::task::Poll::Pending => return std::task::Poll::Pending, }?; match guard.try_io(|inner| inner.get_ref().flush()) { Ok(_) => return std::task::Poll::Ready(Ok(())), Err(_would_block) => continue, } } } fn poll_shutdown( self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } } pty-process-0.4.0/src/sys.rs000064400000000000000000000107351046102023000140760ustar 00000000000000use std::os::{ fd::{AsRawFd as _, FromRawFd as _}, unix::prelude::OsStrExt as _, }; #[derive(Debug)] pub struct Pty(std::os::fd::OwnedFd); impl Pty { pub fn open() -> crate::Result { let pt = rustix::pty::openpt( // can't use CLOEXEC here because it's linux-specific rustix::pty::OpenptFlags::RDWR | rustix::pty::OpenptFlags::NOCTTY, )?; rustix::pty::grantpt(&pt)?; rustix::pty::unlockpt(&pt)?; let mut flags = rustix::io::fcntl_getfd(&pt)?; flags |= rustix::io::FdFlags::CLOEXEC; rustix::io::fcntl_setfd(&pt, flags)?; Ok(Self(pt)) } pub fn set_term_size(&self, size: crate::Size) -> crate::Result<()> { let size = libc::winsize::from(size); let fd = self.0.as_raw_fd(); // TODO: upstream this to rustix let ret = unsafe { libc::ioctl(fd, libc::TIOCSWINSZ, std::ptr::addr_of!(size)) }; if ret == -1 { Err(rustix::io::Errno::from_raw_os_error( std::io::Error::last_os_error().raw_os_error().unwrap_or(0), ) .into()) } else { Ok(()) } } pub fn pts(&self) -> crate::Result { Ok(Pts(std::fs::OpenOptions::new() .read(true) .write(true) .open(std::ffi::OsStr::from_bytes( rustix::pty::ptsname(&self.0, vec![])?.as_bytes(), ))? .into())) } #[cfg(feature = "async")] pub fn set_nonblocking(&self) -> rustix::io::Result<()> { let mut opts = rustix::fs::fcntl_getfl(&self.0)?; opts |= rustix::fs::OFlags::NONBLOCK; rustix::fs::fcntl_setfl(&self.0, opts)?; Ok(()) } } impl From for std::os::fd::OwnedFd { fn from(pty: Pty) -> Self { let Pty(nix_ptymaster) = pty; let raw_fd = nix_ptymaster.as_raw_fd(); std::mem::forget(nix_ptymaster); // Safety: nix::pty::PtyMaster is required to contain a valid file // descriptor, and we ensured that the file descriptor will remain // valid by skipping the drop implementation for nix::pty::PtyMaster unsafe { Self::from_raw_fd(raw_fd) } } } impl std::os::fd::AsFd for Pty { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { let raw_fd = self.0.as_raw_fd(); // Safety: nix::pty::PtyMaster is required to contain a valid file // descriptor, and it is owned by self unsafe { std::os::fd::BorrowedFd::borrow_raw(raw_fd) } } } impl std::os::fd::AsRawFd for Pty { fn as_raw_fd(&self) -> std::os::fd::RawFd { self.0.as_raw_fd() } } impl std::io::Read for Pty { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { rustix::io::read(&self.0, buf).map_err(std::io::Error::from) } } impl std::io::Write for Pty { fn write(&mut self, buf: &[u8]) -> std::io::Result { rustix::io::write(&self.0, buf).map_err(std::io::Error::from) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl std::io::Read for &Pty { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { rustix::io::read(&self.0, buf).map_err(std::io::Error::from) } } impl std::io::Write for &Pty { fn write(&mut self, buf: &[u8]) -> std::io::Result { rustix::io::write(&self.0, buf).map_err(std::io::Error::from) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } pub struct Pts(std::os::fd::OwnedFd); impl Pts { pub fn setup_subprocess( &self, ) -> std::io::Result<( std::process::Stdio, std::process::Stdio, std::process::Stdio, )> { Ok(( self.0.try_clone()?.into(), self.0.try_clone()?.into(), self.0.try_clone()?.into(), )) } pub fn session_leader(&self) -> impl FnMut() -> std::io::Result<()> { let pts_fd = self.0.as_raw_fd(); move || { rustix::process::setsid()?; rustix::process::ioctl_tiocsctty(unsafe { std::os::fd::BorrowedFd::borrow_raw(pts_fd) })?; Ok(()) } } } impl From for std::os::fd::OwnedFd { fn from(pts: Pts) -> Self { pts.0 } } impl std::os::fd::AsFd for Pts { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { self.0.as_fd() } } impl std::os::fd::AsRawFd for Pts { fn as_raw_fd(&self) -> std::os::fd::RawFd { self.0.as_raw_fd() } } pty-process-0.4.0/src/types.rs000064400000000000000000000020171046102023000144160ustar 00000000000000/// Represents the size of the pty. #[derive(Debug, Clone, Copy)] pub struct Size { row: u16, col: u16, xpixel: u16, ypixel: u16, } impl Size { /// Returns a [`Size`](Size) instance with the given number of rows and /// columns. #[must_use] pub fn new(row: u16, col: u16) -> Self { Self { row, col, xpixel: 0, ypixel: 0, } } /// Returns a [`Size`](Size) instance with the given number of rows and /// columns, as well as the given pixel dimensions. #[must_use] pub fn new_with_pixel( row: u16, col: u16, xpixel: u16, ypixel: u16, ) -> Self { Self { row, col, xpixel, ypixel, } } } impl From for libc::winsize { fn from(size: Size) -> Self { Self { ws_row: size.row, ws_col: size.col, ws_xpixel: size.xpixel, ws_ypixel: size.ypixel, } } }