Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/uu/cksum/src/cksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Set the default algorithm to CRC when not '--check'ing.
let algo_kind = algo_cli.unwrap_or(AlgoKind::Crc);

let tag = matches.get_flag(options::TAG) || !matches.get_flag(options::UNTAGGED);
let tag = !matches.get_flag(options::UNTAGGED); // Making TAG default at clap blocks --untagged
let binary = matches.get_flag(options::BINARY);

let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
Expand Down
35 changes: 12 additions & 23 deletions src/uu/pr/src/pr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,34 +1180,23 @@ fn header_content(options: &OutputOptions, page: usize) -> Vec<String> {
// Use the line width if available, otherwise use default of 72
let total_width = options.line_width.unwrap_or(DEFAULT_COLUMN_WIDTH);

// GNU pr uses a specific layout:
// Date takes up the left part, filename is centered, page is right-aligned
let date_len = date_part.chars().count();
let filename_len = filename.chars().count();
let page_len = page_part.chars().count();

let header_line = if date_len + filename_len + page_len + 2 < total_width {
// Check if we're using a custom date format that needs centered alignment
// This preserves backward compatibility while fixing the GNU time-style test
if date_part.starts_with('+') {
// GNU pr uses centered layout for headers with custom date formats
// The filename should be centered between the date and page parts
let space_for_filename = total_width - date_len - page_len;
let padding_before_filename = (space_for_filename - filename_len) / 2;
let padding_after_filename =
space_for_filename - filename_len - padding_before_filename;

format!(
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
"",
"",
width1 = padding_before_filename,
width2 = padding_after_filename
)
} else {
// For standard date formats, use simple spacing for backward compatibility
format!("{date_part} {filename} {page_part}")
}
// The filename should be centered between the date and page parts
let space_for_filename = total_width - date_len - page_len;
let padding_before_filename = (space_for_filename - filename_len) / 2;
let padding_after_filename = space_for_filename - filename_len - padding_before_filename;

format!(
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
"",
"",
width1 = padding_before_filename,
width2 = padding_after_filename
)
} else {
// If content is too long, just use single spaces
format!("{date_part} {filename} {page_part}")
Expand Down
2 changes: 1 addition & 1 deletion src/uu/touch/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ touch-error-missing-file-operand = missing file operand
Try '{ $help_command } --help' for more information.
touch-error-setting-times-of = setting times of { $filename }
touch-error-setting-times-no-such-file = setting times of { $filename }: No such file or directory
touch-error-cannot-touch = cannot touch { $filename }
touch-error-cannot-touch = cannot touch { $filename }: { $error }
touch-error-no-such-file-or-directory = No such file or directory
touch-error-failed-to-get-attributes = failed to get attributes of { $path }
touch-error-setting-times-of-path = setting times of { $path }
Expand Down
2 changes: 1 addition & 1 deletion src/uu/touch/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ touch-error-missing-file-operand = opérande de fichier manquant
Essayez '{ $help_command } --help' pour plus d'informations.
touch-error-setting-times-of = définition des temps de { $filename }
touch-error-setting-times-no-such-file = définition des temps de { $filename } : Aucun fichier ou répertoire de ce type
touch-error-cannot-touch = impossible de toucher { $filename }
touch-error-cannot-touch = impossible de toucher { $filename }: { $error }
touch-error-no-such-file-or-directory = Aucun fichier ou répertoire de ce type
touch-error-failed-to-get-attributes = échec d'obtention des attributs de { $path }
touch-error-setting-times-of-path = définition des temps de { $path }
Expand Down
162 changes: 88 additions & 74 deletions src/uu/touch/src/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@
use filetime::{FileTime, set_file_times, set_symlink_file_times};
use jiff::{Timestamp, Zoned};
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::fs::{self, File};
use std::io::{Error, ErrorKind};
use std::ffi::OsString;
use std::fs;
use std::io::ErrorKind;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
#[cfg(target_os = "linux")]
use uucore::error::{FromIo, UIoError, UResult, USimpleError};
use uucore::format_usage;
#[cfg(any(unix, target_os = "macos"))]
use uucore::libc;
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
use uucore::translate;
use uucore::{format_usage, show};
use uucore::{show, translate};

use crate::error::TouchError;

Expand Down Expand Up @@ -417,13 +419,19 @@
InputFile::Stdout => (Cow::Owned(pathbuf_from_stdout()?), true),
InputFile::Path(path) => (Cow::Borrowed(path), false),
};
touch_file(&path, is_stdout, opts, atime, mtime).map_err(|e| {
if let Err(e) = touch_file(&path, is_stdout, opts, atime, mtime).map_err(|e| {
TouchError::TouchFileError {
path: path.into_owned(),
index: ind,
error: e,
}
})?;
}) {
if opts.strict {
return Err(e);
}
// If not in strict mode, show the error, but move to the next file.
show!(e);
}
}

Ok(())
Expand All @@ -444,70 +452,57 @@
atime: FileTime,
mtime: FileTime,
) -> UResult<()> {
let filename = if is_stdout {
OsStr::new("-")
} else {
path.as_os_str()
};

let metadata_result = if opts.no_deref {
path.symlink_metadata()
} else {
path.metadata()
};

if let Err(e) = metadata_result {
if e.kind() != ErrorKind::NotFound {
return Err(e.map_err_context(
|| translate!("touch-error-setting-times-of", "filename" => filename.quote()),
));
}

if opts.no_create {
return Ok(());
}

if opts.no_deref {
let e = USimpleError::new(
1,
translate!("touch-error-setting-times-no-such-file", "filename" => filename.quote()),
);
if opts.strict {
return Err(e);
}
show!(e);
return Ok(());
}

if let Err(e) = File::create(path) {
// we need to check if the path is the path to a directory (ends with a separator)
// we can't use File::create to create a directory
// we cannot use path.is_dir() because it calls fs::metadata which we already called
// when stable, we can change to use e.kind() == std::io::ErrorKind::IsADirectory
let is_directory = if let Some(last_char) = path.to_string_lossy().chars().last() {
last_char == std::path::MAIN_SEPARATOR
} else {
false
};
if is_directory {
let custom_err = Error::other(translate!("touch-error-no-such-file-or-directory"));
return Err(custom_err.map_err_context(
|| translate!("touch-error-cannot-touch", "filename" => filename.quote()),
));
}
let e = e.map_err_context(
|| translate!("touch-error-cannot-touch", "filename" => path.quote()),
);
if opts.strict {
return Err(e);
}
show!(e);
return Ok(());
}
// A symlink must exist to set its times, even with -h/--no-dereference.
// We get "setting times of" error later if it doesn't exist. This is
// handled by update_times() and is consistent with GNU.
if opts.no_create || opts.no_deref {
return update_times(path, is_stdout, opts, atime, mtime);
}

// Minor optimization: if no reference time, timestamp, or date was specified, we're done.
if opts.source == Source::Now && opts.date.is_none() {
return Ok(());
// Use OpenOptions to create the file if it doesn't exist. This is used in lieu of
// stat() to avoid TOCTOU issues.

Check failure on line 463 in src/uu/touch/src/touch.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'TOCTOU' (file:'src/uu/touch/src/touch.rs', line:463)

#[cfg(not(unix))]
let touched_file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(path);

// We need to add UNIX flags O_NOCTTY and O_NONBLOCK to match GNU `touch` behavior when
// on UNIX systems. Without O_NONBLOCK, opening something like a FIFO would block indefinitely.
#[cfg(unix)]
let touched_file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
// Use custom flags on UNIX targets to add O_NOCTTY and O_NONBLOCK. O_NONBLOCK is
// necessary for special files like a FIFO to avoid blocking behavior.
.custom_flags(libc::O_NOCTTY | libc::O_NONBLOCK)
.open(path);

if let Err(e) = touched_file {
// We have to check to see if the reason .open() failed above is something
// that is fatal or something expected. The former is handled here, whereas
// the latter is handled in update_times().
let err_good_cause = e.kind() != ErrorKind::IsADirectory;

// For UNIX/MAC, we also have to check special files, like FIFOs.
// If we can't unwrap a raw OS error, default it to 0 so that it's considered true
// for the first Boolean condition.
#[cfg(any(unix, target_os = "macos"))]
let err_good_cause = e.raw_os_error().unwrap_or(0) != libc::ENXIO && err_good_cause;

Check failure on line 494 in src/uu/touch/src/touch.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'ENXIO' (file:'src/uu/touch/src/touch.rs', line:494)

// err_good_cause indicates that the error is not one we should ignore
if err_good_cause {
return Err(TouchError::TouchFileError {
path: path.to_owned(),
index: 0,
error: USimpleError::new(
1,
translate!("touch-error-cannot-touch", "filename" => path.quote(), "error" => UIoError::from(e)),
),
}.into());
}
}

Expand Down Expand Up @@ -579,12 +574,31 @@
// sets the file access and modification times for a file or a symbolic link.
// The filename, access time (atime), and modification time (mtime) are provided as inputs.

if opts.no_deref && !is_stdout {
let result = if opts.no_deref && !is_stdout {
set_symlink_file_times(path, atime, mtime)
} else {
set_file_times(path, atime, mtime)
};

// Originally, the metadata stat() in touch_file() would catch "no create",
// but since it was removed to fix TOCTOU issues, we need to handle it here.

Check failure on line 584 in src/uu/touch/src/touch.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'TOCTOU' (file:'src/uu/touch/src/touch.rs', line:584)
if let Err(ref e) = result {
if opts.no_create && e.kind() != ErrorKind::NotADirectory {
#[cfg(any(unix, target_os = "macos"))]
if e.raw_os_error() != Some(libc::ELOOP) {

Check failure on line 588 in src/uu/touch/src/touch.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'ELOOP' (file:'src/uu/touch/src/touch.rs', line:588)
// ELOOP is returned when trying to stat a dangling symlink with -h/--no-dereference.

Check failure on line 589 in src/uu/touch/src/touch.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'ELOOP' (file:'src/uu/touch/src/touch.rs', line:589)
// However, the ErrorKind is unstable in Rust, so we have to kind
// of hack it like this.
return Ok(());
}
#[cfg(not(any(unix, target_os = "macos")))]
return Ok(());
}
return result.map_err_context(
|| translate!("touch-error-setting-times-of-path", "path" => path.quote()),
);
}
.map_err_context(|| translate!("touch-error-setting-times-of-path", "path" => path.quote()))
Ok(())
}

/// Get metadata of the provided path
Expand Down
29 changes: 1 addition & 28 deletions src/uu/uniq/src/uniq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ struct Uniq {
struct LineMeta {
key_start: usize,
key_end: usize,
lowercase: Vec<u8>,
use_lowercase: bool,
}

macro_rules! write_line_terminator {
Expand Down Expand Up @@ -152,18 +150,7 @@ impl Uniq {
return first_slice != second_slice;
}

let first_cmp = if first_meta.use_lowercase {
first_meta.lowercase.as_slice()
} else {
first_slice
};
let second_cmp = if second_meta.use_lowercase {
second_meta.lowercase.as_slice()
} else {
second_slice
};

first_cmp != second_cmp
!first_slice.eq_ignore_ascii_case(second_slice)
}

fn key_bounds(&self, line: &[u8]) -> (usize, usize) {
Expand Down Expand Up @@ -230,20 +217,6 @@ impl Uniq {
let (key_start, key_end) = self.key_bounds(line);
meta.key_start = key_start;
meta.key_end = key_end;

if self.ignore_case && key_start < key_end {
let slice = &line[key_start..key_end];
if slice.iter().any(|b| b.is_ascii_uppercase()) {
meta.lowercase.clear();
meta.lowercase.reserve(slice.len());
meta.lowercase
.extend(slice.iter().map(|b| b.to_ascii_lowercase()));
meta.use_lowercase = true;
return;
}
}

meta.use_lowercase = false;
}

fn read_line(
Expand Down
3 changes: 0 additions & 3 deletions src/uucore/src/lib/features/checksum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,6 @@ pub enum ChecksumError {
#[error("the --raw option is not supported with multiple files")]
RawMultipleFiles,

#[error("the --{0} option is meaningful only when verifying checksums")]
CheckOnlyFlag(String),

// --length sanitization errors
#[error("--length required for {}", .0.quote())]
LengthRequired(String),
Expand Down
25 changes: 25 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7444,3 +7444,28 @@ fn test_cp_archive_deref_flag_ordering() {
assert_eq!(at.is_symlink(&dest), expect_symlink, "failed for {flags}");
}
}

/// Test that copying to an existing file maintains its permissions, unix only because .mode() only
/// works on Unix
#[test]
#[cfg(unix)]
fn test_cp_to_existing_file_permissions() {
let (at, mut ucmd) = at_and_ucmd!();

at.touch("src");
at.touch("dst");

let src_path = at.plus("src");
let dst_path = at.plus("dst");

let mut src_permissions = std::fs::metadata(&src_path).unwrap().permissions();
src_permissions.set_readonly(true);
std::fs::set_permissions(&src_path, src_permissions).unwrap();

let dst_mode = std::fs::metadata(&dst_path).unwrap().permissions().mode();

ucmd.args(&["src", "dst"]).succeeds();

let new_dst_mode = std::fs::metadata(&dst_path).unwrap().permissions().mode();
assert_eq!(dst_mode, new_dst_mode);
}
Loading
Loading