1//! linux_raw syscalls supporting `rustix::termios`.
2//!
3//! # Safety
4//!
5//! See the `rustix::backend` module documentation for details.
6#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
7
8use crate::backend::c;
9use crate::backend::conv::{by_ref, c_uint, ret};
10use crate::fd::BorrowedFd;
11use crate::io;
12use crate::pid::Pid;
13#[cfg(all(feature = "alloc", feature = "procfs"))]
14use crate::procfs;
15use crate::termios::{
16 Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes, QueueSelector,
17 SpecialCodeIndex, Termios, Winsize,
18};
19#[cfg(all(feature = "alloc", feature = "procfs"))]
20use crate::{ffi::CStr, fs::FileType, path::DecInt};
21use core::mem::MaybeUninit;
22use linux_raw_sys::general::IBSHIFT;
23use linux_raw_sys::ioctl::{
24 TCFLSH, TCSBRK, TCXONC, TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCSPGRP, TIOCSWINSZ,
25};
26
27#[inline]
28pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
29 unsafe {
30 let mut result: MaybeUninit = MaybeUninit::<Winsize>::uninit();
31 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGWINSZ), &mut result))?;
32 Ok(result.assume_init())
33 }
34}
35
36#[inline]
37pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
38 unsafe {
39 let mut result = MaybeUninit::<Termios>::uninit();
40
41 // QEMU's `TCGETS2` doesn't currently set `input_speed` or
42 // `output_speed` on PowerPC, so zero out the fields ourselves.
43 #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
44 {
45 result.write(core::mem::zeroed());
46 }
47
48 ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result))?;
49
50 let result = result.assume_init();
51
52 // QEMU's `TCGETS2` doesn't currently set `input_speed` or
53 // `output_speed` on PowerPC, so set them manually if we can.
54 #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
55 let result = {
56 use crate::termios::speed;
57 let mut result = result;
58 if result.output_speed == 0 && (result.control_modes.bits() & c::CBAUD) != c::BOTHER {
59 if let Some(output_speed) = speed::decode(result.control_modes.bits() & c::CBAUD) {
60 result.output_speed = output_speed;
61 }
62 }
63 if result.input_speed == 0
64 && ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER
65 {
66 // For input speeds, `B0` is special-cased to mean the input
67 // speed is the same as the output speed.
68 if ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) == c::B0 {
69 result.input_speed = result.output_speed;
70 } else if let Some(input_speed) =
71 speed::decode((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT)
72 {
73 result.input_speed = input_speed;
74 }
75 }
76 result
77 };
78
79 Ok(result)
80 }
81}
82
83#[inline]
84pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
85 unsafe {
86 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
87 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGPGRP), &mut result))?;
88 let pid: i32 = result.assume_init();
89
90 // This doesn't appear to be documented, but it appears `tcsetpgrp` can
91 // succceed and set the pid to 0 if we pass it a pseudo-terminal device
92 // fd. For now, fail with `OPNOTSUPP`.
93 if pid == 0 {
94 return Err(io::Errno::OPNOTSUPP);
95 }
96
97 Ok(Pid::from_raw_unchecked(raw:pid))
98 }
99}
100
101#[inline]
102pub(crate) fn tcsetattr(
103 fd: BorrowedFd<'_>,
104 optional_actions: OptionalActions,
105 termios: &Termios,
106) -> io::Result<()> {
107 // Translate from `optional_actions` into an ioctl request code. On MIPS,
108 // `optional_actions` already has `TCGETS` added to it.
109 let request: u32 = linux_raw_sys::ioctl::TCSETS2
110 + if cfg!(any(
111 target_arch = "mips",
112 target_arch = "mips32r6",
113 target_arch = "mips64",
114 target_arch = "mips64r6"
115 )) {
116 optional_actions as u32 - linux_raw_sys::ioctl::TCSETS
117 } else {
118 optional_actions as u32
119 };
120 unsafe {
121 ret(raw:syscall_readonly!(
122 __NR_ioctl,
123 fd,
124 c_uint(request),
125 by_ref(termios)
126 ))
127 }
128}
129
130#[inline]
131pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
132 unsafe { ret(raw:syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(0))) }
133}
134
135#[inline]
136pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
137 unsafe { ret(raw:syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(1))) }
138}
139
140#[inline]
141pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
142 unsafe {
143 ret(raw:syscall_readonly!(
144 __NR_ioctl,
145 fd,
146 c_uint(TCFLSH),
147 c_uint(queue_selector as u32)
148 ))
149 }
150}
151
152#[inline]
153pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
154 unsafe {
155 ret(raw:syscall_readonly!(
156 __NR_ioctl,
157 fd,
158 c_uint(TCXONC),
159 c_uint(action as u32)
160 ))
161 }
162}
163
164#[inline]
165pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
166 unsafe {
167 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
168 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGSID), &mut result))?;
169 let pid: i32 = result.assume_init();
170 Ok(Pid::from_raw_unchecked(raw:pid))
171 }
172}
173
174#[inline]
175pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
176 unsafe {
177 ret(raw:syscall_readonly!(
178 __NR_ioctl,
179 fd,
180 c_uint(TIOCSWINSZ),
181 by_ref(&winsize)
182 ))
183 }
184}
185
186#[inline]
187pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
188 let raw_pid: c::c_int = pid.as_raw_nonzero().get();
189 unsafe {
190 ret(raw:syscall_readonly!(
191 __NR_ioctl,
192 fd,
193 c_uint(TIOCSPGRP),
194 by_ref(&raw_pid)
195 ))
196 }
197}
198
199/// A wrapper around a conceptual `cfsetspeed` which handles an arbitrary
200/// integer speed value.
201#[inline]
202pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
203 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
204
205 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
206
207 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD | c::CIBAUD);
208 termios.control_modes |=
209 ControlModes::from_bits_retain(bits:encoded_speed | (encoded_speed << IBSHIFT));
210
211 termios.input_speed = arbitrary_speed;
212 termios.output_speed = arbitrary_speed;
213
214 Ok(())
215}
216
217/// A wrapper around a conceptual `cfsetospeed` which handles an arbitrary
218/// integer speed value.
219#[inline]
220pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
221 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
222
223 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
224
225 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD);
226 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed);
227
228 termios.output_speed = arbitrary_speed;
229
230 Ok(())
231}
232
233/// A wrapper around a conceptual `cfsetispeed` which handles an arbitrary
234/// integer speed value.
235#[inline]
236pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
237 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
238
239 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
240
241 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CIBAUD);
242 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed << IBSHIFT);
243
244 termios.input_speed = arbitrary_speed;
245
246 Ok(())
247}
248
249#[inline]
250pub(crate) fn cfmakeraw(termios: &mut Termios) {
251 // From the Linux [`cfmakeraw` manual page]:
252 //
253 // [`cfmakeraw` manual page]: https://man7.org/linux/man-pages/man3/cfmakeraw.3.html
254 termios.input_modes -= InputModes::IGNBRK
255 | InputModes::BRKINT
256 | InputModes::PARMRK
257 | InputModes::ISTRIP
258 | InputModes::INLCR
259 | InputModes::IGNCR
260 | InputModes::ICRNL
261 | InputModes::IXON;
262 termios.output_modes -= OutputModes::OPOST;
263 termios.local_modes -= LocalModes::ECHO
264 | LocalModes::ECHONL
265 | LocalModes::ICANON
266 | LocalModes::ISIG
267 | LocalModes::IEXTEN;
268 termios.control_modes -= ControlModes::CSIZE | ControlModes::PARENB;
269 termios.control_modes |= ControlModes::CS8;
270
271 // Musl and glibc also do these:
272 termios.special_codes[SpecialCodeIndex::VMIN] = 1;
273 termios.special_codes[SpecialCodeIndex::VTIME] = 0;
274}
275
276#[inline]
277pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
278 // On error, Linux will return either `EINVAL` (2.6.32) or `ENOTTY`
279 // (otherwise), because we assume we're never passing an invalid
280 // file descriptor (which would get `EBADF`). Either way, an error
281 // means we don't have a tty.
282 tcgetwinsize(fd).is_ok()
283}
284
285#[cfg(all(feature = "alloc", feature = "procfs"))]
286pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
287 let fd_stat = crate::backend::fs::syscalls::fstat(fd)?;
288
289 // Quick check: if `fd` isn't a character device, it's not a tty.
290 if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice {
291 return Err(io::Errno::NOTTY);
292 }
293
294 // Check that `fd` is really a tty.
295 tcgetwinsize(fd)?;
296
297 // Get a fd to "/proc/self/fd".
298 let proc_self_fd = procfs::proc_self_fd()?;
299
300 // Gather the ttyname by reading the "fd" file inside `proc_self_fd`.
301 let r = crate::backend::fs::syscalls::readlinkat(
302 proc_self_fd,
303 DecInt::from_fd(fd).as_c_str(),
304 buf,
305 )?;
306
307 // If the number of bytes is equal to the buffer length, truncation may
308 // have occurred. This check also ensures that we have enough space for
309 // adding a NUL terminator.
310 if r == buf.len() {
311 return Err(io::Errno::RANGE);
312 }
313
314 // `readlinkat` returns the number of bytes placed in the buffer.
315 // NUL-terminate the string at that offset.
316 buf[r].write(b'\0');
317
318 // Check that the path we read refers to the same file as `fd`.
319 {
320 // SAFETY: We just wrote the NUL byte above
321 let path = unsafe { CStr::from_ptr(buf.as_ptr().cast()) };
322
323 let path_stat = crate::backend::fs::syscalls::stat(path)?;
324 if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino {
325 return Err(io::Errno::NODEV);
326 }
327 }
328
329 Ok(r)
330}
331