1 //! libc syscalls supporting `rustix::termios`.
2 //!
3 //! # Safety
4 //!
5 //! See the `rustix::backend::syscalls` module documentation for details.
6 
7 use crate::backend::c;
8 #[cfg(not(target_os = "wasi"))]
9 use crate::backend::conv::ret_pid_t;
10 use crate::backend::conv::{borrowed_fd, ret};
11 use crate::fd::BorrowedFd;
12 #[cfg(all(feature = "alloc", feature = "procfs"))]
13 #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
14 use crate::ffi::CStr;
15 #[cfg(any(
16     not(target_os = "espidf"),
17     all(
18         feature = "procfs",
19         not(any(target_os = "fuchsia", target_os = "wasi"))
20     )
21 ))]
22 use core::mem::MaybeUninit;
23 #[cfg(not(target_os = "wasi"))]
24 use {crate::io, crate::pid::Pid};
25 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
26 use {
27     crate::termios::{Action, OptionalActions, QueueSelector, Termios, Winsize},
28     crate::utils::as_mut_ptr,
29 };
30 
31 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios>32 pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
33     // If we have `TCGETS2`, use it, so that we fill in the `c_ispeed` and
34     // `c_ospeed` fields.
35     #[cfg(linux_kernel)]
36     {
37         use crate::termios::{ControlModes, InputModes, LocalModes, OutputModes, SpecialCodes};
38         use crate::utils::default_array;
39 
40         let termios2 = unsafe {
41             let mut termios2 = MaybeUninit::<c::termios2>::uninit();
42 
43             // QEMU's `TCGETS2` doesn't currently set `input_speed` or
44             // `output_speed` on PowerPC, so zero out the fields ourselves.
45             #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
46             {
47                 termios2.write(core::mem::zeroed());
48             }
49 
50             ret(c::ioctl(
51                 borrowed_fd(fd),
52                 c::TCGETS2 as _,
53                 termios2.as_mut_ptr(),
54             ))?;
55 
56             termios2.assume_init()
57         };
58 
59         // Convert from the Linux `termios2` to our `Termios`.
60         let mut result = Termios {
61             input_modes: InputModes::from_bits_retain(termios2.c_iflag),
62             output_modes: OutputModes::from_bits_retain(termios2.c_oflag),
63             control_modes: ControlModes::from_bits_retain(termios2.c_cflag),
64             local_modes: LocalModes::from_bits_retain(termios2.c_lflag),
65             line_discipline: termios2.c_line,
66             special_codes: SpecialCodes(default_array()),
67             input_speed: termios2.c_ispeed,
68             output_speed: termios2.c_ospeed,
69         };
70 
71         // QEMU's `TCGETS2` doesn't currently set `input_speed` or
72         // `output_speed` on PowerPC, so set them manually if we can.
73         #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
74         {
75             use crate::termios::speed;
76 
77             if result.output_speed == 0 && (termios2.c_cflag & c::CBAUD) != c::BOTHER {
78                 if let Some(output_speed) = speed::decode(termios2.c_cflag & c::CBAUD) {
79                     result.output_speed = output_speed;
80                 }
81             }
82             if result.input_speed == 0
83                 && ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER
84             {
85                 // For input speeds, `B0` is special-cased to mean the input
86                 // speed is the same as the output speed.
87                 if ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) == c::B0 {
88                     result.input_speed = result.output_speed;
89                 } else if let Some(input_speed) =
90                     speed::decode((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT)
91                 {
92                     result.input_speed = input_speed;
93                 }
94             }
95         }
96 
97         result.special_codes.0[..termios2.c_cc.len()].copy_from_slice(&termios2.c_cc);
98 
99         Ok(result)
100     }
101 
102     #[cfg(not(linux_kernel))]
103     unsafe {
104         let mut result = MaybeUninit::<Termios>::uninit();
105 
106         // `result` is a `Termios` which starts with the same layout as
107         // `libc::termios`, so we can cast the pointer.
108         ret(c::tcgetattr(borrowed_fd(fd), result.as_mut_ptr().cast()))?;
109 
110         Ok(result.assume_init())
111     }
112 }
113 
114 #[cfg(not(target_os = "wasi"))]
tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid>115 pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
116     unsafe {
117         let pid = ret_pid_t(c::tcgetpgrp(borrowed_fd(fd)))?;
118 
119         // This doesn't appear to be documented, but on Linux, it appears
120         // `tcsetpgrp` can succceed and set the pid to 0 if we pass it a
121         // pseudo-terminal device fd. For now, translate it into `OPNOTSUPP`.
122         #[cfg(linux_kernel)]
123         if pid == 0 {
124             return Err(io::Errno::OPNOTSUPP);
125         }
126 
127         Ok(Pid::from_raw_unchecked(pid))
128     }
129 }
130 
131 #[cfg(not(target_os = "wasi"))]
tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()>132 pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
133     unsafe { ret(c::tcsetpgrp(borrowed_fd(fd), pid.as_raw_nonzero().get())) }
134 }
135 
136 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcsetattr( fd: BorrowedFd<'_>, optional_actions: OptionalActions, termios: &Termios, ) -> io::Result<()>137 pub(crate) fn tcsetattr(
138     fd: BorrowedFd<'_>,
139     optional_actions: OptionalActions,
140     termios: &Termios,
141 ) -> io::Result<()> {
142     // If we have `TCSETS2`, use it, so that we use the `c_ispeed` and
143     // `c_ospeed` fields.
144     #[cfg(linux_kernel)]
145     {
146         use crate::termios::speed;
147         use crate::utils::default_array;
148         use linux_raw_sys::general::{termios2, BOTHER, CBAUD, IBSHIFT};
149 
150         #[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
151         use linux_raw_sys::ioctl::{TCSETS, TCSETS2};
152 
153         // linux-raw-sys' ioctl-generation script for sparc isn't working yet,
154         // so as a temporary workaround, declare these manually.
155         #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
156         const TCSETS: u32 = 0x8024_5409;
157         #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
158         const TCSETS2: u32 = 0x802c_540d;
159 
160         // Translate from `optional_actions` into an ioctl request code. On
161         // MIPS, `optional_actions` already has `TCGETS` added to it.
162         let request = TCSETS2
163             + if cfg!(any(
164                 target_arch = "mips",
165                 target_arch = "mips32r6",
166                 target_arch = "mips64",
167                 target_arch = "mips64r6"
168             )) {
169                 optional_actions as u32 - TCSETS
170             } else {
171                 optional_actions as u32
172             };
173 
174         let input_speed = termios.input_speed();
175         let output_speed = termios.output_speed();
176         let mut termios2 = termios2 {
177             c_iflag: termios.input_modes.bits(),
178             c_oflag: termios.output_modes.bits(),
179             c_cflag: termios.control_modes.bits(),
180             c_lflag: termios.local_modes.bits(),
181             c_line: termios.line_discipline,
182             c_cc: default_array(),
183             c_ispeed: input_speed,
184             c_ospeed: output_speed,
185         };
186         // Ensure that our input and output speeds are set, as `libc`
187         // routines don't always support setting these separately.
188         termios2.c_cflag &= !CBAUD;
189         termios2.c_cflag |= speed::encode(output_speed).unwrap_or(BOTHER);
190         termios2.c_cflag &= !(CBAUD << IBSHIFT);
191         termios2.c_cflag |= speed::encode(input_speed).unwrap_or(BOTHER) << IBSHIFT;
192         let nccs = termios2.c_cc.len();
193         termios2
194             .c_cc
195             .copy_from_slice(&termios.special_codes.0[..nccs]);
196 
197         unsafe { ret(c::ioctl(borrowed_fd(fd), request as _, &termios2)) }
198     }
199 
200     #[cfg(not(linux_kernel))]
201     unsafe {
202         ret(c::tcsetattr(
203             borrowed_fd(fd),
204             optional_actions as _,
205             crate::utils::as_ptr(termios).cast(),
206         ))
207     }
208 }
209 
210 #[cfg(not(target_os = "wasi"))]
tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()>211 pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
212     unsafe { ret(c::tcsendbreak(borrowed_fd(fd), 0)) }
213 }
214 
215 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcdrain(fd: BorrowedFd<'_>) -> io::Result<()>216 pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
217     unsafe { ret(c::tcdrain(borrowed_fd(fd))) }
218 }
219 
220 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()>221 pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
222     unsafe { ret(c::tcflush(borrowed_fd(fd), queue_selector as _)) }
223 }
224 
225 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()>226 pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
227     unsafe { ret(c::tcflow(borrowed_fd(fd), action as _)) }
228 }
229 
230 #[cfg(not(target_os = "wasi"))]
tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid>231 pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
232     unsafe {
233         let pid = ret_pid_t(c::tcgetsid(borrowed_fd(fd)))?;
234         Ok(Pid::from_raw_unchecked(pid))
235     }
236 }
237 
238 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()>239 pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
240     unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCSWINSZ, &winsize)) }
241 }
242 
243 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize>244 pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
245     unsafe {
246         let mut buf = MaybeUninit::<Winsize>::uninit();
247         ret(c::ioctl(
248             borrowed_fd(fd),
249             c::TIOCGWINSZ.into(),
250             buf.as_mut_ptr(),
251         ))?;
252         Ok(buf.assume_init())
253     }
254 }
255 
256 #[cfg(not(any(target_os = "espidf", target_os = "nto", target_os = "wasi")))]
257 #[inline]
set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()>258 pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
259     #[cfg(bsd)]
260     let encoded_speed = arbitrary_speed;
261 
262     #[cfg(not(bsd))]
263     let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
264         Some(encoded_speed) => encoded_speed,
265         #[cfg(linux_kernel)]
266         None => c::BOTHER,
267         #[cfg(not(linux_kernel))]
268         None => return Err(io::Errno::INVAL),
269     };
270 
271     #[cfg(not(linux_kernel))]
272     unsafe {
273         ret(c::cfsetspeed(
274             as_mut_ptr(termios).cast(),
275             encoded_speed.into(),
276         ))
277     }
278 
279     // Linux libc implementations don't support arbitrary speeds, so we encode
280     // the speed manually.
281     #[cfg(linux_kernel)]
282     {
283         use crate::termios::ControlModes;
284 
285         debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
286 
287         termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD | c::CIBAUD);
288         termios.control_modes |=
289             ControlModes::from_bits_retain(encoded_speed | (encoded_speed << c::IBSHIFT));
290 
291         termios.input_speed = arbitrary_speed;
292         termios.output_speed = arbitrary_speed;
293 
294         Ok(())
295     }
296 }
297 
298 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
299 #[inline]
set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()>300 pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
301     #[cfg(bsd)]
302     let encoded_speed = arbitrary_speed;
303 
304     #[cfg(not(bsd))]
305     let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
306         Some(encoded_speed) => encoded_speed,
307         #[cfg(linux_kernel)]
308         None => c::BOTHER,
309         #[cfg(not(linux_kernel))]
310         None => return Err(io::Errno::INVAL),
311     };
312 
313     #[cfg(not(linux_kernel))]
314     unsafe {
315         ret(c::cfsetospeed(
316             as_mut_ptr(termios).cast(),
317             encoded_speed.into(),
318         ))
319     }
320 
321     // Linux libc implementations don't support arbitrary speeds or setting the
322     // input and output speeds separately, so we encode the speed manually.
323     #[cfg(linux_kernel)]
324     {
325         use crate::termios::ControlModes;
326 
327         debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
328 
329         termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD);
330         termios.control_modes |= ControlModes::from_bits_retain(encoded_speed);
331 
332         termios.output_speed = arbitrary_speed;
333 
334         Ok(())
335     }
336 }
337 
338 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
339 #[inline]
set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()>340 pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
341     #[cfg(bsd)]
342     let encoded_speed = arbitrary_speed;
343 
344     #[cfg(not(bsd))]
345     let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
346         Some(encoded_speed) => encoded_speed,
347         #[cfg(linux_kernel)]
348         None => c::BOTHER,
349         #[cfg(not(linux_kernel))]
350         None => return Err(io::Errno::INVAL),
351     };
352 
353     #[cfg(not(linux_kernel))]
354     unsafe {
355         ret(c::cfsetispeed(
356             as_mut_ptr(termios).cast(),
357             encoded_speed.into(),
358         ))
359     }
360 
361     // Linux libc implementations don't support arbitrary speeds or setting the
362     // input and output speeds separately, so we encode the speed manually.
363     #[cfg(linux_kernel)]
364     {
365         use crate::termios::ControlModes;
366 
367         debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
368 
369         termios.control_modes -= ControlModes::from_bits_retain(c::CIBAUD);
370         termios.control_modes |= ControlModes::from_bits_retain(encoded_speed << c::IBSHIFT);
371 
372         termios.input_speed = arbitrary_speed;
373 
374         Ok(())
375     }
376 }
377 
378 #[cfg(not(any(target_os = "espidf", target_os = "nto", target_os = "wasi")))]
379 #[inline]
cfmakeraw(termios: &mut Termios)380 pub(crate) fn cfmakeraw(termios: &mut Termios) {
381     unsafe { c::cfmakeraw(as_mut_ptr(termios).cast()) }
382 }
383 
isatty(fd: BorrowedFd<'_>) -> bool384 pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
385     // Use the return value of `isatty` alone. We don't check `errno` because
386     // we return `bool` rather than `io::Result<bool>`, because we assume
387     // `BorrowedFd` protects us from `EBADF`, and any other reasonably
388     // anticipated `errno` value would end up interpreted as “assume it's not a
389     // terminal” anyway.
390     unsafe { c::isatty(borrowed_fd(fd)) != 0 }
391 }
392 
393 #[cfg(all(feature = "alloc", feature = "procfs"))]
394 #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
ttyname(dirfd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize>395 pub(crate) fn ttyname(dirfd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
396     unsafe {
397         // `ttyname_r` returns its error status rather than using `errno`.
398         match c::ttyname_r(borrowed_fd(dirfd), buf.as_mut_ptr().cast(), buf.len()) {
399             0 => Ok(CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len()),
400             err => Err(io::Errno::from_raw_os_error(err)),
401         }
402     }
403 }
404