Slight sys cleanup & Fix more sys::unix bugs

This commit is contained in:
Leonard Hecker
2025-03-23 00:33:44 +01:00
parent df35ee1283
commit 70b8df2a58
3 changed files with 175 additions and 149 deletions

View File

@@ -8,22 +8,27 @@ use std::ptr::{null, null_mut};
use std::thread;
use std::time;
pub fn preferred_languages() -> Vec<String> {
let mut locales = Vec::new();
for key in ["LANGUAGE", "LC_ALL", "LANG"] {
if let Ok(val) = std::env::var(key) {
locales.extend(
val.split(':')
.filter(|val| !val.is_empty())
.map(String::from),
);
}
}
locales
struct State {
stdin: libc::c_int,
stdin_flags: libc::c_int,
stdout: libc::c_int,
stdout_initial_termios: libc::termios,
inject_resize: bool,
// Buffer for incomplete UTF-8 sequences (max 4 bytes needed)
utf8_buf: [u8; 4],
utf8_len: usize,
}
static mut STATE: State = State {
stdin: libc::STDIN_FILENO,
stdin_flags: 0,
stdout: libc::STDOUT_FILENO,
stdout_initial_termios: unsafe { mem::zeroed() },
inject_resize: false,
utf8_buf: [0; 4],
utf8_len: 0,
};
extern "C" fn sigwinch_handler(_: libc::c_int) {
unsafe {
STATE.inject_resize = true;
@@ -40,6 +45,8 @@ pub fn init() -> apperr::Result<()> {
STATE.stdout = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_WRONLY))?;
}
STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?;
check_int_return(libc::tcgetattr(
STATE.stdout,
&raw mut STATE.stdout_initial_termios,
@@ -108,25 +115,6 @@ fn get_window_size() -> (u16, u16) {
(w, h)
}
struct State {
stdin: libc::c_int,
stdout: libc::c_int,
stdout_initial_termios: libc::termios,
inject_resize: bool,
// Buffer for incomplete UTF-8 sequences (max 4 bytes needed)
utf8_buf: [u8; 4],
utf8_len: usize,
}
static mut STATE: State = State {
stdin: libc::STDIN_FILENO,
stdout: libc::STDOUT_FILENO,
stdout_initial_termios: unsafe { mem::zeroed() },
inject_resize: false,
utf8_buf: [0; 4],
utf8_len: 0,
};
/// Reads from stdin.
///
/// Returns `None` if there was an error reading from stdin.
@@ -134,6 +122,13 @@ static mut STATE: State = State {
/// Otherwise, it returns the read, non-empty string.
pub fn read_stdin(timeout: time::Duration) -> Option<String> {
unsafe {
let mut read_more = true;
let mut read_poll = false;
if STATE.inject_resize {
read_poll = true;
}
if timeout != time::Duration::MAX {
let mut pollfd = libc::pollfd {
fd: STATE.stdin,
@@ -148,62 +143,78 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
if ret < 0 {
return None;
}
read_poll = true; // there is a timeout -> don't block in read()
if ret == 0 {
return Some(String::new());
read_more = false; // if timeout -> don't read
}
};
let mut input = String::new();
if read_more {
let mut buf = Vec::with_capacity(1024);
if STATE.utf8_len != 0 {
buf.extend_from_slice(&STATE.utf8_buf[..STATE.utf8_len]);
STATE.utf8_len = 0;
}
let is_nonblock = (STATE.stdin_flags & libc::O_NONBLOCK) != 0;
if read_poll != is_nonblock {
STATE.stdin_flags ^= libc::O_NONBLOCK;
let _ = libc::fcntl(STATE.stdin, libc::F_SETFL, STATE.stdin_flags);
}
loop {
let spare = buf.spare_capacity_mut();
let ret = libc::read(STATE.stdin, spare.as_mut_ptr() as *mut _, spare.len());
if ret > 0 {
buf.set_len(buf.len() + ret as usize);
break;
}
if ret == 0 {
return None;
}
match *libc::__errno_location() {
libc::EINTR => continue,
libc::EAGAIN => break,
_ => return None,
}
}
if !buf.is_empty() {
// We only need to check the last 3 bytes for UTF-8 continuation bytes,
// because we should be able to assume that any 4 byte sequence is complete.
let lim = buf.len().saturating_sub(3);
let mut off = buf.len() - 1;
// Find the start of the last potentially incomplete UTF-8 sequence.
while off > lim && buf[off] & 0b1100_0000 == 0b1000_0000 {
off -= 1;
}
let seq_len = match buf[off] {
b if b & 0b1000_0000 == 0 => 1,
b if b & 0b1110_0000 == 0b1100_0000 => 2,
b if b & 0b1111_0000 == 0b1110_0000 => 3,
b if b & 0b1111_1000 == 0b1111_0000 => 4,
// If the lead byte we found isn't actually one, we don't cache it.
// `string_from_utf8_lossy_owned` will replace it with U+FFFD.
_ => 0,
};
// Cache incomplete sequence if any.
if off + seq_len > buf.len() {
STATE.utf8_len = buf.len() - off;
STATE.utf8_buf[..STATE.utf8_len].copy_from_slice(&buf[off..]);
buf.truncate(off);
}
}
input = helpers::string_from_utf8_lossy_owned(buf);
}
let mut buf = Vec::with_capacity(1024);
if STATE.utf8_len != 0 {
buf.extend_from_slice(&STATE.utf8_buf[..STATE.utf8_len]);
STATE.utf8_len = 0;
}
// Read new data
loop {
let spare = buf.spare_capacity_mut();
let ret = libc::read(STATE.stdin, spare.as_mut_ptr() as *mut _, spare.len());
if ret > 0 {
buf.set_len(buf.len() + ret as usize);
break;
}
if ret == 0 || *libc::__errno_location() != libc::EINTR {
return None;
}
}
if !buf.is_empty() {
// We only need to check the last 3 bytes for UTF-8 continuation bytes,
// because we should be able to assume that any 4 byte sequence is complete.
let lim = buf.len().saturating_sub(3);
let mut off = buf.len() - 1;
// Find the start of the last potentially incomplete UTF-8 sequence.
while off > lim && buf[off] & 0b1100_0000 == 0b1000_0000 {
off -= 1;
}
let seq_len = match buf[off] {
b if b & 0b1000_0000 == 0 => 1,
b if b & 0b1110_0000 == 0b1100_0000 => 2,
b if b & 0b1111_0000 == 0b1110_0000 => 3,
b if b & 0b1111_1000 == 0b1111_0000 => 4,
// If the lead byte we found isn't actually one, we don't cache it.
// `string_from_utf8_lossy_owned` will replace it with U+FFFD.
_ => 0,
};
// Cache incomplete sequence if any.
if off + seq_len > buf.len() {
STATE.utf8_len = buf.len() - off;
STATE.utf8_buf[..STATE.utf8_len].copy_from_slice(&buf[off..]);
buf.truncate(off);
}
}
let mut input = helpers::string_from_utf8_lossy_owned(buf);
if STATE.inject_resize {
STATE.inject_resize = false;
let (w, h) = get_window_size();
@@ -310,6 +321,22 @@ pub unsafe fn load_icu() -> apperr::Result<*mut c_void> {
unsafe { load_library(c"icu.dll") }
}
pub fn preferred_languages() -> Vec<String> {
let mut locales = Vec::new();
for key in ["LANGUAGE", "LC_ALL", "LANG"] {
if let Ok(val) = std::env::var(key) {
locales.extend(
val.split(':')
.filter(|val| !val.is_empty())
.map(String::from),
);
}
}
locales
}
#[inline]
pub fn io_error_to_apperr(err: std::io::Error) -> apperr::Error {
unsafe { apperr::Error::new(err.raw_os_error().unwrap_or(0) as u32) }

View File

@@ -19,51 +19,6 @@ use windows_sys::Win32::System::Memory;
use windows_sys::Win32::System::Threading;
use windows_sys::w;
pub fn preferred_languages() -> Vec<String> {
unsafe {
const LEN: usize = 256;
let mut lang_num = 0;
let mut lang_buf = [const { MaybeUninit::<u16>::uninit() }; LEN];
let mut lang_buf_len = lang_buf.len() as u32;
if Globalization::GetUserPreferredUILanguages(
Globalization::MUI_LANGUAGE_NAME,
&mut lang_num,
lang_buf[0].as_mut_ptr(),
&mut lang_buf_len,
) == 0
|| lang_num == 0
{
return Vec::new();
}
// Drop the terminating double-null character.
lang_buf_len = lang_buf_len.saturating_sub(1);
let mut lang_buf_utf8 = [const { MaybeUninit::<u8>::uninit() }; 3 * LEN];
let lang_buf_utf8_len = Globalization::WideCharToMultiByte(
Globalization::CP_UTF8,
0,
lang_buf[0].as_mut_ptr(),
lang_buf_len as i32,
lang_buf_utf8[0].as_mut_ptr(),
lang_buf_utf8.len() as i32,
null(),
null_mut(),
);
if lang_buf_utf8_len == 0 {
return Vec::new();
}
let result = helpers::str_from_raw_parts_mut(
lang_buf_utf8[0].as_mut_ptr(),
lang_buf_utf8_len as usize,
);
result.make_ascii_lowercase();
result.split_terminator('\0').map(String::from).collect()
}
}
type ReadConsoleInputExW = unsafe extern "system" fn(
h_console_input: Foundation::HANDLE,
lp_buffer: *mut Console::INPUT_RECORD,
@@ -72,6 +27,16 @@ type ReadConsoleInputExW = unsafe extern "system" fn(
w_flags: u16,
) -> Foundation::BOOL;
unsafe extern "system" fn read_console_input_ex_placeholder(
_: Foundation::HANDLE,
_: *mut Console::INPUT_RECORD,
_: u32,
_: *mut u32,
_: u16,
) -> Foundation::BOOL {
panic!();
}
const CONSOLE_READ_NOWAIT: u16 = 0x0002;
struct State {
@@ -100,16 +65,6 @@ static mut STATE: State = State {
wants_exit: false,
};
unsafe extern "system" fn read_console_input_ex_placeholder(
_: Foundation::HANDLE,
_: *mut Console::INPUT_RECORD,
_: u32,
_: *mut u32,
_: u16,
) -> Foundation::BOOL {
panic!();
}
extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL {
unsafe {
STATE.wants_exit = true;
@@ -235,13 +190,6 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
unsafe { STATE.inject_resize = false };
}
if unsafe { STATE.leading_surrogate } != 0 {
utf16_buf[0] = MaybeUninit::new(unsafe { STATE.leading_surrogate });
utf16_buf_len = 1;
input_buf_cap -= 1;
unsafe { STATE.leading_surrogate = 0 };
}
if timeout != time::Duration::MAX {
read_poll = true;
let wait_result =
@@ -260,6 +208,13 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
// This loops exists, just in case there's events in the input buffer that we aren't interested in.
// It should be rare for this to loop.
while read_more {
if unsafe { STATE.leading_surrogate } != 0 {
utf16_buf[0] = MaybeUninit::new(unsafe { STATE.leading_surrogate });
utf16_buf_len = 1;
input_buf_cap -= 1;
unsafe { STATE.leading_surrogate = 0 };
}
let input = unsafe {
// If we had a `inject_resize`, we don't want to block indefinitely for other pending input on startup,
// but are still interested in any other pending input that may be waiting for us.
@@ -476,6 +431,51 @@ pub unsafe fn load_icu() -> apperr::Result<Foundation::HMODULE> {
unsafe { load_library(w!("icu.dll")) }
}
pub fn preferred_languages() -> Vec<String> {
unsafe {
const LEN: usize = 256;
let mut lang_num = 0;
let mut lang_buf = [const { MaybeUninit::<u16>::uninit() }; LEN];
let mut lang_buf_len = lang_buf.len() as u32;
if Globalization::GetUserPreferredUILanguages(
Globalization::MUI_LANGUAGE_NAME,
&mut lang_num,
lang_buf[0].as_mut_ptr(),
&mut lang_buf_len,
) == 0
|| lang_num == 0
{
return Vec::new();
}
// Drop the terminating double-null character.
lang_buf_len = lang_buf_len.saturating_sub(1);
let mut lang_buf_utf8 = [const { MaybeUninit::<u8>::uninit() }; 3 * LEN];
let lang_buf_utf8_len = Globalization::WideCharToMultiByte(
Globalization::CP_UTF8,
0,
lang_buf[0].as_mut_ptr(),
lang_buf_len as i32,
lang_buf_utf8[0].as_mut_ptr(),
lang_buf_utf8.len() as i32,
null(),
null_mut(),
);
if lang_buf_utf8_len == 0 {
return Vec::new();
}
let result = helpers::str_from_raw_parts_mut(
lang_buf_utf8[0].as_mut_ptr(),
lang_buf_utf8_len as usize,
);
result.make_ascii_lowercase();
result.split_terminator('\0').map(String::from).collect()
}
}
#[cold]
fn get_last_error() -> apperr::Error {
unsafe { gle_to_apperr(Foundation::GetLastError()) }