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