From 47f82f0de296103d317aafdc0378149e73fc9e8f Mon Sep 17 00:00:00 2001 From: Peter Atashian Date: Sun, 19 Jul 2015 20:25:48 -0400 Subject: [PATCH] Various Windows fixes Improve handling of unicode on Windows Disable a few crates on Windows that abuse unix APIs too much Signed-off-by: Peter Atashian --- Makefile | 12 ++-- deps/Cargo.toml | 3 + src/chmod/chmod.rs | 20 ++++-- src/common/util.rs | 6 +- src/common/wide.rs | 35 ++++++++++ src/du/deps.mk | 2 +- src/fmt/fmt.rs | 2 +- src/hashsum/deps.mk | 2 +- src/mkdir/mkdir.rs | 22 ++++-- src/sync/deps.mk | 1 + src/sync/sync.rs | 119 +++++++++++---------------------- src/test/test.rs | 42 ++++++------ src/whoami/deps.mk | 1 + src/whoami/platform/windows.rs | 25 +++---- 14 files changed, 153 insertions(+), 139 deletions(-) create mode 100644 src/common/wide.rs create mode 100644 src/sync/deps.mk create mode 100644 src/whoami/deps.mk diff --git a/Makefile b/Makefile index 23e7c3816..9aa9d139d 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,6 @@ PROGS := \ dirname \ echo \ env \ - du \ expand \ factor \ false \ @@ -62,7 +61,6 @@ PROGS := \ hashsum \ ln \ mkdir \ - mv \ nl \ nproc \ od \ @@ -85,13 +83,11 @@ PROGS := \ tac \ tee \ test \ - touch \ tr \ true \ truncate \ tsort \ unexpand \ - unlink \ uniq \ wc \ yes \ @@ -101,6 +97,7 @@ PROGS := \ UNIX_PROGS := \ chroot \ + du \ groups \ hostid \ hostname \ @@ -108,12 +105,15 @@ UNIX_PROGS := \ kill \ logname \ mkfifo \ + mv \ nice \ nohup \ stdbuf \ timeout \ + touch \ tty \ uname \ + unlink \ uptime \ users @@ -256,6 +256,10 @@ define DEP_BUILD DEP_$(1): ifeq ($(1),crypto) cd $(BASEDIR)/deps && $(CARGO) build --package rust-crypto --release +else ifeq ($(1),kernel32) + cd $(BASEDIR)/deps && $(CARGO) build --package kernel32-sys --release +else ifeq ($(1),advapi32) + cd $(BASEDIR)/deps && $(CARGO) build --package advapi32-sys --release else cd $(BASEDIR)/deps && $(CARGO) build --package $(1) --release endif diff --git a/deps/Cargo.toml b/deps/Cargo.toml index 8d77d6907..e220f7ebd 100644 --- a/deps/Cargo.toml +++ b/deps/Cargo.toml @@ -17,3 +17,6 @@ rust-crypto = "0.2.31" rustc-serialize = "0.3.15" time = "0.1.26" unicode-width = "0.1.1" +winapi = "0.2" +advapi32-sys = "0.1" +kernel32-sys = "0.1" diff --git a/src/chmod/chmod.rs b/src/chmod/chmod.rs index 269093d2d..f41c5bba1 100644 --- a/src/chmod/chmod.rs +++ b/src/chmod/chmod.rs @@ -124,17 +124,17 @@ fn verify_mode(modes: &str) -> Result<(), String> { #[cfg(windows)] #[inline] // XXX: THIS IS NOT TESTED!!! -fn verify_mode(mode: &str) -> Result<(), String> { +fn verify_mode(modes: &str) -> Result<(), String> { let re: regex::Regex = Regex::new(r"^[ugoa]*(?:[-+=](?:([rwxXst]*)|[ugo]))+|[-+=]?([0-7]+)$").unwrap(); for mode in modes.split(',') { match re.captures(mode) { Some(cap) => { - let symbols = cap.at(1); - let numbers = cap.at(2); + let symbols = cap.at(1).unwrap(); + let numbers = cap.at(2).unwrap(); if symbols.contains("s") || symbols.contains("t") { - return Err("The 's' and 't' modes are not supported on Windows".to_string()); - } else if numbers.len() >= 4 && numbers.slice_to(num_len - 3).find(|ch| ch != '0').is_some() { - return Err("Setuid, setgid, and sticky modes are not supported on Windows".to_string()); + return Err("The 's' and 't' modes are not supported on Windows".into()); + } else if numbers.len() >= 4 && numbers[..numbers.len() - 3].find(|ch| ch != '0').is_some() { + return Err("Setuid, setgid, and sticky modes are not supported on Windows".into()); } } None => return Err(format!("invalid mode '{}'", mode)) @@ -191,6 +191,14 @@ fn chmod(files: Vec, changes: bool, quiet: bool, verbose: bool, preserve r } +#[cfg(windows)] +fn chmod_file(file: &Path, name: &str, changes: bool, quiet: bool, verbose: bool, fmode: Option, cmode: Option<&String>) -> Result<(), i32> { + // chmod is useless on Windows + // it doesn't set any permissions at all + // instead it just sets the readonly attribute on the file + Err(0) +} +#[cfg(unix)] fn chmod_file(file: &Path, name: &str, changes: bool, quiet: bool, verbose: bool, fmode: Option, cmode: Option<&String>) -> Result<(), i32> { let path = CString::new(name).unwrap_or_else(|e| panic!("{}", e)); match fmode { diff --git a/src/common/util.rs b/src/common/util.rs index 690848ff8..d333def3e 100644 --- a/src/common/util.rs +++ b/src/common/util.rs @@ -7,8 +7,6 @@ * file that was distributed with this source code. */ -extern crate libc; - macro_rules! show_error( ($($args:tt)+) => ({ pipe_write!(&mut ::std::io::stderr(), "{}: error: ", ::NAME); @@ -46,14 +44,14 @@ macro_rules! eprintln( macro_rules! crash( ($exitcode:expr, $($args:tt)+) => ({ show_error!($($args)+); - unsafe { ::util::libc::exit($exitcode as ::util::libc::c_int); } + ::std::process::exit($exitcode) }) ); #[macro_export] macro_rules! exit( ($exitcode:expr) => ({ - unsafe { ::util::libc::exit($exitcode); } + ::std::process::exit($exitcode) }) ); diff --git a/src/common/wide.rs b/src/common/wide.rs new file mode 100644 index 000000000..189e84438 --- /dev/null +++ b/src/common/wide.rs @@ -0,0 +1,35 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Peter Atashian + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use std::ffi::{OsStr, OsString}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +pub trait ToWide { + fn to_wide(&self) -> Vec; + fn to_wide_null(&self) -> Vec; +} +impl ToWide for T where T: AsRef { + fn to_wide(&self) -> Vec { + self.as_ref().encode_wide().collect() + } + fn to_wide_null(&self) -> Vec { + self.as_ref().encode_wide().chain(Some(0)).collect() + } +} +pub trait FromWide { + fn from_wide(wide: &[u16]) -> Self; + fn from_wide_null(wide: &[u16]) -> Self; +} +impl FromWide for String { + fn from_wide(wide: &[u16]) -> String { + OsString::from_wide(wide).to_string_lossy().into_owned() + } + fn from_wide_null(wide: &[u16]) -> String { + let len = wide.iter().take_while(|&&c| c != 0).count(); + OsString::from_wide(&wide[..len]).to_string_lossy().into_owned() + } +} \ No newline at end of file diff --git a/src/du/deps.mk b/src/du/deps.mk index b6534caec..bc0c2cf05 100644 --- a/src/du/deps.mk +++ b/src/du/deps.mk @@ -1 +1 @@ -DEPLIBS += time +DEPLIBS += time kernel32 winapi diff --git a/src/fmt/fmt.rs b/src/fmt/fmt.rs index a665ae7b4..9d2b2df2e 100644 --- a/src/fmt/fmt.rs +++ b/src/fmt/fmt.rs @@ -25,7 +25,7 @@ macro_rules! silent_unwrap( ($exp:expr) => ( match $exp { Ok(_) => (), - Err(_) => unsafe { ::util::libc::exit(1) } + Err(_) => ::std::process::exit(1), } ) ); diff --git a/src/hashsum/deps.mk b/src/hashsum/deps.mk index 77a7f879f..83178fa58 100644 --- a/src/hashsum/deps.mk +++ b/src/hashsum/deps.mk @@ -1 +1 @@ -DEPLIBS += regex regex-syntax crypto rand rustc-serialize time +DEPLIBS += regex regex-syntax crypto rand rustc-serialize time winapi kernel32 diff --git a/src/mkdir/mkdir.rs b/src/mkdir/mkdir.rs index d70cee5c9..3cffc1f0d 100644 --- a/src/mkdir/mkdir.rs +++ b/src/mkdir/mkdir.rs @@ -136,13 +136,21 @@ fn mkdir(path: &Path, mode: u16, verbose: bool) -> i32 { show_info!("created directory '{}'", path.display()); } - let directory = CString::new(path.as_os_str().to_str().unwrap()).unwrap_or_else(|e| crash!(1, "{}", e)); - let mode = mode as libc::mode_t; + #[cfg(unix)] + fn chmod(path: &Path, mode: u16) -> i32 { + let directory = CString::new(path.as_os_str().to_str().unwrap()).unwrap_or_else(|e| crash!(1, "{}", e)); + let mode = mode as libc::mode_t; - if unsafe { libc::chmod(directory.as_ptr(), mode) } != 0 { - show_info!("{}: errno {}", path.display(), Error::last_os_error().raw_os_error().unwrap()); - return 1; + if unsafe { libc::chmod(directory.as_ptr(), mode) } != 0 { + show_info!("{}: errno {}", path.display(), Error::last_os_error().raw_os_error().unwrap()); + return 1; + } + 0 } - - 0 + #[cfg(windows)] + fn chmod(path: &Path, mode: u16) -> i32 { + // chmod on Windows only sets the readonly flag, which isn't even honored on directories + 0 + } + chmod(path, mode) } diff --git a/src/sync/deps.mk b/src/sync/deps.mk new file mode 100644 index 000000000..bd85e154d --- /dev/null +++ b/src/sync/deps.mk @@ -0,0 +1 @@ +DEPLIBS += winapi kernel32 diff --git a/src/sync/sync.rs b/src/sync/sync.rs index ed5fb22eb..af2b6166c 100644 --- a/src/sync/sync.rs +++ b/src/sync/sync.rs @@ -35,103 +35,62 @@ mod platform { #[cfg(windows)] mod platform { - pub use super::libc; - use std::{mem, string}; - use std::ptr::null; + extern crate winapi; + extern crate kernel32; + #[path = "../../common/wide.rs"] mod wide; + use std::{mem}; + use std::fs::OpenOptions; + use std::io::{Write}; + use std::os::windows::prelude::*; + use self::wide::{FromWide, ToWide}; - extern "system" { - fn CreateFileA(lpFileName: *const libc::c_char, - dwDesiredAccess: libc::uint32_t, - dwShareMode: libc::uint32_t, - lpSecurityAttributes: *const libc::c_void, // *LPSECURITY_ATTRIBUTES - dwCreationDisposition: libc::uint32_t, - dwFlagsAndAttributes: libc::uint32_t, - hTemplateFile: *const libc::c_void) -> *const libc::c_void; - fn GetDriveTypeA(lpRootPathName: *const libc::c_char) -> libc::c_uint; - fn GetLastError() -> libc::uint32_t; - fn FindFirstVolumeA(lpszVolumeName: *mut libc::c_char, - cchBufferLength: libc::uint32_t) -> *const libc::c_void; - fn FindNextVolumeA(hFindVolume: *const libc::c_void, - lpszVolumeName: *mut libc::c_char, - cchBufferLength: libc::uint32_t) -> libc::c_int; - fn FindVolumeClose(hFindVolume: *const libc::c_void) -> libc::c_int; - fn FlushFileBuffers(hFile: *const libc::c_void) -> libc::c_int; - } - - #[allow(unused_unsafe)] unsafe fn flush_volume(name: &str) { - let name_buffer = name.to_c_str().as_ptr(); - if 0x00000003 == GetDriveTypeA(name_buffer) { // DRIVE_FIXED + let name_wide = name.to_wide_null(); + if kernel32::GetDriveTypeW(name_wide.as_ptr()) == winapi::DRIVE_FIXED { let sliced_name = &name[..name.len() - 1]; // eliminate trailing backslash - let sliced_name_buffer = sliced_name.to_c_str().as_ptr(); - match CreateFileA(sliced_name_buffer, - 0xC0000000, // GENERIC_WRITE - 0x00000003, // FILE_SHARE_WRITE, - null(), - 0x00000003, // OPEN_EXISTING - 0, - null()) { - -1 => { // INVALID_HANDLE_VALUE - crash!(GetLastError(), "failed to create volume handle"); - } - handle => { - if FlushFileBuffers(handle) == 0 { - crash!(GetLastError(), "failed to flush file buffer"); - } - } + match OpenOptions::new().write(true).open(sliced_name) { + Ok(file) => if kernel32::FlushFileBuffers(file.as_raw_handle()) == 0 { + crash!(kernel32::GetLastError() as i32, "failed to flush file buffer"); + }, + Err(e) => crash!(e.raw_os_error().unwrap_or(1), "failed to create volume handle") } } } - #[allow(unused_unsafe)] - unsafe fn find_first_volume() -> (String, *const libc::c_void) { - let mut name: [libc::c_char; 260] = mem::uninitialized(); // MAX_PATH - match FindFirstVolumeA(name.as_mut_ptr(), - name.len() as libc::uint32_t) { - -1 => { // INVALID_HANDLE_VALUE - crash!(GetLastError(), "failed to find first volume"); - } - handle => { - (string::raw::from_buf(name.as_ptr() as *const u8), handle) - } + unsafe fn find_first_volume() -> (String, winapi::HANDLE) { + let mut name: [winapi::WCHAR; winapi::MAX_PATH] = mem::uninitialized(); + let handle = kernel32::FindFirstVolumeW(name.as_mut_ptr(), name.len() as winapi::DWORD); + if handle == winapi::INVALID_HANDLE_VALUE { + crash!(kernel32::GetLastError() as i32, "failed to find first volume"); } + (String::from_wide_null(&name), handle) } - #[allow(unused_unsafe)] unsafe fn find_all_volumes() -> Vec { - match find_first_volume() { - (first_volume, next_volume_handle) => { - let mut volumes = vec![first_volume]; - loop { - let mut name: [libc::c_char; 260] = mem::uninitialized(); // MAX_PATH - match FindNextVolumeA(next_volume_handle, - name.as_mut_ptr(), - name.len() as libc::uint32_t) { - 0 => { - match GetLastError() { - 0x12 => { // ERROR_NO_MORE_FILES - FindVolumeClose(next_volume_handle); // ignore FindVolumeClose() failures - break; - } - err => { - crash!(err, "failed to find next volume"); - } - } - } - _ => { - volumes.push(string::raw::from_buf(name.as_ptr() as *const u8)); - } - } + let (first_volume, next_volume_handle) = find_first_volume(); + let mut volumes = vec![first_volume]; + loop { + let mut name: [winapi::WCHAR; winapi::MAX_PATH] = mem::uninitialized(); + if kernel32::FindNextVolumeW( + next_volume_handle, name.as_mut_ptr(), name.len() as winapi::DWORD + ) == 0 { + match kernel32::GetLastError() { + winapi::ERROR_NO_MORE_FILES => { + kernel32::FindVolumeClose(next_volume_handle); + return volumes + }, + err => crash!(err as i32, "failed to find next volume"), } - volumes + } else { + volumes.push(String::from_wide_null(&name)); } } } - pub unsafe fn do_sync() -> int { + pub unsafe fn do_sync() -> isize { let volumes = find_all_volumes(); - for vol in volumes.iter() { - flush_volume(&vol); + for vol in &volumes { + flush_volume(vol); } 0 } diff --git a/src/test/test.rs b/src/test/test.rs index f9ef059e7..f1d587c75 100644 --- a/src/test/test.rs +++ b/src/test/test.rs @@ -20,8 +20,10 @@ use std::str::{from_utf8}; static NAME: &'static str = "test"; // TODO: decide how to handle non-UTF8 input for all the utils +// Definitely don't use [u8], try keeping it as OsStr or OsString instead pub fn uumain(_: Vec) -> i32 { let args = args_os().collect::>(); + // This is completely disregarding valid windows paths that aren't valid unicode let args = args.iter().map(|a| a.to_bytes().unwrap()).collect::>(); if args.len() == 0 { return 2; @@ -382,32 +384,26 @@ fn path(path: &[u8], cond: PathCondition) -> bool { #[cfg(windows)] fn path(path: &[u8], cond: PathCondition) -> bool { - use std::old_io::{TypeFile, TypeDirectory, TypeBlockSpecial, TypeNamedPipe}; - use std::old_io::fs::{stat}; - use std::old_path::{Path}; - - let path = match Path::new_opt(path) { - Some(p) => p, - None => return false, - }; - let stat = match stat(&path) { + use std::fs::metadata; + let path = from_utf8(path).unwrap(); + let stat = match metadata(path) { Ok(s) => s, _ => return false, }; match cond { - BlockSpecial => stat.kind == TypeBlockSpecial, - CharacterSpecial => false, - Directory => stat.kind == TypeDirectory, - Exists => true, - Regular => stat.kind == TypeFile, - GroupIDFlag => false, - SymLink => false, - FIFO => stat.kind == TypeNamedPipe, - Readable => false, // TODO - Socket => false, // TODO? - NonEmpty => stat.size > 0, - UserIDFlag => false, - Writable => false, // TODO - Executable => false, // TODO + PathCondition::BlockSpecial => false, + PathCondition::CharacterSpecial => false, + PathCondition::Directory => stat.is_dir(), + PathCondition::Exists => true, + PathCondition::Regular => stat.is_file(), + PathCondition::GroupIDFlag => false, + PathCondition::SymLink => false, + PathCondition::FIFO => false, + PathCondition::Readable => false, // TODO + PathCondition::Socket => false, + PathCondition::NonEmpty => stat.len() > 0, + PathCondition::UserIDFlag => false, + PathCondition::Writable => false, // TODO + PathCondition::Executable => false, // TODO } } diff --git a/src/whoami/deps.mk b/src/whoami/deps.mk new file mode 100644 index 000000000..dab27f417 --- /dev/null +++ b/src/whoami/deps.mk @@ -0,0 +1 @@ +DEPLIBS += winapi advapi32 kernel32 diff --git a/src/whoami/platform/windows.rs b/src/whoami/platform/windows.rs index 927ba4d74..9676d6d18 100644 --- a/src/whoami/platform/windows.rs +++ b/src/whoami/platform/windows.rs @@ -7,21 +7,22 @@ * file that was distributed with this source code. */ -use ::libc; +extern crate winapi; +extern crate advapi32; + +#[path = "../../common/wide.rs"] #[macro_use] mod wide; + use std::mem; use std::io::Write; +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use self::wide::FromWide; -extern "system" { - pub fn GetUserNameA(out: *mut libc::c_char, len: *mut libc::uint32_t) -> libc::uint8_t; -} - -#[allow(unused_unsafe)] pub unsafe fn getusername() -> String { - // usernames can only be up to 104 characters in windows - let mut buffer: [libc::c_char; 105] = mem::uninitialized(); - - if !GetUserNameA(buffer.as_mut_ptr(), &mut (buffer.len() as libc::uint32_t)) == 0 { - crash!(1, "username is too long"); + let mut buffer: [winapi::WCHAR; winapi::UNLEN as usize + 1] = mem::uninitialized(); + let mut len = buffer.len() as winapi::DWORD; + if advapi32::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { + crash!(1, "failed to get username"); } - String::from_utf8_lossy(::std::ffi::CStr::from_ptr(buffer.as_ptr()).to_bytes()).to_string() + String::from_wide(&buffer[..len as usize - 1]) }