diff --git a/ci/tcgets2-tcsets2.patch b/ci/tcgets2-tcsets2.patch index 0fb89cbb6..11fe910b2 100644 --- a/ci/tcgets2-tcsets2.patch +++ b/ci/tcgets2-tcsets2.patch @@ -6,15 +6,22 @@ This implements the `TCGETS2` and `TCSETS2` ioctls. diff -ur a/linux-user/ioctls.h b/linux-user/ioctls.h --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h -@@ -2,6 +2,8 @@ +@@ -1,5 +1,15 @@ + /* emulated ioctl list */ - IOCTL(TCGETS, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios))) - IOCTL(TCSETS, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) ++ /* ++ * Put `TCGETS2`/`TCGETS2` before `TCGETS`/`TCSETS` so that on targets ++ * where these have the same value, we find the `TCGETS2`/`TCSETS2` ++ * entries first, which ensures that we set the `c_ispeed` and `c_ospeed` ++ * fields if the target needed them. ++ */ + IOCTL(TCGETS2, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios2))) + IOCTL(TCSETS2, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios2))) ++ IOCTL(TCSETSF2, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios2))) ++ IOCTL(TCSETSW2, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios2))) + IOCTL(TCGETS, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios))) + IOCTL(TCSETS, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) IOCTL(TCSETSF, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) - IOCTL(TCSETSW, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) - IOCTL(TIOCGWINSZ, IOC_R, MK_PTR(MK_STRUCT(STRUCT_winsize))) diff -ur a/linux-user/ppc/termbits.h b/linux-user/ppc/termbits.h --- a/linux-user/ppc/termbits.h +++ b/linux-user/ppc/termbits.h @@ -53,12 +60,14 @@ diff -ur a/linux-user/ppc/termbits.h b/linux-user/ppc/termbits.h #define TARGET_CSIZE 00001400 #define TARGET_CS5 00000000 -@@ -178,6 +192,8 @@ +@@ -178,6 +192,10 @@ #define TARGET_TCSETS TARGET_IOW('t', 20, struct target_termios) #define TARGET_TCSETSW TARGET_IOW('t', 21, struct target_termios) #define TARGET_TCSETSF TARGET_IOW('t', 22, struct target_termios) +#define TARGET_TCGETS2 TARGET_TCGETS +#define TARGET_TCSETS2 TARGET_TCSETS ++#define TARGET_TCSETSW2 TARGET_TCSETSW ++#define TARGET_TCSETSF2 TARGET_TCSETSF #define TARGET_TCGETA TARGET_IOR('t', 23, struct target_termio) #define TARGET_TCSETA TARGET_IOW('t', 24, struct target_termio) @@ -157,6 +166,15 @@ diff -ur a/linux-user/strace.c b/linux-user/strace.c #undef UNUSED +@@ -4161,7 +4161,7 @@ + int target_size; + + for (ie = ioctl_entries; ie->target_cmd != 0; ie++) { +- if (ie->target_cmd == arg1) { ++ if (ie->target_cmd == (int)arg1) { + break; + } + } diff -ur a/linux-user/syscall.c b/linux-user/syscall.c --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -326,3 +344,60 @@ diff -ur a/linux-user/user-internals.h b/linux-user/user-internals.h /* ARM EABI and MIPS expect 64bit types aligned even on pairs or registers */ #ifdef TARGET_ARM +diff -ur -x build -x roms a/linux-user/mips/termbits.h b/linux-user/mips/termbits.h +--- a/linux-user/mips/termbits.h ++++ b/linux-user/mips/termbits.h +@@ -18,6 +18,17 @@ + target_cc_t c_cc[TARGET_NCCS]; /* control characters */ + }; + ++struct target_termios2 { ++ target_tcflag_t c_iflag; /* input mode flags */ ++ target_tcflag_t c_oflag; /* output mode flags */ ++ target_tcflag_t c_cflag; /* control mode flags */ ++ target_tcflag_t c_lflag; /* local mode flags */ ++ target_cc_t c_line; /* line discipline */ ++ target_cc_t c_cc[TARGET_NCCS]; /* control characters */ ++ target_speed_t c_ispeed; /* input speed */ ++ target_speed_t c_ospeed; /* output speed */ ++}; ++ + /* c_iflag bits */ + #define TARGET_IGNBRK 0000001 + #define TARGET_BRKINT 0000002 +@@ -117,6 +128,7 @@ + #define TARGET_B3500000 0010016 + #define TARGET_B4000000 0010017 + #define TARGET_CIBAUD 002003600000 /* input baud rate (not used) */ ++#define TARGET_IBSHIFT 16 + #define TARGET_CMSPAR 010000000000 /* mark or space (stick) parity */ + #define TARGET_CRTSCTS 020000000000 /* flow control */ + +@@ -217,9 +229,9 @@ + #define TARGET_TIOCSETP 0x7409 + #define TARGET_TIOCSETN 0x740a /* TIOCSETP wo flush */ + +-/* #define TARGET_TIOCSETA TARGET_IOW('t', 20, struct termios) set termios struct */ +-/* #define TARGET_TIOCSETAW TARGET_IOW('t', 21, struct termios) drain output, set */ +-/* #define TARGET_TIOCSETAF TARGET_IOW('t', 22, struct termios) drn out, fls in, set */ ++/* #define TARGET_TIOCSETA TARGET_IOW('t', 20, struct target_termios) set termios struct */ ++/* #define TARGET_TIOCSETAW TARGET_IOW('t', 21, struct target_termios) drain output, set */ ++/* #define TARGET_TIOCSETAF TARGET_IOW('t', 22, struct target_termios) drn out, fls in, set */ + /* #define TARGET_TIOCGETD TARGET_IOR('t', 26, int) get line discipline */ + /* #define TARGET_TIOCSETD TARGET_IOW('t', 27, int) set line discipline */ + /* 127-124 compat */ +@@ -227,10 +239,10 @@ + #define TARGET_TIOCSBRK 0x5427 /* BSD compatibility */ + #define TARGET_TIOCCBRK 0x5428 /* BSD compatibility */ + #define TARGET_TIOCGSID 0x7416 /* Return the session ID of FD */ +-#define TARGET_TCGETS2 TARGET_IOR('T', 0x2A, struct termios2) +-#define TARGET_TCSETS2 TARGET_IOW('T', 0x2B, struct termios2) +-#define TARGET_TCSETSW2 TARGET_IOW('T', 0x2C, struct termios2) +-#define TARGET_TCSETSF2 TARGET_IOW('T', 0x2D, struct termios2) ++#define TARGET_TCGETS2 TARGET_IOR('T', 0x2A, struct target_termios2) ++#define TARGET_TCSETS2 TARGET_IOW('T', 0x2B, struct target_termios2) ++#define TARGET_TCSETSW2 TARGET_IOW('T', 0x2C, struct target_termios2) ++#define TARGET_TCSETSF2 TARGET_IOW('T', 0x2D, struct target_termios2) + #define TARGET_TIOCGRS485 TARGET_IOR('T', 0x2E, struct serial_rs485) + #define TARGET_TIOCSRS485 TARGET_IOWR('T', 0x2F, struct serial_rs485) + #define TARGET_TIOCGPTN TARGET_IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */ diff --git a/src/backend/libc/c.rs b/src/backend/libc/c.rs index 1a5dbf79d..ec140a327 100644 --- a/src/backend/libc/c.rs +++ b/src/backend/libc/c.rs @@ -102,19 +102,26 @@ pub(crate) const O_LARGEFILE: c_int = 0x2000; pub(crate) const MAP_DROPPABLE: u32 = 0x8; // On PowerPC, the regular `termios` has the `termios2` fields and there is no -// `termios2`. linux-raw-sys has aliases `termios2` to `termios` to cover this -// difference, but we still need to manually import it since `libc` doesn't -// have this. +// `termios2`, so we define aliases. #[cfg(all( linux_kernel, feature = "termios", any(target_arch = "powerpc", target_arch = "powerpc64") ))] -pub(crate) use { - linux_raw_sys::general::{termios2, CIBAUD}, - linux_raw_sys::ioctl::{TCGETS2, TCSETS2, TCSETSF2, TCSETSW2}, +pub(crate) use libc::{ + termios as termios2, TCGETS as TCGETS2, TCSETS as TCSETS2, TCSETSF as TCSETSF2, + TCSETSW as TCSETSW2, }; +// And PowerPC doesn't define `CIBAUD`, but it does define `IBSHIFT`, so we can +// compute `CIBAUD` ourselves. +#[cfg(all( + linux_kernel, + feature = "termios", + any(target_arch = "powerpc", target_arch = "powerpc64") +))] +pub(crate) const CIBAUD: u32 = libc::CBAUD << libc::IBSHIFT; + // Automatically enable “large file” support (LFS) features. #[cfg(target_os = "vxworks")] diff --git a/src/backend/libc/termios/syscalls.rs b/src/backend/libc/termios/syscalls.rs index 9eba5637f..26662862a 100644 --- a/src/backend/libc/termios/syscalls.rs +++ b/src/backend/libc/termios/syscalls.rs @@ -30,31 +30,69 @@ use { #[cfg(not(any(target_os = "espidf", target_os = "wasi")))] pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result { - // If we have `TCGETS2`, use it, so that we fill in the `c_ispeed` and - // `c_ospeed` fields. + // On Linux, use `TCGETS`, and fall back to `TCGETS2` if we need to get the + // `c_ispeed` and `c_ospeed` field values. + // + // SAFETY: This invokes the `TCGETS` and optionally also `TCGETS2` ioctls. + // `TCGETS` doesn't initialize the `input_speed` and `output_speed` fields + // of the result, so in the case where we don't call `TCGETS2`, we infer + // their values from other fields and then manually initialize them (except + // on PowerPC where `TCGETS` does initialize those fields). #[cfg(linux_kernel)] - { + unsafe { use crate::termios::{ControlModes, InputModes, LocalModes, OutputModes, SpecialCodes}; use crate::utils::default_array; - let termios2 = unsafe { - let mut termios2 = MaybeUninit::::uninit(); + let mut termios2 = MaybeUninit::::uninit(); - // QEMU's `TCGETS2` doesn't currently set `input_speed` or - // `output_speed` on PowerPC, so zero out the fields ourselves. - #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] - { - termios2.write(core::mem::zeroed()); - } + // Use the old `TCGETS`. Linux has supported `TCGETS2` since Linux + // 2.6.40, however glibc and musl haven't migrated to it yet, so it + // tends to get left out of seccomp sandboxes. It's also unsupported + // by WSL, and likely enough other things too, so we use the old + // `TCGETS` for compatibility. + ret(c::ioctl( + borrowed_fd(fd), + c::TCGETS as _, + termios2.as_mut_ptr(), + ))?; - ret(c::ioctl( - borrowed_fd(fd), - c::TCGETS2 as _, - termios2.as_mut_ptr(), - ))?; + // If `BOTHER` is set for either the input or output, that means + // there's a custom speed, which means we really do need to use + // `TCGETS2`. Unless we're on PowerPC, where `TCGETS` is the same + // as `TCGETS2`. + #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))] + { + use crate::termios::speed; + use core::ptr::{addr_of, addr_of_mut}; + + // At this point, `termios2` is initialized except for the + // `c_ispeed` and `c_ospeed` fields, so we don't form a reference + // yet. + let ptr = termios2.as_mut_ptr(); + + let control_modes = addr_of!((*ptr).c_cflag).read(); + let encoded_out = control_modes & c::CBAUD; + let encoded_in = (control_modes & c::CIBAUD) >> c::IBSHIFT; + if encoded_out == c::BOTHER || encoded_in == c::BOTHER { + ret(c::ioctl(borrowed_fd(fd), c::TCGETS2 as _, ptr))?; + } else { + // Otherwise, set the speeds from other fields. + let output_speed = speed::decode(encoded_out).unwrap(); + addr_of_mut!((*ptr).c_ospeed).write(output_speed); - termios2.assume_init() - }; + // For input speeds, `B0` is special-cased to mean the input + // speed is the same as the output speed. + let input_speed = if encoded_in == c::B0 { + output_speed + } else { + speed::decode(encoded_in).unwrap() + }; + addr_of_mut!((*ptr).c_ispeed).write(input_speed); + } + } + + // Now all the fields are set. + let termios2 = termios2.assume_init(); // Convert from the Linux `termios2` to our `Termios`. let mut result = Termios { @@ -68,32 +106,6 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result { output_speed: termios2.c_ospeed, }; - // QEMU's `TCGETS2` doesn't currently set `input_speed` or - // `output_speed` on PowerPC, so set them manually if we can. - #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] - { - use crate::termios::speed; - - if result.output_speed == 0 && (termios2.c_cflag & c::CBAUD) != c::BOTHER { - if let Some(output_speed) = speed::decode(termios2.c_cflag & c::CBAUD) { - result.output_speed = output_speed; - } - } - if result.input_speed == 0 - && ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER - { - // For input speeds, `B0` is special-cased to mean the input - // speed is the same as the output speed. - if ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) == c::B0 { - result.input_speed = result.output_speed; - } else if let Some(input_speed) = - speed::decode((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) - { - result.input_speed = input_speed; - } - } - } - result.special_codes.0[..termios2.c_cc.len()].copy_from_slice(&termios2.c_cc); Ok(result) @@ -139,41 +151,50 @@ pub(crate) fn tcsetattr( optional_actions: OptionalActions, termios: &Termios, ) -> io::Result<()> { - // If we have `TCSETS2`, use it, so that we use the `c_ispeed` and - // `c_ospeed` fields. #[cfg(linux_kernel)] { use crate::termios::speed; use crate::utils::default_array; - use linux_raw_sys::general::{termios2, BOTHER, CBAUD, IBSHIFT}; - - #[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))] - use linux_raw_sys::ioctl::{TCSETS, TCSETS2}; - - // linux-raw-sys' ioctl-generation script for sparc isn't working yet, - // so as a temporary workaround, declare these manually. - #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] - const TCSETS: u32 = 0x8024_5409; - #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] - const TCSETS2: u32 = 0x802c_540d; - - // Translate from `optional_actions` into an ioctl request code. On - // MIPS, `optional_actions` already has `TCGETS` added to it. - let request = TCSETS2 - + if cfg!(any( + + // Similar to `tcgetattr`, use the old `TCSETS`, unless either input + // speed or output speed is custom, then we really have to use + // `TCSETS2`. + let encoded_out = termios.control_modes.bits() & c::CBAUD; + let encoded_in = (termios.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT; + let request = if encoded_out == c::BOTHER || encoded_in == c::BOTHER { + // Translate from `optional_actions` into a `TCGETS2` ioctl request + // code. On MIPS, `optional_actions` already has `TCGETS` added to + // it. + c::TCSETS2 as c::c_ulong + + if cfg!(any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" + )) { + optional_actions as c::c_ulong - c::TCSETS as c::c_ulong + } else { + optional_actions as c::c_ulong + } + } else { + // Translate from `optional_actions` into a `TCGETS` ioctl request + // code. On MIPS, `optional_actions` already has `TCGETS` added to + // it. + if cfg!(any( target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6" )) { - optional_actions as u32 - TCSETS + optional_actions as c::c_ulong } else { - optional_actions as u32 - }; + optional_actions as c::c_ulong + c::TCSETS as c::c_ulong + } + }; - let input_speed = termios.input_speed(); let output_speed = termios.output_speed(); - let mut termios2 = termios2 { + let input_speed = termios.input_speed(); + let mut termios2 = c::termios2 { c_iflag: termios.input_modes.bits(), c_oflag: termios.output_modes.bits(), c_cflag: termios.control_modes.bits(), @@ -183,18 +204,25 @@ pub(crate) fn tcsetattr( c_ispeed: input_speed, c_ospeed: output_speed, }; + // Ensure that our input and output speeds are set, as `libc` // routines don't always support setting these separately. - termios2.c_cflag &= !CBAUD; - termios2.c_cflag |= speed::encode(output_speed).unwrap_or(BOTHER); - termios2.c_cflag &= !(CBAUD << IBSHIFT); - termios2.c_cflag |= speed::encode(input_speed).unwrap_or(BOTHER) << IBSHIFT; + termios2.c_cflag &= !c::CBAUD; + termios2.c_cflag |= speed::encode(output_speed).unwrap_or(c::BOTHER); + termios2.c_cflag &= !c::CIBAUD; + termios2.c_cflag |= speed::encode(input_speed).unwrap_or(c::BOTHER) << c::IBSHIFT; let nccs = termios2.c_cc.len(); termios2 .c_cc .copy_from_slice(&termios.special_codes.0[..nccs]); - unsafe { ret(c::ioctl(borrowed_fd(fd), request as _, &termios2)) } + unsafe { + ret(c::ioctl( + borrowed_fd(fd), + request as _, + crate::utils::as_ptr(&termios2), + )) + } } #[cfg(not(linux_kernel))] diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index 70bc46a8c..4035bf945 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -190,22 +190,12 @@ pub(crate) use linux_raw_sys::{ VKILL, VLNEXT, VMIN, VQUIT, VREPRINT, VSTART, VSTOP, VSUSP, VSWTC, VT0, VT1, VTDLY, VTIME, VWERASE, XCASE, XTABS, }, - ioctl::{TCGETS2, TCSETS2, TCSETSF2, TCSETSW2, TIOCEXCL, TIOCNXCL}, + ioctl::{ + TCFLSH, TCGETS, TCGETS2, TCSBRK, TCSETS, TCSETS2, TCSETSF2, TCSETSW2, TCXONC, TIOCEXCL, + TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCNXCL, TIOCSPGRP, TIOCSWINSZ, + }, }; -// On MIPS, `TCSANOW` et al have `TCSETS` added to them, so we need it to -// subtract it out. -#[cfg(all( - feature = "termios", - any( - target_arch = "mips", - target_arch = "mips32r6", - target_arch = "mips64", - target_arch = "mips64r6" - ) -))] -pub(crate) use linux_raw_sys::ioctl::TCSETS; - // Define our own `uid_t` and `gid_t` if the kernel's versions are not 32-bit. #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))] pub(crate) type uid_t = u32; diff --git a/src/backend/linux_raw/termios/syscalls.rs b/src/backend/linux_raw/termios/syscalls.rs index 75eeb5edf..0b98ca17c 100644 --- a/src/backend/linux_raw/termios/syscalls.rs +++ b/src/backend/linux_raw/termios/syscalls.rs @@ -13,70 +13,77 @@ use crate::pid::Pid; #[cfg(all(feature = "alloc", feature = "procfs"))] use crate::procfs; use crate::termios::{ - Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes, QueueSelector, - SpecialCodeIndex, Termios, Winsize, + speed, Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes, + QueueSelector, SpecialCodeIndex, Termios, Winsize, }; #[cfg(all(feature = "alloc", feature = "procfs"))] use crate::{ffi::CStr, fs::FileType, path::DecInt}; use core::mem::MaybeUninit; -use linux_raw_sys::general::IBSHIFT; -use linux_raw_sys::ioctl::{ - TCFLSH, TCSBRK, TCXONC, TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCSPGRP, TIOCSWINSZ, -}; #[inline] pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result { unsafe { let mut result = MaybeUninit::::uninit(); - ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGWINSZ), &mut result))?; + ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGWINSZ), &mut result))?; Ok(result.assume_init()) } } #[inline] pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result { + // SAFETY: This invokes the `TCGETS` and optionally also `TCGETS2` ioctls. + // `TCGETS` doesn't initialize the `input_speed` and `output_speed` fields + // of the result, so in the case where we don't call `TCGETS2`, we infer + // their values from other fields and then manually initialize them (except + // on PowerPC where `TCGETS` does initialize those fields). unsafe { let mut result = MaybeUninit::::uninit(); - // QEMU's `TCGETS2` doesn't currently set `input_speed` or - // `output_speed` on PowerPC, so zero out the fields ourselves. - #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] + // Use the old `TCGETS`. Linux has supported `TCGETS2` since Linux + // 2.6.40, however glibc and musl haven't migrated to it yet, so it + // tends to get left out of seccomp sandboxes. It's also unsupported + // by WSL, and likely enough other things too, so we use the old + // `TCGETS` for compatibility. + ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS), &mut result))?; + + // If `BOTHER` is set for either the input or output, that means + // there's a custom speed, which means we really do need to use + // `TCGETS2`. Unless we're on PowerPC, where `TCGETS` is the same + // as `TCGETS2`. + #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))] { - result.write(core::mem::zeroed()); - } - - ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result))?; - - let result = result.assume_init(); - - // QEMU's `TCGETS2` doesn't currently set `input_speed` or - // `output_speed` on PowerPC, so set them manually if we can. - #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] - let result = { - use crate::termios::speed; - let mut result = result; - if result.output_speed == 0 && (result.control_modes.bits() & c::CBAUD) != c::BOTHER { - if let Some(output_speed) = speed::decode(result.control_modes.bits() & c::CBAUD) { - result.output_speed = output_speed; - } - } - if result.input_speed == 0 - && ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER - { - // For input speeds, `B0` is special-cased to mean the input - // speed is the same as the output speed. - if ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) == c::B0 { - result.input_speed = result.output_speed; - } else if let Some(input_speed) = - speed::decode((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) - { - result.input_speed = input_speed; - } + use core::ptr::{addr_of, addr_of_mut}; + + // At this point, `result` is initialized except for the + // `input_speed` and `output_speed` fields, so we don't form a + // reference yet. + let ptr = result.as_mut_ptr(); + + let control_modes = addr_of!((*ptr).control_modes).read(); + let encoded_out = control_modes.bits() & c::CBAUD; + let encoded_in = (control_modes.bits() & c::CIBAUD) >> c::IBSHIFT; + if encoded_out == c::BOTHER || encoded_in == c::BOTHER { + // Do a `TCGETS2` ioctl, which will fill in `input_speed` and + // `output_speed`. + ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), ptr))?; + } else { + // Infer `output_speed`. + let output_speed = speed::decode(encoded_out).unwrap(); + addr_of_mut!((*ptr).output_speed).write(output_speed); + + // Infer `input_speed`. For input speeds, `B0` is special-cased + // to mean the input speed is the same as the output speed. + let input_speed = if encoded_in == c::B0 { + output_speed + } else { + speed::decode(encoded_in).unwrap() + }; + addr_of_mut!((*ptr).input_speed).write(input_speed); } - result - }; + } - Ok(result) + // Now all the fields are set. + Ok(result.assume_init()) } } @@ -84,7 +91,7 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result { pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result { unsafe { let mut result = MaybeUninit::::uninit(); - ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPGRP), &mut result))?; + ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGPGRP), &mut result))?; let pid = result.assume_init(); // This doesn't appear to be documented, but it appears `tcsetpgrp` can @@ -104,19 +111,39 @@ pub(crate) fn tcsetattr( optional_actions: OptionalActions, termios: &Termios, ) -> io::Result<()> { - // Translate from `optional_actions` into an ioctl request code. On MIPS, - // `optional_actions` already has `TCGETS` added to it. - let request = linux_raw_sys::ioctl::TCSETS2 - + if cfg!(any( + // Similar to `tcgetattr`, use the old `TCSETS`, unless either input speed + // or output speed is custom, then we really have to use `TCSETS2`. + let encoded_out = termios.control_modes.bits() & c::CBAUD; + let encoded_in = (termios.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT; + let request = if encoded_out == c::BOTHER || encoded_in == c::BOTHER { + // Translate from `optional_actions` into a `TCGETS2` ioctl request + // code. On MIPS, `optional_actions` already has `TCGETS` added to it. + c::TCSETS2 + + if cfg!(any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" + )) { + optional_actions as u32 - c::TCSETS + } else { + optional_actions as u32 + } + } else { + // Translate from `optional_actions` into a `TCGETS` ioctl request + // code. On MIPS, `optional_actions` already has `TCGETS` added to it. + if cfg!(any( target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6" )) { - optional_actions as u32 - linux_raw_sys::ioctl::TCSETS - } else { optional_actions as u32 - }; + } else { + optional_actions as u32 + c::TCSETS + } + }; + unsafe { ret(syscall_readonly!( __NR_ioctl, @@ -129,12 +156,26 @@ pub(crate) fn tcsetattr( #[inline] pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> { - unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(0))) } + unsafe { + ret(syscall_readonly!( + __NR_ioctl, + fd, + c_uint(c::TCSBRK), + c_uint(0) + )) + } } #[inline] pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> { - unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(1))) } + unsafe { + ret(syscall_readonly!( + __NR_ioctl, + fd, + c_uint(c::TCSBRK), + c_uint(1) + )) + } } #[inline] @@ -143,7 +184,7 @@ pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io:: ret(syscall_readonly!( __NR_ioctl, fd, - c_uint(TCFLSH), + c_uint(c::TCFLSH), c_uint(queue_selector as u32) )) } @@ -155,7 +196,7 @@ pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> { ret(syscall_readonly!( __NR_ioctl, fd, - c_uint(TCXONC), + c_uint(c::TCXONC), c_uint(action as u32) )) } @@ -165,7 +206,7 @@ pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> { pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result { unsafe { let mut result = MaybeUninit::::uninit(); - ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGSID), &mut result))?; + ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGSID), &mut result))?; let pid = result.assume_init(); Ok(Pid::from_raw_unchecked(pid)) } @@ -177,7 +218,7 @@ pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<( ret(syscall_readonly!( __NR_ioctl, fd, - c_uint(TIOCSWINSZ), + c_uint(c::TIOCSWINSZ), by_ref(&winsize) )) } @@ -190,7 +231,7 @@ pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> { ret(syscall_readonly!( __NR_ioctl, fd, - c_uint(TIOCSPGRP), + c_uint(c::TIOCSPGRP), by_ref(&raw_pid) )) } @@ -200,13 +241,13 @@ pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> { /// integer speed value. #[inline] pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> { - let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); + let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); debug_assert_eq!(encoded_speed & !c::CBAUD, 0); termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD | c::CIBAUD); termios.control_modes |= - ControlModes::from_bits_retain(encoded_speed | (encoded_speed << IBSHIFT)); + ControlModes::from_bits_retain(encoded_speed | (encoded_speed << c::IBSHIFT)); termios.input_speed = arbitrary_speed; termios.output_speed = arbitrary_speed; @@ -218,7 +259,7 @@ pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Resu /// integer speed value. #[inline] pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> { - let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); + let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); debug_assert_eq!(encoded_speed & !c::CBAUD, 0); @@ -234,12 +275,12 @@ pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> i /// integer speed value. #[inline] pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> { - let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); + let encoded_speed = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER); debug_assert_eq!(encoded_speed & !c::CBAUD, 0); termios.control_modes -= ControlModes::from_bits_retain(c::CIBAUD); - termios.control_modes |= ControlModes::from_bits_retain(encoded_speed << IBSHIFT); + termios.control_modes |= ControlModes::from_bits_retain(encoded_speed << c::IBSHIFT); termios.input_speed = arbitrary_speed; diff --git a/src/termios/types.rs b/src/termios/types.rs index 0a45800f4..0772af297 100644 --- a/src/termios/types.rs +++ b/src/termios/types.rs @@ -810,15 +810,17 @@ pub mod speed { /// `u32`. /// /// On BSD platforms, integer speed values are already the same as their - /// encoded values, and on Linux platforms, we use `TCGETS2`/`TCSETS2` and - /// the `c_ispeed`/`c_ospeed` fields, except that on Linux on PowerPC on - /// QEMU, `TCGETS2`/`TCSETS2` don't set `c_ispeed`/`c_ospeed`. + /// encoded values. + /// + /// On Linux on PowerPC, `TCGETS`/`TCSETS` support the `c_ispeed` and + /// `c_ospeed` fields. + /// + /// On Linux on architectures other than PowerPC, `TCGETS`/`TCSETS` don't + /// support the `c_ispeed` and `c_ospeed` fields, so we have to fall back + /// to `TCGETS2`/`TCSETS2` to support them. #[cfg(not(any( bsd, - all( - linux_kernel, - not(any(target_arch = "powerpc", target_arch = "powerpc64")) - ) + all(linux_kernel, any(target_arch = "powerpc", target_arch = "powerpc64")) )))] pub(crate) const fn decode(encoded_speed: c::speed_t) -> Option { match encoded_speed { diff --git a/tests/termios/termios.rs b/tests/termios/termios.rs index 0e4071640..471a7bbae 100644 --- a/tests/termios/termios.rs +++ b/tests/termios/termios.rs @@ -112,9 +112,7 @@ fn test_termios_speeds() { assert_eq!(tio.output_speed(), speed::B50); tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); - #[allow(unused_variables)] let new_tio = tcgetattr(&pty).unwrap(); - assert_eq!(new_tio.input_speed(), speed::B50); assert_eq!(new_tio.output_speed(), speed::B50); @@ -124,9 +122,7 @@ fn test_termios_speeds() { assert_eq!(tio.output_speed(), speed::B134); tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); - #[allow(unused_variables)] let new_tio = tcgetattr(&pty).unwrap(); - assert_eq!(new_tio.input_speed(), speed::B134); assert_eq!(new_tio.output_speed(), speed::B134); @@ -134,22 +130,16 @@ fn test_termios_speeds() { // speeds. #[cfg(any(bsd, linux_kernel))] { - tio.set_input_speed(51).unwrap(); - tio.set_output_speed(51).unwrap(); - assert_eq!(tio.input_speed(), 51); - assert_eq!(tio.output_speed(), 51); - tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); - - #[allow(unused_variables)] - let new_tio = tcgetattr(&pty).unwrap(); - - // QEMU's `TCGETS2` doesn't currently set `input_speed` or - // `output_speed` on PowerPC, so we can't use them to set - // arbitrary speed values. - #[cfg(not(all(linux_kernel, any(target_arch = "powerpc", target_arch = "powerpc64"))))] - { - assert_eq!(new_tio.input_speed(), 51); - assert_eq!(new_tio.output_speed(), 51); + for custom_speed in [51, 31250, libc::B50 as _] { + tio.set_input_speed(custom_speed).unwrap(); + tio.set_output_speed(custom_speed).unwrap(); + assert_eq!(tio.input_speed(), custom_speed); + assert_eq!(tio.output_speed(), custom_speed); + tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); + + let new_tio = tcgetattr(&pty).unwrap(); + assert_eq!(new_tio.input_speed(), custom_speed); + assert_eq!(new_tio.output_speed(), custom_speed); } } @@ -162,12 +152,39 @@ fn test_termios_speeds() { assert_eq!(tio.output_speed(), speed::B110); tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); - #[allow(unused_variables)] let new_tio = tcgetattr(&pty).unwrap(); - assert_eq!(new_tio.input_speed(), speed::B75); assert_eq!(new_tio.output_speed(), speed::B110); } + + // These platforms are known to support arbitrary not-pre-defined-by-POSIX + // speeds that also differ between input and output. + #[cfg(any(bsd, linux_kernel))] + { + tio.set_output_speed(speed::B134).unwrap(); + for custom_speed in [51, 31250, libc::B50 as _] { + tio.set_input_speed(custom_speed).unwrap(); + assert_eq!(tio.input_speed(), custom_speed); + assert_eq!(tio.output_speed(), speed::B134); + tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); + + let new_tio = tcgetattr(&pty).unwrap(); + assert_eq!(new_tio.input_speed(), custom_speed); + assert_eq!(new_tio.output_speed(), speed::B134); + } + + tio.set_input_speed(speed::B150).unwrap(); + for custom_speed in [51, 31250, libc::B50 as _] { + tio.set_output_speed(custom_speed).unwrap(); + assert_eq!(tio.input_speed(), speed::B150); + assert_eq!(tio.output_speed(), custom_speed); + tcsetattr(&pty, OptionalActions::Now, &tio).unwrap(); + + let new_tio = tcgetattr(&pty).unwrap(); + assert_eq!(new_tio.input_speed(), speed::B150); + assert_eq!(new_tio.output_speed(), custom_speed); + } + } } #[test]