6 Commits

Author SHA1 Message Date
Sylvestre Ledru efe6dc05f9 find: use GNU wording "unknown predicate" for unrecognized args
GNU find rejects an unrecognized predicate with
"find: unknown predicate '<arg>'", but we printed
"find: Unrecognized flag: '<arg>'". Match GNU so the
tests/find/refuse-noop GNU test (and others hitting the same path)
pass. Add an integration test covering -noop and ---noop.
2026-06-09 22:40:43 +02:00
weili 53980035af find: -printf: don't panic on a multibyte char after an octal escape
`\NNN` octal escapes were parsed by slicing a fixed 3 bytes off the
format string, which panics when fewer than 3 octal digits are followed
by a multibyte character (e.g. `-printf '\0€'`): the 3-byte slice lands
inside the multibyte char and trips a char-boundary assertion.

Parse the octal escape from the leading octal digits only (1 to 3, all
ASCII) and advance by their byte length. This also fixes `\1`..`\7`,
which previously fell through to the single-character escape table and
errored instead of being treated as octal, matching GNU find.
2026-06-09 22:23:39 +02:00
weili e2d84e98d5 locate: don't panic on a too-short --database file
`locate -d <file> <pattern>` panicked (slice index underflow, exit 101) when
the database file is exactly one byte: `check_db` consumes that byte with
`read_exact`, then `read_until(b'\0', ...)` hits EOF and appends nothing,
leaving `buf` empty. `&buf[..buf.len() - 1]` then underflows `buf.len() - 1`
to `usize::MAX` and slices out of bounds.

Strip the trailing nul with `strip_suffix(b"\0")` (falling back to the whole
buffer when `read_until` stopped at EOF without one) instead of slicing
`buf.len() - 1`. An empty buffer now yields `None` (unrecognized format), so
the too-short database is skipped like any other invalid one and locate exits
1 instead of crashing. Adds a regression test.
2026-06-09 22:23:23 +02:00
dependabot[bot] 865ab0991e build(deps): bump moonrepo/setup-rust from 0 to 1
Bumps [moonrepo/setup-rust](https://github.com/moonrepo/setup-rust) from 0 to 1.
- [Release notes](https://github.com/moonrepo/setup-rust/releases)
- [Changelog](https://github.com/moonrepo/setup-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moonrepo/setup-rust/compare/v0...v1)

---
updated-dependencies:
- dependency-name: moonrepo/setup-rust
  dependency-version: '1'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 09:35:56 +02:00
dependabot[bot] 8add685cc6 build(deps): bump codecov/codecov-action from 6 to 7
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 09:29:29 +02:00
dependabot[bot] c4afeab1bc build(deps): bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 09:29:07 +02:00
8 changed files with 80 additions and 17 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ jobs:
path: lcov.info
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
files: lcov.info
fail_ci_if_error: true
+2 -2
View File
@@ -18,10 +18,10 @@ jobs:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Rust toolchain, cache and cargo-codspeed binary
uses: moonrepo/setup-rust@v0
uses: moonrepo/setup-rust@v1
with:
channel: stable
cache-target: release
+1 -1
View File
@@ -975,7 +975,7 @@ fn build_matcher_tree(
)
}
}
None => return Err(From::from(format!("Unrecognized flag: '{}'", args[i]))),
None => return Err(From::from(format!("unknown predicate '{}'", args[i]))),
}
}
};
+40 -10
View File
@@ -167,16 +167,21 @@ impl FormatStringParser<'_> {
// Try parsing an octal sequence first.
let first = self.front()?;
if first.is_digit(OCTAL_RADIX) {
if let Ok(code) = self.peek(OCTAL_LEN).and_then(|octal| {
u32::from_str_radix(octal, OCTAL_RADIX).map_err(std::convert::Into::into)
}) {
// safe to unwrap: .peek() already succeeded above.
let octal = self.advance_by(OCTAL_LEN).unwrap();
return match char::from_u32(code) {
Some(c) => Ok(FormatComponent::Literal(c.to_string())),
None => Err(format!("Invalid character value: \\{octal}").into()),
};
}
// A GNU octal escape is 1 to 3 octal digits. Consume only the leading
// octal digits (which are ASCII), rather than slicing a fixed 3 bytes
// that can land inside a following multibyte char.
let octal: String = self
.string
.chars()
.take(OCTAL_LEN)
.take_while(|c| c.is_digit(OCTAL_RADIX))
.collect();
let code = u32::from_str_radix(&octal, OCTAL_RADIX)?;
self.advance_by(octal.len())?;
return match char::from_u32(code) {
Some(c) => Ok(FormatComponent::Literal(c.to_string())),
None => Err(format!("Invalid character value: \\{octal}").into()),
};
}
self.advance_one()?;
@@ -688,6 +693,31 @@ mod tests {
assert!(FormatString::parse("\\").is_err());
}
#[test]
fn test_parse_octal_escape_before_multibyte_char() {
assert_eq!(
FormatString::parse("\\0€").unwrap().components,
vec![
FormatComponent::Literal("\0".to_owned()),
FormatComponent::Literal("".to_owned()),
]
);
assert_eq!(
FormatString::parse("\\1😀").unwrap().components,
vec![
FormatComponent::Literal("\u{1}".to_owned()),
FormatComponent::Literal("😀".to_owned()),
]
);
assert_eq!(
FormatString::parse("\\00é").unwrap().components,
vec![
FormatComponent::Literal("\0".to_owned()),
FormatComponent::Literal("é".to_owned()),
]
);
}
#[test]
fn test_parse_formatting() {
fn unaligned_directive(directive: FormatDirective) -> FormatComponent {
+1 -1
View File
@@ -546,7 +546,7 @@ mod tests {
//
let result = super::parse_args(&["-asdadsafsfsadcs"]);
if let Err(e) = result {
assert_eq!(e.to_string(), "Unrecognized flag: '-asdadsafsfsadcs'");
assert_eq!(e.to_string(), "unknown predicate '-asdadsafsfsadcs'");
} else {
panic!("parse_args should have returned an error");
}
+4 -2
View File
@@ -490,8 +490,10 @@ impl DbReader {
return None;
};
// drop nul byte when matching
match String::from_utf8_lossy(&buf[..buf.len() - 1]).as_ref() {
// drop the trailing nul byte when matching. `read_until` may stop at EOF
// without one (e.g. a too-short file), so strip it only if present rather
// than slicing `buf.len() - 1`, which underflows when `buf` is empty.
match String::from_utf8_lossy(buf.strip_suffix(b"\0").unwrap_or(&buf)).as_ref() {
"LOCATE02" => Some(DbFormat::Locate02),
_ => None,
}
+13
View File
@@ -253,3 +253,16 @@ fn test_updatedb_locate_roundtrip() {
"locate output did not contain the indexed file: {stdout:?}"
);
}
#[test]
fn test_locate_one_byte_db() {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join("db_1byte");
std::fs::write(&db, b"X").unwrap();
Command::cargo_bin("locate")
.expect("couldn't find locate binary")
.arg(format!("--database={}", db.display()))
.arg("foo")
.assert()
.code(1);
}
+18
View File
@@ -82,6 +82,24 @@ fn multiple_matcher_success() {
.stdout_contains("abbbc");
}
#[test]
fn unknown_predicate_is_rejected() {
// GNU find rejects an unrecognized predicate with
// "find: unknown predicate '<arg>'" and exit code 1 (see the GNU
// testsuite's tests/find/refuse-noop test).
ucmd()
.arg("-noop")
.fails()
.stderr_contains("unknown predicate '-noop'")
.no_stdout();
ucmd()
.arg("---noop")
.fails()
.stderr_contains("unknown predicate '---noop'")
.no_stdout();
}
#[test]
fn multiple_matcher_failure() {
ucmd()