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