mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-01 05:27:45 +00:00
Merge branch 'main' into refactor/add_nix_error_auto_conversion
This commit is contained in:
commit
74624b27f1
28 changed files with 717 additions and 479 deletions
314
Cargo.lock
generated
314
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -74,6 +74,13 @@ skip = [
|
||||||
{ name = "clap", version = "=3.2.22" },
|
{ name = "clap", version = "=3.2.22" },
|
||||||
# bindgen (via selinux-sys & fts-sys)
|
# bindgen (via selinux-sys & fts-sys)
|
||||||
{ name = "clap_lex", version = "=0.2.4" },
|
{ name = "clap_lex", version = "=0.2.4" },
|
||||||
|
# windows-sys (via terminal_size, crossterm -> parking_lot, notify -> filetime)
|
||||||
|
{ name = "windows-sys", version = "=0.36.1" },
|
||||||
|
{ name = "windows_aarch64_msvc", version = "=0.36.1" },
|
||||||
|
{ name = "windows_i686_gnu", version = "=0.36.1" },
|
||||||
|
{ name = "windows_i686_msvc", version = "=0.36.1" },
|
||||||
|
{ name = "windows_x86_64_gnu", version = "=0.36.1" },
|
||||||
|
{ name = "windows_x86_64_msvc", version = "=0.36.1" },
|
||||||
]
|
]
|
||||||
# spell-checker: enable
|
# spell-checker: enable
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,6 @@ selinux = { version="0.3", optional=true }
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
winapi = { version="0.3", features=["fileapi"] }
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xattr="0.2.3"
|
xattr="0.2.3"
|
||||||
exacl= { version = "0.9.0", optional=true }
|
exacl= { version = "0.9.0", optional=true }
|
||||||
|
|
364
src/uu/cp/src/copydir.rs
Normal file
364
src/uu/cp/src/copydir.rs
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
|
// * file that was distributed with this source code.
|
||||||
|
// spell-checker:ignore TODO canonicalizes direntry pathbuf symlinked
|
||||||
|
//! Recursively copy the contents of a directory.
|
||||||
|
//!
|
||||||
|
//! See the [`copy_directory`] function for more information.
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf, StripPrefixError};
|
||||||
|
|
||||||
|
use uucore::display::Quotable;
|
||||||
|
use uucore::error::UIoError;
|
||||||
|
use uucore::fs::{canonicalize, FileInformation, MissingHandling, ResolveMode};
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
copy_attributes, copy_file, copy_link, preserve_hardlinks, CopyResult, Error, Options,
|
||||||
|
TargetSlice,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Ensure a Windows path starts with a `\\?`.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn adjust_canonicalization(p: &Path) -> Cow<Path> {
|
||||||
|
// In some cases, \\? can be missing on some Windows paths. Add it at the
|
||||||
|
// beginning unless the path is prefixed with a device namespace.
|
||||||
|
const VERBATIM_PREFIX: &str = r#"\\?"#;
|
||||||
|
const DEVICE_NS_PREFIX: &str = r#"\\."#;
|
||||||
|
|
||||||
|
let has_prefix = p
|
||||||
|
.components()
|
||||||
|
.next()
|
||||||
|
.and_then(|comp| comp.as_os_str().to_str())
|
||||||
|
.map(|p_str| p_str.starts_with(VERBATIM_PREFIX) || p_str.starts_with(DEVICE_NS_PREFIX))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if has_prefix {
|
||||||
|
p.into()
|
||||||
|
} else {
|
||||||
|
Path::new(VERBATIM_PREFIX).join(p).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a descendant path relative to the given parent directory.
|
||||||
|
///
|
||||||
|
/// If `root_parent` is `None`, then this just returns the `path`
|
||||||
|
/// itself. Otherwise, this function strips the parent prefix from the
|
||||||
|
/// given `path`, leaving only the portion of the path relative to the
|
||||||
|
/// parent.
|
||||||
|
fn get_local_to_root_parent(
|
||||||
|
path: &Path,
|
||||||
|
root_parent: Option<&Path>,
|
||||||
|
) -> Result<PathBuf, StripPrefixError> {
|
||||||
|
match root_parent {
|
||||||
|
Some(parent) => {
|
||||||
|
// On Windows, some paths are starting with \\?
|
||||||
|
// but not always, so, make sure that we are consistent for strip_prefix
|
||||||
|
// See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
|
||||||
|
#[cfg(windows)]
|
||||||
|
let (path, parent) = (
|
||||||
|
adjust_canonicalization(path),
|
||||||
|
adjust_canonicalization(parent),
|
||||||
|
);
|
||||||
|
let path = path.strip_prefix(&parent)?;
|
||||||
|
Ok(path.to_path_buf())
|
||||||
|
}
|
||||||
|
None => Ok(path.to_path_buf()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paths that are invariant throughout the traversal when copying a directory.
|
||||||
|
struct Context<'a> {
|
||||||
|
/// The current working directory at the time of starting the traversal.
|
||||||
|
current_dir: PathBuf,
|
||||||
|
|
||||||
|
/// The path to the parent of the source directory, if any.
|
||||||
|
root_parent: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// The target path to which the directory will be copied.
|
||||||
|
target: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Context<'a> {
|
||||||
|
fn new(root: &'a Path, target: &'a Path) -> std::io::Result<Self> {
|
||||||
|
let current_dir = env::current_dir()?;
|
||||||
|
let root_path = current_dir.join(root);
|
||||||
|
let root_parent = if target.exists() {
|
||||||
|
root_path.parent().map(|p| p.to_path_buf())
|
||||||
|
} else {
|
||||||
|
Some(root_path)
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
current_dir,
|
||||||
|
root_parent,
|
||||||
|
target,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data needed to perform a single copy operation while traversing a directory.
|
||||||
|
///
|
||||||
|
/// For convenience while traversing a directory, the [`Entry::new`]
|
||||||
|
/// function allows creating an entry from a [`Context`] and a
|
||||||
|
/// [`walkdir::DirEntry`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// For example, if the source directory structure is `a/b/c`, the
|
||||||
|
/// target is `d/`, a directory that already exists, and the copy
|
||||||
|
/// command is `cp -r a/b/c d`, then the overall set of copy
|
||||||
|
/// operations could be represented as three entries,
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// let operations = [
|
||||||
|
/// Entry {
|
||||||
|
/// source_absolute: "/tmp/a".into(),
|
||||||
|
/// source_relative: "a".into(),
|
||||||
|
/// local_to_target: "d/a".into(),
|
||||||
|
/// target_is_file: false,
|
||||||
|
/// }
|
||||||
|
/// Entry {
|
||||||
|
/// source_absolute: "/tmp/a/b".into(),
|
||||||
|
/// source_relative: "a/b".into(),
|
||||||
|
/// local_to_target: "d/a/b".into(),
|
||||||
|
/// target_is_file: false,
|
||||||
|
/// }
|
||||||
|
/// Entry {
|
||||||
|
/// source_absolute: "/tmp/a/b/c".into(),
|
||||||
|
/// source_relative: "a/b/c".into(),
|
||||||
|
/// local_to_target: "d/a/b/c".into(),
|
||||||
|
/// target_is_file: false,
|
||||||
|
/// }
|
||||||
|
/// ];
|
||||||
|
/// ```
|
||||||
|
struct Entry {
|
||||||
|
/// The absolute path to file or directory to copy.
|
||||||
|
source_absolute: PathBuf,
|
||||||
|
|
||||||
|
/// The relative path to file or directory to copy.
|
||||||
|
source_relative: PathBuf,
|
||||||
|
|
||||||
|
/// The path to the destination, relative to the target.
|
||||||
|
local_to_target: PathBuf,
|
||||||
|
|
||||||
|
/// Whether the destination is a file.
|
||||||
|
target_is_file: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
fn new(context: &Context, direntry: &DirEntry) -> Result<Self, StripPrefixError> {
|
||||||
|
let source_relative = direntry.path().to_path_buf();
|
||||||
|
let source_absolute = context.current_dir.join(&source_relative);
|
||||||
|
let descendant =
|
||||||
|
get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?;
|
||||||
|
let local_to_target = context.target.join(descendant);
|
||||||
|
let target_is_file = context.target.is_file();
|
||||||
|
Ok(Self {
|
||||||
|
source_absolute,
|
||||||
|
source_relative,
|
||||||
|
local_to_target,
|
||||||
|
target_is_file,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy a single entry during a directory traversal.
|
||||||
|
fn copy_direntry(
|
||||||
|
entry: Entry,
|
||||||
|
options: &Options,
|
||||||
|
symlinked_files: &mut HashSet<FileInformation>,
|
||||||
|
preserve_hard_links: bool,
|
||||||
|
hard_links: &mut Vec<(String, u64)>,
|
||||||
|
) -> CopyResult<()> {
|
||||||
|
let Entry {
|
||||||
|
source_absolute,
|
||||||
|
source_relative,
|
||||||
|
local_to_target,
|
||||||
|
target_is_file,
|
||||||
|
} = entry;
|
||||||
|
|
||||||
|
// If the source is a symbolic link and the options tell us not to
|
||||||
|
// dereference the link, then copy the link object itself.
|
||||||
|
if source_absolute.is_symlink() && !options.dereference {
|
||||||
|
return copy_link(&source_absolute, &local_to_target, symlinked_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the source is a directory and the destination does not
|
||||||
|
// exist, ...
|
||||||
|
if source_absolute.is_dir() && !local_to_target.exists() {
|
||||||
|
if target_is_file {
|
||||||
|
return Err("cannot overwrite non-directory with directory".into());
|
||||||
|
} else {
|
||||||
|
// TODO Since the calling code is traversing from the root
|
||||||
|
// of the directory structure, I don't think
|
||||||
|
// `create_dir_all()` will have any benefit over
|
||||||
|
// `create_dir()`, since all the ancestor directories
|
||||||
|
// should have already been created.
|
||||||
|
fs::create_dir_all(local_to_target)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the source is not a directory, then we need to copy the file.
|
||||||
|
if !source_absolute.is_dir() {
|
||||||
|
if preserve_hard_links {
|
||||||
|
let mut found_hard_link = false;
|
||||||
|
let dest = local_to_target.as_path().to_path_buf();
|
||||||
|
preserve_hardlinks(hard_links, &source_absolute, &dest, &mut found_hard_link)?;
|
||||||
|
if !found_hard_link {
|
||||||
|
match copy_file(
|
||||||
|
&source_absolute,
|
||||||
|
local_to_target.as_path(),
|
||||||
|
options,
|
||||||
|
symlinked_files,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
if source_absolute.is_symlink() {
|
||||||
|
// silent the error with a symlink
|
||||||
|
// In case we do --archive, we might copy the symlink
|
||||||
|
// before the file itself
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, `path` is just a plain old file.
|
||||||
|
// Terminate this function immediately if there is any
|
||||||
|
// kind of error *except* a "permission denied" error.
|
||||||
|
//
|
||||||
|
// TODO What other kinds of errors, if any, should
|
||||||
|
// cause us to continue walking the directory?
|
||||||
|
match copy_file(
|
||||||
|
&source_absolute,
|
||||||
|
local_to_target.as_path(),
|
||||||
|
options,
|
||||||
|
symlinked_files,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(Error::IoErrContext(e, _))
|
||||||
|
if e.kind() == std::io::ErrorKind::PermissionDenied =>
|
||||||
|
{
|
||||||
|
show!(uio_error!(
|
||||||
|
e,
|
||||||
|
"cannot open {} for reading",
|
||||||
|
source_relative.quote(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In any other case, there is nothing to do, so we just return to
|
||||||
|
// continue the traversal.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the contents of the directory `root` and recursively copy the
|
||||||
|
/// contents to `target`.
|
||||||
|
///
|
||||||
|
/// Any errors encountered copying files in the tree will be logged but
|
||||||
|
/// will not cause a short-circuit.
|
||||||
|
pub(crate) fn copy_directory(
|
||||||
|
root: &Path,
|
||||||
|
target: &TargetSlice,
|
||||||
|
options: &Options,
|
||||||
|
symlinked_files: &mut HashSet<FileInformation>,
|
||||||
|
source_in_command_line: bool,
|
||||||
|
) -> CopyResult<()> {
|
||||||
|
if !options.recursive {
|
||||||
|
return Err(format!("omitting directory {}", root.quote()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no-dereference is enabled and this is a symlink, copy it as a file
|
||||||
|
if !options.dereference(source_in_command_line) && root.is_symlink() {
|
||||||
|
return copy_file(
|
||||||
|
root,
|
||||||
|
target,
|
||||||
|
options,
|
||||||
|
symlinked_files,
|
||||||
|
source_in_command_line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if root is a prefix of target
|
||||||
|
if path_has_prefix(target, root)? {
|
||||||
|
return Err(format!(
|
||||||
|
"cannot copy a directory, {}, into itself, {}",
|
||||||
|
root.quote(),
|
||||||
|
target.join(root.file_name().unwrap()).quote()
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||||
|
let preserve_hard_links = options.preserve_hard_links();
|
||||||
|
|
||||||
|
// Collect some paths here that are invariant during the traversal
|
||||||
|
// of the given directory, like the current working directory and
|
||||||
|
// the target directory.
|
||||||
|
let context = match Context::new(root, target) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(format!("failed to get current directory {}", e).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Traverse the contents of the directory, copying each one.
|
||||||
|
for direntry_result in WalkDir::new(root)
|
||||||
|
.same_file_system(options.one_file_system)
|
||||||
|
.follow_links(options.dereference)
|
||||||
|
{
|
||||||
|
match direntry_result {
|
||||||
|
Ok(direntry) => {
|
||||||
|
let entry = Entry::new(&context, &direntry)?;
|
||||||
|
copy_direntry(
|
||||||
|
entry,
|
||||||
|
options,
|
||||||
|
symlinked_files,
|
||||||
|
preserve_hard_links,
|
||||||
|
&mut hard_links,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
// Print an error message, but continue traversing the directory.
|
||||||
|
Err(e) => show_error!("{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy the attributes from the root directory to the target directory.
|
||||||
|
copy_attributes(root, target, &options.preserve_attributes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decide whether the second path is a prefix of the first.
|
||||||
|
///
|
||||||
|
/// This function canonicalizes the paths via
|
||||||
|
/// [`uucore::fs::canonicalize`] before comparing.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If there is an error determining the canonical, absolute form of
|
||||||
|
/// either path.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// assert!(path_has_prefix(Path::new("/usr/bin"), Path::new("/usr")))
|
||||||
|
/// assert!(!path_has_prefix(Path::new("/usr"), Path::new("/usr/bin")))
|
||||||
|
/// assert!(!path_has_prefix(Path::new("/usr/bin"), Path::new("/var/log")))
|
||||||
|
/// ```
|
||||||
|
pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result<bool> {
|
||||||
|
let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||||
|
let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||||
|
|
||||||
|
Ok(pathbuf1.starts_with(pathbuf2))
|
||||||
|
}
|
|
@ -9,7 +9,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE file
|
// For the full copyright and license information, please view the LICENSE file
|
||||||
// that was distributed with this source code.
|
// that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) ficlone ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked fiemap
|
// spell-checker:ignore (ToDO) copydir ficlone ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked fiemap
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate quick_error;
|
extern crate quick_error;
|
||||||
|
@ -38,12 +38,14 @@ use libc::mkfifo;
|
||||||
use quick_error::ResultExt;
|
use quick_error::ResultExt;
|
||||||
use uucore::backup_control::{self, BackupMode};
|
use uucore::backup_control::{self, BackupMode};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{set_exit_code, UClapError, UError, UIoError, UResult, UUsageError};
|
use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
use uucore::fs::{
|
use uucore::fs::{
|
||||||
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
||||||
};
|
};
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
mod copydir;
|
||||||
|
use crate::copydir::copy_directory;
|
||||||
|
|
||||||
mod platform;
|
mod platform;
|
||||||
use platform::copy_on_write;
|
use platform::copy_on_write;
|
||||||
|
@ -103,17 +105,6 @@ impl UError for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Continue next iteration of loop if result of expression is error
|
|
||||||
macro_rules! or_continue(
|
|
||||||
($expr:expr) => (match $expr {
|
|
||||||
Ok(temp) => temp,
|
|
||||||
Err(error) => {
|
|
||||||
show_error!("{}", error);
|
|
||||||
continue
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Prompts the user yes/no and returns `true` if they successfully
|
/// Prompts the user yes/no and returns `true` if they successfully
|
||||||
/// answered yes.
|
/// answered yes.
|
||||||
macro_rules! prompt_yes(
|
macro_rules! prompt_yes(
|
||||||
|
@ -835,6 +826,15 @@ impl Options {
|
||||||
fn dereference(&self, in_command_line: bool) -> bool {
|
fn dereference(&self, in_command_line: bool) -> bool {
|
||||||
self.dereference || (in_command_line && self.cli_dereference)
|
self.dereference || (in_command_line && self.cli_dereference)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn preserve_hard_links(&self) -> bool {
|
||||||
|
for attribute in &self.preserve_attributes {
|
||||||
|
if *attribute == Attribute::Links {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetType {
|
impl TargetType {
|
||||||
|
@ -938,12 +938,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
|
||||||
let target_type = TargetType::determine(sources, target);
|
let target_type = TargetType::determine(sources, target);
|
||||||
verify_target_type(target, &target_type)?;
|
verify_target_type(target, &target_type)?;
|
||||||
|
|
||||||
let mut preserve_hard_links = false;
|
let preserve_hard_links = options.preserve_hard_links();
|
||||||
for attribute in &options.preserve_attributes {
|
|
||||||
if *attribute == Attribute::Links {
|
|
||||||
preserve_hard_links = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hard_links: Vec<(String, u64)> = vec![];
|
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||||
|
|
||||||
|
@ -1037,184 +1032,6 @@ fn copy_source(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn adjust_canonicalization(p: &Path) -> Cow<Path> {
|
|
||||||
// In some cases, \\? can be missing on some Windows paths. Add it at the
|
|
||||||
// beginning unless the path is prefixed with a device namespace.
|
|
||||||
const VERBATIM_PREFIX: &str = r#"\\?"#;
|
|
||||||
const DEVICE_NS_PREFIX: &str = r#"\\."#;
|
|
||||||
|
|
||||||
let has_prefix = p
|
|
||||||
.components()
|
|
||||||
.next()
|
|
||||||
.and_then(|comp| comp.as_os_str().to_str())
|
|
||||||
.map(|p_str| p_str.starts_with(VERBATIM_PREFIX) || p_str.starts_with(DEVICE_NS_PREFIX))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if has_prefix {
|
|
||||||
p.into()
|
|
||||||
} else {
|
|
||||||
Path::new(VERBATIM_PREFIX).join(p).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the contents of the directory `root` and recursively copy the
|
|
||||||
/// contents to `target`.
|
|
||||||
///
|
|
||||||
/// Any errors encountered copying files in the tree will be logged but
|
|
||||||
/// will not cause a short-circuit.
|
|
||||||
fn copy_directory(
|
|
||||||
root: &Path,
|
|
||||||
target: &TargetSlice,
|
|
||||||
options: &Options,
|
|
||||||
symlinked_files: &mut HashSet<FileInformation>,
|
|
||||||
source_in_command_line: bool,
|
|
||||||
) -> CopyResult<()> {
|
|
||||||
if !options.recursive {
|
|
||||||
return Err(format!("omitting directory {}", root.quote()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no-dereference is enabled and this is a symlink, copy it as a file
|
|
||||||
if !options.dereference(source_in_command_line) && root.is_symlink() {
|
|
||||||
return copy_file(
|
|
||||||
root,
|
|
||||||
target,
|
|
||||||
options,
|
|
||||||
symlinked_files,
|
|
||||||
source_in_command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if root is a prefix of target
|
|
||||||
if path_has_prefix(target, root)? {
|
|
||||||
return Err(format!(
|
|
||||||
"cannot copy a directory, {}, into itself, {}",
|
|
||||||
root.quote(),
|
|
||||||
target.join(root.file_name().unwrap()).quote()
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_dir =
|
|
||||||
env::current_dir().unwrap_or_else(|e| crash!(1, "failed to get current directory {}", e));
|
|
||||||
|
|
||||||
let root_path = current_dir.join(root);
|
|
||||||
|
|
||||||
let root_parent = if target.exists() {
|
|
||||||
root_path.parent()
|
|
||||||
} else {
|
|
||||||
Some(root_path.as_path())
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
let mut hard_links: Vec<(String, u64)> = vec![];
|
|
||||||
let mut preserve_hard_links = false;
|
|
||||||
for attribute in &options.preserve_attributes {
|
|
||||||
if *attribute == Attribute::Links {
|
|
||||||
preserve_hard_links = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should be changed once Redox supports hardlinks
|
|
||||||
#[cfg(any(windows, target_os = "redox"))]
|
|
||||||
let mut hard_links: Vec<(String, u64)> = vec![];
|
|
||||||
|
|
||||||
for path in WalkDir::new(root)
|
|
||||||
.same_file_system(options.one_file_system)
|
|
||||||
.follow_links(options.dereference)
|
|
||||||
{
|
|
||||||
let p = or_continue!(path);
|
|
||||||
let path = current_dir.join(p.path());
|
|
||||||
|
|
||||||
let local_to_root_parent = match root_parent {
|
|
||||||
Some(parent) => {
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
// On Windows, some paths are starting with \\?
|
|
||||||
// but not always, so, make sure that we are consistent for strip_prefix
|
|
||||||
// See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
|
|
||||||
let parent_can = adjust_canonicalization(parent);
|
|
||||||
let path_can = adjust_canonicalization(&path);
|
|
||||||
|
|
||||||
or_continue!(&path_can.strip_prefix(&parent_can)).to_path_buf()
|
|
||||||
}
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
{
|
|
||||||
or_continue!(path.strip_prefix(parent)).to_path_buf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => path.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let local_to_target = target.join(&local_to_root_parent);
|
|
||||||
if p.path().is_symlink() && !options.dereference {
|
|
||||||
copy_link(&path, &local_to_target, symlinked_files)?;
|
|
||||||
} else if path.is_dir() && !local_to_target.exists() {
|
|
||||||
if target.is_file() {
|
|
||||||
return Err("cannot overwrite non-directory with directory".into());
|
|
||||||
}
|
|
||||||
fs::create_dir_all(local_to_target)?;
|
|
||||||
} else if !path.is_dir() {
|
|
||||||
if preserve_hard_links {
|
|
||||||
let mut found_hard_link = false;
|
|
||||||
let source = path.to_path_buf();
|
|
||||||
let dest = local_to_target.as_path().to_path_buf();
|
|
||||||
preserve_hardlinks(&mut hard_links, &source, &dest, &mut found_hard_link)?;
|
|
||||||
if !found_hard_link {
|
|
||||||
match copy_file(
|
|
||||||
path.as_path(),
|
|
||||||
local_to_target.as_path(),
|
|
||||||
options,
|
|
||||||
symlinked_files,
|
|
||||||
false,
|
|
||||||
) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(err) => {
|
|
||||||
if source.is_symlink() {
|
|
||||||
// silent the error with a symlink
|
|
||||||
// In case we do --archive, we might copy the symlink
|
|
||||||
// before the file itself
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, `path` is just a plain old file.
|
|
||||||
// Terminate this function immediately if there is any
|
|
||||||
// kind of error *except* a "permission denied" error.
|
|
||||||
//
|
|
||||||
// TODO What other kinds of errors, if any, should
|
|
||||||
// cause us to continue walking the directory?
|
|
||||||
match copy_file(
|
|
||||||
path.as_path(),
|
|
||||||
local_to_target.as_path(),
|
|
||||||
options,
|
|
||||||
symlinked_files,
|
|
||||||
false,
|
|
||||||
) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(Error::IoErrContext(e, _))
|
|
||||||
if e.kind() == std::io::ErrorKind::PermissionDenied =>
|
|
||||||
{
|
|
||||||
show!(uio_error!(
|
|
||||||
e,
|
|
||||||
"cannot open {} for reading",
|
|
||||||
p.path().quote()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Copy the attributes from the root directory to the target directory.
|
|
||||||
copy_attributes(root, target, &options.preserve_attributes)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OverwriteMode {
|
impl OverwriteMode {
|
||||||
fn verify(&self, path: &Path) -> CopyResult<()> {
|
fn verify(&self, path: &Path) -> CopyResult<()> {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -1235,7 +1052,11 @@ impl OverwriteMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy the specified attributes from one path to another.
|
/// Copy the specified attributes from one path to another.
|
||||||
fn copy_attributes(source: &Path, dest: &Path, attributes: &[Attribute]) -> CopyResult<()> {
|
pub(crate) fn copy_attributes(
|
||||||
|
source: &Path,
|
||||||
|
dest: &Path,
|
||||||
|
attributes: &[Attribute],
|
||||||
|
) -> CopyResult<()> {
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
copy_attribute(source, dest, attribute)?;
|
copy_attribute(source, dest, attribute)?;
|
||||||
}
|
}
|
||||||
|
@ -1708,13 +1529,6 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu
|
||||||
Ok(target.join(local_to_root))
|
Ok(target.join(local_to_root))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result<bool> {
|
|
||||||
let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
|
|
||||||
let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;
|
|
||||||
|
|
||||||
Ok(pathbuf1.starts_with(pathbuf2))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_localize_to_target() {
|
fn test_cp_localize_to_target() {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
@ -23,7 +23,7 @@ uucore = { version=">=0.0.16", package="uucore", path="../../uucore" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Foundation", "Win32_System_SystemInformation"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "date"
|
name = "date"
|
||||||
|
|
|
@ -23,10 +23,7 @@ use uucore::error::FromIo;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::{format_usage, show_error};
|
use uucore::{format_usage, show_error};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::{
|
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
||||||
shared::minwindef::WORD,
|
|
||||||
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
const DATE: &str = "date";
|
const DATE: &str = "date";
|
||||||
|
@ -409,16 +406,16 @@ fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
|
||||||
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
|
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
|
||||||
fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
|
fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
|
||||||
let system_time = SYSTEMTIME {
|
let system_time = SYSTEMTIME {
|
||||||
wYear: date.year() as WORD,
|
wYear: date.year() as u16,
|
||||||
wMonth: date.month() as WORD,
|
wMonth: date.month() as u16,
|
||||||
// Ignored
|
// Ignored
|
||||||
wDayOfWeek: 0,
|
wDayOfWeek: 0,
|
||||||
wDay: date.day() as WORD,
|
wDay: date.day() as u16,
|
||||||
wHour: date.hour() as WORD,
|
wHour: date.hour() as u16,
|
||||||
wMinute: date.minute() as WORD,
|
wMinute: date.minute() as u16,
|
||||||
wSecond: date.second() as WORD,
|
wSecond: date.second() as u16,
|
||||||
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
|
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
|
||||||
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
|
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = unsafe { SetSystemTime(&system_time) };
|
let result = unsafe { SetSystemTime(&system_time) };
|
||||||
|
|
|
@ -22,7 +22,7 @@ clap = { version = "4.0", features = ["wrap_help", "cargo"] }
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version="0.3", features=[] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "du"
|
name = "du"
|
||||||
|
|
|
@ -41,15 +41,12 @@ use uucore::format_usage;
|
||||||
use uucore::parse_glob;
|
use uucore::parse_glob;
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::shared::minwindef::{DWORD, LPVOID};
|
use windows_sys::Win32::Foundation::HANDLE;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::fileapi::{FILE_ID_INFO, FILE_STANDARD_INFO};
|
use windows_sys::Win32::Storage::FileSystem::{
|
||||||
#[cfg(windows)]
|
FileIdInfo, FileStandardInfo, GetFileInformationByHandleEx, FILE_ID_128, FILE_ID_INFO,
|
||||||
use winapi::um::minwinbase::{FileIdInfo, FileStandardInfo};
|
FILE_STANDARD_INFO,
|
||||||
#[cfg(windows)]
|
};
|
||||||
use winapi::um::winbase::GetFileInformationByHandleEx;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::um::winnt::FILE_ID_128;
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const HELP: &str = "help";
|
pub const HELP: &str = "help";
|
||||||
|
@ -208,21 +205,19 @@ fn get_size_on_disk(path: &Path) -> u64 {
|
||||||
Err(_) => return size_on_disk, // opening directories will fail
|
Err(_) => return size_on_disk, // opening directories will fail
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = file.as_raw_handle();
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
|
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
|
||||||
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
|
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
|
||||||
|
|
||||||
let success = GetFileInformationByHandleEx(
|
let success = GetFileInformationByHandleEx(
|
||||||
handle,
|
file.as_raw_handle() as HANDLE,
|
||||||
FileStandardInfo,
|
FileStandardInfo,
|
||||||
file_info_ptr as LPVOID,
|
file_info_ptr as _,
|
||||||
std::mem::size_of::<FILE_STANDARD_INFO>() as DWORD,
|
std::mem::size_of::<FILE_STANDARD_INFO>() as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
if success != 0 {
|
if success != 0 {
|
||||||
size_on_disk = *file_info.AllocationSize.QuadPart() as u64;
|
size_on_disk = file_info.AllocationSize as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,17 +233,15 @@ fn get_file_info(path: &Path) -> Option<FileInfo> {
|
||||||
Err(_) => return result,
|
Err(_) => return result,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = file.as_raw_handle();
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut file_info: FILE_ID_INFO = core::mem::zeroed();
|
let mut file_info: FILE_ID_INFO = core::mem::zeroed();
|
||||||
let file_info_ptr: *mut FILE_ID_INFO = &mut file_info;
|
let file_info_ptr: *mut FILE_ID_INFO = &mut file_info;
|
||||||
|
|
||||||
let success = GetFileInformationByHandleEx(
|
let success = GetFileInformationByHandleEx(
|
||||||
handle,
|
file.as_raw_handle() as HANDLE,
|
||||||
FileIdInfo,
|
FileIdInfo,
|
||||||
file_info_ptr as LPVOID,
|
file_info_ptr as _,
|
||||||
std::mem::size_of::<FILE_ID_INFO>() as DWORD,
|
std::mem::size_of::<FILE_ID_INFO>() as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
if success != 0 {
|
if success != 0 {
|
||||||
|
|
|
@ -23,7 +23,7 @@ md-5 = "0.10.5"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
sha1 = "0.10.1"
|
sha1 = "0.10.1"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
sha3 = "0.10.2"
|
sha3 = "0.10.6"
|
||||||
blake2b_simd = "1.0.0"
|
blake2b_simd = "1.0.0"
|
||||||
blake3 = "1.3.1"
|
blake3 = "1.3.1"
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore" }
|
||||||
|
|
|
@ -498,7 +498,8 @@ where
|
||||||
I: Iterator<Item = &'a OsStr>,
|
I: Iterator<Item = &'a OsStr>,
|
||||||
{
|
{
|
||||||
let mut bad_format = 0;
|
let mut bad_format = 0;
|
||||||
let mut failed = 0;
|
let mut failed_cksum = 0;
|
||||||
|
let mut failed_open_file = 0;
|
||||||
let binary_marker = if options.binary { "*" } else { " " };
|
let binary_marker = if options.binary { "*" } else { " " };
|
||||||
for filename in files {
|
for filename in files {
|
||||||
let filename = Path::new(filename);
|
let filename = Path::new(filename);
|
||||||
|
@ -574,8 +575,19 @@ where
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let f = File::open(ck_filename)
|
let f = match File::open(ck_filename) {
|
||||||
.map_err_context(|| "failed to open file".to_string())?;
|
Err(_) => {
|
||||||
|
failed_open_file += 1;
|
||||||
|
println!(
|
||||||
|
"{}: {}: No such file or directory",
|
||||||
|
uucore::util_name(),
|
||||||
|
ck_filename
|
||||||
|
);
|
||||||
|
println!("{}: FAILED open or read", ck_filename);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(file) => file,
|
||||||
|
};
|
||||||
let mut ckf = BufReader::new(Box::new(f) as Box<dyn Read>);
|
let mut ckf = BufReader::new(Box::new(f) as Box<dyn Read>);
|
||||||
let real_sum = digest_reader(
|
let real_sum = digest_reader(
|
||||||
&mut options.digest,
|
&mut options.digest,
|
||||||
|
@ -602,7 +614,7 @@ where
|
||||||
if !options.status {
|
if !options.status {
|
||||||
println!("{}: FAILED", ck_filename);
|
println!("{}: FAILED", ck_filename);
|
||||||
}
|
}
|
||||||
failed += 1;
|
failed_cksum += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -628,8 +640,15 @@ where
|
||||||
Ordering::Greater => show_warning!("{} lines are improperly formatted", bad_format),
|
Ordering::Greater => show_warning!("{} lines are improperly formatted", bad_format),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
if failed > 0 {
|
if failed_cksum > 0 {
|
||||||
show_warning!("{} computed checksum did NOT match", failed);
|
show_warning!("{} computed checksum did NOT match", failed_cksum);
|
||||||
|
}
|
||||||
|
match failed_open_file.cmp(&1) {
|
||||||
|
Ordering::Equal => show_warning!("{} listed file could not be read", failed_open_file),
|
||||||
|
Ordering::Greater => {
|
||||||
|
show_warning!("{} listed files could not be read", failed_open_file);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ hostname = { version = "0.3", features = ["set"] }
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["wide"] }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["wide"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version="0.3", features=["sysinfoapi", "winsock2"] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Networking_WinSock", "Win32_Foundation"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "hostname"
|
name = "hostname"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[package.metadata.cargo-udeps.ignore]
|
[package.metadata.cargo-udeps.ignore]
|
||||||
normal = ["uucore_procs", "winapi"]
|
normal = ["uucore_procs"]
|
||||||
|
|
|
@ -32,15 +32,14 @@ static OPT_HOST: &str = "host";
|
||||||
mod wsa {
|
mod wsa {
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use winapi::shared::minwindef::MAKEWORD;
|
use windows_sys::Win32::Networking::WinSock::{WSACleanup, WSAStartup, WSADATA};
|
||||||
use winapi::um::winsock2::{WSACleanup, WSAStartup, WSADATA};
|
|
||||||
|
|
||||||
pub(super) struct WsaHandle(());
|
pub(super) struct WsaHandle(());
|
||||||
|
|
||||||
pub(super) fn start() -> io::Result<WsaHandle> {
|
pub(super) fn start() -> io::Result<WsaHandle> {
|
||||||
let err = unsafe {
|
let err = unsafe {
|
||||||
let mut data = std::mem::MaybeUninit::<WSADATA>::uninit();
|
let mut data = std::mem::MaybeUninit::<WSADATA>::uninit();
|
||||||
WSAStartup(MAKEWORD(2, 2), data.as_mut_ptr())
|
WSAStartup(0x0202, data.as_mut_ptr())
|
||||||
};
|
};
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
Err(io::Error::from_raw_os_error(err))
|
Err(io::Error::from_raw_os_error(err))
|
||||||
|
|
|
@ -21,7 +21,7 @@ remove_dir_all = "0.7.0"
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["fs"] }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["fs"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version="0.3", features=[] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rm"
|
name = "rm"
|
||||||
|
|
|
@ -510,7 +510,7 @@ use std::os::windows::prelude::MetadataExt;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn is_symlink_dir(metadata: &fs::Metadata) -> bool {
|
fn is_symlink_dir(metadata: &fs::Metadata) -> bool {
|
||||||
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
|
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY;
|
||||||
|
|
||||||
metadata.file_type().is_symlink()
|
metadata.file_type().is_symlink()
|
||||||
&& ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
&& ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
||||||
|
|
|
@ -23,11 +23,11 @@ uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=[
|
||||||
nix = "0.25"
|
nix = "0.25"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_System_WindowsProgramming", "Win32_Foundation"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "sync"
|
name = "sync"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[package.metadata.cargo-udeps.ignore]
|
[package.metadata.cargo-udeps.ignore]
|
||||||
normal = ["uucore_procs", "winapi"]
|
normal = ["uucore_procs"]
|
||||||
|
|
|
@ -68,29 +68,27 @@ mod platform {
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod platform {
|
mod platform {
|
||||||
extern crate winapi;
|
|
||||||
use self::winapi::shared::minwindef;
|
|
||||||
use self::winapi::shared::winerror;
|
|
||||||
use self::winapi::um::handleapi;
|
|
||||||
use self::winapi::um::winbase;
|
|
||||||
use self::winapi::um::winnt;
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::os::windows::prelude::*;
|
use std::os::windows::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::crash;
|
use uucore::crash;
|
||||||
use uucore::wide::{FromWide, ToWide};
|
use uucore::wide::{FromWide, ToWide};
|
||||||
|
use windows_sys::Win32::Foundation::{
|
||||||
|
GetLastError, ERROR_NO_MORE_FILES, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::Storage::FileSystem::{
|
||||||
|
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, FlushFileBuffers, GetDriveTypeW,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED;
|
||||||
|
|
||||||
unsafe fn flush_volume(name: &str) {
|
unsafe fn flush_volume(name: &str) {
|
||||||
let name_wide = name.to_wide_null();
|
let name_wide = name.to_wide_null();
|
||||||
if winapi::um::fileapi::GetDriveTypeW(name_wide.as_ptr()) == winbase::DRIVE_FIXED {
|
if GetDriveTypeW(name_wide.as_ptr()) == DRIVE_FIXED {
|
||||||
let sliced_name = &name[..name.len() - 1]; // eliminate trailing backslash
|
let sliced_name = &name[..name.len() - 1]; // eliminate trailing backslash
|
||||||
match OpenOptions::new().write(true).open(sliced_name) {
|
match OpenOptions::new().write(true).open(sliced_name) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
if winapi::um::fileapi::FlushFileBuffers(file.as_raw_handle()) == 0 {
|
if FlushFileBuffers(file.as_raw_handle() as HANDLE) == 0 {
|
||||||
crash!(
|
crash!(GetLastError() as i32, "failed to flush file buffer");
|
||||||
winapi::um::errhandlingapi::GetLastError() as i32,
|
|
||||||
"failed to flush file buffer"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => crash!(
|
Err(e) => crash!(
|
||||||
|
@ -101,17 +99,11 @@ mod platform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn find_first_volume() -> (String, winnt::HANDLE) {
|
unsafe fn find_first_volume() -> (String, HANDLE) {
|
||||||
let mut name: [winnt::WCHAR; minwindef::MAX_PATH] = [0; minwindef::MAX_PATH];
|
let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
|
||||||
let handle = winapi::um::fileapi::FindFirstVolumeW(
|
let handle = FindFirstVolumeW(name.as_mut_ptr(), name.len() as u32);
|
||||||
name.as_mut_ptr(),
|
if handle == INVALID_HANDLE_VALUE {
|
||||||
name.len() as minwindef::DWORD,
|
crash!(GetLastError() as i32, "failed to find first volume");
|
||||||
);
|
|
||||||
if handle == handleapi::INVALID_HANDLE_VALUE {
|
|
||||||
crash!(
|
|
||||||
winapi::um::errhandlingapi::GetLastError() as i32,
|
|
||||||
"failed to find first volume"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
(String::from_wide_null(&name), handle)
|
(String::from_wide_null(&name), handle)
|
||||||
}
|
}
|
||||||
|
@ -120,16 +112,11 @@ mod platform {
|
||||||
let (first_volume, next_volume_handle) = find_first_volume();
|
let (first_volume, next_volume_handle) = find_first_volume();
|
||||||
let mut volumes = vec![first_volume];
|
let mut volumes = vec![first_volume];
|
||||||
loop {
|
loop {
|
||||||
let mut name: [winnt::WCHAR; minwindef::MAX_PATH] = [0; minwindef::MAX_PATH];
|
let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
|
||||||
if winapi::um::fileapi::FindNextVolumeW(
|
if FindNextVolumeW(next_volume_handle, name.as_mut_ptr(), name.len() as u32) == 0 {
|
||||||
next_volume_handle,
|
match GetLastError() {
|
||||||
name.as_mut_ptr(),
|
ERROR_NO_MORE_FILES => {
|
||||||
name.len() as minwindef::DWORD,
|
FindVolumeClose(next_volume_handle);
|
||||||
) == 0
|
|
||||||
{
|
|
||||||
match winapi::um::errhandlingapi::GetLastError() {
|
|
||||||
winerror::ERROR_NO_MORE_FILES => {
|
|
||||||
winapi::um::fileapi::FindVolumeClose(next_volume_handle);
|
|
||||||
return volumes;
|
return volumes;
|
||||||
}
|
}
|
||||||
err => crash!(err as i32, "failed to find next volume"),
|
err => crash!(err as i32, "failed to find next volume"),
|
||||||
|
|
|
@ -24,7 +24,7 @@ uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=[
|
||||||
same-file = "1.0.6"
|
same-file = "1.0.6"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
||||||
winapi-util = { version="0.1.5" }
|
winapi-util = { version="0.1.5" }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
|
|
@ -6,17 +6,12 @@
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
use windows_sys::Win32::Foundation::{CloseHandle, BOOL, HANDLE, WAIT_FAILED, WAIT_OBJECT_0};
|
||||||
|
use windows_sys::Win32::System::Threading::{
|
||||||
|
OpenProcess, WaitForSingleObject, PROCESS_SYNCHRONIZE,
|
||||||
|
};
|
||||||
|
|
||||||
extern crate winapi;
|
pub type Pid = u32;
|
||||||
|
|
||||||
use self::winapi::shared::minwindef::DWORD;
|
|
||||||
use self::winapi::um::handleapi::CloseHandle;
|
|
||||||
use self::winapi::um::processthreadsapi::OpenProcess;
|
|
||||||
use self::winapi::um::synchapi::WaitForSingleObject;
|
|
||||||
use self::winapi::um::winbase::{WAIT_FAILED, WAIT_OBJECT_0};
|
|
||||||
use self::winapi::um::winnt::{HANDLE, SYNCHRONIZE};
|
|
||||||
|
|
||||||
pub type Pid = DWORD;
|
|
||||||
|
|
||||||
pub struct ProcessChecker {
|
pub struct ProcessChecker {
|
||||||
dead: bool,
|
dead: bool,
|
||||||
|
@ -26,10 +21,10 @@ pub struct ProcessChecker {
|
||||||
impl ProcessChecker {
|
impl ProcessChecker {
|
||||||
pub fn new(process_id: self::Pid) -> Self {
|
pub fn new(process_id: self::Pid) -> Self {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let FALSE = 0i32;
|
let FALSE: BOOL = 0;
|
||||||
let h = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD) };
|
let h = unsafe { OpenProcess(PROCESS_SYNCHRONIZE, FALSE, process_id) };
|
||||||
Self {
|
Self {
|
||||||
dead: h.is_null(),
|
dead: h == 0,
|
||||||
handle: h,
|
handle: h,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ time = { version = "0.3", features = ["parsing", "formatting", "local-offset", "
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["libc"] }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["libc"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3" }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "touch"
|
name = "touch"
|
||||||
|
|
|
@ -472,19 +472,16 @@ fn pathbuf_from_stdout() -> UResult<PathBuf> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
use std::os::windows::prelude::AsRawHandle;
|
use std::os::windows::prelude::AsRawHandle;
|
||||||
use winapi::shared::minwindef::{DWORD, MAX_PATH};
|
use windows_sys::Win32::Foundation::{
|
||||||
use winapi::shared::winerror::{
|
GetLastError, ERROR_INVALID_PARAMETER, ERROR_NOT_ENOUGH_MEMORY, ERROR_PATH_NOT_FOUND,
|
||||||
ERROR_INVALID_PARAMETER, ERROR_NOT_ENOUGH_MEMORY, ERROR_PATH_NOT_FOUND,
|
HANDLE, MAX_PATH,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::Storage::FileSystem::{
|
||||||
|
GetFinalPathNameByHandleW, FILE_NAME_OPENED,
|
||||||
};
|
};
|
||||||
use winapi::um::errhandlingapi::GetLastError;
|
|
||||||
use winapi::um::fileapi::GetFinalPathNameByHandleW;
|
|
||||||
use winapi::um::winnt::WCHAR;
|
|
||||||
|
|
||||||
let handle = std::io::stdout().lock().as_raw_handle();
|
let handle = std::io::stdout().lock().as_raw_handle() as HANDLE;
|
||||||
let mut file_path_buffer: [WCHAR; MAX_PATH as usize] = [0; MAX_PATH as usize];
|
let mut file_path_buffer: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
|
||||||
|
|
||||||
// Couldn't find this in winapi
|
|
||||||
const FILE_NAME_OPENED: DWORD = 0x8;
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea#examples
|
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea#examples
|
||||||
// SAFETY: We transmute the handle to be able to cast *mut c_void into a
|
// SAFETY: We transmute the handle to be able to cast *mut c_void into a
|
||||||
|
|
|
@ -19,7 +19,7 @@ clap = { version = "4.0", features = ["wrap_help", "cargo"] }
|
||||||
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["entries"] }
|
uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["entries"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["lmcons"] }
|
windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_NetworkManagement_NetManagement", "Win32_System_WindowsProgramming", "Win32_Foundation"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.135"
|
libc = "0.2.135"
|
||||||
|
|
|
@ -10,17 +10,15 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::windows::ffi::OsStringExt;
|
use std::os::windows::ffi::OsStringExt;
|
||||||
|
use windows_sys::Win32::NetworkManagement::NetManagement::UNLEN;
|
||||||
use winapi::shared::lmcons;
|
use windows_sys::Win32::System::WindowsProgramming::GetUserNameW;
|
||||||
use winapi::shared::minwindef::DWORD;
|
|
||||||
use winapi::um::winbase;
|
|
||||||
|
|
||||||
pub fn get_username() -> io::Result<OsString> {
|
pub fn get_username() -> io::Result<OsString> {
|
||||||
const BUF_LEN: DWORD = lmcons::UNLEN + 1;
|
const BUF_LEN: u32 = UNLEN + 1;
|
||||||
let mut buffer = [0_u16; BUF_LEN as usize];
|
let mut buffer = [0_u16; BUF_LEN as usize];
|
||||||
let mut len = BUF_LEN;
|
let mut len = BUF_LEN;
|
||||||
// SAFETY: buffer.len() == len
|
// SAFETY: buffer.len() == len
|
||||||
if unsafe { winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) } == 0 {
|
if unsafe { GetUserNameW(buffer.as_mut_ptr(), &mut len) } == 0 {
|
||||||
return Err(io::Error::last_os_error());
|
return Err(io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
Ok(OsString::from_wide(&buffer[..len as usize - 1]))
|
Ok(OsString::from_wide(&buffer[..len as usize - 1]))
|
||||||
|
|
|
@ -45,16 +45,16 @@ clap = "4.0"
|
||||||
once_cell = "1.13"
|
once_cell = "1.13"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
|
|
||||||
winapi-util = { version= "0.1.5", optional=true }
|
winapi-util = { version= "0.1.5", optional=true }
|
||||||
|
windows-sys = { version = "0.42.0", optional = true, default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_WindowsProgramming"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
# * non-default features
|
# * non-default features
|
||||||
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
||||||
entries = ["libc"]
|
entries = ["libc"]
|
||||||
fs = ["libc", "winapi-util"]
|
fs = ["libc", "winapi-util", "windows-sys"]
|
||||||
fsext = ["libc", "time"]
|
fsext = ["libc", "time", "windows-sys"]
|
||||||
lines = []
|
lines = []
|
||||||
memo = ["itertools"]
|
memo = ["itertools"]
|
||||||
mode = ["libc"]
|
mode = ["libc"]
|
||||||
|
|
|
@ -81,9 +81,10 @@ impl FileInformation {
|
||||||
let mut open_options = OpenOptions::new();
|
let mut open_options = OpenOptions::new();
|
||||||
let mut custom_flags = 0;
|
let mut custom_flags = 0;
|
||||||
if !dereference {
|
if !dereference {
|
||||||
custom_flags |= winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
|
custom_flags |=
|
||||||
|
windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
|
||||||
}
|
}
|
||||||
custom_flags |= winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
|
custom_flags |= windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
open_options.custom_flags(custom_flags);
|
open_options.custom_flags(custom_flags);
|
||||||
let file = open_options.read(true).open(path.as_ref())?;
|
let file = open_options.read(true).open(path.as_ref())?;
|
||||||
Self::from_file(&file)
|
Self::from_file(&file)
|
||||||
|
|
|
@ -32,18 +32,14 @@ use std::ffi::OsStr;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::ffi::OsStrExt;
|
use std::os::windows::ffi::OsStrExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::shared::minwindef::DWORD;
|
use windows_sys::Win32::Foundation::{ERROR_NO_MORE_FILES, INVALID_HANDLE_VALUE};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::fileapi::GetDiskFreeSpaceW;
|
use windows_sys::Win32::Storage::FileSystem::{
|
||||||
#[cfg(windows)]
|
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceW, GetDriveTypeW,
|
||||||
use winapi::um::fileapi::{
|
GetVolumeInformationW, GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
|
||||||
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW,
|
|
||||||
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
|
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
use windows_sys::Win32::System::WindowsProgramming::DRIVE_REMOTE;
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::um::winbase::DRIVE_REMOTE;
|
|
||||||
|
|
||||||
// Warning: the pointer has to be used *immediately* or the Vec
|
// Warning: the pointer has to be used *immediately* or the Vec
|
||||||
// it points to will be dropped!
|
// it points to will be dropped!
|
||||||
|
@ -265,7 +261,7 @@ impl MountInfo {
|
||||||
.collect::<Vec<u16>>()
|
.collect::<Vec<u16>>()
|
||||||
.as_ptr(),
|
.as_ptr(),
|
||||||
dev_name_buf.as_mut_ptr(),
|
dev_name_buf.as_mut_ptr(),
|
||||||
dev_name_buf.len() as DWORD,
|
dev_name_buf.len() as u32,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
volume_name.push('\\');
|
volume_name.push('\\');
|
||||||
|
@ -276,7 +272,7 @@ impl MountInfo {
|
||||||
GetVolumePathNamesForVolumeNameW(
|
GetVolumePathNamesForVolumeNameW(
|
||||||
String2LPWSTR!(volume_name),
|
String2LPWSTR!(volume_name),
|
||||||
mount_root_buf.as_mut_ptr(),
|
mount_root_buf.as_mut_ptr(),
|
||||||
mount_root_buf.len() as DWORD,
|
mount_root_buf.len() as u32,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -296,7 +292,7 @@ impl MountInfo {
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
fs_type_buf.as_mut_ptr(),
|
fs_type_buf.as_mut_ptr(),
|
||||||
fs_type_buf.len() as DWORD,
|
fs_type_buf.len() as u32,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let fs_type = if 0 != success {
|
let fs_type = if 0 != success {
|
||||||
|
@ -443,9 +439,8 @@ pub fn read_fs_list() -> Result<Vec<MountInfo>, std::io::Error> {
|
||||||
{
|
{
|
||||||
let mut volume_name_buf = [0u16; MAX_PATH];
|
let mut volume_name_buf = [0u16; MAX_PATH];
|
||||||
// As recommended in the MS documentation, retrieve the first volume before the others
|
// As recommended in the MS documentation, retrieve the first volume before the others
|
||||||
let find_handle = unsafe {
|
let find_handle =
|
||||||
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
|
unsafe { FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as u32) };
|
||||||
};
|
|
||||||
if INVALID_HANDLE_VALUE == find_handle {
|
if INVALID_HANDLE_VALUE == find_handle {
|
||||||
crash!(
|
crash!(
|
||||||
EXIT_ERR,
|
EXIT_ERR,
|
||||||
|
@ -467,12 +462,11 @@ pub fn read_fs_list() -> Result<Vec<MountInfo>, std::io::Error> {
|
||||||
FindNextVolumeW(
|
FindNextVolumeW(
|
||||||
find_handle,
|
find_handle,
|
||||||
volume_name_buf.as_mut_ptr(),
|
volume_name_buf.as_mut_ptr(),
|
||||||
volume_name_buf.len() as DWORD,
|
volume_name_buf.len() as u32,
|
||||||
)
|
)
|
||||||
} {
|
} {
|
||||||
let err = IOError::last_os_error();
|
let err = IOError::last_os_error();
|
||||||
if err.raw_os_error() != Some(winapi::shared::winerror::ERROR_NO_MORE_FILES as i32)
|
if err.raw_os_error() != Some(ERROR_NO_MORE_FILES as i32) {
|
||||||
{
|
|
||||||
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
|
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -524,7 +518,7 @@ impl FsUsage {
|
||||||
//path_utf8.as_ptr(),
|
//path_utf8.as_ptr(),
|
||||||
String2LPWSTR!(path.as_os_str()),
|
String2LPWSTR!(path.as_os_str()),
|
||||||
root_path.as_mut_ptr(),
|
root_path.as_mut_ptr(),
|
||||||
root_path.len() as DWORD,
|
root_path.len() as u32,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
// * feature-gated external crates (re-shared as public internal modules)
|
// * feature-gated external crates (re-shared as public internal modules)
|
||||||
#[cfg(feature = "libc")]
|
#[cfg(feature = "libc")]
|
||||||
pub extern crate libc;
|
pub extern crate libc;
|
||||||
#[cfg(feature = "winapi")]
|
#[cfg(all(feature = "windows-sys", target_os = "windows"))]
|
||||||
pub extern crate winapi;
|
pub extern crate windows_sys;
|
||||||
|
|
||||||
//## internal modules
|
//## internal modules
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
// spell-checker:ignore checkfile, nonames, testf
|
// spell-checker:ignore checkfile, nonames, testf, ntestf
|
||||||
macro_rules! get_hash(
|
macro_rules! get_hash(
|
||||||
($str:expr) => (
|
($str:expr) => (
|
||||||
$str.split(' ').collect::<Vec<&str>>()[0]
|
$str.split(' ').collect::<Vec<&str>>()[0]
|
||||||
|
@ -117,6 +117,26 @@ fn test_check_sha1() {
|
||||||
.stderr_is("");
|
.stderr_is("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_file_not_found_warning() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.write("testf", "foobar\n");
|
||||||
|
at.write(
|
||||||
|
"testf.sha1",
|
||||||
|
"988881adc9fc3655077dc2d4d757d480b5ea0e11 testf\n",
|
||||||
|
);
|
||||||
|
at.remove("testf");
|
||||||
|
scene
|
||||||
|
.ccmd("sha1sum")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(at.subdir.join("testf.sha1"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("sha1sum: testf: No such file or directory\ntestf: FAILED open or read\n")
|
||||||
|
.stderr_is("sha1sum: warning: 1 listed file could not be read");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_arg() {
|
fn test_invalid_arg() {
|
||||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue