mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Merge branch 'main' into bug/cp-preserve-xattr-9704
This commit is contained in:
commit
b0d6c49630
11 changed files with 473 additions and 298 deletions
73
.github/workflows/GnuTests.yml
vendored
73
.github/workflows/GnuTests.yml
vendored
|
|
@ -2,7 +2,7 @@ name: GnuTests
|
|||
|
||||
# spell-checker:ignore (abbrev/names) CodeCov gnulib GnuTests Swatinem
|
||||
# spell-checker:ignore (jargon) submodules devel
|
||||
# spell-checker:ignore (libs/utils) autopoint chksum getenforce gperf lcov libexpect limactl pyinotify setenforce shopt texinfo valgrind libattr libcap taiki-e
|
||||
# spell-checker:ignore (libs/utils) autopoint chksum dpkg getenforce getlimits gperf lcov libexpect limactl pyinotify setenforce shopt texinfo valgrind libattr libcap taiki-e
|
||||
# spell-checker:ignore (options) Ccodegen Coverflow Cpanic Zpanic
|
||||
# spell-checker:ignore (people) Dawid Dziurla * dawidd dtolnay
|
||||
# spell-checker:ignore (vars) FILESET SUBDIRS XPASS
|
||||
|
|
@ -42,16 +42,6 @@ jobs:
|
|||
with:
|
||||
path: 'uutils'
|
||||
persist-credentials: false
|
||||
- name: Extract GNU version from build-gnu.sh
|
||||
id: gnu-version
|
||||
run: |
|
||||
GNU_VERSION=$(grep '^release_tag_GNU=' uutils/util/build-gnu.sh | cut -d'"' -f2)
|
||||
if [ -z "$GNU_VERSION" ]; then
|
||||
echo "Error: Failed to extract GNU version from build-gnu.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "REPO_GNU_REF=${GNU_VERSION}" >> $GITHUB_ENV
|
||||
echo "Extracted GNU version: ${GNU_VERSION}"
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
|
@ -60,21 +50,18 @@ jobs:
|
|||
with:
|
||||
workspaces: "./uutils -> target"
|
||||
- name: Checkout code (GNU coreutils)
|
||||
uses: actions/checkout@v6
|
||||
run: (mkdir -p gnu && cd gnu && bash ../uutils/util/fetch-gnu.sh)
|
||||
- name: Restore files for faster configure and skipping make
|
||||
uses: actions/cache@v5
|
||||
id: cache-config-gnu
|
||||
with:
|
||||
repository: 'coreutils/coreutils'
|
||||
path: 'gnu'
|
||||
ref: ${{ env.REPO_GNU_REF }}
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
- name: Override submodule URL and initialize submodules
|
||||
# Use github instead of upstream git server
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git config submodule.gnulib.url https://github.com/coreutils/gnulib.git
|
||||
git submodule update --init --recursive --depth 1
|
||||
working-directory: gnu
|
||||
|
||||
path: |
|
||||
gnu/config.cache
|
||||
gnu/src/getlimits
|
||||
key: ${{ runner.os }}-gnu-config-${{ env.REPO_GNU_REF }}-${{ hashFiles('gnu/configure') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gnu-config-${{ env.REPO_GNU_REF }}-
|
||||
${{ runner.os }}-gnu-config-
|
||||
#### Build environment setup
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
|
|
@ -83,6 +70,8 @@ jobs:
|
|||
sudo apt-get update
|
||||
## Check that build-gnu.sh works on the non SELinux system by installing libselinux only on lima
|
||||
sudo apt-get install -y autopoint gperf gdb python3-pyinotify valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev attr quilt
|
||||
curl http://launchpadlibrarian.net/831710181/automake_1.18.1-3_all.deb > automake-1.18.deb
|
||||
sudo dpkg -i --force-depends automake-1.18.deb
|
||||
- name: Add various locales
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
@ -115,6 +104,15 @@ jobs:
|
|||
## Build binaries
|
||||
cd 'uutils'
|
||||
env PROFILE=release-small bash util/build-gnu.sh
|
||||
|
||||
- name: Save files for faster configure and skipping make
|
||||
uses: actions/cache/save@v5
|
||||
if: always() && steps.cache-config-gnu.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: |
|
||||
gnu/config.cache
|
||||
gnu/src/getlimits
|
||||
key: ${{ runner.os }}-gnu-config-${{ env.REPO_GNU_REF }}-${{ hashFiles('gnu/configure') }}
|
||||
|
||||
### Run tests as user
|
||||
- name: Run GNU tests
|
||||
|
|
@ -206,16 +204,6 @@ jobs:
|
|||
with:
|
||||
path: 'uutils'
|
||||
persist-credentials: false
|
||||
- name: Extract GNU version from build-gnu.sh
|
||||
id: gnu-version-selinux
|
||||
run: |
|
||||
GNU_VERSION=$(grep '^release_tag_GNU=' uutils/util/build-gnu.sh | cut -d'"' -f2)
|
||||
if [ -z "$GNU_VERSION" ]; then
|
||||
echo "Error: Failed to extract GNU version from build-gnu.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "REPO_GNU_REF=${GNU_VERSION}" >> $GITHUB_ENV
|
||||
echo "Extracted GNU version: ${GNU_VERSION}"
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
|
@ -224,20 +212,7 @@ jobs:
|
|||
with:
|
||||
workspaces: "./uutils -> target"
|
||||
- name: Checkout code (GNU coreutils)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: 'coreutils/coreutils'
|
||||
path: 'gnu'
|
||||
ref: ${{ env.REPO_GNU_REF }}
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
- name: Override submodule URL and initialize submodules
|
||||
# Use github instead of upstream git server
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git config submodule.gnulib.url https://github.com/coreutils/gnulib.git
|
||||
git submodule update --init --recursive --depth 1
|
||||
working-directory: gnu
|
||||
run: (mkdir -p gnu && cd gnu && bash ../uutils/util/fetch-gnu.sh)
|
||||
|
||||
#### Lima build environment setup
|
||||
- name: Setup Lima
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ iflag
|
|||
iflags
|
||||
kibi
|
||||
kibibytes
|
||||
langinfo
|
||||
libacl
|
||||
lcase
|
||||
listxattr
|
||||
|
|
@ -129,6 +130,7 @@ semiprimes
|
|||
setcap
|
||||
setfacl
|
||||
setfattr
|
||||
setlocale
|
||||
shortcode
|
||||
shortcodes
|
||||
siginfo
|
||||
|
|
@ -163,6 +165,8 @@ xattrs
|
|||
xpass
|
||||
|
||||
# * abbreviations
|
||||
AMPM
|
||||
ampm
|
||||
consts
|
||||
deps
|
||||
dev
|
||||
|
|
|
|||
|
|
@ -244,8 +244,6 @@ DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
|||
|
||||
***Tip:*** First time you run `bash util/build-gnu.sh` command, it will provide instructions on how to checkout GNU coreutils repository at the correct release tag. Please follow those instructions and when done, run `bash util/build-gnu.sh` command again.
|
||||
|
||||
Note that GNU test suite relies on individual utilities (not the multicall binary).
|
||||
|
||||
You also need to install [quilt](https://savannah.nongnu.org/projects/quilt), a tool used to manage a stack of patches for modifying GNU tests.
|
||||
|
||||
On FreeBSD, you need to install packages for GNU coreutils and sed (used in shell scripts instead of system commands):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, ErrorKind, Read, Write};
|
||||
use std::io::{self, BufRead, BufReader, ErrorKind, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::encoding::{
|
||||
|
|
@ -146,20 +146,26 @@ pub fn base_app(about: String, usage: String) -> Command {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn get_input(config: &Config) -> UResult<Box<dyn Read>> {
|
||||
pub fn get_input(config: &Config) -> UResult<Box<dyn BufRead>> {
|
||||
match &config.to_read {
|
||||
Some(path_buf) => {
|
||||
let file =
|
||||
File::open(path_buf).map_err_context(|| path_buf.maybe_quote().to_string())?;
|
||||
Ok(Box::new(BufReader::new(file)))
|
||||
Ok(Box::new(BufReader::with_capacity(
|
||||
DEFAULT_BUFFER_SIZE,
|
||||
file,
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
// Stdin is already buffered by the OS; wrap once more to reduce syscalls per read.
|
||||
Ok(Box::new(BufReader::new(io::stdin())))
|
||||
Ok(Box::new(BufReader::with_capacity(
|
||||
DEFAULT_BUFFER_SIZE,
|
||||
io::stdin(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_input<R: Read>(input: &mut R, format: Format, config: Config) -> UResult<()> {
|
||||
pub fn handle_input<R: BufRead>(input: &mut R, format: Format, config: Config) -> UResult<()> {
|
||||
// Always allow padding for Base64 to avoid a full pre-scan of the input.
|
||||
let supports_fast_decode_and_encode =
|
||||
get_supports_fast_decode_and_encode(format, config.decode, true);
|
||||
|
|
@ -292,11 +298,11 @@ pub fn get_supports_fast_decode_and_encode(
|
|||
}
|
||||
|
||||
pub mod fast_encode {
|
||||
use crate::base_common::{DEFAULT_BUFFER_SIZE, WRAP_DEFAULT};
|
||||
use crate::base_common::WRAP_DEFAULT;
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::VecDeque,
|
||||
io::{self, Read, Write},
|
||||
io::{self, BufRead, Write},
|
||||
num::NonZeroUsize,
|
||||
};
|
||||
use uucore::{
|
||||
|
|
@ -519,7 +525,7 @@ pub mod fast_encode {
|
|||
/// Remaining bytes are encoded and flushed at the end. I/O or encoding
|
||||
/// failures are propagated via `UResult`.
|
||||
pub fn fast_encode_stream(
|
||||
input: &mut dyn Read,
|
||||
input: &mut dyn BufRead,
|
||||
output: &mut dyn Write,
|
||||
supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode,
|
||||
wrap: Option<usize>,
|
||||
|
|
@ -544,47 +550,79 @@ pub mod fast_encode {
|
|||
};
|
||||
|
||||
// Buffers
|
||||
let mut leftover_buffer = VecDeque::<u8>::new();
|
||||
let mut encoded_buffer = VecDeque::<u8>::new();
|
||||
|
||||
let mut read_buffer = vec![0u8; encode_in_chunks_of_size.max(DEFAULT_BUFFER_SIZE)];
|
||||
let mut leftover_buffer = Vec::<u8>::with_capacity(encode_in_chunks_of_size);
|
||||
|
||||
loop {
|
||||
let read = input
|
||||
.read(&mut read_buffer)
|
||||
let read_buffer = input
|
||||
.fill_buf()
|
||||
.map_err(|err| USimpleError::new(1, super::format_read_error(err.kind())))?;
|
||||
if read == 0 {
|
||||
if read_buffer.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
leftover_buffer.extend(&read_buffer[..read]);
|
||||
let mut consumed = 0;
|
||||
|
||||
while leftover_buffer.len() >= encode_in_chunks_of_size {
|
||||
{
|
||||
let contiguous = leftover_buffer.make_contiguous();
|
||||
if !leftover_buffer.is_empty() {
|
||||
let needed = encode_in_chunks_of_size - leftover_buffer.len();
|
||||
let take = needed.min(read_buffer.len());
|
||||
leftover_buffer.extend_from_slice(&read_buffer[..take]);
|
||||
consumed += take;
|
||||
|
||||
if leftover_buffer.len() == encode_in_chunks_of_size {
|
||||
encode_in_chunks_to_buffer(
|
||||
supports_fast_decode_and_encode,
|
||||
&contiguous[..encode_in_chunks_of_size],
|
||||
leftover_buffer.as_slice(),
|
||||
&mut encoded_buffer,
|
||||
)?;
|
||||
leftover_buffer.clear();
|
||||
|
||||
write_to_output(
|
||||
&mut line_wrapping,
|
||||
&mut encoded_buffer,
|
||||
output,
|
||||
false,
|
||||
wrap == Some(0),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Drop the data we just encoded
|
||||
leftover_buffer.drain(..encode_in_chunks_of_size);
|
||||
|
||||
write_to_output(
|
||||
&mut line_wrapping,
|
||||
&mut encoded_buffer,
|
||||
output,
|
||||
false,
|
||||
wrap == Some(0),
|
||||
)?;
|
||||
}
|
||||
|
||||
let remaining = &read_buffer[consumed..];
|
||||
let full_chunk_bytes =
|
||||
(remaining.len() / encode_in_chunks_of_size) * encode_in_chunks_of_size;
|
||||
|
||||
if full_chunk_bytes > 0 {
|
||||
for chunk in remaining[..full_chunk_bytes].chunks_exact(encode_in_chunks_of_size) {
|
||||
encode_in_chunks_to_buffer(
|
||||
supports_fast_decode_and_encode,
|
||||
chunk,
|
||||
&mut encoded_buffer,
|
||||
)?;
|
||||
write_to_output(
|
||||
&mut line_wrapping,
|
||||
&mut encoded_buffer,
|
||||
output,
|
||||
false,
|
||||
wrap == Some(0),
|
||||
)?;
|
||||
}
|
||||
consumed += full_chunk_bytes;
|
||||
}
|
||||
|
||||
if consumed < read_buffer.len() {
|
||||
leftover_buffer.extend_from_slice(&read_buffer[consumed..]);
|
||||
consumed = read_buffer.len();
|
||||
}
|
||||
|
||||
input.consume(consumed);
|
||||
|
||||
// `leftover_buffer` should never exceed one partial chunk.
|
||||
debug_assert!(leftover_buffer.len() < encode_in_chunks_of_size);
|
||||
}
|
||||
|
||||
// Encode any remaining bytes and flush
|
||||
supports_fast_decode_and_encode
|
||||
.encode_to_vec_deque(leftover_buffer.make_contiguous(), &mut encoded_buffer)?;
|
||||
.encode_to_vec_deque(&leftover_buffer, &mut encoded_buffer)?;
|
||||
|
||||
write_to_output(
|
||||
&mut line_wrapping,
|
||||
|
|
@ -599,8 +637,7 @@ pub mod fast_encode {
|
|||
}
|
||||
|
||||
pub mod fast_decode {
|
||||
use crate::base_common::DEFAULT_BUFFER_SIZE;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{self, BufRead, Write};
|
||||
use uucore::{
|
||||
encoding::SupportsFastDecodeAndEncode,
|
||||
error::{UResult, USimpleError},
|
||||
|
|
@ -630,7 +667,6 @@ pub mod fast_decode {
|
|||
fn write_to_output(decoded_buffer: &mut Vec<u8>, output: &mut dyn Write) -> io::Result<()> {
|
||||
// Write all data in `decoded_buffer` to `output`
|
||||
output.write_all(decoded_buffer.as_slice())?;
|
||||
output.flush()?;
|
||||
|
||||
decoded_buffer.clear();
|
||||
|
||||
|
|
@ -764,7 +800,7 @@ pub mod fast_decode {
|
|||
}
|
||||
|
||||
pub fn fast_decode_stream(
|
||||
input: &mut dyn Read,
|
||||
input: &mut dyn BufRead,
|
||||
output: &mut dyn Write,
|
||||
supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode,
|
||||
ignore_garbage: bool,
|
||||
|
|
@ -783,17 +819,17 @@ pub mod fast_decode {
|
|||
|
||||
let mut buffer = Vec::with_capacity(decode_in_chunks_of_size);
|
||||
let mut decoded_buffer = Vec::<u8>::new();
|
||||
let mut read_buffer = [0u8; DEFAULT_BUFFER_SIZE];
|
||||
|
||||
loop {
|
||||
let read = input
|
||||
.read(&mut read_buffer)
|
||||
let read_buffer = input
|
||||
.fill_buf()
|
||||
.map_err(|err| USimpleError::new(1, super::format_read_error(err.kind())))?;
|
||||
if read == 0 {
|
||||
let read_len = read_buffer.len();
|
||||
if read_len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
for &byte in &read_buffer[..read] {
|
||||
for &byte in read_buffer {
|
||||
if byte == b'\n' || byte == b'\r' {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -845,6 +881,8 @@ pub mod fast_decode {
|
|||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
input.consume(read_len);
|
||||
}
|
||||
|
||||
if supports_partial_decode {
|
||||
|
|
@ -902,7 +940,7 @@ fn format_read_error(kind: ErrorKind) -> String {
|
|||
|
||||
/// Determines if the input buffer contains any padding ('=') ignoring trailing whitespace.
|
||||
#[cfg(test)]
|
||||
fn read_and_has_padding<R: Read>(input: &mut R) -> UResult<(bool, Vec<u8>)> {
|
||||
fn read_and_has_padding<R: std::io::Read>(input: &mut R) -> UResult<(bool, Vec<u8>)> {
|
||||
let mut buf = Vec::new();
|
||||
input
|
||||
.read_to_end(&mut buf)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes getres AWST ACST AEST
|
||||
|
||||
mod locale;
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use jiff::fmt::strtime;
|
||||
use jiff::tz::{TimeZone, TimeZoneDatabase};
|
||||
|
|
@ -534,7 +536,7 @@ fn make_format_string(settings: &Settings) -> &str {
|
|||
},
|
||||
Format::Resolution => "%s.%N",
|
||||
Format::Custom(ref fmt) => fmt,
|
||||
Format::Default => "%a %b %e %X %Z %Y",
|
||||
Format::Default => locale::get_locale_default_format(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
177
src/uu/date/src/locale.rs
Normal file
177
src/uu/date/src/locale.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Locale detection for time format preferences
|
||||
|
||||
// nl_langinfo is available on glibc (Linux), Apple platforms, and BSDs
|
||||
// but not on Android, Redox or other minimal Unix systems
|
||||
|
||||
// Macro to reduce cfg duplication across the module
|
||||
macro_rules! cfg_langinfo {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
cfg_langinfo! {
|
||||
use std::ffi::CStr;
|
||||
use std::sync::OnceLock;
|
||||
}
|
||||
|
||||
cfg_langinfo! {
|
||||
/// Cached result of locale time format detection
|
||||
static TIME_FORMAT_CACHE: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
/// Safe wrapper around libc setlocale
|
||||
fn set_time_locale() {
|
||||
unsafe {
|
||||
nix::libc::setlocale(nix::libc::LC_TIME, c"".as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around libc nl_langinfo that returns `Option<String>`
|
||||
fn get_locale_info(item: nix::libc::nl_item) -> Option<String> {
|
||||
unsafe {
|
||||
let ptr = nix::libc::nl_langinfo(item);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
CStr::from_ptr(ptr).to_str().ok().map(String::from)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal function that performs the actual locale detection
|
||||
fn detect_12_hour_format() -> bool {
|
||||
// Helper function to check for 12-hour format indicators
|
||||
fn has_12_hour_indicators(format_str: &str) -> bool {
|
||||
const INDICATORS: &[&str] = &["%I", "%l", "%r"];
|
||||
INDICATORS.iter().any(|&indicator| format_str.contains(indicator))
|
||||
}
|
||||
|
||||
// Helper function to check for 24-hour format indicators
|
||||
fn has_24_hour_indicators(format_str: &str) -> bool {
|
||||
const INDICATORS: &[&str] = &["%H", "%k", "%R", "%T"];
|
||||
INDICATORS.iter().any(|&indicator| format_str.contains(indicator))
|
||||
}
|
||||
|
||||
// Set locale from environment variables (empty string = use LC_TIME/LANG env vars)
|
||||
set_time_locale();
|
||||
|
||||
// Get locale format strings using safe wrappers
|
||||
let d_t_fmt = get_locale_info(nix::libc::D_T_FMT);
|
||||
let t_fmt_opt = get_locale_info(nix::libc::T_FMT);
|
||||
let t_fmt_ampm_opt = get_locale_info(nix::libc::T_FMT_AMPM);
|
||||
|
||||
// Check D_T_FMT first
|
||||
if let Some(ref format) = d_t_fmt {
|
||||
// Check for 12-hour indicators first (higher priority)
|
||||
if has_12_hour_indicators(format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we find 24-hour indicators, it's definitely not 12-hour
|
||||
if has_24_hour_indicators(format) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check the time-only format as a fallback
|
||||
if let Some(ref time_format) = t_fmt_opt {
|
||||
if has_12_hour_indicators(time_format) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there's a specific 12-hour format defined
|
||||
if let Some(ref ampm_format) = t_fmt_ampm_opt {
|
||||
// If T_FMT_AMPM is non-empty and different from T_FMT, locale supports 12-hour
|
||||
if !ampm_format.is_empty() {
|
||||
if let Some(ref time_format) = t_fmt_opt {
|
||||
if ampm_format != time_format {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to 24-hour format if we can't determine
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
cfg_langinfo! {
|
||||
/// Detects whether the current locale prefers 12-hour or 24-hour time format
|
||||
/// Results are cached for performance
|
||||
pub fn uses_12_hour_format() -> bool {
|
||||
*TIME_FORMAT_CACHE.get_or_init(detect_12_hour_format)
|
||||
}
|
||||
|
||||
/// Cached default format string
|
||||
static DEFAULT_FORMAT_CACHE: OnceLock<&'static str> = OnceLock::new();
|
||||
|
||||
/// Get the locale-appropriate default format string for date output
|
||||
/// This respects the locale's preference for 12-hour vs 24-hour time
|
||||
/// Results are cached for performance (following uucore patterns)
|
||||
pub fn get_locale_default_format() -> &'static str {
|
||||
DEFAULT_FORMAT_CACHE.get_or_init(|| {
|
||||
if uses_12_hour_format() {
|
||||
// Use 12-hour format with AM/PM
|
||||
"%a %b %e %r %Z %Y"
|
||||
} else {
|
||||
// Use 24-hour format
|
||||
"%a %b %e %X %Z %Y"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// On platforms without nl_langinfo support, use 24-hour format by default
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
pub fn get_locale_default_format() -> &'static str {
|
||||
"%a %b %e %X %Z %Y"
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
cfg_langinfo! {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_locale_detection() {
|
||||
// Just verify the function doesn't panic
|
||||
let _ = uses_12_hour_format();
|
||||
let _ = get_locale_default_format();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_format_contains_valid_codes() {
|
||||
let format = get_locale_default_format();
|
||||
assert!(format.contains("%a")); // abbreviated weekday
|
||||
assert!(format.contains("%b")); // abbreviated month
|
||||
assert!(format.contains("%Y")); // year
|
||||
assert!(format.contains("%Z")); // timezone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,10 @@ impl TruncateMode {
|
|||
/// reduce by is greater than `fsize`, then this function returns
|
||||
/// 0 (since it cannot return a negative number).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `None` if rounding by 0, else the target size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Extending a file of 10 bytes by 5 bytes:
|
||||
|
|
@ -45,7 +49,7 @@ impl TruncateMode {
|
|||
/// ```rust,ignore
|
||||
/// let mode = TruncateMode::Extend(5);
|
||||
/// let fsize = 10;
|
||||
/// assert_eq!(mode.to_size(fsize), 15);
|
||||
/// assert_eq!(mode.to_size(fsize), Some(15));
|
||||
/// ```
|
||||
///
|
||||
/// Reducing a file by more than its size results in 0:
|
||||
|
|
@ -53,25 +57,36 @@ impl TruncateMode {
|
|||
/// ```rust,ignore
|
||||
/// let mode = TruncateMode::Reduce(5);
|
||||
/// let fsize = 3;
|
||||
/// assert_eq!(mode.to_size(fsize), 0);
|
||||
/// assert_eq!(mode.to_size(fsize), Some(0));
|
||||
/// ```
|
||||
fn to_size(&self, fsize: u64) -> u64 {
|
||||
///
|
||||
/// Rounding a file by 0:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let mode = TruncateMode::RoundDown(0);
|
||||
/// let fsize = 17;
|
||||
/// assert_eq!(mode.to_size(fsize), None);
|
||||
/// ```
|
||||
fn to_size(&self, fsize: u64) -> Option<u64> {
|
||||
match self {
|
||||
Self::Absolute(size) => *size,
|
||||
Self::Extend(size) => fsize + size,
|
||||
Self::Reduce(size) => {
|
||||
if *size > fsize {
|
||||
0
|
||||
} else {
|
||||
fsize - size
|
||||
}
|
||||
}
|
||||
Self::AtMost(size) => fsize.min(*size),
|
||||
Self::AtLeast(size) => fsize.max(*size),
|
||||
Self::RoundDown(size) => fsize - fsize % size,
|
||||
Self::RoundUp(size) => fsize + fsize % size,
|
||||
Self::Absolute(size) => Some(*size),
|
||||
Self::Extend(size) => Some(fsize + size),
|
||||
Self::Reduce(size) => Some(fsize.saturating_sub(*size)),
|
||||
Self::AtMost(size) => Some(fsize.min(*size)),
|
||||
Self::AtLeast(size) => Some(fsize.max(*size)),
|
||||
Self::RoundDown(size) => fsize.checked_rem(*size).map(|remainder| fsize - remainder),
|
||||
Self::RoundUp(size) => fsize.checked_next_multiple_of(*size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if mode is absolute
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` is self matches Self::Absolute(_), `false` otherwise.
|
||||
fn is_absolute(&self) -> bool {
|
||||
matches!(self, Self::Absolute(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod options {
|
||||
|
|
@ -170,18 +185,9 @@ pub fn uu_app() -> Command {
|
|||
///
|
||||
/// If the file could not be opened, or there was a problem setting the
|
||||
/// size of the file.
|
||||
fn file_truncate(filename: &OsString, create: bool, size: u64) -> UResult<()> {
|
||||
fn do_file_truncate(filename: &Path, create: bool, size: u64) -> UResult<()> {
|
||||
let path = Path::new(filename);
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Ok(metadata) = metadata(path) {
|
||||
if metadata.file_type().is_fifo() {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
|
||||
));
|
||||
}
|
||||
}
|
||||
match OpenOptions::new().write(true).create(create).open(path) {
|
||||
Ok(file) => file.set_len(size),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound && !create => Ok(()),
|
||||
|
|
@ -192,155 +198,44 @@ fn file_truncate(filename: &OsString, create: bool, size: u64) -> UResult<()> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Truncate files to a size relative to a given file.
|
||||
///
|
||||
/// `rfilename` is the name of the reference file.
|
||||
///
|
||||
/// `size_string` gives the size relative to the reference file to which
|
||||
/// to set the target files. For example, "+3K" means "set each file to
|
||||
/// be three kilobytes larger than the size of the reference file".
|
||||
///
|
||||
/// If `create` is true, then each file will be created if it does not
|
||||
/// already exist.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If any file could not be opened, or there was a problem setting
|
||||
/// the size of at least one file.
|
||||
///
|
||||
/// If at least one file is a named pipe (also known as a fifo).
|
||||
fn truncate_reference_and_size(
|
||||
rfilename: &str,
|
||||
size_string: &str,
|
||||
filenames: &[OsString],
|
||||
create: bool,
|
||||
fn file_truncate(
|
||||
no_create: bool,
|
||||
reference_size: Option<u64>,
|
||||
mode: &TruncateMode,
|
||||
filename: &OsString,
|
||||
) -> UResult<()> {
|
||||
let mode = match parse_mode_and_size(size_string) {
|
||||
Err(e) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-invalid-number", "error" => e),
|
||||
));
|
||||
let path = Path::new(filename);
|
||||
|
||||
// Get the length of the file.
|
||||
let file_size = match metadata(path) {
|
||||
Ok(metadata) => {
|
||||
// A pipe has no length. Do this check here to avoid duplicate `stat()` syscall.
|
||||
#[cfg(unix)]
|
||||
if metadata.file_type().is_fifo() {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
|
||||
));
|
||||
}
|
||||
metadata.len()
|
||||
}
|
||||
Ok(TruncateMode::Absolute(_)) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-must-specify-relative-size"),
|
||||
));
|
||||
}
|
||||
Ok(m) => m,
|
||||
Err(_) => 0,
|
||||
};
|
||||
|
||||
if let TruncateMode::RoundDown(0) | TruncateMode::RoundUp(0) = mode {
|
||||
// The reference size can be either:
|
||||
//
|
||||
// 1. The size of a given file
|
||||
// 2. The size of the file to be truncated if no reference has been provided.
|
||||
let actual_reference_size = reference_size.unwrap_or(file_size);
|
||||
|
||||
let Some(truncate_size) = mode.to_size(actual_reference_size) else {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-division-by-zero"),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let metadata = metadata(rfilename).map_err(|e| match e.kind() {
|
||||
ErrorKind::NotFound => USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-stat-no-such-file", "filename" => rfilename.quote()),
|
||||
),
|
||||
_ => e.map_err_context(String::new),
|
||||
})?;
|
||||
|
||||
let fsize = metadata.len();
|
||||
let tsize = mode.to_size(fsize);
|
||||
|
||||
for filename in filenames {
|
||||
file_truncate(filename, create, tsize)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Truncate files to match the size of a given reference file.
|
||||
///
|
||||
/// `rfilename` is the name of the reference file.
|
||||
///
|
||||
/// If `create` is true, then each file will be created if it does not
|
||||
/// already exist.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If any file could not be opened, or there was a problem setting
|
||||
/// the size of at least one file.
|
||||
///
|
||||
/// If at least one file is a named pipe (also known as a fifo).
|
||||
fn truncate_reference_file_only(
|
||||
rfilename: &str,
|
||||
filenames: &[OsString],
|
||||
create: bool,
|
||||
) -> UResult<()> {
|
||||
let metadata = metadata(rfilename).map_err(|e| match e.kind() {
|
||||
ErrorKind::NotFound => USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-stat-no-such-file", "filename" => rfilename.quote()),
|
||||
),
|
||||
_ => e.map_err_context(String::new),
|
||||
})?;
|
||||
|
||||
let tsize = metadata.len();
|
||||
|
||||
for filename in filenames {
|
||||
file_truncate(filename, create, tsize)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Truncate files to a specified size.
|
||||
///
|
||||
/// `size_string` gives either an absolute size or a relative size. A
|
||||
/// relative size adjusts the size of each file relative to its current
|
||||
/// size. For example, "3K" means "set each file to be three kilobytes"
|
||||
/// whereas "+3K" means "set each file to be three kilobytes larger than
|
||||
/// its current size".
|
||||
///
|
||||
/// If `create` is true, then each file will be created if it does not
|
||||
/// already exist.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If any file could not be opened, or there was a problem setting
|
||||
/// the size of at least one file.
|
||||
///
|
||||
/// If at least one file is a named pipe (also known as a fifo).
|
||||
fn truncate_size_only(size_string: &str, filenames: &[OsString], create: bool) -> UResult<()> {
|
||||
let mode = parse_mode_and_size(size_string).map_err(|e| {
|
||||
USimpleError::new(1, translate!("truncate-error-invalid-number", "error" => e))
|
||||
})?;
|
||||
|
||||
if let TruncateMode::RoundDown(0) | TruncateMode::RoundUp(0) = mode {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-division-by-zero"),
|
||||
));
|
||||
}
|
||||
|
||||
for filename in filenames {
|
||||
let path = Path::new(filename);
|
||||
let fsize = match metadata(path) {
|
||||
Ok(m) => {
|
||||
#[cfg(unix)]
|
||||
if m.file_type().is_fifo() {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
|
||||
));
|
||||
}
|
||||
m.len()
|
||||
}
|
||||
Err(_) => 0,
|
||||
};
|
||||
let tsize = mode.to_size(fsize);
|
||||
// TODO: Fix duplicate call to stat
|
||||
file_truncate(filename, create, tsize)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
do_file_truncate(path, !no_create, truncate_size)
|
||||
}
|
||||
|
||||
fn truncate(
|
||||
|
|
@ -350,21 +245,50 @@ fn truncate(
|
|||
size: Option<String>,
|
||||
filenames: &[OsString],
|
||||
) -> UResult<()> {
|
||||
let create = !no_create;
|
||||
let reference_size = match reference {
|
||||
Some(reference_path) => {
|
||||
let reference_metadata = metadata(&reference_path).map_err(|error| match error.kind() {
|
||||
ErrorKind::NotFound => USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-cannot-stat-no-such-file", "filename" => reference_path.quote()),
|
||||
),
|
||||
_ => error.map_err_context(String::new),
|
||||
})?;
|
||||
|
||||
// There are four possibilities
|
||||
// - reference file given and size given,
|
||||
// - reference file given but no size given,
|
||||
// - no reference file given but size given,
|
||||
// - no reference file given and no size given,
|
||||
match (reference, size) {
|
||||
(Some(rfilename), Some(size_string)) => {
|
||||
truncate_reference_and_size(&rfilename, &size_string, filenames, create)
|
||||
Some(reference_metadata.len())
|
||||
}
|
||||
(Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create),
|
||||
(None, Some(size_string)) => truncate_size_only(&size_string, filenames, create),
|
||||
(None, None) => unreachable!(), // this case cannot happen anymore because it's handled by clap
|
||||
None => None,
|
||||
};
|
||||
|
||||
let size_string = size.as_deref();
|
||||
|
||||
// Omitting the mode is equivalent to extending a file by 0 bytes.
|
||||
let mode = match size_string {
|
||||
Some(string) => match parse_mode_and_size(string) {
|
||||
Err(error) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-invalid-number", "error" => error),
|
||||
));
|
||||
}
|
||||
Ok(mode) => mode,
|
||||
},
|
||||
None => TruncateMode::Extend(0),
|
||||
};
|
||||
|
||||
// If a reference file has been given, the truncate mode cannot be absolute.
|
||||
if reference_size.is_some() && mode.is_absolute() {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("truncate-error-must-specify-relative-size"),
|
||||
));
|
||||
}
|
||||
|
||||
for filename in filenames {
|
||||
file_truncate(no_create, reference_size, &mode, filename)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decide whether a character is one of the size modifiers, like '+' or '<'.
|
||||
|
|
@ -382,13 +306,12 @@ fn is_modifier(c: char) -> bool {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `size_string` is empty, or if no number could be parsed from the
|
||||
/// given string (for example, if the string were `"abc"`).
|
||||
/// If `size_string` is empty.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
|
||||
/// assert_eq!(parse_mode_and_size("+123"), Ok(TruncateMode::Extend(123)));
|
||||
/// ```
|
||||
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ParseSizeError> {
|
||||
// Trim any whitespace.
|
||||
|
|
@ -432,8 +355,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_to_size() {
|
||||
assert_eq!(TruncateMode::Extend(5).to_size(10), 15);
|
||||
assert_eq!(TruncateMode::Reduce(5).to_size(10), 5);
|
||||
assert_eq!(TruncateMode::Reduce(5).to_size(3), 0);
|
||||
assert_eq!(TruncateMode::Extend(5).to_size(10), Some(15));
|
||||
assert_eq!(TruncateMode::Reduce(5).to_size(10), Some(5));
|
||||
assert_eq!(TruncateMode::Reduce(5).to_size(3), Some(0));
|
||||
assert_eq!(TruncateMode::RoundDown(4).to_size(13), Some(12));
|
||||
assert_eq!(TruncateMode::RoundDown(4).to_size(16), Some(16));
|
||||
assert_eq!(TruncateMode::RoundUp(8).to_size(10), Some(16));
|
||||
assert_eq!(TruncateMode::RoundUp(8).to_size(16), Some(16));
|
||||
assert_eq!(TruncateMode::RoundDown(0).to_size(123), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1092,3 +1092,58 @@ fn test_date_military_timezone_with_offset_variations() {
|
|||
.stdout_is(format!("{expected}\n"));
|
||||
}
|
||||
}
|
||||
|
||||
// Locale-aware hour formatting tests
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_date_locale_hour_c_locale() {
|
||||
// C locale should use 24-hour format
|
||||
new_ucmd!()
|
||||
.env("LC_ALL", "C")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg("2025-10-11T13:00")
|
||||
.succeeds()
|
||||
.stdout_contains("13:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
fn test_date_locale_hour_en_us() {
|
||||
// en_US locale typically uses 12-hour format when available
|
||||
// Note: If locale is not installed on system, falls back to C locale (24-hour)
|
||||
let result = new_ucmd!()
|
||||
.env("LC_ALL", "en_US.UTF-8")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg("2025-10-11T13:00")
|
||||
.succeeds();
|
||||
|
||||
let stdout = result.stdout_str();
|
||||
// Accept either 12-hour (if locale available) or 24-hour (if locale unavailable)
|
||||
// The important part is that the code doesn't crash and handles locale detection gracefully
|
||||
assert!(
|
||||
stdout.contains("1:00") || stdout.contains("13:00"),
|
||||
"date output should contain either 1:00 (12-hour) or 13:00 (24-hour), got: {stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_explicit_format_overrides_locale() {
|
||||
// Explicit format should override locale preferences
|
||||
new_ucmd!()
|
||||
.env("LC_ALL", "en_US.UTF-8")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg("2025-10-11T13:00")
|
||||
.arg("+%H:%M")
|
||||
.succeeds()
|
||||
.stdout_is("13:00\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,18 +33,13 @@ path_GNU="$("${READLINK}" -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")"
|
|||
|
||||
###
|
||||
|
||||
release_tag_GNU="v9.9"
|
||||
|
||||
# check if the GNU coreutils has been cloned, if not print instructions
|
||||
# note: the ${path_GNU} might already exist, so we check for the .git directory
|
||||
if test ! -d "${path_GNU}/.git"; then
|
||||
# note: the ${path_GNU} might already exist, so we check for the configure
|
||||
if test ! -f "${path_GNU}/configure"; then
|
||||
echo "Could not find the GNU coreutils (expected at '${path_GNU}')"
|
||||
echo "Download them to the expected path:"
|
||||
echo " git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\""
|
||||
echo "Afterwards, checkout the latest release tag:"
|
||||
echo " cd \"${path_GNU}\""
|
||||
echo " git fetch --all --tags"
|
||||
echo " git checkout tags/${release_tag_GNU}"
|
||||
echo " (cd '${path_GNU}' && fetch-gnu.sh ) "
|
||||
echo "You can edit fetch-gnu.sh to change the tag"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -125,23 +120,24 @@ done
|
|||
|
||||
if test -f gnu-built; then
|
||||
echo "GNU build already found. Skip"
|
||||
echo "'rm -f $(pwd)/gnu-built' to force the build"
|
||||
echo "'rm -f $(pwd)/{gnu-built,src/getlimits}' to force the build"
|
||||
echo "Note: the customization of the tests will still happen"
|
||||
else
|
||||
# Disable useless checks
|
||||
"${SED}" -i 's|check-texinfo: $(syntax_checks)|check-texinfo:|' doc/local.mk
|
||||
"${SED}" -i '/^wget.*/d' bootstrap.conf # wget is used to DL po. Remove the dep.
|
||||
./bootstrap --skip-po
|
||||
# Use CFLAGS for best build time since we discard GNU coreutils
|
||||
CFLAGS="${CFLAGS} -pipe -O0 -s" ./configure --quiet --disable-gcc-warnings --disable-nls --disable-dependency-tracking --disable-bold-man-page-references \
|
||||
CFLAGS="${CFLAGS} -pipe -O0 -s" ./configure -C --quiet --disable-gcc-warnings --disable-nls --disable-dependency-tracking --disable-bold-man-page-references \
|
||||
--enable-single-binary=symlinks \
|
||||
"$([ "${SELINUX_ENABLED}" = 1 ] && echo --with-selinux || echo --without-selinux)"
|
||||
#Add timeout to to protect against hangs
|
||||
"${SED}" -i 's|^"\$@|'"${SYSTEM_TIMEOUT}"' 600 "\$@|' build-aux/test-driver
|
||||
# Use a better diff
|
||||
"${SED}" -i 's|diff -c|diff -u|g' tests/Coreutils.pm
|
||||
|
||||
# Skip make if possible
|
||||
# Use our nproc for *BSD and macOS
|
||||
"${MAKE}" -j "$("${UU_BUILD_DIR}/nproc")"
|
||||
test -f src/getlimits || "${MAKE}" -j "$("${UU_BUILD_DIR}/nproc")"
|
||||
cp -f src/getlimits "${UU_BUILD_DIR}"
|
||||
|
||||
# Handle generated factor tests
|
||||
t_first=00
|
||||
|
|
@ -175,9 +171,6 @@ grep -rl '\$abs_path_dir_' tests/*/*.sh | xargs -r "${SED}" -i "s|\$abs_path_dir
|
|||
# Different message
|
||||
"${SED}" -i "s|coreutils: unknown program 'blah'|blah: function/utility not found|" tests/misc/coreutils.sh
|
||||
|
||||
# Remove hfs dependency (should be merged to upstream)
|
||||
"${SED}" -i -e "s|hfsplus|ext4 -O casefold|" -e "s|cd mnt|rm -d mnt/lost+found;chattr +F mnt;cd mnt|" tests/mv/hardlink-case.sh
|
||||
|
||||
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
|
||||
"${SED}" -i "s|grep '^#define HAVE_CAP 1' \$CONFIG_HEADER > /dev/null|true|" tests/ls/capability.sh
|
||||
|
||||
|
|
@ -229,8 +222,6 @@ sed -i -e "s|---dis ||g" tests/tail/overlay-headers.sh
|
|||
-e "s|strace -e inotify_add_watch|strace -f -e inotify_add_watch|" \
|
||||
tests/tail/inotify-dir-recreate.sh
|
||||
|
||||
test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}"
|
||||
|
||||
# pr produces very long log and this command isn't super interesting
|
||||
# SKIP for now
|
||||
"${SED}" -i -e "s|my \$prog = 'pr';$|my \$prog = 'pr';CuSkip::skip \"\$prog: SKIP for producing too long logs\";|" tests/pr/pr-tests.pl
|
||||
|
|
|
|||
9
util/fetch-gnu.sh
Executable file
9
util/fetch-gnu.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash -e
|
||||
ver="9.9"
|
||||
repo=https://github.com/coreutils/coreutils
|
||||
curl -L "${repo}/releases/download/v${ver}/coreutils-${ver}.tar.xz" | tar --strip-components=1 -xJf -
|
||||
|
||||
# backport from coreutils > 9.9
|
||||
curl ${repo}/raw/refs/heads/master/tests/mv/hardlink-case.sh > tests/mv/hardlink-case.sh
|
||||
curl ${repo}/raw/refs/heads/master/tests/mkdir/writable-under-readonly.sh > tests/mkdir/writable-under-readonly.sh
|
||||
curl ${repo}/raw/refs/heads/master/tests/cp/cp-mv-enotsup-xattr.sh > tests/cp/cp-mv-enotsup-xattr.sh #spell-checker:disable-line
|
||||
|
|
@ -31,5 +31,3 @@
|
|||
|
||||
= Disabled. Enabled at GNU coreutils > 9.9 =
|
||||
* tests/misc/tac-continue.sh
|
||||
* tests/mkdir/writable-under-readonly.sh
|
||||
* tests/cp/cp-mv-enotsup-xattr.sh
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue