16 Commits
Author SHA1 Message Date
Sylvestre Ledru 2ce633cb8d prepare version 0.2.0 (#519) 2026-08-09 01:57:25 +02:00
Mukunda Rao Katta 91465e566b fix: identify uutils in cli output (#435) 2026-08-09 00:55:37 +02:00
Sylvestre Ledru d842aa305e sed: fix clippy cast_possible_truncation errors (#518) 2026-08-09 00:52:26 +02:00
xtqqczze d28a1f8e73 chore(cargo): remove deprecated package authors field (#444) 2026-08-08 23:28:22 +02:00
renovate[bot] af5f8afdcb Update Rust crate clap_complete to v4.6.9 2026-08-07 08:07:56 +02:00
renovate[bot] 8f3f126816 Update Rust crate clap_mangen to v0.3.2 2026-08-07 08:05:08 +02:00
renovate[bot] c771318a7a Update Rust crate clap to v4.6.6 2026-08-07 08:03:52 +02:00
renovate[bot] 36a1cf4954 Update Rust crate uucore to 0.10.0 2026-08-06 09:02:46 +02:00
renovate[bot] 13bb82ba13 Update Rust crate clap_mangen to v0.3.1 2026-08-06 08:32:10 +02:00
renovate[bot] cb9eb1da42 Update Rust crate uufuzz to 0.10.0 2026-08-06 08:15:55 +02:00
renovate[bot] b101462d1b Update Rust crate uutests to 0.10.0 2026-08-06 08:10:31 +02:00
oech3 9f2e1a1ad4 remove sysinfo dep 2026-08-05 14:18:30 +02:00
Franklin-Qi 2b092d53b0 fast_io.rs: replace the unsafe libc::copy_file_range call by rustix (#458)
Replace the unsafe `libc::copy_file_range` call with the safe `rustix::fs::copy_file_range`.

Closes: #442
2026-08-05 13:32:45 +02:00
renovate[bot] 550d545d72 Update Rust crate similar to v3.1.2 2026-08-05 08:01:27 +02:00
Diomidis Spinellisandmukunda katta a5d219764f feat: add T command (branch on no-substitution) (#508)
* feat: add T command (branch on no-substitution)

Implements the GNU `T LABEL` command: branch to LABEL if no s///
substitution has succeeded since the last input line was read or the
last t/T was taken. It is the inverse of `t` and shares the same
address and label parsing.

Wires 'T' into get_cmd_spec, resolve_branch_targets, and the processor
branch logic, resetting the substitution flag on evaluation just like
't'. Adds integration tests (simple branch + flag-clear) and a cmd-spec
unit test.

Closes #395

Co-authored-by: Diomidis Spinellis <dds@aueb.gr>

* ci: re-trigger checks (windows runner crates.io download flake)

* Disable T in POSIX mode

---------

Co-authored-by: mukunda katta <mukunda.vjcs6@gmail.com>
2026-08-02 17:47:18 +03:00
Develandpre-commit-ci[bot] 397ab86302 Implement v command #397 (#420)
* implement version command

* add tests for version option

* fix patch comparsion

* absurd extreme comparsion fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* accept missing minor or patch

* guard 'v' with if posix

* add a few tests

* add test_default_version

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* check if minor is empty and push zero instead of shadowing

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-01 18:18:05 +03:00
12 changed files with 493 additions and 520 deletions

No files matched your search

Generated
+62 -323
View File
File diff suppressed because it is too large. Load diff
+6 -8
View File
@@ -5,8 +5,7 @@
[package]
name = "sed"
version = "0.1.1"
authors = ["uutils developers"]
version = "0.2.0"
license = "MIT"
description = "sed ~ implemented as universal (cross-platform) utils, written in Rust"
default-run = "sed"
@@ -29,7 +28,6 @@ sed = []
[workspace.dependencies]
assert_fs = "1.1.3"
bytesize = "2.0.0"
chrono = { version = "0.4.37", default-features = false, features = [
"clock",
] }
@@ -48,11 +46,11 @@ predicates = "3.1.3"
rand = { version = "0.10.0" }
regex = "1.10.4"
sha2 = "0.11"
sysinfo = "0.38"
tempfile = "3.10.1"
terminal_size = "0.4.2"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
uucore = { version = "0.9.0", features = ["libc"] }
uucore = { version = "0.10.0", features = ["libc"] }
rustix = "1.1.4"
xattr = "1.3.1"
@@ -66,11 +64,11 @@ memmap2.workspace = true
phf = { workspace = true }
predicates = { workspace = true }
regex = { workspace = true }
sysinfo = { workspace = true }
tempfile = { workspace = true }
terminal_size = { workspace = true }
textwrap = { workspace = true }
uucore = { workspace = true }
rustix = { workspace = true }
[dev-dependencies]
assert_fs = { workspace = true }
@@ -85,7 +83,7 @@ regex = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
uutests = "0.9.0"
uutests = "0.10.0"
[target.'cfg(unix)'.dev-dependencies]
xattr = { workspace = true }
@@ -135,7 +133,7 @@ must_use_candidate = "allow" # 56
needless_raw_string_hashes = "allow" # 20
needless_pass_by_value = "allow" # 15
missing_panics_doc = "allow" # 12
cast_possible_truncation = "allow" # 7
# cast_possible_truncation = "allow" # 7
unnecessary_wraps = "allow" # 6
match_wildcard_for_single_variants = "allow" # 4
cast_sign_loss = "allow" # 4
+11 -125
View File
@@ -646,15 +646,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "ntapi"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -683,25 +674,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -937,7 +909,7 @@ dependencies = [
"phf_codegen",
"predicates",
"regex",
"sysinfo",
"rustix",
"tempfile",
"terminal_size",
"textwrap",
@@ -1013,9 +985,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "similar"
version = "3.1.1"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16"
checksum = "85ee016af5d736b69fc89e19254540fa4b5f5492853fb5503920f084011c78b6"
dependencies = [
"bstr",
]
@@ -1055,20 +1027,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sysinfo"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows",
]
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -1076,7 +1034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys",
@@ -1194,9 +1152,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uucore"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "069b34217c27f611e1589f540f58118dbf226a9e407d38ab472052ff075a1dc2"
checksum = "e97dd0fd8bb2ed12affba9f956c3b94b8ad3f9432746717475420e916ae0b8d4"
dependencies = [
"bigdecimal",
"clap",
@@ -1212,6 +1170,7 @@ dependencies = [
"rustix",
"thiserror",
"unic-langid",
"unicode-width",
"uucore_procs",
"wild",
]
@@ -1233,9 +1192,9 @@ dependencies = [
[[package]]
name = "uucore_procs"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34337da211e7abfff7189b794afb3b5018fe356fb36474b73645458fc1201350"
checksum = "907f1b828046ea42cdb4bd898f05b48ab873a41937bf75ebe12f09a5bf411f58"
dependencies = [
"proc-macro2",
"quote",
@@ -1243,9 +1202,9 @@ dependencies = [
[[package]]
name = "uufuzz"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4192b67d5e7f0a3300db353ec3217beee89442f02c11d8ae108867eee392d0"
checksum = "72d4c5bac0f8ccabaa8e8ccbf2724446f8b007c06ad343fabfe1a1525a4ff4f2"
dependencies = [
"console",
"rand",
@@ -1361,49 +1320,6 @@ dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -1417,17 +1333,6 @@ dependencies = [
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
@@ -1456,16 +1361,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.4.1"
@@ -1493,15 +1388,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
+2 -2
View File
@@ -14,8 +14,8 @@ libc = "0.2.153"
tempfile = "3.15.0"
rand = { version = "0.10.0" }
similar = "3.0.0"
uucore = { version = "0.9.0", features = ["libc"] }
uufuzz = "0.9.0"
uucore = { version = "0.10.0", features = ["libc"] }
uufuzz = "0.10.0"
sed = { path = ".." }
+8 -8
View File
@@ -255,12 +255,12 @@ impl Default for Transliteration {
/// Create a new Transliteration with identity mapping for the fast-path.
fn default() -> Self {
let mut fast = [0u8; 256];
for (i, slot) in fast.iter_mut().enumerate() {
*slot = i as u8;
for (slot, value) in fast.iter_mut().zip(0..=u8::MAX) {
*slot = value;
}
let mut unicode_fast = ['\0'; COMMON_UNICODE];
for (i, slot) in unicode_fast.iter_mut().enumerate() {
*slot = char::from_u32(i as u32).unwrap_or('\0');
for (slot, cp) in unicode_fast.iter_mut().zip(0u32..) {
*slot = char::from_u32(cp).unwrap_or('\0');
}
Self {
byte_fast: fast,
@@ -361,11 +361,11 @@ impl Command {
#[derive(Debug)]
/// Command-specific data
/// After parsing, t, b Label elements are converted into BranchTarget ones.
/// After parsing, t, T, b Label elements are converted into BranchTarget ones.
pub enum CommandData {
None,
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', '{'
Label(Option<String>), // Label name for 'b', 't', ':'
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', 'T', '{'
Label(Option<String>), // Label name for 'b', 't', 'T', ':'
Path(PathBuf), // File path for 'r'
NamedWriter(Rc<RefCell<NamedWriter>>), // File output for 'w'
Number(usize), // Number for 'l', 'q', 'Q' (GNU)
@@ -593,7 +593,7 @@ mod tests {
fn test_all_fast_path_mapped_to_space() {
let mut t = Transliteration::default();
for cp in 0..COMMON_UNICODE {
if let Some(ch) = char::from_u32(cp as u32) {
if let Some(ch) = u32::try_from(cp).ok().and_then(char::from_u32) {
t.insert(ch, ' ');
}
}
+86 -2
View File
@@ -213,8 +213,8 @@ fn resolve_branch_targets(
resolve_branch_targets(Some(sub_head.clone()), context)?;
}
// Only for 't' or 'b' commands:
if matches!(cmd.code, 't' | 'b') {
// Only for 't', 'T', or 'b' commands:
if matches!(cmd.code, 't' | 'T' | 'b') {
// Take ownership of the current data
let old_data = mem::replace(&mut cmd.data, CommandData::None);
@@ -1373,6 +1373,72 @@ fn compile_text_command_posix(
Ok(CommandHandling::Continue)
}
// Handle v
fn compile_version_command(
lines: &mut ScriptLineProvider,
line: &mut ScriptCharProvider,
_cmd: &mut Command,
_context: &mut ProcessingContext,
) -> UResult<CommandHandling> {
// Claim version partify with GNU sed 4.9
const GNU_MAJOR: u8 = 4;
const GNU_MINOR: u8 = 9;
const GNU_PATCH: u8 = 0;
line.advance();
line.eat_spaces(); // Skip any leading whitespace.
let mut major = String::new();
let mut minor = String::new();
let mut patch = String::new();
let mut ver_semantic = 0;
while !line.eol() {
if line.current() == '.' {
ver_semantic += 1;
line.advance();
} else {
match ver_semantic {
0 => major.push(line.current()),
1 => minor.push(line.current()),
2 => patch.push(line.current()),
_ => return compilation_error(lines, line, "invalid version of sed"),
}
line.advance();
}
}
if major.is_empty() {
major = GNU_MAJOR.to_string();
minor = GNU_MINOR.to_string();
patch = GNU_PATCH.to_string();
}
if minor.is_empty() {
minor.push('0');
}
if patch.is_empty() {
patch.push('0');
}
match major.parse::<u8>() {
Ok(major_int) => match minor.parse::<u8>() {
Ok(minor_int) => match patch.parse::<u8>() {
Ok(patch_int) => {
if minor_int <= GNU_MINOR && major_int <= GNU_MAJOR && patch_int == GNU_PATCH {
return Ok(CommandHandling::Continue);
}
compilation_error(lines, line, "expected newer version of sed")
}
Err(_) => compilation_error(lines, line, "invalid version of sed"),
},
Err(_) => compilation_error(lines, line, "invalid version of sed"),
},
Err(_) => compilation_error(lines, line, "invalid version of sed"),
}
}
// Handles e
// With no argument, the command executes the pattern space as a shell
// command at runtime. With an argument, the rest of the line is the
@@ -1574,6 +1640,10 @@ fn get_cmd_spec(
n_addr: 2,
handler: compile_subst_command,
}),
'T' if !posix => Ok(CommandSpec {
n_addr: 2,
handler: compile_label_command,
}),
'w' => Ok(CommandSpec {
n_addr: 2,
handler: compile_write_file_command,
@@ -1582,6 +1652,10 @@ fn get_cmd_spec(
n_addr: 2,
handler: compile_trans_command,
}),
'v' if !posix => Ok(CommandSpec {
n_addr: 0,
handler: compile_version_command,
}),
_ => compilation_error(lines, line, format!("invalid command code `{cmd_code}'")),
}
}
@@ -1665,6 +1739,16 @@ mod tests {
);
}
#[test]
fn test_lookup_branch_commands() {
// b, t, and T all share compile_label_command and accept 2 addresses.
for code in ['b', 't', 'T'] {
let (lines, line) = make_providers("123abc");
let cmd = get_cmd_spec(&lines, &line, code, false).unwrap();
assert_eq!(cmd.n_addr, 2, "command `{code}` should accept 2 addresses");
}
}
// Utility to create a ScriptCharProvider from a &str
fn char_provider_from(s: &str) -> ScriptCharProvider {
ScriptCharProvider::new(s)
+156 -46
View File
@@ -29,6 +29,11 @@ use std::marker::PhantomData;
#[cfg(unix)]
use std::os::fd::RawFd;
#[cfg(unix)]
use rustix::fd::BorrowedFd;
#[cfg(target_os = "linux")]
use rustix::fs::copy_file_range as rustix_copy_file_range;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
@@ -402,9 +407,24 @@ impl FastCopy {
Self {
fd,
is_regular: ftype as libc::mode_t == libc::S_IFREG,
block_size: st.st_blksize as usize,
// st_blksize is always positive and small; fall back to a common
// block size if it ever doesn't fit in usize.
block_size: usize::try_from(st.st_blksize).unwrap_or(4096),
}
}
/// Return a borrowed file descriptor.
///
/// # Safety invariant
///
/// `FastCopy` only stores file descriptors obtained from open
/// [`File`](std::fs::File) objects. Those `File`s are kept alive
/// by the owning [`MmapLineCursor`] / output-buffer, guaranteeing
/// the fd remains valid for the duration of any borrow.
pub fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: self.fd is a valid file descriptor owned by a live File.
unsafe { BorrowedFd::borrow_raw(self.fd) }
}
}
#[cfg(test)]
@@ -798,10 +818,10 @@ impl OutputBuffer {
if chunk.in_fast_copy.is_regular && self.fast_copy.is_regular {
portable_copy_file_range(
chunk.out_ptr,
chunk.in_fast_copy.fd,
chunk.in_fast_copy.as_fd(),
// Input file offset
unsafe { chunk.out_ptr.offset_from(chunk.base_ptr) } as libc::off_t,
self.fast_copy.fd,
self.fast_copy.as_fd(),
chunk.len,
// Alignment block size: the largest of the two
chunk.in_fast_copy.block_size.max(self.fast_copy.block_size),
@@ -916,9 +936,9 @@ fn reliable_write(fd: i32, ptr: *const u8, len: usize) -> std::io::Result<usize>
#[allow(unused_variables)]
fn portable_copy_file_range(
in_ptr: *const u8,
in_fd: i32,
in_fd: BorrowedFd<'_>,
in_off: libc::off_t,
out_fd: i32,
out_fd: BorrowedFd<'_>,
len: usize,
block_size: usize,
cover: WriteRange,
@@ -933,7 +953,7 @@ fn portable_copy_file_range(
}
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
{
reliable_write(out_fd, in_ptr, len)
reliable_write(out_fd.as_raw_fd(), in_ptr, len)
}
}
@@ -944,37 +964,34 @@ fn portable_copy_file_range(
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn reliable_copy_file_range(
in_ptr: *const u8,
in_fd: i32,
in_fd: BorrowedFd<'_>,
mut in_off: libc::off_t,
out_fd: i32,
out_fd: BorrowedFd<'_>,
len: usize,
) -> std::io::Result<usize> {
let mut pending = len;
while pending > 0 {
let ret = unsafe {
libc::copy_file_range(
in_fd,
&raw mut in_off,
out_fd,
std::ptr::null_mut(), // Use and update output offset
pending,
0,
)
};
if ret < 0 {
let err = io::Error::last_os_error();
return match err.raw_os_error() {
Some(libc::ENOSYS) | Some(libc::EOPNOTSUPP) | Some(libc::EXDEV) => {
// Fallback to write(2).
reliable_write(out_fd, in_ptr, pending)
}
_ => Err(err),
};
} else if ret == 0 {
// EOF reached
break;
let mut in_off_u64 = in_off as u64;
let result: std::io::Result<usize> =
rustix_copy_file_range(in_fd, Some(&mut in_off_u64), out_fd, None, pending)
.map_err(std::io::Error::from);
match result {
Ok(0) => break,
Ok(ret) => {
pending -= ret;
in_off = in_off_u64 as libc::off_t;
}
Err(err) => {
return match err.raw_os_error() {
Some(libc::ENOSYS) | Some(libc::EOPNOTSUPP) | Some(libc::EXDEV) => {
// Fallback to write(2).
reliable_write(out_fd.as_raw_fd(), in_ptr, pending)
}
_ => Err(err),
};
}
}
pending -= ret as usize;
}
Ok(len)
}
@@ -986,24 +1003,27 @@ fn reliable_copy_file_range(
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn aligned_copy_file_range(
mut in_ptr: *const u8,
in_fd: i32,
in_fd: BorrowedFd<'_>,
mut in_off: libc::off_t,
out_fd: i32,
out_fd: BorrowedFd<'_>,
len: usize,
block_size: usize,
cover: WriteRange,
) -> std::io::Result<usize> {
// 1. Get current output offset.
let res = unsafe { libc::lseek(out_fd, 0, libc::SEEK_CUR) as i64 };
if res < 0 {
return Err(std::io::Error::last_os_error());
}
let out_off = res as usize;
// Get current output offset.
// If either offset doesn't fit in usize (only possible on 32-bit
// targets), give up on alignment and just copy everything.
let (Ok(out_off), Ok(in_off_bytes)) = (
usize::try_from(rustix::fs::tell(out_fd)?),
usize::try_from(in_off),
) else {
return reliable_copy_file_range(in_ptr, in_fd, in_off, out_fd, len);
};
let mut pending = len;
// Obtain head alignment.
// Bytes to end of block:
let remainder = in_off as usize % block_size;
let remainder = in_off_bytes % block_size;
// Bytes to write (block_size becomes 0):
let head_align = (block_size - remainder) % block_size;
@@ -1015,7 +1035,7 @@ fn aligned_copy_file_range(
if head_align > 0 {
// Align the two files on a block boundary.
let head_len = head_align.min(pending);
reliable_write(out_fd, in_ptr, head_len)?;
reliable_write(out_fd.as_raw_fd(), in_ptr, head_len)?;
in_ptr = unsafe { in_ptr.add(head_len) };
in_off += head_len as i64;
pending -= head_len;
@@ -1029,7 +1049,7 @@ fn aligned_copy_file_range(
// Copy tail if needed.
if pending > 0 && cover == WriteRange::Complete {
in_ptr = unsafe { in_ptr.add(aligned_len) };
pending -= reliable_write(out_fd, in_ptr, pending)?;
pending -= reliable_write(out_fd.as_raw_fd(), in_ptr, pending)?;
}
Ok(len - pending)
@@ -1062,6 +1082,8 @@ mod tests {
#[cfg(all(target_os = "linux", target_env = "gnu"))]
use std::io::{self, Write};
use std::io::{Seek, SeekFrom};
#[cfg(target_os = "linux")]
use std::os::unix::io::AsFd;
use tempfile::NamedTempFile;
use tempfile::tempfile;
@@ -1793,8 +1815,8 @@ mod tests {
infile.write_all(data).unwrap();
infile.rewind().unwrap();
let in_fd = infile.as_raw_fd();
let out_fd = outfile.as_raw_fd();
let in_fd = infile.as_fd();
let out_fd = outfile.as_fd();
let in_ptr = data.as_ptr();
// Copy with block size 4, cover = Complete
@@ -1829,8 +1851,8 @@ mod tests {
infile.write_all(data).unwrap();
infile.rewind().unwrap();
let in_fd = infile.as_raw_fd();
let out_fd = outfile.as_raw_fd();
let in_fd = infile.as_fd();
let out_fd = outfile.as_fd();
let in_ptr = data.as_ptr();
// Copy with block size 4, cover = Blocks
@@ -1848,6 +1870,94 @@ mod tests {
assert_eq!(&buf, b"abcdefgh");
}
#[test]
#[cfg(target_os = "linux")]
fn test_aligned_copy_respects_existing_out_offset() {
let mut infile = tempfile().unwrap();
let mut outfile = tempfile().unwrap();
// Write a prefix into the output file so tell() returns a non-zero offset.
let prefix = b"HEADER";
outfile.write_all(prefix).unwrap();
outfile.flush().unwrap();
// Input data = 12 bytes (3 blocks of 4 when block_size=4).
let data = b"abcdefghijkl";
infile.write_all(data).unwrap();
infile.rewind().unwrap();
let in_fd = infile.as_fd();
let out_fd = outfile.as_fd();
let in_ptr = data.as_ptr();
let copied = aligned_copy_file_range(
in_ptr,
in_fd,
0,
out_fd,
data.len(),
4,
WriteRange::Complete,
)
.unwrap();
assert_eq!(copied, data.len());
outfile.rewind().unwrap();
let mut buf = Vec::new();
outfile.read_to_end(&mut buf).unwrap();
assert_eq!(&buf, b"HEADERabcdefghijkl");
}
#[test]
#[cfg(target_os = "linux")]
fn test_reliable_copy_file_range_rustix_path() {
let mut infile = tempfile().unwrap();
let mut outfile = tempfile().unwrap();
let data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
infile.write_all(data).unwrap();
infile.rewind().unwrap();
let in_fd = infile.as_fd();
let out_fd = outfile.as_fd();
let in_ptr = data.as_ptr();
// Copy a subrange starting at offset 10.
let copied = reliable_copy_file_range(in_ptr, in_fd, 10, out_fd, 16).unwrap();
assert_eq!(copied, 16);
outfile.rewind().unwrap();
let mut buf = Vec::new();
outfile.read_to_end(&mut buf).unwrap();
assert_eq!(&buf, b"ABCDEFGHIJKLMNOP");
// Now verify that copying into a file with existing data appends correctly.
let mut infile2 = tempfile().unwrap();
let mut outfile2 = tempfile().unwrap();
let input_data = b"abcdefghijklmnopqrstuvwxyz";
infile2.write_all(input_data).unwrap();
infile2.rewind().unwrap();
outfile2.write_all(b"PRE:").unwrap();
outfile2.flush().unwrap();
let in_fd2 = infile2.as_fd();
let out_fd2 = outfile2.as_fd();
let in_ptr2 = input_data.as_ptr();
let copied2 = reliable_copy_file_range(in_ptr2, in_fd2, 5, out_fd2, 10).unwrap();
assert_eq!(copied2, 10);
outfile2.rewind().unwrap();
let mut buf2 = Vec::new();
outfile2.read_to_end(&mut buf2).unwrap();
assert_eq!(&buf2, b"PRE:fghijklmno");
}
///////////////////////////////
// Unit tests for write_chunk()
///////////////////////////////
+4 -3
View File
@@ -24,15 +24,16 @@ use crate::sed::command::{ByteSpace, CharacterMode, ProcessingContext};
use crate::sed::compiler::compile;
use crate::sed::processor::process_all_files;
use crate::sed::script_line_provider::ScriptValue;
use clap::{Arg, ArgMatches, Command, arg, crate_version};
use clap::{Arg, ArgMatches, Command, arg};
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::format_usage;
const ABOUT: &str = "Stream editor for filtering and transforming text";
const ABOUT: &str = "Stream editor for filtering and transforming text (part of uutils)";
const USAGE: &str = "sed [OPTION]... [script] [file]...";
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (uutils)");
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@@ -62,7 +63,7 @@ pub fn uu_app() -> Command {
let util_name = uucore::util_name();
Command::new(util_name)
.version(crate_version!())
.version(VERSION)
.about(ABOUT)
.override_usage(format_usage(USAGE))
.args_override_self(true)
+23 -2
View File
@@ -796,13 +796,17 @@ fn process_file(
}
'q' => {
// Quit after printing the pattern space.
set_exit_code(*extract_variant!(command, Number) as i32);
set_exit_code(
i32::try_from(*extract_variant!(command, Number)).unwrap_or(i32::MAX),
);
context.stop_processing = true;
break;
}
'Q' => {
// Quit immediatelly.
set_exit_code(*extract_variant!(command, Number) as i32);
set_exit_code(
i32::try_from(*extract_variant!(command, Number)).unwrap_or(i32::MAX),
);
context.stop_processing = true;
context.quiet = true;
break;
@@ -831,6 +835,23 @@ fn process_file(
// Branch to the end of the script.
break;
}
'T' if context.substitution_made => {
// A substitution was made since the last cycle or T,
// so reset the flag and fall through without branching.
context.substitution_made = false;
}
'T' => {
// Branch to the specified label or end if none is given
// if no substitution was made since last cycle or T.
let target = extract_variant!(command, BranchTarget);
if target.is_some() {
// New command to execute
current.clone_from(target);
continue;
}
// Branch to the end of the script.
break;
}
'w' => {
// Append the pattern space to the specified file.
let writer = extract_variant!(command, NamedWriter);
+107 -1
View File
@@ -38,7 +38,18 @@ fn test_version() {
.arg("--version")
.succeeds()
.no_stderr()
.stdout_is(short);
.stdout_is(&short);
assert!(short.contains("uutils"));
}
#[test]
fn test_help_mentions_uutils() {
new_ucmd!()
.arg("--help")
.succeeds()
.no_stderr()
.stdout_contains("part of uutils");
}
#[test]
@@ -1414,6 +1425,59 @@ tb"#,
]
);
// T branches when NO substitution was made (inverse of t).
// Lines not ending in "2" leave s/2$/X/ with no match, so T branches to
// :skip and keeps the original line; lines ending in "2" fall through and
// get the "SUB " mark.
check_output!(
branch_no_sub_simple,
[
"-n",
"-e",
r#"
s/2$/X/
Tskip
s/^/SUB /
:skip
p
"#,
LINES1
]
);
// Check that T also clears the substitution-done flag, matching t.
// After s/// sets the flag, the first T sees a substitution so it does
// not branch but still clears the flag; the second T then sees no
// substitution and branches to :y. Reaching :y (CLEARED) rather than
// falling through to :x (NOTCLEARED) proves the first T cleared the flag.
check_output!(
branch_no_sub_clears,
[
"-n",
"-e",
r#"
s/^/^/
Tx
Ty
:x
s/^/NOTCLEARED /p
b
:y
s/^/CLEARED /p
"#,
LINES1
]
);
#[test]
fn test_branch_no_sub_non_posix() {
new_ucmd!()
.args(&["--posix", "T"])
.fails()
.code_is(1)
.stderr_contains("invalid command code");
}
////////////////////////////////////////////////////////////
// Text: a, c, i
@@ -2208,6 +2272,48 @@ fn test_print_first_line_no_newline() {
.stdout_is("foo");
}
#[test]
fn test_expected_newer_version() {
new_ucmd!()
.args(&["v4.10"])
.fails()
.stderr_is("sed: <script argument 1>:1:6: error: expected newer version of sed\n");
}
#[test]
fn test_invalid_version() {
new_ucmd!()
.args(&["v4.a"])
.fails()
.stderr_is("sed: <script argument 1>:1:5: error: invalid version of sed\n");
}
#[test]
fn test_valid_version() {
new_ucmd!().args(&["v4.9"]).succeeds();
}
#[test]
fn test_valid_only_major_version() {
// v4.0.0
new_ucmd!().args(&["v4"]).succeeds();
}
#[test]
fn test_invalid_only_major_version() {
// v999.0.0
new_ucmd!()
.args(&["v999"])
.fails()
.stderr_is("sed: <script argument 1>:1:5: error: invalid version of sed\n");
}
#[test]
fn test_default_version() {
// defaults to v4.9.0
new_ucmd!().args(&["v"]).succeeds();
}
//--posix should reject GNU substitute flags i/I https://github.com/uutils/sed/issues/401
#[test]
fn test_posix_reject_flags() {
+14
View File
@@ -0,0 +1,14 @@
CLEARED ^l1_1
CLEARED ^l1_2
CLEARED ^l1_3
CLEARED ^l1_4
CLEARED ^l1_5
CLEARED ^l1_6
CLEARED ^l1_7
CLEARED ^l1_8
CLEARED ^l1_9
CLEARED ^l1_10
CLEARED ^l1_11
CLEARED ^l1_12
CLEARED ^l1_13
CLEARED ^l1_14
+14
View File
@@ -0,0 +1,14 @@
l1_1
SUB l1_X
l1_3
l1_4
l1_5
l1_6
l1_7
l1_8
l1_9
l1_10
l1_11
SUB l1_1X
l1_13
l1_14