Skip to content

Commit 8671fff

Browse files
authored
Merge branch 'main' into yes-sigpipe
2 parents 9a5c675 + 938039e commit 8671fff

29 files changed

Lines changed: 795 additions & 137 deletions

File tree

.github/workflows/code-quality.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ jobs:
147147
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)"
148148
S=$(cargo clippy --all-targets $extra --tests --benches -pcoreutils ${CARGO_UTILITY_LIST_OPTIONS} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
149149
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
150+
- name: "cargo clippy on fuzz dir"
151+
if: runner.os != 'Windows'
152+
shell: bash
153+
run: |
154+
cd fuzz
155+
cargo clippy --workspace --all-targets --all-features -- -D warnings
150156
151157
style_spellcheck:
152158
name: Style/spelling

Cargo.lock

Lines changed: 15 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ rand = { version = "0.9.0", features = ["small_rng"] }
358358
rand_core = "0.9.0"
359359
rayon = "1.10"
360360
regex = "1.10.4"
361+
rlimit = "0.10.1"
361362
rstest = "0.26.0"
362363
rust-ini = "0.21.0"
363364
same-file = "1.0.6"
@@ -535,13 +536,15 @@ filetime.workspace = true
535536
glob.workspace = true
536537
jiff.workspace = true
537538
libc.workspace = true
539+
bytecount.workspace = true
538540
num-prime.workspace = true
539541
pretty_assertions = "1.4.0"
540542
rand.workspace = true
541543
regex.workspace = true
542544
sha1 = { workspace = true, features = ["std"] }
543545
tempfile.workspace = true
544546
time = { workspace = true, features = ["local-offset"] }
547+
unicode-width.workspace = true
545548
unindent = "0.2.3"
546549
uutests.workspace = true
547550
uucore = { workspace = true, features = [
@@ -561,11 +564,10 @@ nix = { workspace = true, features = [
561564
"process",
562565
"signal",
563566
"socket",
564-
"user",
565567
"term",
568+
"user",
566569
] }
567-
rlimit = "0.10.1"
568-
xattr.workspace = true
570+
rlimit = { workspace = true }
569571

570572
# Used in test_uptime::test_uptime_with_file_containing_valid_boot_time_utmpx_record
571573
# to deserialize an utmpx struct into a binary file

fuzz/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
rustflags = ["--cfg", "fuzzing"]

fuzz/fuzz_targets/fuzz_non_utf8_paths.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::env::temp_dir;
1414
use std::ffi::{OsStr, OsString};
1515
use std::fs;
1616
use std::os::unix::ffi::{OsStrExt, OsStringExt};
17-
use std::path::PathBuf;
17+
use std::path::{Path, PathBuf};
1818

1919
use uufuzz::{CommandResult, run_gnu_cmd};
2020
// Programs that typically take file/path arguments and should be tested
@@ -148,15 +148,15 @@ fn setup_test_files() -> Result<(PathBuf, Vec<PathBuf>), std::io::Error> {
148148
// Try to create the file - this may fail on some filesystems
149149
if let Ok(mut file) = fs::File::create(&file_path) {
150150
use std::io::Write;
151-
let _ = write!(file, "test content for file {}\n", i);
151+
let _ = writeln!(file, "test content for file {}", i);
152152
test_files.push(file_path);
153153
}
154154
}
155155

156156
Ok((temp_root, test_files))
157157
}
158158

159-
fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResult {
159+
fn test_program_with_non_utf8_path(program: &str, path: &Path) -> CommandResult {
160160
let path_os = path.as_os_str();
161161

162162
// Use the locally built uutils binary instead of system PATH

fuzz/fuzz_targets/fuzz_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ fn generate_test_arg() -> String {
135135
if test_arg.arg_type == ArgType::INTEGER {
136136
arg.push_str(&format!(
137137
"{} {} {}",
138-
rng.random_range(-100..=100).to_string(),
138+
rng.random_range(-100..=100),
139139
test_arg.arg,
140-
rng.random_range(-100..=100).to_string()
140+
rng.random_range(-100..=100)
141141
));
142142
} else if test_arg.arg_type == ArgType::STRINGSTRING {
143143
let random_str = generate_random_string(rng.random_range(1..=10));

src/uu/date/src/date.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes getres AWST ACST AEST
6+
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes getres AWST ACST AEST foobarbaz
77

88
mod locale;
99

1010
use clap::{Arg, ArgAction, Command};
1111
use jiff::fmt::strtime::{self, BrokenDownTime, Config, PosixCustom};
1212
use jiff::tz::{TimeZone, TimeZoneDatabase};
1313
use jiff::{Timestamp, Zoned};
14+
use std::borrow::Cow;
1415
use std::collections::HashMap;
1516
use std::fs::File;
1617
use std::io::{BufRead, BufReader, BufWriter, Write};
@@ -130,6 +131,42 @@ enum DayDelta {
130131
Next,
131132
}
132133

134+
/// Strip parenthesized comments from a date string.
135+
///
136+
/// GNU date removes balanced parentheses and their content, treating them as comments.
137+
/// If parentheses are unbalanced, everything from the unmatched '(' onwards is ignored.
138+
///
139+
/// Examples:
140+
/// - "2026(comment)-01-05" -> "2026-01-05"
141+
/// - "1(ignore comment to eol" -> "1"
142+
/// - "(" -> ""
143+
/// - "((foo)2026-01-05)" -> ""
144+
fn strip_parenthesized_comments(input: &str) -> Cow<'_, str> {
145+
if !input.contains('(') {
146+
return Cow::Borrowed(input);
147+
}
148+
149+
let mut result = String::with_capacity(input.len());
150+
let mut depth = 0;
151+
152+
for c in input.chars() {
153+
match c {
154+
'(' => {
155+
depth += 1;
156+
}
157+
')' if depth > 0 => {
158+
depth -= 1;
159+
}
160+
_ if depth == 0 => {
161+
result.push(c);
162+
}
163+
_ => {}
164+
}
165+
}
166+
167+
Cow::Owned(result)
168+
}
169+
133170
/// Parse military timezone with optional hour offset.
134171
/// Pattern: single letter (a-z except j) optionally followed by 1-2 digits.
135172
/// Returns Some(total_hours_in_utc) or None if pattern doesn't match.
@@ -286,7 +323,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
286323
// Iterate over all dates - whether it's a single date or a file.
287324
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
288325
DateSource::Human(ref input) => {
326+
// GNU compatibility (Comments in parentheses)
327+
let input = strip_parenthesized_comments(input);
289328
let input = input.trim();
329+
290330
// GNU compatibility (Empty string):
291331
// An empty string (or whitespace-only) should be treated as midnight today.
292332
let is_empty_or_whitespace = input.is_empty();
@@ -887,4 +927,38 @@ mod tests {
887927
assert_eq!(parse_military_timezone_with_offset("m999"), None); // Too long
888928
assert_eq!(parse_military_timezone_with_offset("9m"), None); // Starts with digit
889929
}
930+
931+
#[test]
932+
fn test_strip_parenthesized_comments() {
933+
assert_eq!(strip_parenthesized_comments("hello"), "hello");
934+
assert_eq!(strip_parenthesized_comments("2026-01-05"), "2026-01-05");
935+
assert_eq!(strip_parenthesized_comments("("), "");
936+
assert_eq!(strip_parenthesized_comments("1(comment"), "1");
937+
assert_eq!(
938+
strip_parenthesized_comments("2026-01-05(this is a comment"),
939+
"2026-01-05"
940+
);
941+
assert_eq!(
942+
strip_parenthesized_comments("2026(comment)-01-05"),
943+
"2026-01-05"
944+
);
945+
assert_eq!(strip_parenthesized_comments("()"), "");
946+
assert_eq!(strip_parenthesized_comments("((foo)2026-01-05)"), "");
947+
948+
// These cases test the balanced parentheses removal feature
949+
// which extends beyond what GNU date strictly supports
950+
assert_eq!(strip_parenthesized_comments("a(b)c"), "ac");
951+
assert_eq!(strip_parenthesized_comments("a(b)c(d)e"), "ace");
952+
assert_eq!(strip_parenthesized_comments("(a)(b)"), "");
953+
954+
// When parentheses are unmatched, processing stops at the unmatched opening paren
955+
// In this case "a(b)c(d", the (b) is balanced but (d is unmatched
956+
// We process "a(b)c" and stop at the unmatched "(d"
957+
assert_eq!(strip_parenthesized_comments("a(b)c(d"), "ac");
958+
959+
// Additional edge cases for nested and complex parentheses
960+
assert_eq!(strip_parenthesized_comments("a(b(c)d)e"), "ae"); // Nested balanced
961+
assert_eq!(strip_parenthesized_comments("a(b(c)d"), "a"); // Nested unbalanced
962+
assert_eq!(strip_parenthesized_comments("a(b)c(d)e(f"), "ace"); // Multiple groups, last unmatched
963+
}
890964
}

src/uu/dd/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ thiserror = { workspace = true }
3232
fluent = { workspace = true }
3333

3434
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
35-
signal-hook = { workspace = true }
3635
nix = { workspace = true, features = ["fs"] }
36+
signal-hook = { workspace = true }
3737

3838
[[bin]]
3939
name = "dd"

0 commit comments

Comments
 (0)