diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index a0e67dcb43b..5a6e69f0f8d 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -61,6 +61,7 @@ MinGW Minix MS-DOS MSDOS +msys NetBSD Novell Nushell diff --git a/Cargo.lock b/Cargo.lock index 0c93fd5f5d1..f302f31bcb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4374,6 +4374,7 @@ dependencies = [ "fluent", "rustix", "uucore", + "windows-sys 0.61.2", ] [[package]] diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index f54137ab30a..321ac2f082b 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -27,6 +27,12 @@ fluent = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["fs", "termios"] } +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Storage_FileSystem", + "Win32_Foundation", +] } + [[bin]] name = "tty" path = "src/main.rs" diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 78b8d55eea4..504e34a88b2 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -58,11 +58,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { writeln!(stdout, "{}", translate!("tty-not-a-tty")) }; #[cfg(target_os = "windows")] - let write_result = if std::io::stdin().is_terminal() { - writeln!(stdout, r"\\.\CON") - } else { - set_exit_code(1); - writeln!(stdout, "{}", translate!("tty-not-a-tty")) + let write_result = { + use std::os::windows::io::AsHandle; + let stdin = std::io::stdin(); + let stdin_handle = stdin.as_handle(); + if stdin_handle.is_terminal() { + writeln!( + stdout, + "{}", + file_name(stdin_handle).as_deref().unwrap_or(r"\\.\CON") + ) + } else { + set_exit_code(1); + writeln!(stdout, "{}", translate!("tty-not-a-tty")) + } }; if write_result.is_err() || stdout.flush().is_err() { @@ -74,6 +83,44 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +#[cfg(target_os = "windows")] +fn file_name(handle: std::os::windows::io::BorrowedHandle) -> Option { + // This code is adapted from rust's standard library + // https://github.com/rust-lang/rust/blob/0424cc16731e6141a18077f8ccde77ba148d9649/library/std/src/sys/io/is_terminal/windows.rs#L25 + use std::mem::MaybeUninit; + use std::os::windows::io::AsRawHandle; + use windows_sys::Win32::Foundation::MAX_PATH; + use windows_sys::Win32::Storage::FileSystem::{FileNameInfo, GetFileInformationByHandleEx}; + // Manually define FILE_NAME_INFO so we can more easily construct a stack buffer. + #[repr(C)] + #[allow(non_snake_case)] + struct FILE_NAME_INFO { + FileNameLength: u32, + FileName: [MaybeUninit; MAX_PATH as usize], + } + let mut name = FILE_NAME_INFO { + FileNameLength: 0, + FileName: [MaybeUninit::uninit(); MAX_PATH as usize], + }; + unsafe { + let result = GetFileInformationByHandleEx( + handle.as_raw_handle(), + FileNameInfo, + (&raw mut name).cast(), + size_of::() as u32, + ); + if result == 0 { + None + } else { + let name = name.FileName.get(..name.FileNameLength as usize / 2)?; + // SAFETY: all elements up to FileNameLength have been initialized + let name: &[u16] = &*(std::ptr::from_ref::<[MaybeUninit]>(name) as *const [u16]); + // This should never fail for a valid msys terminal because they use ASCII names. + String::from_utf16(name).ok() + } + } +} + pub fn uu_app() -> Command { let cmd = Command::new("tty") .version(uucore::crate_version!())