Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

touch: implement - #3158

Merged
merged 37 commits into from
Mar 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d1bb27f
touch: reproduce empty-file.sh test failure
dgunay Feb 19, 2022
7818c6b
touch: fix test semantics
dgunay Feb 19, 2022
8f3f3c1
touch: implement '-'
dgunay Feb 19, 2022
7b6a2c2
touch: scratch work for '-' support on windows
Feb 19, 2022
cf3556b
touch: WIP using GetFinalPathNameByHandleA on windows
dgunay Feb 20, 2022
029acaa
touch: working '-' on Windows CMD
dgunay Feb 20, 2022
df6633b
move windows import to cfg(windows)
dgunay Feb 20, 2022
1ce271f
touch: don't use std::mem::uninitialized
dgunay Feb 20, 2022
d8f0a4c
touch: use UResult instead of panicking
dgunay Feb 20, 2022
ec3b0e7
touch: make unsafe blocks smaller, add safety comments
dgunay Feb 20, 2022
4c3baed
fix compilation on linux
dgunay Feb 20, 2022
3025162
touch: condense error handling of GetFinalPathNameByHandleW
dgunay Feb 20, 2022
6297d1a
appease spellchecker
dgunay Feb 20, 2022
efa4b4c
touch: appease spellchecker
dgunay Feb 21, 2022
1f0b323
fix test failures from mixing windows crates
dgunay Feb 23, 2022
3bbbf55
rename path_from_stdout -> pathbuf_...
dgunay Feb 23, 2022
27d8872
satisfy Style/deps
dgunay Feb 23, 2022
84a87be
touch: reproduce empty-file.sh test failure
dgunay Feb 19, 2022
5602c4a
touch: fix test semantics
dgunay Feb 19, 2022
cf1a846
touch: implement '-'
dgunay Feb 19, 2022
8f508d9
touch: scratch work for '-' support on windows
Feb 19, 2022
eac35ff
touch: WIP using GetFinalPathNameByHandleA on windows
dgunay Feb 20, 2022
b8af2ef
touch: working '-' on Windows CMD
dgunay Mar 2, 2022
b3508c4
move windows import to cfg(windows)
dgunay Mar 2, 2022
daf5fce
touch: don't use std::mem::uninitialized
dgunay Feb 20, 2022
fdfeceb
touch: use UResult instead of panicking
dgunay Feb 20, 2022
815f2cf
touch: make unsafe blocks smaller, add safety comments
dgunay Feb 20, 2022
bdd3d63
fix compilation on linux
dgunay Feb 20, 2022
4ff0bff
touch: condense error handling of GetFinalPathNameByHandleW
dgunay Feb 20, 2022
7d08128
appease spellchecker
dgunay Feb 20, 2022
3625525
touch: appease spellchecker
dgunay Feb 21, 2022
a2d1e7a
fix test failures from mixing windows crates
dgunay Mar 2, 2022
e504640
rename path_from_stdout -> pathbuf_...
dgunay Feb 23, 2022
ef689c9
satisfy Style/deps
dgunay Feb 23, 2022
ad4f3d0
Merge branch 'main' into bug/stdin-touch-change-mtime
sylvestre Mar 4, 2022
ad1399e
add test for one error branch of pathbuf_from_stdout
dgunay Mar 5, 2022
f1adb06
Merge branch 'bug/stdin-touch-change-mtime' of https://github.com/dgu…
dgunay Mar 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/uu/touch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ clap = { version = "3.0", features = ["wrap_help", "cargo"] }
time = "0.1.40"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3" }

[[bin]]
name = "touch"
path = "src/main.rs"
100 changes: 97 additions & 3 deletions src/uu/touch/src/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// For the full copyright and license information, please view the LICENSE file
// 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;

Expand All @@ -16,7 +16,7 @@ extern crate uucore;
use clap::{crate_version, App, AppSettings, Arg, ArgGroup};
use filetime::*;
use std::fs::{self, File};
use std::path::Path;
use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::error::{FromIo, UError, UResult, USimpleError};
use uucore::format_usage;
Expand Down Expand Up @@ -77,7 +77,15 @@ Try 'touch --help' for more information."##,
};

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 matches.is_present(options::NO_CREATE) {
continue;
Expand Down Expand Up @@ -299,3 +307,89 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {

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"))
dgunay marked this conversation as resolved.
Show resolved Hide resolved
}
#[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!(
dgunay marked this conversation as resolved.
Show resolved Hide resolved
"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"));
}
}
21 changes: 21 additions & 0 deletions tests/by-util/test_touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
#[cfg(unix)]
fn test_touch_permission_denied_error_msg() {
Expand Down