1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

Merge pull request #8191 from sylvestre/l10n-dd

l10n: port dd for translation + add french
This commit is contained in:
Daniel Hofstetter 2025-06-30 11:02:15 +02:00 committed by GitHub
commit ba447eb869
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 435 additions and 95 deletions

View file

@ -113,3 +113,48 @@ dd-after-help = ### Operands
- nocache : request that OS drop cache.
- noctty : do not assign a controlling tty.
- nofollow : do not follow system links.
# Error messages
dd-error-failed-to-open = failed to open { $path }
dd-error-write-error = write error
dd-error-failed-to-seek = failed to seek in output file
dd-error-io-error = IO error
dd-error-cannot-skip-offset = '{ $file }': cannot skip to specified offset
dd-error-cannot-skip-invalid = '{ $file }': cannot skip: Invalid argument
dd-error-cannot-seek-invalid = '{ $output }': cannot seek: Invalid argument
dd-error-not-directory = setting flags for '{ $file }': Not a directory
dd-error-failed-discard-cache-input = failed to discard cache for: 'standard input'
dd-error-failed-discard-cache-output = failed to discard cache for: 'standard output'
# Parse errors
dd-error-unrecognized-operand = Unrecognized operand '{ $operand }'
dd-error-multiple-format-table = Only one of conv=ascii conv=ebcdic or conv=ibm may be specified
dd-error-multiple-case = Only one of conv=lcase or conv=ucase may be specified
dd-error-multiple-block = Only one of conv=block or conv=unblock may be specified
dd-error-multiple-excl = Only one ov conv=excl or conv=nocreat may be specified
dd-error-invalid-flag = invalid input flag: { $flag }
Try '{ $cmd } --help' for more information.
dd-error-conv-flag-no-match = Unrecognized conv=CONV -> { $flag }
dd-error-multiplier-parse-failure = invalid number: '{ $input }'
dd-error-multiplier-overflow = Multiplier string would overflow on current system -> { $input }
dd-error-block-without-cbs = conv=block or conv=unblock specified without cbs=N
dd-error-status-not-recognized = status=LEVEL not recognized -> { $level }
dd-error-unimplemented = feature not implemented on this system -> { $feature }
dd-error-bs-out-of-range = { $param }=N cannot fit into memory
dd-error-invalid-number = invalid number: { $input }
# Progress messages
dd-progress-records-in = { $complete }+{ $partial } records in
dd-progress-records-out = { $complete }+{ $partial } records out
dd-progress-truncated-record = { $count ->
[one] { $count } truncated record
*[other] { $count } truncated records
}
dd-progress-byte-copied = { $bytes } byte copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied = { $bytes } bytes copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si = { $bytes } bytes ({ $si }) copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si-iec = { $bytes } bytes ({ $si }, { $iec }) copied, { $duration } s, { $rate }/s
# Warnings
dd-warning-zero-multiplier = { $zero } is a zero multiplier; use { $alternative } if that is intended
dd-warning-signal-handler = Internal dd Warning: Unable to register signal handler

160
src/uu/dd/locales/fr-FR.ftl Normal file
View file

@ -0,0 +1,160 @@
dd-about = Copier, et optionnellement convertir, une ressource du système de fichiers
dd-usage = dd [OPÉRANDE]...
dd OPTION
dd-after-help = ### Opérandes
- bs=OCTETS : lire et écrire jusqu'à OCTETS octets à la fois (par défaut : 512) ;
remplace ibs et obs.
- cbs=OCTETS : la 'taille de bloc de conversion' en octets. S'applique aux
opérations conv=block et conv=unblock.
- conv=CONVS : une liste séparée par des virgules d'options de conversion ou (pour des
raisons historiques) d'indicateurs de fichier.
- count=N : arrêter la lecture de l'entrée après N opérations de lecture de taille ibs
plutôt que de continuer jusqu'à EOF. Voir iflag=count_bytes si l'arrêt après N octets
est préféré
- ibs=N : la taille du tampon utilisé pour les lectures (par défaut : 512)
- if=FICHIER : le fichier utilisé pour l'entrée. Quand non spécifié, stdin est utilisé à la place
- iflag=INDICATEURS : une liste séparée par des virgules d'indicateurs d'entrée qui spécifient comment
la source d'entrée est traitée. INDICATEURS peut être n'importe lequel des indicateurs d'entrée ou
indicateurs généraux spécifiés ci-dessous.
- skip=N (ou iseek=N) : ignorer N enregistrements de taille ibs dans l'entrée avant de commencer
les opérations de copie/conversion. Voir iflag=seek_bytes si la recherche de N octets est préférée.
- obs=N : la taille du tampon utilisé pour les écritures (par défaut : 512)
- of=FICHIER : le fichier utilisé pour la sortie. Quand non spécifié, stdout est utilisé
à la place
- oflag=INDICATEURS : liste séparée par des virgules d'indicateurs de sortie qui spécifient comment la
source de sortie est traitée. INDICATEURS peut être n'importe lequel des indicateurs de sortie ou
indicateurs généraux spécifiés ci-dessous
- seek=N (ou oseek=N) : recherche N enregistrements de taille obs dans la sortie avant de
commencer les opérations de copie/conversion. Voir oflag=seek_bytes si la recherche de N octets est
préférée
- status=NIVEAU : contrôle si les statistiques de volume et de performance sont écrites sur
stderr.
Quand non spécifié, dd affichera les statistiques à la fin. Un exemple est ci-dessous.
```plain
6+0 enregistrements en entrée
16+0 enregistrements en sortie
8192 octets (8.2 kB, 8.0 KiB) copiés, 0.00057009 s,
14.4 MB/s
Les deux premières lignes sont les statistiques de 'volume' et la dernière ligne est les
statistiques de 'performance'.
Les statistiques de volume indiquent le nombre de lectures complètes et partielles de taille ibs,
ou d'écritures de taille obs qui ont eu lieu pendant la copie. Le format des statistiques de
volume est <complètes>+<partielles>. Si des enregistrements ont été tronqués (voir
conv=block), les statistiques de volume contiendront le nombre d'enregistrements tronqués.
Les valeurs possibles de NIVEAU sont :
- progress : Afficher les statistiques de performance périodiques pendant la copie.
- noxfer : Afficher les statistiques de volume finales, mais pas les statistiques de performance.
- none : N'afficher aucune statistique.
L'affichage des statistiques de performance est aussi déclenché par le signal INFO (quand supporté),
ou le signal USR1. Définir la variable d'environnement POSIXLY_CORRECT à n'importe quelle valeur
(y compris une valeur vide) fera ignorer le signal USR1.
### Options de conversion
- ascii : convertir d'EBCDIC vers ASCII. C'est l'inverse de l'option ebcdic.
Implique conv=unblock.
- ebcdic : convertir d'ASCII vers EBCDIC. C'est l'inverse de l'option ascii.
Implique conv=block.
- ibm : convertir d'ASCII vers EBCDIC, en appliquant les conventions pour [, ]
et ~ spécifiées dans POSIX. Implique conv=block.
- ucase : convertir de minuscules vers majuscules.
- lcase : convertir de majuscules vers minuscules.
- block : pour chaque nouvelle ligne inférieure à la taille indiquée par cbs=OCTETS, supprimer
la nouvelle ligne et remplir avec des espaces jusqu'à cbs. Les lignes plus longues que cbs sont tronquées.
- unblock : pour chaque bloc d'entrée de la taille indiquée par cbs=OCTETS, supprimer
les espaces de fin à droite et remplacer par un caractère de nouvelle ligne.
- sparse : tente de rechercher la sortie quand un bloc de taille obs ne contient que
des zéros.
- swab : échange chaque paire d'octets adjacents. Si un nombre impair d'octets est
présent, l'octet final est omis.
- sync : remplit chaque bloc de taille ibs avec des zéros. Si block ou unblock est
spécifié, remplit avec des espaces à la place.
- excl : le fichier de sortie doit être créé. Échoue si le fichier de sortie est déjà
présent.
- nocreat : le fichier de sortie ne sera pas créé. Échoue si le fichier de sortie n'est
pas déjà présent.
- notrunc : le fichier de sortie ne sera pas tronqué. Si cette option n'est pas
présente, la sortie sera tronquée à l'ouverture.
- noerror : toutes les erreurs de lecture seront ignorées. Si cette option n'est pas présente,
dd n'ignorera que Error::Interrupted.
- fdatasync : les données seront écrites avant la fin.
- fsync : les données et les métadonnées seront écrites avant la fin.
### Indicateurs d'entrée
- count_bytes : une valeur pour count=N sera interprétée comme des octets.
- skip_bytes : une valeur pour skip=N sera interprétée comme des octets.
- fullblock : attendre ibs octets de chaque lecture. les lectures de longueur zéro sont toujours
considérées comme EOF.
### Indicateurs de sortie
- append : ouvrir le fichier en mode ajout. Considérez définir conv=notrunc aussi.
- seek_bytes : une valeur pour seek=N sera interprétée comme des octets.
### Indicateurs généraux
- direct : utiliser les E/S directes pour les données.
- directory : échouer sauf si l'entrée donnée (si utilisée comme iflag) ou
la sortie (si utilisée comme oflag) est un répertoire.
- dsync : utiliser les E/S synchronisées pour les données.
- sync : utiliser les E/S synchronisées pour les données et les métadonnées.
- nonblock : utiliser les E/S non-bloquantes.
- noatime : ne pas mettre à jour l'heure d'accès.
- nocache : demander au système d'exploitation de supprimer le cache.
- noctty : ne pas assigner un tty de contrôle.
- nofollow : ne pas suivre les liens système.
# Error messages
dd-error-failed-to-open = échec de l'ouverture de { $path }
dd-error-write-error = erreur d'écriture
dd-error-failed-to-seek = échec de la recherche dans le fichier de sortie
dd-error-io-error = erreur E/S
dd-error-cannot-skip-offset = '{ $file }' : impossible d'ignorer jusqu'au décalage spécifié
dd-error-cannot-skip-invalid = '{ $file }' : impossible d'ignorer : Argument invalide
dd-error-cannot-seek-invalid = '{ $output }' : impossible de rechercher : Argument invalide
dd-error-not-directory = définir les indicateurs pour '{ $file }' : N'est pas un répertoire
dd-error-failed-discard-cache-input = échec de la suppression du cache pour : 'entrée standard'
dd-error-failed-discard-cache-output = échec de la suppression du cache pour : 'sortie standard'
# Parse errors
dd-error-unrecognized-operand = Opérande non reconnue '{ $operand }'
dd-error-multiple-format-table = Seul un seul de conv=ascii conv=ebcdic ou conv=ibm peut être spécifié
dd-error-multiple-case = Seul un seul de conv=lcase ou conv=ucase peut être spécifié
dd-error-multiple-block = Seul un seul de conv=block ou conv=unblock peut être spécifié
dd-error-multiple-excl = Seul un seul de conv=excl ou conv=nocreat peut être spécifié
dd-error-invalid-flag = indicateur d'entrée invalide : '{ $flag }'
Essayez '{ $cmd } --help' pour plus d'informations.
dd-error-conv-flag-no-match = conv=CONV non reconnu -> { $flag }
dd-error-multiplier-parse-failure = nombre invalide : { $input }
dd-error-multiplier-overflow = La chaîne de multiplicateur déborderait sur le système actuel -> { $input }
dd-error-block-without-cbs = conv=block ou conv=unblock spécifié sans cbs=N
dd-error-status-not-recognized = status=NIVEAU non reconnu -> { $level }
dd-error-unimplemented = fonctionnalité non implémentée sur ce système -> { $feature }
dd-error-bs-out-of-range = { $param }=N ne peut pas tenir en mémoire
dd-error-invalid-number = nombre invalide : { $input }
# Progress messages
dd-progress-records-in = { $complete }+{ $partial } enregistrements en entrée
dd-progress-records-out = { $complete }+{ $partial } enregistrements en sortie
dd-progress-truncated-record = { $count ->
[one] { $count } enregistrement tronqué
*[other] { $count } enregistrements tronqués
}
dd-progress-byte-copied = { $bytes } octet copié, { $duration } s, { $rate }/s
dd-progress-bytes-copied = { $bytes } octets copiés, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si = { $bytes } octets ({ $si }) copiés, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si-iec = { $bytes } octets ({ $si }, { $iec }) copiés, { $duration } s, { $rate }/s
# Warnings
dd-warning-zero-multiplier = { $zero } est un multiplicateur zéro ; utilisez { $alternative } si c'est voulu
dd-warning-signal-handler = Avertissement dd interne : Impossible d'enregistrer le gestionnaire de signal

View file

@ -26,6 +26,7 @@ use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater};
use uucore::io::OwnedFileDescriptorOrHandle;
use std::cmp;
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fs::{File, OpenOptions};
@ -62,7 +63,7 @@ use uucore::error::{USimpleError, set_exit_code};
use uucore::show_if_err;
use uucore::{format_usage, show_error};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
const BUF_INIT_BYTE: u8 = 0xDD;
/// Final settings after parsing
@ -235,7 +236,13 @@ impl Source {
#[cfg(not(unix))]
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-offset",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
Ok(m)
}
Ok(m) => Ok(m),
@ -247,14 +254,26 @@ impl Source {
if len < n {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard input': cannot skip: Invalid argument");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-invalid",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
set_exit_code(1);
return Ok(len);
}
}
match io::copy(&mut f.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-offset",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
Ok(m)
}
Ok(m) => Ok(m),
@ -343,7 +362,10 @@ impl<'a> Input<'a> {
if settings.iflags.directory && !f.metadata()?.is_dir() {
return Err(USimpleError::new(
1,
"setting flags for 'standard input': Not a directory",
get_message_with_args(
"dd-error-not-directory",
HashMap::from([("file".to_string(), "standard input".to_string())]),
),
));
}
};
@ -364,8 +386,12 @@ impl<'a> Input<'a> {
opts.custom_flags(libc_flags);
}
opts.open(filename)
.map_err_context(|| format!("failed to open {}", filename.quote()))?
opts.open(filename).map_err_context(|| {
get_message_with_args(
"dd-error-failed-to-open",
HashMap::from([("path".to_string(), filename.quote().to_string())]),
)
})?
};
let mut src = Source::File(src);
@ -457,10 +483,11 @@ impl Input<'_> {
fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
#[cfg(target_os = "linux")]
{
show_if_err!(self
.src
show_if_err!(
self.src
.discard_cache(offset, len)
.map_err_context(|| "failed to discard cache for: 'standard input'".to_string()));
.map_err_context(|| get_message("dd-error-failed-discard-cache-input"))
);
}
#[cfg(not(target_os = "linux"))]
{
@ -609,7 +636,16 @@ impl Dest {
if len < n {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard output': cannot seek: Invalid argument");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-seek-invalid",
HashMap::from([(
"output".to_string(),
"standard output".to_string()
)])
)
);
set_exit_code(1);
return Ok(len);
}
@ -723,7 +759,7 @@ impl<'a> Output<'a> {
fn new_stdout(settings: &'a Settings) -> UResult<Self> {
let mut dst = Dest::Stdout(io::stdout());
dst.seek(settings.seek)
.map_err_context(|| "write error".to_string())?;
.map_err_context(|| get_message("dd-error-write-error"))?;
Ok(Self { dst, settings })
}
@ -744,8 +780,12 @@ impl<'a> Output<'a> {
opts.open(path)
}
let dst = open_dst(filename, &settings.oconv, &settings.oflags)
.map_err_context(|| format!("failed to open {}", filename.quote()))?;
let dst = open_dst(filename, &settings.oconv, &settings.oflags).map_err_context(|| {
get_message_with_args(
"dd-error-failed-to-open",
HashMap::from([("path".to_string(), filename.quote().to_string())]),
)
})?;
// Seek to the index in the output file, truncating if requested.
//
@ -770,7 +810,7 @@ impl<'a> Output<'a> {
};
let mut dst = Dest::File(dst, density);
dst.seek(settings.seek)
.map_err_context(|| "failed to seek in output file".to_string())?;
.map_err_context(|| get_message("dd-error-failed-to-seek"))?;
Ok(Self { dst, settings })
}
@ -832,9 +872,11 @@ impl<'a> Output<'a> {
fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
#[cfg(target_os = "linux")]
{
show_if_err!(self.dst.discard_cache(offset, len).map_err_context(|| {
"failed to discard cache for: 'standard output'".to_string()
}));
show_if_err!(
self.dst
.discard_cache(offset, len)
.map_err_context(|| { get_message("dd-error-failed-discard-cache-output") })
);
}
#[cfg(not(target_os = "linux"))]
{
@ -1083,7 +1125,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
#[cfg(target_os = "linux")]
if let Err(e) = &signal_handler {
if Some(StatusLevel::None) != i.settings.status {
eprintln!("Internal dd Warning: Unable to register signal handler \n\t{e}");
eprintln!("{}\n\t{e}", get_message("dd-warning-signal-handler"));
}
}
@ -1419,7 +1461,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
None if is_stdout_redirected_to_seekable_file() => Output::new_file_from_stdout(&settings)?,
None => Output::new_stdout(&settings)?,
};
dd_copy(i, o).map_err_context(|| "IO error".to_string())
dd_copy(i, o).map_err_context(|| get_message("dd-error-io-error"))
}
pub fn uu_app() -> Command {

View file

@ -9,42 +9,53 @@ mod unit_tests;
use super::{ConversionMode, IConvFlags, IFlags, Num, OConvFlags, OFlags, Settings, StatusLevel};
use crate::conversion_tables::ConversionTable;
use std::collections::HashMap;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::UError;
use uucore::locale::{get_message, get_message_with_args};
use uucore::parser::parse_size::{ParseSizeError, Parser as SizeParser};
use uucore::show_warning;
/// Parser Errors describe errors with parser input
#[derive(Debug, PartialEq, Eq, Error)]
pub enum ParseError {
#[error("Unrecognized operand '{0}'")]
#[error("{}", get_message_with_args("dd-error-unrecognized-operand",
HashMap::from([("operand".to_string(), .0.clone())])))]
UnrecognizedOperand(String),
#[error("Only one of conv=ascii conv=ebcdic or conv=ibm may be specified")]
#[error("{}", get_message("dd-error-multiple-format-table"))]
MultipleFmtTable,
#[error("Only one of conv=lcase or conv=ucase may be specified")]
#[error("{}", get_message("dd-error-multiple-case"))]
MultipleUCaseLCase,
#[error("Only one of conv=block or conv=unblock may be specified")]
#[error("{}", get_message("dd-error-multiple-block"))]
MultipleBlockUnblock,
#[error("Only one ov conv=excl or conv=nocreat may be specified")]
#[error("{}", get_message("dd-error-multiple-excl"))]
MultipleExclNoCreate,
#[error("invalid input flag: {}\nTry '{} --help' for more information.", .0, uucore::execution_phrase())]
#[error("{}", get_message_with_args("dd-error-invalid-flag",
HashMap::from([("flag".to_string(), .0.clone()),("cmd".to_string(), uucore::execution_phrase().to_string())])))]
FlagNoMatch(String),
#[error("Unrecognized conv=CONV -> {0}")]
#[error("{}", get_message_with_args("dd-error-conv-flag-no-match",
HashMap::from([("flag".to_string(), .0.clone())])))]
ConvFlagNoMatch(String),
#[error("invalid number: {0}")]
#[error("{}", get_message_with_args("dd-error-multiplier-parse-failure",
HashMap::from([("input".to_string(), .0.clone())])))]
MultiplierStringParseFailure(String),
#[error("Multiplier string would overflow on current system -> {0}")]
#[error("{}", get_message_with_args("dd-error-multiplier-overflow",
HashMap::from([("input".to_string(), .0.clone())])))]
MultiplierStringOverflow(String),
#[error("conv=block or conv=unblock specified without cbs=N")]
#[error("{}", get_message("dd-error-block-without-cbs"))]
BlockUnblockWithoutCBS,
#[error("status=LEVEL not recognized -> {0}")]
#[error("{}", get_message_with_args("dd-error-status-not-recognized",
HashMap::from([("level".to_string(), .0.clone())])))]
StatusLevelNotRecognized(String),
#[error("feature not implemented on this system -> {0}")]
#[error("{}", get_message_with_args("dd-error-unimplemented",
HashMap::from([("feature".to_string(), .0.clone())])))]
Unimplemented(String),
#[error("{0}=N cannot fit into memory")]
#[error("{}", get_message_with_args("dd-error-bs-out-of-range",
HashMap::from([("param".to_string(), .0.clone())])))]
BsOutOfRange(String),
#[error("invalid number: {0}")]
#[error("{}", get_message_with_args("dd-error-invalid-number",
HashMap::from([("input".to_string(), .0.clone())])))]
InvalidNumber(String),
}
@ -424,9 +435,14 @@ impl UError for ParseError {
fn show_zero_multiplier_warning() {
show_warning!(
"{} is a zero multiplier; use {} if that is intended",
"0x".quote(),
"00x".quote()
"{}",
get_message_with_args(
"dd-warning-zero-multiplier",
HashMap::from([
("zero".to_string(), "0x".quote().to_string()),
("alternative".to_string(), "00x".quote().to_string())
])
)
);
}

View file

@ -9,6 +9,7 @@
//! read and write progress of a running `dd` process. The
//! [`gen_prog_updater`] function can be used to implement a progress
//! updater that runs in its own thread.
use std::collections::HashMap;
use std::io::Write;
use std::sync::mpsc;
#[cfg(target_os = "linux")]
@ -20,6 +21,8 @@ use signal_hook::iterator::Handle;
use uucore::{
error::UResult,
format::num_format::{FloatVariant, Formatter},
locale::get_message_with_args,
locale::setup_localization,
};
use crate::numbers::{SuffixType, to_magnitude_and_suffix};
@ -102,8 +105,13 @@ impl ProgUpdate {
self.write_stat.report(w)?;
match self.read_stat.records_truncated {
0 => {}
1 => writeln!(w, "1 truncated record")?,
n => writeln!(w, "{n} truncated records")?,
count => {
let message = get_message_with_args(
"dd-progress-truncated-record",
HashMap::from([("count".to_string(), count.to_string())]),
);
writeln!(w, "{}", message)?;
}
}
Ok(())
}
@ -164,24 +172,45 @@ impl ProgUpdate {
// If the number of bytes written is sufficiently large, then
// print a more concise representation of the number, like
// "1.2 kB" and "1.0 KiB".
match btotal {
1 => write!(
w,
"{carriage_return}{btotal} byte copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
0..=999 => write!(
w,
"{carriage_return}{btotal} bytes copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
1000..=1023 => write!(
w,
"{carriage_return}{btotal} bytes ({btotal_metric}) copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
_ => write!(
w,
"{carriage_return}{btotal} bytes ({btotal_metric}, {btotal_bin}) copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
let message = match btotal {
1 => get_message_with_args(
"dd-progress-byte-copied",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
0..=999 => get_message_with_args(
"dd-progress-bytes-copied",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
1000..=1023 => get_message_with_args(
"dd-progress-bytes-copied-si",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("si".to_string(), btotal_metric.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
_ => get_message_with_args(
"dd-progress-bytes-copied-si-iec",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("si".to_string(), btotal_metric.to_string()),
("iec".to_string(), btotal_bin.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
};
write!(w, "{carriage_return}{message}{newline}")?;
Ok(())
}
@ -311,11 +340,14 @@ impl ReadStat {
///
/// If there is a problem writing to `w`.
fn report(&self, w: &mut impl Write) -> std::io::Result<()> {
writeln!(
w,
"{}+{} records in",
self.reads_complete, self.reads_partial
)?;
let message = get_message_with_args(
"dd-progress-records-in",
HashMap::from([
("complete".to_string(), self.reads_complete.to_string()),
("partial".to_string(), self.reads_partial.to_string()),
]),
);
writeln!(w, "{}", message)?;
Ok(())
}
}
@ -368,11 +400,14 @@ impl WriteStat {
///
/// If there is a problem writing to `w`.
fn report(&self, w: &mut impl Write) -> std::io::Result<()> {
writeln!(
w,
"{}+{} records out",
self.writes_complete, self.writes_partial
)
let message = get_message_with_args(
"dd-progress-records-out",
HashMap::from([
("complete".to_string(), self.writes_complete.to_string()),
("partial".to_string(), self.writes_partial.to_string()),
]),
);
writeln!(w, "{}", message)
}
}
@ -428,6 +463,9 @@ pub(crate) fn gen_prog_updater(
print_level: Option<StatusLevel>,
) -> impl Fn() {
move || {
// As we are in a thread, we need to set up localization independently.
let _ = setup_localization("dd");
let mut progress_printed = false;
while let Ok(update) = rx.recv() {
// Print the final read/write statistics.
@ -502,6 +540,9 @@ pub(crate) fn gen_prog_updater(
) -> impl Fn() {
// --------------------------------------------------------------
move || {
// As we are in a thread, we need to set up localization independently.
let _ = setup_localization("dd");
// Holds the state of whether we have printed the current progress.
// This is needed so that we know whether or not to print a newline
// character before outputting non-progress data.
@ -532,11 +573,18 @@ pub(crate) fn gen_prog_updater(
#[cfg(test)]
mod tests {
use std::env;
use std::io::Cursor;
use std::time::Duration;
use uucore::locale::setup_localization;
use super::{ProgUpdate, ReadStat, WriteStat};
fn init() {
unsafe {
env::set_var("LANG", "C");
}
let _ = setup_localization("dd");
}
fn prog_update_write(n: u128) -> ProgUpdate {
ProgUpdate {
@ -561,22 +609,31 @@ mod tests {
#[test]
fn test_read_stat_report() {
init();
let read_stat = ReadStat::new(1, 2, 3, 4);
let mut cursor = Cursor::new(vec![]);
read_stat.report(&mut cursor).unwrap();
assert_eq!(cursor.get_ref(), b"1+2 records in\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records in\n"
);
}
#[test]
fn test_write_stat_report() {
init();
let write_stat = WriteStat::new(1, 2, 3);
let mut cursor = Cursor::new(vec![]);
write_stat.report(&mut cursor).unwrap();
assert_eq!(cursor.get_ref(), b"1+2 records out\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records out\n"
);
}
#[test]
fn test_prog_update_write_io_lines() {
init();
let read_stat = ReadStat::new(1, 2, 3, 4);
let write_stat = WriteStat::new(4, 5, 6);
let duration = Duration::new(789, 0);
@ -591,13 +648,14 @@ mod tests {
let mut cursor = Cursor::new(vec![]);
prog_update.write_io_lines(&mut cursor).unwrap();
assert_eq!(
cursor.get_ref(),
b"1+2 records in\n4+5 records out\n3 truncated records\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records in\n4+5 records out\n3 truncated records\n"
);
}
#[test]
fn test_prog_update_write_prog_line() {
init();
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
write_stat: WriteStat::default(),
@ -615,45 +673,55 @@ mod tests {
// 0 bytes copied, 7.9151e-05 s, 0.0 kB/s
//
// The throughput still does not match GNU dd.
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"0 bytes copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(1);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"1 byte copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1 byte copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(999);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"999 bytes copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"999 bytes copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(1000);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1000 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1000 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
);
let prog_update = prog_update_write(1023);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1023 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1023 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
);
let prog_update = prog_update_write(1024);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1024 bytes (1.0 kB, 1.0 KiB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1024 bytes (1.0 kB, 1.0 KiB) copied, 1 s, 1.0 kB/s\n"
);
}
#[test]
fn write_transfer_stats() {
init();
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
write_stat: WriteStat::default(),
@ -664,16 +732,18 @@ mod tests {
prog_update
.write_transfer_stats(&mut cursor, false)
.unwrap();
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
assert_eq!(iter.next().unwrap(), b"0+0 records in");
assert_eq!(iter.next().unwrap(), b"0+0 records out");
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"");
let output_str = std::str::from_utf8(cursor.get_ref()).unwrap();
let mut iter = output_str.split('\n');
assert_eq!(iter.next().unwrap(), "0+0 records in");
assert_eq!(iter.next().unwrap(), "0+0 records out");
assert_eq!(iter.next().unwrap(), "0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "");
assert!(iter.next().is_none());
}
#[test]
fn write_final_transfer_stats() {
init();
// Tests the formatting of the final statistics written after a progress line.
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
@ -685,21 +755,26 @@ mod tests {
let rewrite = true;
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
prog_update.write_transfer_stats(&mut cursor, true).unwrap();
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
assert_eq!(iter.next().unwrap(), b"\r0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"0+0 records in");
assert_eq!(iter.next().unwrap(), b"0+0 records out");
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"");
let output_str = std::str::from_utf8(cursor.get_ref()).unwrap();
let mut iter = output_str.split('\n');
assert_eq!(iter.next().unwrap(), "\r0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "0+0 records in");
assert_eq!(iter.next().unwrap(), "0+0 records out");
assert_eq!(iter.next().unwrap(), "0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "");
assert!(iter.next().is_none());
}
#[test]
fn test_duration_precision() {
init();
let prog_update = prog_update_duration(Duration::from_nanos(123));
let mut cursor = Cursor::new(vec![]);
let rewrite = false;
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.23e-07 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"0 bytes copied, 0.000000123 s, 0.0 B/s\n"
);
}
}

View file

@ -1617,6 +1617,7 @@ fn test_reading_partial_blocks_from_fifo() {
.args(["dd", "ibs=3", "obs=3", &format!("if={fifoname}")])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env("LANG", "C")
.spawn()
.unwrap();
@ -1661,6 +1662,7 @@ fn test_reading_partial_blocks_from_fifo_unbuffered() {
.args(["dd", "bs=3", "ibs=1", "obs=1", &format!("if={fifoname}")])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env("LANG", "C")
.spawn()
.unwrap();
@ -1767,10 +1769,10 @@ fn test_wrong_number_err_msg() {
new_ucmd!()
.args(&["count=kBb"])
.fails()
.stderr_contains("dd: invalid number: kBb\n");
.stderr_contains("dd: invalid number: 'kBb'\n");
new_ucmd!()
.args(&["count=1kBb555"])
.fails()
.stderr_contains("dd: invalid number: 1kBb555\n");
.stderr_contains("dd: invalid number: '1kBb555'\n");
}