rustix/backend/linux_raw/termios/
syscalls.rs
1#![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;
11#[cfg(feature = "alloc")]
12use crate::ffi::CStr;
13use crate::io;
14use crate::pid::Pid;
15use crate::termios::{
16 speed, Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes,
17 QueueSelector, SpecialCodeIndex, Termios, Winsize,
18};
19#[cfg(feature = "alloc")]
20#[cfg(feature = "fs")]
21use crate::{fs::FileType, path::DecInt};
22use core::mem::MaybeUninit;
23
24#[inline]
25pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
26 unsafe {
27 let mut result = MaybeUninit::<Winsize>::uninit();
28 ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGWINSZ), &mut result))?;
29 Ok(result.assume_init())
30 }
31}
32
33#[inline]
34pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
35 let mut result = MaybeUninit::<Termios>::uninit();
36
37 unsafe {
40 match ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result)) {
41 Ok(()) => Ok(result.assume_init()),
42
43 #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
47 Err(io::Errno::NOTTY) | Err(io::Errno::ACCESS) => tcgetattr_fallback(fd),
48
49 Err(err) => Err(err),
50 }
51 }
52}
53
54#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
56#[cold]
57fn tcgetattr_fallback(fd: BorrowedFd<'_>) -> io::Result<Termios> {
58 use core::ptr::{addr_of, addr_of_mut};
59
60 let mut result = MaybeUninit::<Termios>::uninit();
61
62 unsafe {
66 ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS), &mut result))?;
68
69 let ptr = result.as_mut_ptr();
72 let control_modes = addr_of!((*ptr).control_modes).read();
73
74 let encoded_out = control_modes.bits() & c::CBAUD;
76 let output_speed = match speed::decode(encoded_out) {
77 Some(output_speed) => output_speed,
78 None => return Err(io::Errno::RANGE),
79 };
80 addr_of_mut!((*ptr).output_speed).write(output_speed);
81
82 let encoded_in = (control_modes.bits() & c::CIBAUD) >> c::IBSHIFT;
85 let input_speed = if encoded_in == c::B0 {
86 output_speed
87 } else {
88 match speed::decode(encoded_in) {
89 Some(input_speed) => input_speed,
90 None => return Err(io::Errno::RANGE),
91 }
92 };
93 addr_of_mut!((*ptr).input_speed).write(input_speed);
94
95 Ok(result.assume_init())
97 }
98}
99
100#[inline]
101pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
102 unsafe {
103 let mut result = MaybeUninit::<c::pid_t>::uninit();
104 ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGPGRP), &mut result))?;
105 let pid = result.assume_init();
106
107 if pid == 0 {
111 return Err(io::Errno::OPNOTSUPP);
112 }
113
114 Ok(Pid::from_raw_unchecked(pid))
115 }
116}
117
118#[inline]
119pub(crate) fn tcsetattr(
120 fd: BorrowedFd<'_>,
121 optional_actions: OptionalActions,
122 termios: &Termios,
123) -> io::Result<()> {
124 let request = c::TCSETS2
127 + if cfg!(any(
128 target_arch = "mips",
129 target_arch = "mips32r6",
130 target_arch = "mips64",
131 target_arch = "mips64r6"
132 )) {
133 optional_actions as u32 - c::TCSETS
134 } else {
135 optional_actions as u32
136 };
137
138 unsafe {
140 match ret(syscall_readonly!(
141 __NR_ioctl,
142 fd,
143 c_uint(request),
144 by_ref(termios)
145 )) {
146 Ok(()) => Ok(()),
147
148 #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
151 Err(io::Errno::NOTTY) | Err(io::Errno::ACCESS) => {
152 tcsetattr_fallback(fd, optional_actions, termios)
153 }
154
155 Err(err) => Err(err),
156 }
157 }
158}
159
160#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
162#[cold]
163fn tcsetattr_fallback(
164 fd: BorrowedFd<'_>,
165 optional_actions: OptionalActions,
166 termios: &Termios,
167) -> io::Result<()> {
168 let control_modes_bits = termios.control_modes.bits();
171 let encoded_out = control_modes_bits & c::CBAUD;
172 let encoded_in = (control_modes_bits & c::CIBAUD) >> c::IBSHIFT;
173 if encoded_out == c::BOTHER || encoded_in == c::BOTHER {
174 return Err(io::Errno::RANGE);
175 }
176
177 let request = if cfg!(any(
180 target_arch = "mips",
181 target_arch = "mips32r6",
182 target_arch = "mips64",
183 target_arch = "mips64r6"
184 )) {
185 optional_actions as u32
186 } else {
187 optional_actions as u32 + c::TCSETS
188 };
189
190 unsafe {
192 ret(syscall_readonly!(
193 __NR_ioctl,
194 fd,
195 c_uint(request),
196 by_ref(termios)
197 ))
198 }
199}
200
201#[inline]
202pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
203 unsafe {
204 ret(syscall_readonly!(
205 __NR_ioctl,
206 fd,
207 c_uint(c::TCSBRK),
208 c_uint(0)
209 ))
210 }
211}
212
213#[inline]
214pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
215 unsafe {
216 ret(syscall_readonly!(
217 __NR_ioctl,
218 fd,
219 c_uint(c::TCSBRK),
220 c_uint(1)
221 ))
222 }
223}
224
225#[inline]
226pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
227 unsafe {
228 ret(syscall_readonly!(
229 __NR_ioctl,
230 fd,
231 c_uint(c::TCFLSH),
232 c_uint(queue_selector as u32)
233 ))
234 }
235}
236
237#[inline]
238pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
239 unsafe {
240 ret(syscall_readonly!(
241 __NR_ioctl,
242 fd,
243 c_uint(c::TCXONC),
244 c_uint(action as u32)
245 ))
246 }
247}
248
249#[inline]
250pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
251 unsafe {
252 let mut result = MaybeUninit::<c::pid_t>::uninit();
253 ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGSID), &mut result))?;
254 let pid = result.assume_init();
255 Ok(Pid::from_raw_unchecked(pid))
256 }
257}
258
259#[inline]
260pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
261 unsafe {
262 ret(syscall_readonly!(
263 __NR_ioctl,
264 fd,
265 c_uint(c::TIOCSWINSZ),
266 by_ref(&winsize)
267 ))
268 }
269}
270
271#[inline]
272pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
273 let raw_pid: c::c_int = pid.as_raw_nonzero().get();
274 unsafe {
275 ret(syscall_readonly!(
276 __NR_ioctl,
277 fd,
278 c_uint(c::TIOCSPGRP),
279 by_ref(&raw_pid)
280 ))
281 }
282}
283
284#[inline]
287pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
288 let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
289
290 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
291
292 termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD | c::CIBAUD);
293 termios.control_modes |=
294 ControlModes::from_bits_retain(encoded_speed | (encoded_speed << c::IBSHIFT));
295
296 termios.input_speed = arbitrary_speed;
297 termios.output_speed = arbitrary_speed;
298
299 Ok(())
300}
301
302#[inline]
305pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
306 let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
307
308 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
309
310 termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD);
311 termios.control_modes |= ControlModes::from_bits_retain(encoded_speed);
312
313 termios.output_speed = arbitrary_speed;
314
315 Ok(())
316}
317
318#[inline]
321pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
322 let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
323
324 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
325
326 termios.control_modes -= ControlModes::from_bits_retain(c::CIBAUD);
327 termios.control_modes |= ControlModes::from_bits_retain(encoded_speed << c::IBSHIFT);
328
329 termios.input_speed = arbitrary_speed;
330
331 Ok(())
332}
333
334#[inline]
335pub(crate) fn cfmakeraw(termios: &mut Termios) {
336 termios.input_modes -= InputModes::IGNBRK
340 | InputModes::BRKINT
341 | InputModes::PARMRK
342 | InputModes::ISTRIP
343 | InputModes::INLCR
344 | InputModes::IGNCR
345 | InputModes::ICRNL
346 | InputModes::IXON;
347 termios.output_modes -= OutputModes::OPOST;
348 termios.local_modes -= LocalModes::ECHO
349 | LocalModes::ECHONL
350 | LocalModes::ICANON
351 | LocalModes::ISIG
352 | LocalModes::IEXTEN;
353 termios.control_modes -= ControlModes::CSIZE | ControlModes::PARENB;
354 termios.control_modes |= ControlModes::CS8;
355
356 termios.special_codes[SpecialCodeIndex::VMIN] = 1;
358 termios.special_codes[SpecialCodeIndex::VTIME] = 0;
359}
360
361#[inline]
362pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
363 tcgetwinsize(fd).is_ok()
368}
369
370#[cfg(feature = "alloc")]
371#[cfg(feature = "fs")]
372pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
373 let fd_stat = crate::backend::fs::syscalls::fstat(fd)?;
374
375 if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice {
377 return Err(io::Errno::NOTTY);
378 }
379
380 tcgetwinsize(fd)?;
382
383 let mut proc_self_fd_buf: [u8; 25] = *b"/proc/self/fd/\0\0\0\0\0\0\0\0\0\0\0";
385 let dec_int = DecInt::from_fd(fd);
386 let bytes_with_nul = dec_int.as_bytes_with_nul();
387 proc_self_fd_buf[b"/proc/self/fd/".len()..][..bytes_with_nul.len()]
388 .copy_from_slice(bytes_with_nul);
389
390 let proc_self_fd_path = unsafe { CStr::from_ptr(proc_self_fd_buf.as_ptr().cast()) };
392
393 let ptr = buf.as_mut_ptr();
394 let len = {
395 let (init, uninit) = crate::fs::readlinkat_raw(crate::fs::CWD, proc_self_fd_path, buf)?;
397
398 if uninit.is_empty() {
402 return Err(io::Errno::RANGE);
403 }
404
405 uninit[0].write(b'\0');
408
409 init.len()
410 };
411
412 {
414 let path = unsafe { CStr::from_ptr(ptr.cast()) };
416
417 let path_stat = crate::backend::fs::syscalls::stat(path)?;
418 if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino {
419 return Err(io::Errno::NODEV);
420 }
421 }
422
423 Ok(len)
425}