From 64e536cbe6a2abdf68dde1522106cd51688a49f7 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:03:24 +0900 Subject: [PATCH] dd: replace nix by rustix (#12567) Co-authored-by: Sylvestre Ledru --- Cargo.lock | 2 +- src/uu/dd/Cargo.toml | 2 +- src/uu/dd/src/bufferedoutput.rs | 2 +- src/uu/dd/src/dd.rs | 80 ++++++++++++++++----------------- src/uu/dd/src/progress.rs | 8 ++-- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcdc4f572..16573de78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3419,7 +3419,7 @@ dependencies = [ "fluent", "gcd", "libc", - "nix", + "rustix", "tempfile", "thiserror 2.0.18", "uucore", diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 438b9ccf5..f475e6269 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -33,7 +33,7 @@ thiserror = { workspace = true } fluent = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -nix = { workspace = true, features = ["fs", "signal"] } +rustix = { workspace = true, features = ["fs"] } [[bin]] name = "dd" diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs index 6ac3b4300..cd16eb0e7 100644 --- a/src/uu/dd/src/bufferedoutput.rs +++ b/src/uu/dd/src/bufferedoutput.rs @@ -39,7 +39,7 @@ impl<'a> BufferedOutput<'a> { } } - pub(crate) fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + pub(crate) fn discard_cache(&self, offset: u64, len: u64) { self.inner.discard_cache(offset, len); } diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4f975000d..0ae83504e 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat oconv canonicalized Fadvise FADV DONTNEED ESPIPE bufferedoutput, SETFL +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat oconv canonicalized FADV DONTNEED ESPIPE SPIPE bufferedoutput, SETFL mod blocks; mod bufferedoutput; @@ -16,10 +16,6 @@ mod progress; use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; use datastructures::{ConversionMode, IConvFlags, IFlags, OConvFlags, OFlags, options}; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::FcntlArg; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::OFlag; use parseargs::Parser; use progress::ProgUpdateType; use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater}; @@ -36,8 +32,6 @@ use std::fs::Metadata; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Write}; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::os::fd::AsFd; -#[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; #[cfg(unix)] use std::os::unix::{ @@ -54,11 +48,6 @@ use std::time::{Duration, Instant}; use clap::{Arg, Command}; use gcd::Gcd; -#[cfg(target_os = "linux")] -use nix::{ - errno::Errno, - fcntl::{PosixFadviseAdvice, posix_fadvise}, -}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; #[cfg(unix)] @@ -317,14 +306,16 @@ impl Source { /// portion of the source is no longer needed. If not possible, /// then this function returns an error. #[cfg(target_os = "linux")] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> { + fn discard_cache(&self, offset: u64, len: u64) -> io::Result<()> { #[allow(clippy::match_wildcard_for_single_variants)] match self { Self::File(f) => { - let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED; - posix_fadvise(f.as_fd(), offset, len, advice) + use rustix::fs::{Advice::DontNeed, fadvise}; + fadvise(f, offset, std::num::NonZeroU64::new(len), DontNeed)?; + Ok(()) } - _ => Err(Errno::ESPIPE), // "Illegal seek" + // fadvise for nonseekable returns this error. We manually do that... + _ => Err(rustix::io::Errno::SPIPE.into()), } } } @@ -502,7 +493,7 @@ impl Input<'_> { /// function prints an error message to stderr and sets the exit /// status code to 1. #[cfg_attr(not(target_os = "linux"), allow(clippy::unused_self, unused_variables))] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + fn discard_cache(&self, offset: u64, len: u64) { #[cfg(target_os = "linux")] { let file = self @@ -703,13 +694,15 @@ impl Dest { /// specified portion of the destination is no longer needed. If /// not possible, then this function returns an error. #[cfg(target_os = "linux")] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> { + fn discard_cache(&self, offset: u64, len: u64) -> io::Result<()> { match self { Self::File(f, _) => { - let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED; - posix_fadvise(f.as_fd(), offset, len, advice) + use rustix::fs::{Advice::DontNeed, fadvise}; + fadvise(f, offset, std::num::NonZeroU64::new(len), DontNeed)?; + Ok(()) } - _ => Err(Errno::ESPIPE), // "Illegal seek" + // fadvise for nonseekable returns this error. We manually do that... + _ => Err(rustix::io::Errno::SPIPE.into()), } } } @@ -723,31 +716,33 @@ fn is_sparse(buf: &[u8]) -> bool { /// This follows GNU dd behavior for partial block writes with O_DIRECT. #[cfg(any(target_os = "linux", target_os = "android"))] fn handle_o_direct_write(f: &mut File, buf: &[u8], original_error: io::Error) -> io::Result { - use nix::fcntl::{FcntlArg, OFlag, fcntl}; + use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl}; - // Get current flags using nix - let oflags = match fcntl(&mut *f, FcntlArg::F_GETFL) { - Ok(flags) => OFlag::from_bits_retain(flags), - Err(_) => return Err(original_error), + // Get current flags + let Ok(oflags) = fcntl_getfl(&*f) else { + return Err(original_error); }; // If O_DIRECT is set, try removing it temporarily - if oflags.contains(OFlag::O_DIRECT) { - let flags_without_direct = oflags - OFlag::O_DIRECT; + if oflags.contains(OFlags::DIRECT) { + let flags_without_direct = oflags & !OFlags::DIRECT; - // Remove O_DIRECT flag using nix - if fcntl(&mut *f, FcntlArg::F_SETFL(flags_without_direct)).is_err() { + // Remove O_DIRECT flag + if fcntl_setfl(&*f, flags_without_direct).is_err() { return Err(original_error); } // Retry the write without O_DIRECT let write_result = f.write(buf); - // Restore O_DIRECT flag using nix (GNU doesn't restore it, but we'll be safer) + // Restore O_DIRECT flag (GNU doesn't restore it, but we'll be safer) // Log any restoration errors without failing the operation - if let Err(os_err) = fcntl(&mut *f, FcntlArg::F_SETFL(oflags)) { + if let Err(os_err) = fcntl_setfl(&*f, oflags) { // Just log the error, don't fail the whole operation - show_error!("Failed to restore O_DIRECT flag: {os_err}"); + show_error!( + "Failed to restore O_DIRECT flag: {}", + io::Error::from(os_err) + ); } write_result @@ -894,10 +889,11 @@ impl<'a> Output<'a> { let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?; #[cfg(any(target_os = "linux", target_os = "android"))] if let Some(libc_flags) = make_linux_oflags(&settings.oflags) { - nix::fcntl::fcntl( - fx.as_raw().as_fd(), - FcntlArg::F_SETFL(OFlag::from_bits_retain(libc_flags)), - )?; + rustix::fs::fcntl_setfl( + fx.as_raw(), + rustix::fs::OFlags::from_bits_retain(libc_flags as _), + ) + .map_err(|e| uucore::error::UIoError::from(io::Error::from(e)))?; } Self::prepare_file(fx.into_file(), settings) @@ -940,7 +936,7 @@ impl<'a> Output<'a> { /// this function prints an error message to stderr and sets the /// exit status code to 1. #[cfg_attr(not(target_os = "linux"), allow(clippy::unused_self, unused_variables))] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + fn discard_cache(&self, offset: u64, len: u64) { #[cfg(target_os = "linux")] { let file = self @@ -1046,7 +1042,7 @@ enum BlockWriter<'a> { } impl BlockWriter<'_> { - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + fn discard_cache(&self, offset: u64, len: u64) { match self { Self::Unbuffered(o) => o.discard_cache(offset, len), Self::Buffered(o) => o.discard_cache(offset, len), @@ -1228,7 +1224,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?; if rstat_update.is_empty() { if input_nocache { - i.discard_cache(read_offset.try_into().unwrap(), 0); + i.discard_cache(read_offset, 0); } if output_nocache || output_direct { o.discard_cache(write_offset.try_into().unwrap(), 0); @@ -1243,8 +1239,8 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { // TODO Better error handling for overflowing `offset` and `len`. let read_len = rstat_update.bytes_total; if input_nocache { - let offset = read_offset.try_into().unwrap(); - let len = read_len.try_into().unwrap(); + let offset = read_offset; + let len = read_len; i.discard_cache(offset, len); } read_offset += read_len; diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 0177f2918..ce38684ac 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -459,11 +459,9 @@ extern "C" fn sigusr1_handler(_: std::os::raw::c_int) { } #[cfg(target_os = "linux")] -pub(crate) fn install_sigusr1_handler() -> Result<(), nix::errno::Errno> { - uucore::signals::install_signal_handler( - nix::sys::signal::Signal::SIGUSR1 as libc::c_int, - sigusr1_handler, - ) +pub(crate) fn install_sigusr1_handler() -> std::io::Result<()> { + uucore::signals::install_signal_handler(libc::SIGUSR1, sigusr1_handler)?; + Ok(()) } /// Return a closure that can be used in its own thread to print progress info.