mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-31 04:57:45 +00:00
touch: implement -
(#3158)
This commit is contained in:
parent
90cc2bff6a
commit
103dffc12e
4 changed files with 122 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3171,6 +3171,7 @@ dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"time",
|
"time",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -20,6 +20,9 @@ clap = { version = "3.0", features = ["wrap_help", "cargo"] }
|
||||||
time = "0.1.40"
|
time = "0.1.40"
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
winapi = { version = "0.3" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "touch"
|
name = "touch"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -6,7 +6,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) filetime strptime utcoff strs datetime MMDDhhmm clapv
|
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult
|
||||||
|
|
||||||
pub extern crate filetime;
|
pub extern crate filetime;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ extern crate uucore;
|
||||||
use clap::{crate_version, App, AppSettings, Arg, ArgGroup};
|
use clap::{crate_version, App, AppSettings, Arg, ArgGroup};
|
||||||
use filetime::*;
|
use filetime::*;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
@ -77,7 +77,15 @@ Try 'touch --help' for more information."##,
|
||||||
};
|
};
|
||||||
|
|
||||||
for filename in files {
|
for filename in files {
|
||||||
let path = Path::new(filename);
|
// FIXME: find a way to avoid having to clone the path
|
||||||
|
let pathbuf = if filename == "-" {
|
||||||
|
pathbuf_from_stdout()?
|
||||||
|
} else {
|
||||||
|
PathBuf::from(filename)
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = pathbuf.as_path();
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
if matches.is_present(options::NO_CREATE) {
|
if matches.is_present(options::NO_CREATE) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -299,3 +307,89 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||||
|
|
||||||
Ok(ft)
|
Ok(ft)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this may be a good candidate to put in fsext.rs
|
||||||
|
/// Returns a PathBuf to stdout.
|
||||||
|
///
|
||||||
|
/// On Windows, uses GetFinalPathNameByHandleW to attempt to get the path
|
||||||
|
/// from the stdout handle.
|
||||||
|
fn pathbuf_from_stdout() -> UResult<PathBuf> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
Ok(PathBuf::from("/dev/stdout"))
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::prelude::AsRawHandle;
|
||||||
|
use winapi::shared::minwindef::{DWORD, MAX_PATH};
|
||||||
|
use winapi::shared::winerror::{
|
||||||
|
ERROR_INVALID_PARAMETER, ERROR_NOT_ENOUGH_MEMORY, ERROR_PATH_NOT_FOUND,
|
||||||
|
};
|
||||||
|
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 mut file_path_buffer: [WCHAR; 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
|
||||||
|
// SAFETY: We transmute the handle to be able to cast *mut c_void into a
|
||||||
|
// HANDLE (i32) so rustc will let us call GetFinalPathNameByHandleW. The
|
||||||
|
// reference example code for GetFinalPathNameByHandleW implies that
|
||||||
|
// it is safe for us to leave lpszfilepath uninitialized, so long as
|
||||||
|
// the buffer size is correct. We know the buffer size (MAX_PATH) at
|
||||||
|
// compile time. MAX_PATH is a small number (260) so we can cast it
|
||||||
|
// to a u32.
|
||||||
|
let ret = unsafe {
|
||||||
|
GetFinalPathNameByHandleW(
|
||||||
|
std::mem::transmute(handle),
|
||||||
|
file_path_buffer.as_mut_ptr(),
|
||||||
|
file_path_buffer.len() as u32,
|
||||||
|
FILE_NAME_OPENED,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer_size = match ret {
|
||||||
|
ERROR_PATH_NOT_FOUND | ERROR_NOT_ENOUGH_MEMORY | ERROR_INVALID_PARAMETER => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("GetFinalPathNameByHandleW failed with code {}", ret),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
e if e == 0 => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!(
|
||||||
|
"GetFinalPathNameByHandleW failed with code {}",
|
||||||
|
// SAFETY: GetLastError is thread-safe and has no documented memory unsafety.
|
||||||
|
unsafe { GetLastError() }
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
e => e as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't include the null terminator
|
||||||
|
Ok(String::from_utf16(&file_path_buffer[0..buffer_size])
|
||||||
|
.map_err(|e| USimpleError::new(1, e.to_string()))?
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn test_get_pathbuf_from_stdout_fails_if_stdout_is_not_a_file() {
|
||||||
|
// We can trigger an error by not setting stdout to anything (will
|
||||||
|
// fail with code 1)
|
||||||
|
assert!(super::pathbuf_from_stdout()
|
||||||
|
.err()
|
||||||
|
.expect("pathbuf_from_stdout should have failed")
|
||||||
|
.to_string()
|
||||||
|
.contains("GetFinalPathNameByHandleW failed with code 1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -510,6 +510,27 @@ fn test_touch_no_such_file_error_msg() {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_changes_time_of_file_in_stdout() {
|
||||||
|
// command like: `touch - 1< ./c`
|
||||||
|
// should change the timestamp of c
|
||||||
|
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_changes_time_of_file_in_stdout";
|
||||||
|
|
||||||
|
at.touch(file);
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
let (_, mtime) = get_file_times(&at, file);
|
||||||
|
|
||||||
|
ucmd.args(&["-"])
|
||||||
|
.set_stdout(at.make_file(file))
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
let (_, mtime_after) = get_file_times(&at, file);
|
||||||
|
assert!(mtime_after != mtime);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_touch_permission_denied_error_msg() {
|
fn test_touch_permission_denied_error_msg() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue