1#![no_std]
12
13extern crate alloc;
14
15#[cfg(any(target_os = "android", target_os = "linux"))]
16use alloc::vec::Vec;
17use rustix::fd::{AsFd, OwnedFd};
18#[cfg(any(target_os = "android", target_os = "linux"))] use rustix::fd::{AsRawFd, RawFd};
20use rustix::io;
21use rustix::termios::{Termios, Winsize};
22
23pub use rustix;
25
26pub struct Pty {
28 pub controller: OwnedFd,
30
31 pub user: OwnedFd,
34}
35
36pub fn openpty(termios: Option<&Termios>, winsize: Option<&Winsize>) -> io::Result<Pty> {
60 #[cfg(not(any(target_os = "android", target_os = "linux")))]
63 {
64 use core::mem::{align_of, size_of, MaybeUninit};
65 use core::ptr::{null, null_mut};
66 use rustix::fd::FromRawFd;
67
68 assert_eq!(size_of::<Termios>(), size_of::<libc::termios>());
69 assert_eq!(align_of::<Termios>(), align_of::<libc::termios>());
70
71 let termios: *const libc::termios = match termios {
72 Some(termios) => {
73 let termios: *const Termios = termios;
74 termios.cast()
75 }
76 None => null(),
77 };
78 let mut libc_winsize: libc::winsize;
79 let winsize: *mut libc::winsize = match winsize {
80 Some(winsize) => {
81 libc_winsize = libc::winsize {
82 ws_row: winsize.ws_row,
83 ws_col: winsize.ws_col,
84 ws_xpixel: winsize.ws_xpixel,
85 ws_ypixel: winsize.ws_ypixel,
86 };
87 &mut libc_winsize
88 }
89 None => null_mut(),
90 };
91
92 let mut controller = MaybeUninit::<libc::c_int>::uninit();
93 let mut user = MaybeUninit::<libc::c_int>::uninit();
94 unsafe {
95 if libc::openpty(
96 controller.as_mut_ptr(),
97 user.as_mut_ptr(),
98 null_mut(),
99 termios as _,
100 winsize,
101 ) == 0
102 {
103 let controller = OwnedFd::from_raw_fd(controller.assume_init());
104 let user = OwnedFd::from_raw_fd(user.assume_init());
105
106 set_cloexec(&controller)?;
107 set_cloexec(&user)?;
108
109 Ok(Pty { controller, user })
110 } else {
111 Err(io::Errno::from_raw_os_error(errno::errno().0))
112 }
113 }
114 }
115
116 #[cfg(any(target_os = "android", target_os = "linux"))]
120 {
121 use rustix::pty::{grantpt, openpt, unlockpt, OpenptFlags};
122 use rustix::termios::{tcsetattr, tcsetwinsize, OptionalActions};
123
124 let flags = OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC;
125 let controller = openpt(flags)?;
126
127 grantpt(&controller)?;
128 unlockpt(&controller)?;
129
130 let user = open_user(&controller, flags | OpenptFlags::CLOEXEC)?;
131
132 if let Some(termios) = termios {
133 tcsetattr(&user, OptionalActions::Now, termios)?;
134 }
135 if let Some(winsize) = winsize {
136 tcsetwinsize(&user, *winsize)?;
137 }
138
139 Ok(Pty { controller, user })
140 }
141}
142
143pub fn openpty_nocloexec(termios: Option<&Termios>, winsize: Option<&Winsize>) -> io::Result<Pty> {
149 #[cfg(not(any(target_os = "android", target_os = "linux")))]
151 {
152 use core::mem::{align_of, size_of, MaybeUninit};
153 use core::ptr::{null, null_mut};
154 use rustix::fd::FromRawFd;
155
156 assert_eq!(size_of::<Termios>(), size_of::<libc::termios>());
157 assert_eq!(align_of::<Termios>(), align_of::<libc::termios>());
158
159 let termios: *const libc::termios = match termios {
160 Some(termios) => {
161 let termios: *const Termios = termios;
162 termios.cast()
163 }
164 None => null(),
165 };
166 let mut libc_winsize: libc::winsize;
167 let winsize: *mut libc::winsize = match winsize {
168 Some(winsize) => {
169 libc_winsize = libc::winsize {
170 ws_row: winsize.ws_row,
171 ws_col: winsize.ws_col,
172 ws_xpixel: winsize.ws_xpixel,
173 ws_ypixel: winsize.ws_ypixel,
174 };
175 &mut libc_winsize
176 }
177 None => null_mut(),
178 };
179
180 let mut controller = MaybeUninit::<libc::c_int>::uninit();
181 let mut user = MaybeUninit::<libc::c_int>::uninit();
182 unsafe {
183 if libc::openpty(
184 controller.as_mut_ptr(),
185 user.as_mut_ptr(),
186 null_mut(),
187 termios as _,
188 winsize,
189 ) == 0
190 {
191 let controller = OwnedFd::from_raw_fd(controller.assume_init());
192 let user = OwnedFd::from_raw_fd(user.assume_init());
193
194 Ok(Pty { controller, user })
195 } else {
196 Err(io::Errno::from_raw_os_error(errno::errno().0))
197 }
198 }
199 }
200
201 #[cfg(any(target_os = "android", target_os = "linux"))]
203 {
204 use rustix::pty::{grantpt, openpt, unlockpt, OpenptFlags};
205 use rustix::termios::{tcsetattr, tcsetwinsize, OptionalActions};
206
207 let flags = OpenptFlags::RDWR | OpenptFlags::NOCTTY;
208 let controller = openpt(flags)?;
209
210 grantpt(&controller)?;
211 unlockpt(&controller)?;
212
213 let user = open_user(&controller, flags)?;
214
215 if let Some(termios) = termios {
216 tcsetattr(&user, OptionalActions::Now, termios)?;
217 }
218 if let Some(winsize) = winsize {
219 tcsetwinsize(&user, *winsize)?;
220 }
221
222 Ok(Pty { controller, user })
223 }
224}
225
226#[cfg(not(any(target_os = "android", target_os = "linux")))]
227fn set_cloexec<Fd: AsFd>(fd: Fd) -> io::Result<()> {
228 use rustix::io::{fcntl_getfd, fcntl_setfd, FdFlags};
229
230 let fd = fd.as_fd();
231 fcntl_setfd(fd, fcntl_getfd(fd)? | FdFlags::CLOEXEC)
232}
233
234#[cfg(any(target_os = "android", target_os = "linux"))]
235fn open_user(controller: &OwnedFd, flags: rustix::pty::OpenptFlags) -> io::Result<OwnedFd> {
236 use rustix::fs::{openat, Mode, CWD};
237
238 #[cfg(not(target_os = "android"))]
242 {
243 match rustix::pty::ioctl_tiocgptpeer(controller, flags) {
244 Ok(fd) => return Ok(fd),
245 Err(io::Errno::NOSYS) | Err(io::Errno::PERM) => {}
246 Err(e) => return Err(e),
247 }
248 }
249
250 let name = rustix::pty::ptsname(controller, Vec::new())?;
252
253 openat(CWD, name, flags.into(), Mode::empty())
254}
255
256#[cfg(not(any(target_os = "fuchsia", target_os = "illumos", target_os = "solaris")))]
267pub fn login_tty<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<()> {
268 _login_tty(fd.into())
269}
270
271#[cfg(not(any(target_os = "fuchsia", target_os = "illumos", target_os = "solaris")))]
272fn _login_tty(fd: OwnedFd) -> io::Result<()> {
273 #[cfg(not(any(target_os = "android", target_os = "linux")))]
274 unsafe {
275 if libc::login_tty(rustix::fd::IntoRawFd::into_raw_fd(fd)) != 0 {
276 return Err(io::Errno::from_raw_os_error(errno::errno().0));
277 }
278 }
279
280 #[cfg(any(target_os = "android", target_os = "linux"))]
281 {
282 rustix::process::setsid().ok();
284
285 rustix::process::ioctl_tiocsctty(&fd)?;
287
288 rustix::stdio::dup2_stdin(&fd).ok();
290 rustix::stdio::dup2_stdout(&fd).ok();
291 rustix::stdio::dup2_stderr(&fd).ok();
292
293 if rustix::fd::AsRawFd::as_raw_fd(&fd) <= 2 {
295 core::mem::forget(fd);
296 }
297 }
298
299 Ok(())
300}
301
302#[cfg(any(target_os = "android", target_os = "linux"))] pub unsafe fn closefrom(from: RawFd) {
311 use core::mem::MaybeUninit;
312 use core::str;
313 use rustix::fs::{openat, Mode, OFlags, RawDir, CWD};
314
315 let dir = openat(
316 CWD,
317 rustix::cstr!("/dev/fd"),
318 OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
319 Mode::empty(),
320 )
321 .unwrap();
322 let dir_raw_fd = dir.as_fd().as_raw_fd();
323
324 let mut buf = [MaybeUninit::uninit(); 1024];
326 let mut iter = RawDir::new(dir, &mut buf);
327 while let Some(entry) = iter.next() {
328 let entry = entry.unwrap();
329 let name_bytes = entry.file_name().to_bytes();
330 if name_bytes == b"." || name_bytes == b".." {
331 continue;
332 }
333 let name = str::from_utf8(name_bytes).unwrap();
334 let num = name.parse::<RawFd>().unwrap();
335
336 if num >= from && num != dir_raw_fd {
337 io::close(num);
338 }
339 }
340}