mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57:44 +00:00
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 <retep998@gmail.com>
This commit is contained in:
parent
1a2be4bf7f
commit
47f82f0de2
14 changed files with 153 additions and 139 deletions
12
Makefile
12
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
|
||||
|
|
3
deps/Cargo.toml
vendored
3
deps/Cargo.toml
vendored
|
@ -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"
|
||||
|
|
|
@ -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<String>, 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<libc::mode_t>, 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<libc::mode_t>, cmode: Option<&String>) -> Result<(), i32> {
|
||||
let path = CString::new(name).unwrap_or_else(|e| panic!("{}", e));
|
||||
match fmode {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
);
|
||||
|
||||
|
|
35
src/common/wide.rs
Normal file
35
src/common/wide.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the uutils coreutils package.
|
||||
*
|
||||
* (c) Peter Atashian <retep998@gmail.com>
|
||||
*
|
||||
* 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<u16>;
|
||||
fn to_wide_null(&self) -> Vec<u16>;
|
||||
}
|
||||
impl<T> ToWide for T where T: AsRef<OsStr> {
|
||||
fn to_wide(&self) -> Vec<u16> {
|
||||
self.as_ref().encode_wide().collect()
|
||||
}
|
||||
fn to_wide_null(&self) -> Vec<u16> {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
DEPLIBS += time
|
||||
DEPLIBS += time kernel32 winapi
|
||||
|
|
|
@ -25,7 +25,7 @@ macro_rules! silent_unwrap(
|
|||
($exp:expr) => (
|
||||
match $exp {
|
||||
Ok(_) => (),
|
||||
Err(_) => unsafe { ::util::libc::exit(1) }
|
||||
Err(_) => ::std::process::exit(1),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1 +1 @@
|
|||
DEPLIBS += regex regex-syntax crypto rand rustc-serialize time
|
||||
DEPLIBS += regex regex-syntax crypto rand rustc-serialize time winapi kernel32
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
1
src/sync/deps.mk
Normal file
1
src/sync/deps.mk
Normal file
|
@ -0,0 +1 @@
|
|||
DEPLIBS += winapi kernel32
|
119
src/sync/sync.rs
119
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<String> {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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<String>) -> i32 {
|
||||
let args = args_os().collect::<Vec<OsString>>();
|
||||
// This is completely disregarding valid windows paths that aren't valid unicode
|
||||
let args = args.iter().map(|a| a.to_bytes().unwrap()).collect::<Vec<&[u8]>>();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
1
src/whoami/deps.mk
Normal file
1
src/whoami/deps.mk
Normal file
|
@ -0,0 +1 @@
|
|||
DEPLIBS += winapi advapi32 kernel32
|
|
@ -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])
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue