|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h> |
|
|
|
|
|
#include <ctype.h> |
|
|
#include <sys/types.h> |
|
|
#include <signal.h> |
|
|
|
|
|
#include "system.h" |
|
|
#include "alignalloc.h" |
|
|
#include "close-stream.h" |
|
|
#include "fd-reopen.h" |
|
|
#include "gethrxtime.h" |
|
|
#include "human.h" |
|
|
#include "ioblksize.h" |
|
|
#include "long-options.h" |
|
|
#include "quote.h" |
|
|
#include "xstrtol.h" |
|
|
#include "xtime.h" |
|
|
|
|
|
|
|
|
#define PROGRAM_NAME "dd" |
|
|
|
|
|
#define AUTHORS \ |
|
|
proper_name ("Paul Rubin"), \ |
|
|
proper_name ("David MacKenzie"), \ |
|
|
proper_name ("Stuart Kemp") |
|
|
|
|
|
|
|
|
|
|
|
#ifndef SA_NOCLDSTOP |
|
|
# define SA_NOCLDSTOP 0 |
|
|
# define sigprocmask(How, Set, Oset) |
|
|
# define sigset_t int |
|
|
# if ! HAVE_SIGINTERRUPT |
|
|
# define siginterrupt(sig, flag) |
|
|
# endif |
|
|
#endif |
|
|
|
|
|
|
|
|
#ifndef SA_RESETHAND |
|
|
# define SA_RESETHAND 0 |
|
|
#endif |
|
|
|
|
|
#ifndef SIGINFO |
|
|
# define SIGINFO SIGUSR1 |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#ifndef O_CIO |
|
|
# define O_CIO 0 |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#undef O_NOCACHE |
|
|
|
|
|
#define output_char(c) \ |
|
|
do \ |
|
|
{ \ |
|
|
obuf[oc++] = (c); \ |
|
|
if (oc >= output_blocksize) \ |
|
|
write_output (); \ |
|
|
} \ |
|
|
while (0) |
|
|
|
|
|
|
|
|
#define DEFAULT_BLOCKSIZE 512 |
|
|
|
|
|
|
|
|
enum |
|
|
{ |
|
|
C_ASCII = 01, |
|
|
|
|
|
C_EBCDIC = 02, |
|
|
C_IBM = 04, |
|
|
C_BLOCK = 010, |
|
|
C_UNBLOCK = 020, |
|
|
C_LCASE = 040, |
|
|
C_UCASE = 0100, |
|
|
C_SWAB = 0200, |
|
|
C_NOERROR = 0400, |
|
|
C_NOTRUNC = 01000, |
|
|
C_SYNC = 02000, |
|
|
|
|
|
|
|
|
|
|
|
C_TWOBUFS = 04000, |
|
|
|
|
|
C_NOCREAT = 010000, |
|
|
C_EXCL = 020000, |
|
|
C_FDATASYNC = 040000, |
|
|
C_FSYNC = 0100000, |
|
|
|
|
|
C_SPARSE = 0200000 |
|
|
}; |
|
|
|
|
|
|
|
|
enum |
|
|
{ |
|
|
STATUS_NONE = 1, |
|
|
STATUS_NOXFER = 2, |
|
|
STATUS_DEFAULT = 3, |
|
|
STATUS_PROGRESS = 4 |
|
|
}; |
|
|
|
|
|
|
|
|
static char const *input_file = nullptr; |
|
|
|
|
|
|
|
|
static char const *output_file = nullptr; |
|
|
|
|
|
|
|
|
static idx_t page_size; |
|
|
|
|
|
|
|
|
static idx_t input_blocksize = 0; |
|
|
|
|
|
|
|
|
static idx_t output_blocksize = 0; |
|
|
|
|
|
|
|
|
static idx_t conversion_blocksize = 0; |
|
|
|
|
|
|
|
|
static intmax_t skip_records = 0; |
|
|
|
|
|
|
|
|
|
|
|
static idx_t skip_bytes = 0; |
|
|
|
|
|
|
|
|
static intmax_t seek_records = 0; |
|
|
|
|
|
|
|
|
|
|
|
static intmax_t seek_bytes = 0; |
|
|
|
|
|
|
|
|
static bool final_op_was_seek; |
|
|
|
|
|
|
|
|
static intmax_t max_records = INTMAX_MAX; |
|
|
|
|
|
|
|
|
static idx_t max_bytes = 0; |
|
|
|
|
|
|
|
|
static int conversions_mask = 0; |
|
|
|
|
|
|
|
|
static int input_flags = 0; |
|
|
static int output_flags = 0; |
|
|
|
|
|
|
|
|
static int status_level = STATUS_DEFAULT; |
|
|
|
|
|
|
|
|
static bool translation_needed = false; |
|
|
|
|
|
|
|
|
static intmax_t w_partial = 0; |
|
|
|
|
|
|
|
|
static intmax_t w_full = 0; |
|
|
|
|
|
|
|
|
static intmax_t r_partial = 0; |
|
|
|
|
|
|
|
|
static intmax_t r_full = 0; |
|
|
|
|
|
|
|
|
static intmax_t w_bytes = 0; |
|
|
|
|
|
|
|
|
static intmax_t reported_w_bytes = -1; |
|
|
|
|
|
|
|
|
static xtime_t start_time; |
|
|
|
|
|
|
|
|
static xtime_t next_time; |
|
|
|
|
|
|
|
|
static int progress_len; |
|
|
|
|
|
|
|
|
static bool input_seekable; |
|
|
|
|
|
|
|
|
|
|
|
static int input_seek_errno; |
|
|
|
|
|
|
|
|
static off_t input_offset; |
|
|
|
|
|
|
|
|
static bool warn_partial_read; |
|
|
|
|
|
|
|
|
static intmax_t r_truncate = 0; |
|
|
|
|
|
|
|
|
|
|
|
static char newline_character = '\n'; |
|
|
static char space_character = ' '; |
|
|
|
|
|
|
|
|
static char *ibuf; |
|
|
static char *obuf; |
|
|
|
|
|
|
|
|
static idx_t oc = 0; |
|
|
|
|
|
|
|
|
static idx_t col = 0; |
|
|
|
|
|
|
|
|
static sigset_t caught_signals; |
|
|
|
|
|
|
|
|
static sig_atomic_t volatile interrupt_signal; |
|
|
|
|
|
|
|
|
static sig_atomic_t volatile info_signal_count; |
|
|
|
|
|
|
|
|
static bool i_nocache, o_nocache; |
|
|
|
|
|
|
|
|
static bool i_nocache_eof, o_nocache_eof; |
|
|
|
|
|
|
|
|
static ssize_t (*iread_fnc) (int fd, char *buf, idx_t size); |
|
|
|
|
|
|
|
|
#define LONGEST_SYMBOL "count_bytes" |
|
|
|
|
|
|
|
|
struct symbol_value |
|
|
{ |
|
|
char symbol[sizeof LONGEST_SYMBOL]; |
|
|
int value; |
|
|
}; |
|
|
|
|
|
|
|
|
static struct symbol_value const conversions[] = |
|
|
{ |
|
|
{"ascii", C_ASCII | C_UNBLOCK | C_TWOBUFS}, |
|
|
{"ebcdic", C_EBCDIC | C_BLOCK | C_TWOBUFS}, |
|
|
{"ibm", C_IBM | C_BLOCK | C_TWOBUFS}, |
|
|
{"block", C_BLOCK | C_TWOBUFS}, |
|
|
{"unblock", C_UNBLOCK | C_TWOBUFS}, |
|
|
{"lcase", C_LCASE | C_TWOBUFS}, |
|
|
{"ucase", C_UCASE | C_TWOBUFS}, |
|
|
{"sparse", C_SPARSE}, |
|
|
{"swab", C_SWAB | C_TWOBUFS}, |
|
|
{"noerror", C_NOERROR}, |
|
|
{"nocreat", C_NOCREAT}, |
|
|
{"excl", C_EXCL}, |
|
|
{"notrunc", C_NOTRUNC}, |
|
|
{"sync", C_SYNC}, |
|
|
{"fdatasync", C_FDATASYNC}, |
|
|
{"fsync", C_FSYNC}, |
|
|
{"", 0} |
|
|
}; |
|
|
|
|
|
#define FFS_MASK(x) ((x) ^ ((x) & ((x) - 1))) |
|
|
enum |
|
|
{ |
|
|
|
|
|
|
|
|
v = ~(0 |
|
|
| O_APPEND |
|
|
| O_BINARY |
|
|
| O_CIO |
|
|
| O_DIRECT |
|
|
| O_DIRECTORY |
|
|
| O_DSYNC |
|
|
| O_EXCL |
|
|
| O_NOATIME |
|
|
| O_NOCTTY |
|
|
| O_NOFOLLOW |
|
|
| O_NOLINKS |
|
|
| O_NONBLOCK |
|
|
| O_SYNC |
|
|
| O_TEXT |
|
|
), |
|
|
|
|
|
|
|
|
O_FULLBLOCK = FFS_MASK (v), |
|
|
v2 = v ^ O_FULLBLOCK, |
|
|
|
|
|
O_NOCACHE = FFS_MASK (v2), |
|
|
v3 = v2 ^ O_NOCACHE, |
|
|
|
|
|
O_COUNT_BYTES = FFS_MASK (v3), |
|
|
v4 = v3 ^ O_COUNT_BYTES, |
|
|
|
|
|
O_SKIP_BYTES = FFS_MASK (v4), |
|
|
v5 = v4 ^ O_SKIP_BYTES, |
|
|
|
|
|
O_SEEK_BYTES = FFS_MASK (v5) |
|
|
}; |
|
|
|
|
|
|
|
|
static_assert (O_FULLBLOCK != 0); |
|
|
static_assert (O_NOCACHE != 0); |
|
|
static_assert (O_COUNT_BYTES != 0); |
|
|
static_assert (O_SKIP_BYTES != 0); |
|
|
static_assert (O_SEEK_BYTES != 0); |
|
|
|
|
|
#define MULTIPLE_BITS_SET(i) (((i) & ((i) - 1)) != 0) |
|
|
|
|
|
|
|
|
static_assert ( ! MULTIPLE_BITS_SET (O_FULLBLOCK)); |
|
|
static_assert ( ! MULTIPLE_BITS_SET (O_NOCACHE)); |
|
|
static_assert ( ! MULTIPLE_BITS_SET (O_COUNT_BYTES)); |
|
|
static_assert ( ! MULTIPLE_BITS_SET (O_SKIP_BYTES)); |
|
|
static_assert ( ! MULTIPLE_BITS_SET (O_SEEK_BYTES)); |
|
|
|
|
|
|
|
|
static struct symbol_value const flags[] = |
|
|
{ |
|
|
{"append", O_APPEND}, |
|
|
{"binary", O_BINARY}, |
|
|
{"cio", O_CIO}, |
|
|
{"direct", O_DIRECT}, |
|
|
{"directory", O_DIRECTORY}, |
|
|
{"dsync", O_DSYNC}, |
|
|
{"noatime", O_NOATIME}, |
|
|
{"nocache", O_NOCACHE}, |
|
|
{"noctty", O_NOCTTY}, |
|
|
{"nofollow", HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0}, |
|
|
{"nolinks", O_NOLINKS}, |
|
|
{"nonblock", O_NONBLOCK}, |
|
|
{"sync", O_SYNC}, |
|
|
{"text", O_TEXT}, |
|
|
{"fullblock", O_FULLBLOCK}, |
|
|
{"count_bytes", O_COUNT_BYTES}, |
|
|
{"skip_bytes", O_SKIP_BYTES}, |
|
|
{"seek_bytes", O_SEEK_BYTES}, |
|
|
{"", 0} |
|
|
}; |
|
|
|
|
|
|
|
|
static struct symbol_value const statuses[] = |
|
|
{ |
|
|
{"none", STATUS_NONE}, |
|
|
{"noxfer", STATUS_NOXFER}, |
|
|
{"progress", STATUS_PROGRESS}, |
|
|
{"", 0} |
|
|
}; |
|
|
|
|
|
|
|
|
static unsigned char trans_table[256]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char const ascii_to_ebcdic[] = |
|
|
{ |
|
|
'\000', '\001', '\002', '\003', '\067', '\055', '\056', '\057', |
|
|
'\026', '\005', '\045', '\013', '\014', '\015', '\016', '\017', |
|
|
'\020', '\021', '\022', '\023', '\074', '\075', '\062', '\046', |
|
|
'\030', '\031', '\077', '\047', '\034', '\035', '\036', '\037', |
|
|
'\100', '\132', '\177', '\173', '\133', '\154', '\120', '\175', |
|
|
'\115', '\135', '\134', '\116', '\153', '\140', '\113', '\141', |
|
|
'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', |
|
|
'\370', '\371', '\172', '\136', '\114', '\176', '\156', '\157', |
|
|
'\174', '\301', '\302', '\303', '\304', '\305', '\306', '\307', |
|
|
'\310', '\311', '\321', '\322', '\323', '\324', '\325', '\326', |
|
|
'\327', '\330', '\331', '\342', '\343', '\344', '\345', '\346', |
|
|
'\347', '\350', '\351', '\255', '\340', '\275', '\232', '\155', |
|
|
'\171', '\201', '\202', '\203', '\204', '\205', '\206', '\207', |
|
|
'\210', '\211', '\221', '\222', '\223', '\224', '\225', '\226', |
|
|
'\227', '\230', '\231', '\242', '\243', '\244', '\245', '\246', |
|
|
'\247', '\250', '\251', '\300', '\117', '\320', '\137', '\007', |
|
|
'\040', '\041', '\042', '\043', '\044', '\025', '\006', '\027', |
|
|
'\050', '\051', '\052', '\053', '\054', '\011', '\012', '\033', |
|
|
'\060', '\061', '\032', '\063', '\064', '\065', '\066', '\010', |
|
|
'\070', '\071', '\072', '\073', '\004', '\024', '\076', '\341', |
|
|
'\101', '\102', '\103', '\104', '\105', '\106', '\107', '\110', |
|
|
'\111', '\121', '\122', '\123', '\124', '\125', '\126', '\127', |
|
|
'\130', '\131', '\142', '\143', '\144', '\145', '\146', '\147', |
|
|
'\150', '\151', '\160', '\161', '\162', '\163', '\164', '\165', |
|
|
'\166', '\167', '\170', '\200', '\212', '\213', '\214', '\215', |
|
|
'\216', '\217', '\220', '\152', '\233', '\234', '\235', '\236', |
|
|
'\237', '\240', '\252', '\253', '\254', '\112', '\256', '\257', |
|
|
'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', |
|
|
'\270', '\271', '\272', '\273', '\274', '\241', '\276', '\277', |
|
|
'\312', '\313', '\314', '\315', '\316', '\317', '\332', '\333', |
|
|
'\334', '\335', '\336', '\337', '\352', '\353', '\354', '\355', |
|
|
'\356', '\357', '\372', '\373', '\374', '\375', '\376', '\377' |
|
|
}; |
|
|
|
|
|
static char const ascii_to_ibm[] = |
|
|
{ |
|
|
'\000', '\001', '\002', '\003', '\067', '\055', '\056', '\057', |
|
|
'\026', '\005', '\045', '\013', '\014', '\015', '\016', '\017', |
|
|
'\020', '\021', '\022', '\023', '\074', '\075', '\062', '\046', |
|
|
'\030', '\031', '\077', '\047', '\034', '\035', '\036', '\037', |
|
|
'\100', '\132', '\177', '\173', '\133', '\154', '\120', '\175', |
|
|
'\115', '\135', '\134', '\116', '\153', '\140', '\113', '\141', |
|
|
'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', |
|
|
'\370', '\371', '\172', '\136', '\114', '\176', '\156', '\157', |
|
|
'\174', '\301', '\302', '\303', '\304', '\305', '\306', '\307', |
|
|
'\310', '\311', '\321', '\322', '\323', '\324', '\325', '\326', |
|
|
'\327', '\330', '\331', '\342', '\343', '\344', '\345', '\346', |
|
|
'\347', '\350', '\351', '\255', '\340', '\275', '\137', '\155', |
|
|
'\171', '\201', '\202', '\203', '\204', '\205', '\206', '\207', |
|
|
'\210', '\211', '\221', '\222', '\223', '\224', '\225', '\226', |
|
|
'\227', '\230', '\231', '\242', '\243', '\244', '\245', '\246', |
|
|
'\247', '\250', '\251', '\300', '\117', '\320', '\241', '\007', |
|
|
'\040', '\041', '\042', '\043', '\044', '\025', '\006', '\027', |
|
|
'\050', '\051', '\052', '\053', '\054', '\011', '\012', '\033', |
|
|
'\060', '\061', '\032', '\063', '\064', '\065', '\066', '\010', |
|
|
'\070', '\071', '\072', '\073', '\004', '\024', '\076', '\341', |
|
|
'\101', '\102', '\103', '\104', '\105', '\106', '\107', '\110', |
|
|
'\111', '\121', '\122', '\123', '\124', '\125', '\126', '\127', |
|
|
'\130', '\131', '\142', '\143', '\144', '\145', '\146', '\147', |
|
|
'\150', '\151', '\160', '\161', '\162', '\163', '\164', '\165', |
|
|
'\166', '\167', '\170', '\200', '\212', '\213', '\214', '\215', |
|
|
'\216', '\217', '\220', '\232', '\233', '\234', '\235', '\236', |
|
|
'\237', '\240', '\252', '\253', '\254', '\255', '\256', '\257', |
|
|
'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', |
|
|
'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277', |
|
|
'\312', '\313', '\314', '\315', '\316', '\317', '\332', '\333', |
|
|
'\334', '\335', '\336', '\337', '\352', '\353', '\354', '\355', |
|
|
'\356', '\357', '\372', '\373', '\374', '\375', '\376', '\377' |
|
|
}; |
|
|
|
|
|
static char const ebcdic_to_ascii[] = |
|
|
{ |
|
|
'\000', '\001', '\002', '\003', '\234', '\011', '\206', '\177', |
|
|
'\227', '\215', '\216', '\013', '\014', '\015', '\016', '\017', |
|
|
'\020', '\021', '\022', '\023', '\235', '\205', '\010', '\207', |
|
|
'\030', '\031', '\222', '\217', '\034', '\035', '\036', '\037', |
|
|
'\200', '\201', '\202', '\203', '\204', '\012', '\027', '\033', |
|
|
'\210', '\211', '\212', '\213', '\214', '\005', '\006', '\007', |
|
|
'\220', '\221', '\026', '\223', '\224', '\225', '\226', '\004', |
|
|
'\230', '\231', '\232', '\233', '\024', '\025', '\236', '\032', |
|
|
'\040', '\240', '\241', '\242', '\243', '\244', '\245', '\246', |
|
|
'\247', '\250', '\325', '\056', '\074', '\050', '\053', '\174', |
|
|
'\046', '\251', '\252', '\253', '\254', '\255', '\256', '\257', |
|
|
'\260', '\261', '\041', '\044', '\052', '\051', '\073', '\176', |
|
|
'\055', '\057', '\262', '\263', '\264', '\265', '\266', '\267', |
|
|
'\270', '\271', '\313', '\054', '\045', '\137', '\076', '\077', |
|
|
'\272', '\273', '\274', '\275', '\276', '\277', '\300', '\301', |
|
|
'\302', '\140', '\072', '\043', '\100', '\047', '\075', '\042', |
|
|
'\303', '\141', '\142', '\143', '\144', '\145', '\146', '\147', |
|
|
'\150', '\151', '\304', '\305', '\306', '\307', '\310', '\311', |
|
|
'\312', '\152', '\153', '\154', '\155', '\156', '\157', '\160', |
|
|
'\161', '\162', '\136', '\314', '\315', '\316', '\317', '\320', |
|
|
'\321', '\345', '\163', '\164', '\165', '\166', '\167', '\170', |
|
|
'\171', '\172', '\322', '\323', '\324', '\133', '\326', '\327', |
|
|
'\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337', |
|
|
'\340', '\341', '\342', '\343', '\344', '\135', '\346', '\347', |
|
|
'\173', '\101', '\102', '\103', '\104', '\105', '\106', '\107', |
|
|
'\110', '\111', '\350', '\351', '\352', '\353', '\354', '\355', |
|
|
'\175', '\112', '\113', '\114', '\115', '\116', '\117', '\120', |
|
|
'\121', '\122', '\356', '\357', '\360', '\361', '\362', '\363', |
|
|
'\134', '\237', '\123', '\124', '\125', '\126', '\127', '\130', |
|
|
'\131', '\132', '\364', '\365', '\366', '\367', '\370', '\371', |
|
|
'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', |
|
|
'\070', '\071', '\372', '\373', '\374', '\375', '\376', '\377' |
|
|
}; |
|
|
|
|
|
|
|
|
static bool close_stdout_required = true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
maybe_close_stdout (void) |
|
|
{ |
|
|
if (close_stdout_required) |
|
|
close_stdout (); |
|
|
else if (close_stream (stderr) != 0) |
|
|
_exit (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ATTRIBUTE_FORMAT ((__printf__, 2, 3)) |
|
|
static void |
|
|
diagnose (int errnum, char const *fmt, ...) |
|
|
{ |
|
|
if (0 < progress_len) |
|
|
{ |
|
|
fputc ('\n', stderr); |
|
|
progress_len = 0; |
|
|
} |
|
|
|
|
|
va_list ap; |
|
|
va_start (ap, fmt); |
|
|
verror (0, errnum, fmt, ap); |
|
|
va_end (ap); |
|
|
} |
|
|
|
|
|
void |
|
|
usage (int status) |
|
|
{ |
|
|
if (status != EXIT_SUCCESS) |
|
|
emit_try_help (); |
|
|
else |
|
|
{ |
|
|
printf (_("\ |
|
|
Usage: %s [OPERAND]...\n\ |
|
|
or: %s OPTION\n\ |
|
|
"), |
|
|
program_name, program_name); |
|
|
fputs (_("\ |
|
|
Copy a file, converting and formatting according to the operands.\n\ |
|
|
\n\ |
|
|
bs=BYTES read and write up to BYTES bytes at a time (default: 512);\n\ |
|
|
overrides ibs and obs\n\ |
|
|
cbs=BYTES convert BYTES bytes at a time\n\ |
|
|
conv=CONVS convert the file as per the comma separated symbol list\n\ |
|
|
count=N copy only N input blocks\n\ |
|
|
ibs=BYTES read up to BYTES bytes at a time (default: 512)\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
if=FILE read from FILE instead of standard input\n\ |
|
|
iflag=FLAGS read as per the comma separated symbol list\n\ |
|
|
obs=BYTES write BYTES bytes at a time (default: 512)\n\ |
|
|
of=FILE write to FILE instead of standard output\n\ |
|
|
oflag=FLAGS write as per the comma separated symbol list\n\ |
|
|
seek=N (or oseek=N) skip N obs-sized output blocks\n\ |
|
|
skip=N (or iseek=N) skip N ibs-sized input blocks\n\ |
|
|
status=LEVEL The LEVEL of information to print to standard error;\n\ |
|
|
'none' suppresses everything but error messages,\n\ |
|
|
'noxfer' suppresses the final transfer statistics,\n\ |
|
|
'progress' shows periodic transfer statistics\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
N and BYTES may be followed by the following multiplicative suffixes:\n\ |
|
|
c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024, xM=M,\n\ |
|
|
GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y, R, Q.\n\ |
|
|
Binary prefixes can be used, too: KiB=K, MiB=M, and so on.\n\ |
|
|
If N ends in 'B', it counts bytes not blocks.\n\ |
|
|
\n\ |
|
|
Each CONV symbol may be:\n\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
ascii from EBCDIC to ASCII\n\ |
|
|
ebcdic from ASCII to EBCDIC\n\ |
|
|
ibm from ASCII to alternate EBCDIC\n\ |
|
|
block pad newline-terminated records with spaces to cbs-size\n\ |
|
|
unblock replace trailing spaces in cbs-size records with newline\n\ |
|
|
lcase change upper case to lower case\n\ |
|
|
ucase change lower case to upper case\n\ |
|
|
sparse try to seek rather than write all-NUL output blocks\n\ |
|
|
swab swap every pair of input bytes\n\ |
|
|
sync pad every input block with NULs to ibs-size; when used\n\ |
|
|
with block or unblock, pad with spaces rather than NULs\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
excl fail if the output file already exists\n\ |
|
|
nocreat do not create the output file\n\ |
|
|
notrunc do not truncate the output file\n\ |
|
|
noerror continue after read errors\n\ |
|
|
fdatasync physically write output file data before finishing\n\ |
|
|
fsync likewise, but also write metadata\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
Each FLAG symbol may be:\n\ |
|
|
\n\ |
|
|
append append mode (makes sense only for output; conv=notrunc suggested)\n\ |
|
|
"), stdout); |
|
|
if (O_CIO) |
|
|
fputs (_(" cio use concurrent I/O for data\n"), stdout); |
|
|
if (O_DIRECT) |
|
|
fputs (_(" direct use direct I/O for data\n"), stdout); |
|
|
fputs (_(" directory fail unless a directory\n"), stdout); |
|
|
if (O_DSYNC) |
|
|
fputs (_(" dsync use synchronized I/O for data\n"), stdout); |
|
|
if (O_SYNC) |
|
|
fputs (_(" sync likewise, but also for metadata\n"), stdout); |
|
|
fputs (_(" fullblock accumulate full blocks of input (iflag only)\n"), |
|
|
stdout); |
|
|
if (O_NONBLOCK) |
|
|
fputs (_(" nonblock use non-blocking I/O\n"), stdout); |
|
|
if (O_NOATIME) |
|
|
fputs (_(" noatime do not update access time\n"), stdout); |
|
|
#if HAVE_POSIX_FADVISE |
|
|
if (O_NOCACHE) |
|
|
fputs (_(" nocache Request to drop cache. See also oflag=sync\n"), |
|
|
stdout); |
|
|
#endif |
|
|
if (O_NOCTTY) |
|
|
fputs (_(" noctty do not assign controlling terminal from file\n"), |
|
|
stdout); |
|
|
if (HAVE_WORKING_O_NOFOLLOW) |
|
|
fputs (_(" nofollow do not follow symlinks\n"), stdout); |
|
|
if (O_NOLINKS) |
|
|
fputs (_(" nolinks fail if multiply-linked\n"), stdout); |
|
|
if (O_BINARY) |
|
|
fputs (_(" binary use binary I/O for data\n"), stdout); |
|
|
if (O_TEXT) |
|
|
fputs (_(" text use text I/O for data\n"), stdout); |
|
|
|
|
|
{ |
|
|
printf (_("\ |
|
|
\n\ |
|
|
Sending a %s signal to a running 'dd' process makes it\n\ |
|
|
print I/O statistics to standard error and then resume copying.\n\ |
|
|
\n\ |
|
|
Options are:\n\ |
|
|
\n\ |
|
|
"), SIGINFO == SIGUSR1 ? "USR1" : "INFO"); |
|
|
} |
|
|
|
|
|
fputs (HELP_OPTION_DESCRIPTION, stdout); |
|
|
fputs (VERSION_OPTION_DESCRIPTION, stdout); |
|
|
emit_ancillary_info (PROGRAM_NAME); |
|
|
} |
|
|
exit (status); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
enum { human_opts = (human_autoscale | human_round_to_nearest |
|
|
| human_space_before_unit | human_SI | human_B) }; |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
alloc_ibuf (void) |
|
|
{ |
|
|
if (ibuf) |
|
|
return; |
|
|
|
|
|
bool extra_byte_for_swab = !!(conversions_mask & C_SWAB); |
|
|
ibuf = alignalloc (page_size, input_blocksize + extra_byte_for_swab); |
|
|
if (!ibuf) |
|
|
{ |
|
|
char hbuf[LONGEST_HUMAN_READABLE + 1]; |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("memory exhausted by input buffer of size %td bytes (%s)"), |
|
|
input_blocksize, |
|
|
human_readable (input_blocksize, hbuf, |
|
|
human_opts | human_base_1024, 1, 1)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
alloc_obuf (void) |
|
|
{ |
|
|
if (obuf) |
|
|
return; |
|
|
|
|
|
if (conversions_mask & C_TWOBUFS) |
|
|
{ |
|
|
obuf = alignalloc (page_size, output_blocksize); |
|
|
if (!obuf) |
|
|
{ |
|
|
char hbuf[LONGEST_HUMAN_READABLE + 1]; |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("memory exhausted by output buffer of size %td" |
|
|
" bytes (%s)"), |
|
|
output_blocksize, |
|
|
human_readable (output_blocksize, hbuf, |
|
|
human_opts | human_base_1024, 1, 1)); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
alloc_ibuf (); |
|
|
obuf = ibuf; |
|
|
} |
|
|
} |
|
|
|
|
|
static void |
|
|
translate_charset (char const *new_trans) |
|
|
{ |
|
|
for (int i = 0; i < 256; i++) |
|
|
trans_table[i] = new_trans[trans_table[i]]; |
|
|
translation_needed = true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static inline bool |
|
|
multiple_bits_set (int i) |
|
|
{ |
|
|
return MULTIPLE_BITS_SET (i); |
|
|
} |
|
|
|
|
|
static bool |
|
|
abbreviation_lacks_prefix (char const *message) |
|
|
{ |
|
|
return message[strlen (message) - 2] == ' '; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
print_xfer_stats (xtime_t progress_time) |
|
|
{ |
|
|
xtime_t now = progress_time ? progress_time : gethrxtime (); |
|
|
static char const slash_s[] = "/s"; |
|
|
char hbuf[3][LONGEST_HUMAN_READABLE + sizeof slash_s]; |
|
|
double delta_s; |
|
|
char const *bytes_per_second; |
|
|
char const *si = human_readable (w_bytes, hbuf[0], human_opts, 1, 1); |
|
|
char const *iec = human_readable (w_bytes, hbuf[1], |
|
|
human_opts | human_base_1024, 1, 1); |
|
|
|
|
|
|
|
|
|
|
|
char *bpsbuf = hbuf[2]; |
|
|
int bpsbufsize = sizeof hbuf[2]; |
|
|
if (start_time < now) |
|
|
{ |
|
|
double XTIME_PRECISIONe0 = XTIME_PRECISION; |
|
|
xtime_t delta_xtime = now - start_time; |
|
|
delta_s = delta_xtime / XTIME_PRECISIONe0; |
|
|
bytes_per_second = human_readable (w_bytes, bpsbuf, human_opts, |
|
|
XTIME_PRECISION, delta_xtime); |
|
|
strcat (bytes_per_second - bpsbuf + bpsbuf, slash_s); |
|
|
} |
|
|
else |
|
|
{ |
|
|
delta_s = 0; |
|
|
snprintf (bpsbuf, bpsbufsize, "%s B/s", _("Infinity")); |
|
|
bytes_per_second = bpsbuf; |
|
|
} |
|
|
|
|
|
if (progress_time) |
|
|
fputc ('\r', stderr); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char delta_s_buf[24]; |
|
|
snprintf (delta_s_buf, sizeof delta_s_buf, |
|
|
progress_time ? "%.0f s" : "%g s", delta_s); |
|
|
|
|
|
int stats_len |
|
|
= (abbreviation_lacks_prefix (si) |
|
|
? fprintf (stderr, |
|
|
ngettext ("%jd byte copied, %s, %s", |
|
|
"%jd bytes copied, %s, %s", |
|
|
select_plural (w_bytes)), |
|
|
w_bytes, delta_s_buf, bytes_per_second) |
|
|
: abbreviation_lacks_prefix (iec) |
|
|
? fprintf (stderr, |
|
|
_("%jd bytes (%s) copied, %s, %s"), |
|
|
w_bytes, si, delta_s_buf, bytes_per_second) |
|
|
: fprintf (stderr, |
|
|
_("%jd bytes (%s, %s) copied, %s, %s"), |
|
|
w_bytes, si, iec, delta_s_buf, bytes_per_second)); |
|
|
|
|
|
if (progress_time) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (0 <= stats_len && stats_len < progress_len) |
|
|
fprintf (stderr, "%*s", progress_len - stats_len, ""); |
|
|
progress_len = stats_len; |
|
|
} |
|
|
else |
|
|
fputc ('\n', stderr); |
|
|
|
|
|
reported_w_bytes = w_bytes; |
|
|
} |
|
|
|
|
|
static void |
|
|
print_stats (void) |
|
|
{ |
|
|
if (status_level == STATUS_NONE) |
|
|
return; |
|
|
|
|
|
if (0 < progress_len) |
|
|
{ |
|
|
fputc ('\n', stderr); |
|
|
progress_len = 0; |
|
|
} |
|
|
|
|
|
fprintf (stderr, |
|
|
_("%jd+%jd records in\n" |
|
|
"%jd+%jd records out\n"), |
|
|
r_full, r_partial, w_full, w_partial); |
|
|
|
|
|
if (r_truncate != 0) |
|
|
fprintf (stderr, |
|
|
ngettext ("%jd truncated record\n", |
|
|
"%jd truncated records\n", |
|
|
select_plural (r_truncate)), |
|
|
r_truncate); |
|
|
|
|
|
if (status_level == STATUS_NOXFER) |
|
|
return; |
|
|
|
|
|
print_xfer_stats (0); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
interrupt_handler (int sig) |
|
|
{ |
|
|
if (! SA_RESETHAND) |
|
|
signal (sig, SIG_DFL); |
|
|
interrupt_signal = sig; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
siginfo_handler (int sig) |
|
|
{ |
|
|
if (! SA_NOCLDSTOP) |
|
|
signal (sig, siginfo_handler); |
|
|
info_signal_count++; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
install_signal_handlers (void) |
|
|
{ |
|
|
bool catch_siginfo = ! (SIGINFO == SIGUSR1 && getenv ("POSIXLY_CORRECT")); |
|
|
|
|
|
#if SA_NOCLDSTOP |
|
|
|
|
|
struct sigaction act; |
|
|
sigemptyset (&caught_signals); |
|
|
if (catch_siginfo) |
|
|
sigaddset (&caught_signals, SIGINFO); |
|
|
sigaction (SIGINT, nullptr, &act); |
|
|
if (act.sa_handler != SIG_IGN) |
|
|
sigaddset (&caught_signals, SIGINT); |
|
|
act.sa_mask = caught_signals; |
|
|
|
|
|
if (sigismember (&caught_signals, SIGINFO)) |
|
|
{ |
|
|
act.sa_handler = siginfo_handler; |
|
|
|
|
|
|
|
|
|
|
|
act.sa_flags = 0; |
|
|
sigaction (SIGINFO, &act, nullptr); |
|
|
} |
|
|
|
|
|
if (sigismember (&caught_signals, SIGINT)) |
|
|
{ |
|
|
act.sa_handler = interrupt_handler; |
|
|
act.sa_flags = SA_NODEFER | SA_RESETHAND; |
|
|
sigaction (SIGINT, &act, nullptr); |
|
|
} |
|
|
|
|
|
#else |
|
|
|
|
|
if (catch_siginfo) |
|
|
{ |
|
|
signal (SIGINFO, siginfo_handler); |
|
|
siginterrupt (SIGINFO, 1); |
|
|
} |
|
|
if (signal (SIGINT, SIG_IGN) != SIG_IGN) |
|
|
{ |
|
|
signal (SIGINT, interrupt_handler); |
|
|
siginterrupt (SIGINT, 1); |
|
|
} |
|
|
#endif |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
iclose (int fd) |
|
|
{ |
|
|
if (close (fd) != 0) |
|
|
do |
|
|
if (errno != EINTR) |
|
|
return -1; |
|
|
while (close (fd) != 0 && errno != EBADF); |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
static int synchronize_output (void); |
|
|
|
|
|
static void |
|
|
cleanup (void) |
|
|
{ |
|
|
if (!interrupt_signal) |
|
|
{ |
|
|
int sync_status = synchronize_output (); |
|
|
if (sync_status) |
|
|
exit (sync_status); |
|
|
} |
|
|
|
|
|
if (iclose (STDIN_FILENO) != 0) |
|
|
error (EXIT_FAILURE, errno, _("closing input file %s"), |
|
|
quoteaf (input_file)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (iclose (STDOUT_FILENO) != 0) |
|
|
error (EXIT_FAILURE, errno, |
|
|
_("closing output file %s"), quoteaf (output_file)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
process_signals (void) |
|
|
{ |
|
|
while (interrupt_signal || info_signal_count) |
|
|
{ |
|
|
int interrupt; |
|
|
int infos; |
|
|
sigset_t oldset; |
|
|
|
|
|
sigprocmask (SIG_BLOCK, &caught_signals, &oldset); |
|
|
|
|
|
|
|
|
|
|
|
interrupt = interrupt_signal; |
|
|
infos = info_signal_count; |
|
|
|
|
|
if (infos) |
|
|
info_signal_count = infos - 1; |
|
|
|
|
|
sigprocmask (SIG_SETMASK, &oldset, nullptr); |
|
|
|
|
|
if (interrupt) |
|
|
cleanup (); |
|
|
print_stats (); |
|
|
if (interrupt) |
|
|
raise (interrupt); |
|
|
} |
|
|
} |
|
|
|
|
|
static void |
|
|
finish_up (void) |
|
|
{ |
|
|
|
|
|
process_signals (); |
|
|
cleanup (); |
|
|
print_stats (); |
|
|
} |
|
|
|
|
|
static void |
|
|
quit (int code) |
|
|
{ |
|
|
finish_up (); |
|
|
exit (code); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static off_t |
|
|
cache_round (int fd, off_t len) |
|
|
{ |
|
|
static off_t i_pending, o_pending; |
|
|
off_t *pending = (fd == STDIN_FILENO ? &i_pending : &o_pending); |
|
|
|
|
|
if (len) |
|
|
{ |
|
|
intmax_t c_pending; |
|
|
if (ckd_add (&c_pending, *pending, len)) |
|
|
c_pending = INTMAX_MAX; |
|
|
*pending = c_pending % IO_BUFSIZE; |
|
|
if (c_pending > *pending) |
|
|
len = c_pending - *pending; |
|
|
else |
|
|
len = 0; |
|
|
} |
|
|
else |
|
|
len = *pending; |
|
|
|
|
|
return len; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
invalidate_cache (int fd, off_t len) |
|
|
{ |
|
|
int adv_ret = -1; |
|
|
off_t offset; |
|
|
bool nocache_eof = (fd == STDIN_FILENO ? i_nocache_eof : o_nocache_eof); |
|
|
|
|
|
|
|
|
off_t clen = cache_round (fd, len); |
|
|
if (len && !clen) |
|
|
return true; |
|
|
else if (! len && ! clen && ! nocache_eof) |
|
|
return true; |
|
|
off_t pending = len ? cache_round (fd, 0) : 0; |
|
|
|
|
|
if (fd == STDIN_FILENO) |
|
|
{ |
|
|
if (input_seekable) |
|
|
offset = input_offset; |
|
|
else |
|
|
{ |
|
|
offset = -1; |
|
|
errno = ESPIPE; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
static off_t output_offset = -2; |
|
|
|
|
|
if (output_offset != -1) |
|
|
{ |
|
|
if (output_offset < 0) |
|
|
output_offset = lseek (fd, 0, SEEK_CUR); |
|
|
else if (len) |
|
|
output_offset += clen + pending; |
|
|
} |
|
|
|
|
|
offset = output_offset; |
|
|
} |
|
|
|
|
|
if (0 <= offset) |
|
|
{ |
|
|
if (! len && clen && nocache_eof) |
|
|
{ |
|
|
pending = clen; |
|
|
clen = 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if HAVE_POSIX_FADVISE |
|
|
offset = offset - clen - pending; |
|
|
|
|
|
if (clen == 0) |
|
|
offset -= offset % page_size; |
|
|
adv_ret = posix_fadvise (fd, offset, clen, POSIX_FADV_DONTNEED); |
|
|
errno = adv_ret; |
|
|
#else |
|
|
errno = ENOTSUP; |
|
|
#endif |
|
|
} |
|
|
|
|
|
return adv_ret == 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static ssize_t |
|
|
iread (int fd, char *buf, idx_t size) |
|
|
{ |
|
|
ssize_t nread; |
|
|
static ssize_t prev_nread; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
nread = read (fd, buf, size); |
|
|
|
|
|
|
|
|
if (nread == -1 && errno == EINVAL |
|
|
&& 0 < prev_nread && prev_nread < size |
|
|
&& (input_flags & O_DIRECT)) |
|
|
{ |
|
|
errno = 0; |
|
|
nread = 0; |
|
|
} |
|
|
} |
|
|
while (nread < 0 && errno == EINTR); |
|
|
|
|
|
|
|
|
if (0 < nread && nread < size) |
|
|
process_signals (); |
|
|
|
|
|
if (0 < nread && warn_partial_read) |
|
|
{ |
|
|
if (0 < prev_nread && prev_nread < size) |
|
|
{ |
|
|
idx_t prev = prev_nread; |
|
|
if (status_level != STATUS_NONE) |
|
|
diagnose (0, ngettext (("warning: partial read (%td byte); " |
|
|
"suggest iflag=fullblock"), |
|
|
("warning: partial read (%td bytes); " |
|
|
"suggest iflag=fullblock"), |
|
|
select_plural (prev)), |
|
|
prev); |
|
|
warn_partial_read = false; |
|
|
} |
|
|
} |
|
|
|
|
|
prev_nread = nread; |
|
|
return nread; |
|
|
} |
|
|
|
|
|
|
|
|
static ssize_t |
|
|
iread_fullblock (int fd, char *buf, idx_t size) |
|
|
{ |
|
|
ssize_t nread = 0; |
|
|
|
|
|
while (0 < size) |
|
|
{ |
|
|
ssize_t ncurr = iread (fd, buf, size); |
|
|
if (ncurr < 0) |
|
|
return ncurr; |
|
|
if (ncurr == 0) |
|
|
break; |
|
|
nread += ncurr; |
|
|
buf += ncurr; |
|
|
size -= ncurr; |
|
|
} |
|
|
|
|
|
return nread; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static idx_t |
|
|
iwrite (int fd, char const *buf, idx_t size) |
|
|
{ |
|
|
idx_t total_written = 0; |
|
|
|
|
|
if ((output_flags & O_DIRECT) && size < output_blocksize) |
|
|
{ |
|
|
int old_flags = fcntl (STDOUT_FILENO, F_GETFL); |
|
|
if (fcntl (STDOUT_FILENO, F_SETFL, old_flags & ~O_DIRECT) != 0 |
|
|
&& status_level != STATUS_NONE) |
|
|
diagnose (errno, _("failed to turn off O_DIRECT: %s"), |
|
|
quotef (output_file)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
o_nocache_eof = true; |
|
|
invalidate_cache (STDOUT_FILENO, 0); |
|
|
|
|
|
|
|
|
|
|
|
conversions_mask |= C_FSYNC; |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
while (total_written < size) |
|
|
{ |
|
|
ssize_t nwritten = 0; |
|
|
process_signals (); |
|
|
|
|
|
|
|
|
final_op_was_seek = false; |
|
|
if ((conversions_mask & C_SPARSE) && is_nul (buf, size)) |
|
|
{ |
|
|
if (lseek (fd, size, SEEK_CUR) < 0) |
|
|
{ |
|
|
conversions_mask &= ~C_SPARSE; |
|
|
|
|
|
} |
|
|
else |
|
|
{ |
|
|
final_op_was_seek = true; |
|
|
nwritten = size; |
|
|
} |
|
|
} |
|
|
|
|
|
if (!nwritten) |
|
|
nwritten = write (fd, buf + total_written, size - total_written); |
|
|
|
|
|
if (nwritten < 0) |
|
|
{ |
|
|
if (errno != EINTR) |
|
|
break; |
|
|
} |
|
|
else if (nwritten == 0) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
errno = ENOSPC; |
|
|
break; |
|
|
} |
|
|
else |
|
|
total_written += nwritten; |
|
|
} |
|
|
|
|
|
if (o_nocache && total_written) |
|
|
invalidate_cache (fd, total_written); |
|
|
|
|
|
return total_written; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
write_output (void) |
|
|
{ |
|
|
idx_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize); |
|
|
w_bytes += nwritten; |
|
|
if (nwritten != output_blocksize) |
|
|
{ |
|
|
diagnose (errno, _("writing to %s"), quoteaf (output_file)); |
|
|
if (nwritten != 0) |
|
|
w_partial++; |
|
|
quit (EXIT_FAILURE); |
|
|
} |
|
|
else |
|
|
w_full++; |
|
|
oc = 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
ifdatasync (int fd) |
|
|
{ |
|
|
int ret; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
ret = fdatasync (fd); |
|
|
} |
|
|
while (ret < 0 && errno == EINTR); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
ifd_reopen (int desired_fd, char const *file, int flag, mode_t mode) |
|
|
{ |
|
|
int ret; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
ret = fd_reopen (desired_fd, file, flag, mode); |
|
|
} |
|
|
while (ret < 0 && errno == EINTR); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
ifstat (int fd, struct stat *st) |
|
|
{ |
|
|
int ret; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
ret = fstat (fd, st); |
|
|
} |
|
|
while (ret < 0 && errno == EINTR); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
ifsync (int fd) |
|
|
{ |
|
|
int ret; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
ret = fsync (fd); |
|
|
} |
|
|
while (ret < 0 && errno == EINTR); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
iftruncate (int fd, off_t length) |
|
|
{ |
|
|
int ret; |
|
|
|
|
|
do |
|
|
{ |
|
|
process_signals (); |
|
|
ret = ftruncate (fd, length); |
|
|
} |
|
|
while (ret < 0 && errno == EINTR); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ATTRIBUTE_PURE |
|
|
static bool |
|
|
operand_matches (char const *str, char const *pattern, char delim) |
|
|
{ |
|
|
while (*pattern) |
|
|
if (*str++ != *pattern++) |
|
|
return false; |
|
|
return !*str || *str == delim; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
parse_symbols (char const *str, struct symbol_value const *table, |
|
|
bool exclusive, char const *error_msgid) |
|
|
{ |
|
|
int value = 0; |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
char const *strcomma = strchr (str, ','); |
|
|
struct symbol_value const *entry; |
|
|
|
|
|
for (entry = table; |
|
|
! (operand_matches (str, entry->symbol, ',') && entry->value); |
|
|
entry++) |
|
|
{ |
|
|
if (! entry->symbol[0]) |
|
|
{ |
|
|
idx_t slen = strcomma ? strcomma - str : strlen (str); |
|
|
diagnose (0, "%s: %s", _(error_msgid), |
|
|
quotearg_n_style_mem (0, locale_quoting_style, |
|
|
str, slen)); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
} |
|
|
|
|
|
if (exclusive) |
|
|
value = entry->value; |
|
|
else |
|
|
value |= entry->value; |
|
|
if (!strcomma) |
|
|
break; |
|
|
str = strcomma + 1; |
|
|
} |
|
|
|
|
|
return value; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static intmax_t |
|
|
parse_integer (char const *str, strtol_error *invalid) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int indeterminate = 0; |
|
|
uintmax_t n = indeterminate; |
|
|
char *suffix; |
|
|
static char const suffixes[] = "bcEGkKMPQRTwYZ0"; |
|
|
strtol_error e = xstrtoumax (str, &suffix, 10, &n, suffixes); |
|
|
intmax_t result; |
|
|
|
|
|
if ((e & ~LONGINT_OVERFLOW) == LONGINT_INVALID_SUFFIX_CHAR |
|
|
&& *suffix == 'B' && str < suffix && suffix[-1] != 'B') |
|
|
{ |
|
|
suffix++; |
|
|
if (!*suffix) |
|
|
e &= ~LONGINT_INVALID_SUFFIX_CHAR; |
|
|
} |
|
|
|
|
|
if ((e & ~LONGINT_OVERFLOW) == LONGINT_INVALID_SUFFIX_CHAR |
|
|
&& *suffix == 'x') |
|
|
{ |
|
|
strtol_error f = LONGINT_OK; |
|
|
intmax_t o = parse_integer (suffix + 1, &f); |
|
|
if ((f & ~LONGINT_OVERFLOW) != LONGINT_OK) |
|
|
{ |
|
|
e = f; |
|
|
result = indeterminate; |
|
|
} |
|
|
else if (ckd_mul (&result, n, o) |
|
|
|| (result != 0 && ((e | f) & LONGINT_OVERFLOW))) |
|
|
{ |
|
|
e = LONGINT_OVERFLOW; |
|
|
result = INTMAX_MAX; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (result == 0 && STRPREFIX (str, "0x")) |
|
|
diagnose (0, _("warning: %s is a zero multiplier; " |
|
|
"use %s if that is intended"), |
|
|
quote_n (0, "0x"), quote_n (1, "00x")); |
|
|
e = LONGINT_OK; |
|
|
} |
|
|
} |
|
|
else if (n <= INTMAX_MAX) |
|
|
result = n; |
|
|
else |
|
|
{ |
|
|
e = LONGINT_OVERFLOW; |
|
|
result = INTMAX_MAX; |
|
|
} |
|
|
|
|
|
*invalid = e; |
|
|
return result; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ATTRIBUTE_PURE |
|
|
static bool |
|
|
operand_is (char const *operand, char const *name) |
|
|
{ |
|
|
return operand_matches (operand, name, '='); |
|
|
} |
|
|
|
|
|
static void |
|
|
scanargs (int argc, char *const *argv) |
|
|
{ |
|
|
idx_t blocksize = 0; |
|
|
intmax_t count = INTMAX_MAX; |
|
|
intmax_t skip = 0; |
|
|
intmax_t seek = 0; |
|
|
bool count_B = false, skip_B = false, seek_B = false; |
|
|
|
|
|
for (int i = optind; i < argc; i++) |
|
|
{ |
|
|
char const *name = argv[i]; |
|
|
char const *val = strchr (name, '='); |
|
|
|
|
|
if (val == nullptr) |
|
|
{ |
|
|
diagnose (0, _("unrecognized operand %s"), quoteaf (name)); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
val++; |
|
|
|
|
|
if (operand_is (name, "if")) |
|
|
input_file = val; |
|
|
else if (operand_is (name, "of")) |
|
|
output_file = val; |
|
|
else if (operand_is (name, "conv")) |
|
|
conversions_mask |= parse_symbols (val, conversions, false, |
|
|
N_("invalid conversion")); |
|
|
else if (operand_is (name, "iflag")) |
|
|
input_flags |= parse_symbols (val, flags, false, |
|
|
N_("invalid input flag")); |
|
|
else if (operand_is (name, "oflag")) |
|
|
output_flags |= parse_symbols (val, flags, false, |
|
|
N_("invalid output flag")); |
|
|
else if (operand_is (name, "status")) |
|
|
status_level = parse_symbols (val, statuses, true, |
|
|
N_("invalid status level")); |
|
|
else |
|
|
{ |
|
|
strtol_error invalid = LONGINT_OK; |
|
|
intmax_t n = parse_integer (val, &invalid); |
|
|
bool has_B = !!strchr (val, 'B'); |
|
|
intmax_t n_min = 0; |
|
|
intmax_t n_max = INTMAX_MAX; |
|
|
idx_t *converted_idx = nullptr; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
idx_t max_blocksize = MIN (IDX_MAX - 1, MIN (SSIZE_MAX, OFF_T_MAX)); |
|
|
|
|
|
if (operand_is (name, "ibs")) |
|
|
{ |
|
|
n_min = 1; |
|
|
n_max = max_blocksize; |
|
|
converted_idx = &input_blocksize; |
|
|
} |
|
|
else if (operand_is (name, "obs")) |
|
|
{ |
|
|
n_min = 1; |
|
|
n_max = max_blocksize; |
|
|
converted_idx = &output_blocksize; |
|
|
} |
|
|
else if (operand_is (name, "bs")) |
|
|
{ |
|
|
n_min = 1; |
|
|
n_max = max_blocksize; |
|
|
converted_idx = &blocksize; |
|
|
} |
|
|
else if (operand_is (name, "cbs")) |
|
|
{ |
|
|
n_min = 1; |
|
|
n_max = MIN (SIZE_MAX, IDX_MAX); |
|
|
converted_idx = &conversion_blocksize; |
|
|
} |
|
|
else if (operand_is (name, "skip") || operand_is (name, "iseek")) |
|
|
{ |
|
|
skip = n; |
|
|
skip_B = has_B; |
|
|
} |
|
|
else if (operand_is (name + (*name == 'o'), "seek")) |
|
|
{ |
|
|
seek = n; |
|
|
seek_B = has_B; |
|
|
} |
|
|
else if (operand_is (name, "count")) |
|
|
{ |
|
|
count = n; |
|
|
count_B = has_B; |
|
|
} |
|
|
else |
|
|
{ |
|
|
diagnose (0, _("unrecognized operand %s"), quoteaf (name)); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (n < n_min) |
|
|
invalid = LONGINT_INVALID; |
|
|
else if (n_max < n) |
|
|
invalid = LONGINT_OVERFLOW; |
|
|
|
|
|
if (invalid != LONGINT_OK) |
|
|
error (EXIT_FAILURE, invalid == LONGINT_OVERFLOW ? EOVERFLOW : 0, |
|
|
"%s: %s", _("invalid number"), quoteaf (val)); |
|
|
else if (converted_idx) |
|
|
*converted_idx = n; |
|
|
} |
|
|
} |
|
|
|
|
|
if (blocksize) |
|
|
input_blocksize = output_blocksize = blocksize; |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
conversions_mask |= C_TWOBUFS; |
|
|
} |
|
|
|
|
|
if (input_blocksize == 0) |
|
|
input_blocksize = DEFAULT_BLOCKSIZE; |
|
|
if (output_blocksize == 0) |
|
|
output_blocksize = DEFAULT_BLOCKSIZE; |
|
|
if (conversion_blocksize == 0) |
|
|
conversions_mask &= ~(C_BLOCK | C_UNBLOCK); |
|
|
|
|
|
if (input_flags & (O_DSYNC | O_SYNC)) |
|
|
input_flags |= O_RSYNC; |
|
|
|
|
|
if (output_flags & O_FULLBLOCK) |
|
|
{ |
|
|
diagnose (0, "%s: %s", _("invalid output flag"), quote ("fullblock")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (skip_B) |
|
|
input_flags |= O_SKIP_BYTES; |
|
|
if (input_flags & O_SKIP_BYTES && skip != 0) |
|
|
{ |
|
|
skip_records = skip / input_blocksize; |
|
|
skip_bytes = skip % input_blocksize; |
|
|
} |
|
|
else if (skip != 0) |
|
|
skip_records = skip; |
|
|
|
|
|
if (count_B) |
|
|
input_flags |= O_COUNT_BYTES; |
|
|
if (input_flags & O_COUNT_BYTES && count != INTMAX_MAX) |
|
|
{ |
|
|
max_records = count / input_blocksize; |
|
|
max_bytes = count % input_blocksize; |
|
|
} |
|
|
else if (count != INTMAX_MAX) |
|
|
max_records = count; |
|
|
|
|
|
if (seek_B) |
|
|
output_flags |= O_SEEK_BYTES; |
|
|
if (output_flags & O_SEEK_BYTES && seek != 0) |
|
|
{ |
|
|
seek_records = seek / output_blocksize; |
|
|
seek_bytes = seek % output_blocksize; |
|
|
} |
|
|
else if (seek != 0) |
|
|
seek_records = seek; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
warn_partial_read = |
|
|
(! (conversions_mask & C_TWOBUFS) && ! (input_flags & O_FULLBLOCK) |
|
|
&& (skip_records |
|
|
|| (0 < max_records && max_records < INTMAX_MAX) |
|
|
|| (input_flags | output_flags) & O_DIRECT)); |
|
|
|
|
|
iread_fnc = ((input_flags & O_FULLBLOCK) |
|
|
? iread_fullblock |
|
|
: iread); |
|
|
input_flags &= ~O_FULLBLOCK; |
|
|
|
|
|
if (multiple_bits_set (conversions_mask & (C_ASCII | C_EBCDIC | C_IBM))) |
|
|
error (EXIT_FAILURE, 0, _("cannot combine any two of {ascii,ebcdic,ibm}")); |
|
|
if (multiple_bits_set (conversions_mask & (C_BLOCK | C_UNBLOCK))) |
|
|
error (EXIT_FAILURE, 0, _("cannot combine block and unblock")); |
|
|
if (multiple_bits_set (conversions_mask & (C_LCASE | C_UCASE))) |
|
|
error (EXIT_FAILURE, 0, _("cannot combine lcase and ucase")); |
|
|
if (multiple_bits_set (conversions_mask & (C_EXCL | C_NOCREAT))) |
|
|
error (EXIT_FAILURE, 0, _("cannot combine excl and nocreat")); |
|
|
if (multiple_bits_set (input_flags & (O_DIRECT | O_NOCACHE)) |
|
|
|| multiple_bits_set (output_flags & (O_DIRECT | O_NOCACHE))) |
|
|
error (EXIT_FAILURE, 0, _("cannot combine direct and nocache")); |
|
|
|
|
|
if (input_flags & O_NOCACHE) |
|
|
{ |
|
|
i_nocache = true; |
|
|
i_nocache_eof = (max_records == 0 && max_bytes == 0); |
|
|
input_flags &= ~O_NOCACHE; |
|
|
} |
|
|
if (output_flags & O_NOCACHE) |
|
|
{ |
|
|
o_nocache = true; |
|
|
o_nocache_eof = (max_records == 0 && max_bytes == 0); |
|
|
output_flags &= ~O_NOCACHE; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
apply_translations (void) |
|
|
{ |
|
|
int i; |
|
|
|
|
|
if (conversions_mask & C_ASCII) |
|
|
translate_charset (ebcdic_to_ascii); |
|
|
|
|
|
if (conversions_mask & C_UCASE) |
|
|
{ |
|
|
for (i = 0; i < 256; i++) |
|
|
trans_table[i] = toupper (trans_table[i]); |
|
|
translation_needed = true; |
|
|
} |
|
|
else if (conversions_mask & C_LCASE) |
|
|
{ |
|
|
for (i = 0; i < 256; i++) |
|
|
trans_table[i] = tolower (trans_table[i]); |
|
|
translation_needed = true; |
|
|
} |
|
|
|
|
|
if (conversions_mask & C_EBCDIC) |
|
|
{ |
|
|
translate_charset (ascii_to_ebcdic); |
|
|
newline_character = ascii_to_ebcdic['\n']; |
|
|
space_character = ascii_to_ebcdic[' ']; |
|
|
} |
|
|
else if (conversions_mask & C_IBM) |
|
|
{ |
|
|
translate_charset (ascii_to_ibm); |
|
|
newline_character = ascii_to_ibm['\n']; |
|
|
space_character = ascii_to_ibm[' ']; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
translate_buffer (char *buf, idx_t nread) |
|
|
{ |
|
|
idx_t i; |
|
|
char *cp; |
|
|
for (i = nread, cp = buf; i; i--, cp++) |
|
|
*cp = trans_table[to_uchar (*cp)]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char * |
|
|
swab_buffer (char *buf, idx_t *nread, int *saved_byte) |
|
|
{ |
|
|
if (*nread == 0) |
|
|
return buf; |
|
|
|
|
|
|
|
|
int prev_saved = *saved_byte; |
|
|
if ((prev_saved < 0) == (*nread & 1)) |
|
|
{ |
|
|
unsigned char c = buf[--*nread]; |
|
|
*saved_byte = c; |
|
|
} |
|
|
else |
|
|
*saved_byte = -1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (idx_t i = *nread; 1 < i; i -= 2) |
|
|
buf[i] = buf[i - 2]; |
|
|
|
|
|
if (prev_saved < 0) |
|
|
return buf + 1; |
|
|
|
|
|
buf[1] = prev_saved; |
|
|
++*nread; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
advance_input_offset (intmax_t offset) |
|
|
{ |
|
|
if (0 <= input_offset && ckd_add (&input_offset, input_offset, offset)) |
|
|
input_offset = -1; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static intmax_t |
|
|
skip (int fdesc, char const *file, intmax_t records, idx_t blocksize, |
|
|
idx_t *bytes) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
errno = 0; |
|
|
off_t offset; |
|
|
if (! ckd_mul (&offset, records, blocksize) |
|
|
&& ! ckd_add (&offset, offset, *bytes) |
|
|
&& 0 <= lseek (fdesc, offset, SEEK_CUR)) |
|
|
{ |
|
|
if (fdesc == STDIN_FILENO) |
|
|
{ |
|
|
struct stat st; |
|
|
if (ifstat (STDIN_FILENO, &st) != 0) |
|
|
error (EXIT_FAILURE, errno, _("cannot fstat %s"), quoteaf (file)); |
|
|
if (usable_st_size (&st) && 0 < st.st_size && 0 <= input_offset |
|
|
&& st.st_size - input_offset < offset) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
records = ( offset - st.st_size ) / blocksize; |
|
|
offset = st.st_size - input_offset; |
|
|
} |
|
|
else |
|
|
records = 0; |
|
|
advance_input_offset (offset); |
|
|
} |
|
|
else |
|
|
{ |
|
|
records = 0; |
|
|
*bytes = 0; |
|
|
} |
|
|
return records; |
|
|
} |
|
|
else |
|
|
{ |
|
|
int lseek_errno = errno; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (lseek (fdesc, 0, SEEK_END) >= 0) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
if (!lseek_errno) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lseek_errno = EOVERFLOW; |
|
|
} |
|
|
|
|
|
diagnose (lseek_errno, |
|
|
gettext (fdesc == STDIN_FILENO |
|
|
? N_("%s: cannot skip") |
|
|
: N_("%s: cannot seek")), |
|
|
quotef (file)); |
|
|
|
|
|
|
|
|
quit (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
|
|
|
char *buf; |
|
|
if (fdesc == STDIN_FILENO) |
|
|
{ |
|
|
alloc_ibuf (); |
|
|
buf = ibuf; |
|
|
} |
|
|
else |
|
|
{ |
|
|
alloc_obuf (); |
|
|
buf = obuf; |
|
|
} |
|
|
|
|
|
do |
|
|
{ |
|
|
ssize_t nread = iread_fnc (fdesc, buf, records ? blocksize : *bytes); |
|
|
if (nread < 0) |
|
|
{ |
|
|
if (fdesc == STDIN_FILENO) |
|
|
{ |
|
|
diagnose (errno, _("error reading %s"), quoteaf (file)); |
|
|
if (conversions_mask & C_NOERROR) |
|
|
print_stats (); |
|
|
} |
|
|
else |
|
|
diagnose (lseek_errno, _("%s: cannot seek"), quotef (file)); |
|
|
quit (EXIT_FAILURE); |
|
|
} |
|
|
else if (nread == 0) |
|
|
break; |
|
|
else if (fdesc == STDIN_FILENO) |
|
|
advance_input_offset (nread); |
|
|
|
|
|
if (records != 0) |
|
|
records--; |
|
|
else |
|
|
*bytes = 0; |
|
|
} |
|
|
while (records || *bytes); |
|
|
|
|
|
return records; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
advance_input_after_read_error (idx_t nbytes) |
|
|
{ |
|
|
if (! input_seekable) |
|
|
{ |
|
|
if (input_seek_errno == ESPIPE) |
|
|
return true; |
|
|
errno = input_seek_errno; |
|
|
} |
|
|
else |
|
|
{ |
|
|
off_t offset; |
|
|
advance_input_offset (nbytes); |
|
|
if (input_offset < 0) |
|
|
{ |
|
|
diagnose (0, _("offset overflow while reading file %s"), |
|
|
quoteaf (input_file)); |
|
|
return false; |
|
|
} |
|
|
offset = lseek (STDIN_FILENO, 0, SEEK_CUR); |
|
|
if (0 <= offset) |
|
|
{ |
|
|
off_t diff; |
|
|
if (offset == input_offset) |
|
|
return true; |
|
|
diff = input_offset - offset; |
|
|
if (! (0 <= diff && diff <= nbytes) && status_level != STATUS_NONE) |
|
|
diagnose (0, _("warning: invalid file offset after failed read")); |
|
|
if (0 <= lseek (STDIN_FILENO, diff, SEEK_CUR)) |
|
|
return true; |
|
|
if (errno == 0) |
|
|
diagnose (0, _("cannot work around kernel bug after all")); |
|
|
} |
|
|
} |
|
|
|
|
|
diagnose (errno, _("%s: cannot seek"), quotef (input_file)); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
copy_simple (char const *buf, idx_t nread) |
|
|
{ |
|
|
char const *start = buf; |
|
|
|
|
|
do |
|
|
{ |
|
|
idx_t nfree = MIN (nread, output_blocksize - oc); |
|
|
|
|
|
memcpy (obuf + oc, start, nfree); |
|
|
|
|
|
nread -= nfree; |
|
|
start += nfree; |
|
|
oc += nfree; |
|
|
if (oc >= output_blocksize) |
|
|
write_output (); |
|
|
} |
|
|
while (nread != 0); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
copy_with_block (char const *buf, idx_t nread) |
|
|
{ |
|
|
for (idx_t i = nread; i; i--, buf++) |
|
|
{ |
|
|
if (*buf == newline_character) |
|
|
{ |
|
|
if (col < conversion_blocksize) |
|
|
{ |
|
|
idx_t j; |
|
|
for (j = col; j < conversion_blocksize; j++) |
|
|
output_char (space_character); |
|
|
} |
|
|
col = 0; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (col == conversion_blocksize) |
|
|
r_truncate++; |
|
|
else if (col < conversion_blocksize) |
|
|
output_char (*buf); |
|
|
col++; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
copy_with_unblock (char const *buf, idx_t nread) |
|
|
{ |
|
|
static idx_t pending_spaces = 0; |
|
|
|
|
|
for (idx_t i = 0; i < nread; i++) |
|
|
{ |
|
|
char c = buf[i]; |
|
|
|
|
|
if (col++ >= conversion_blocksize) |
|
|
{ |
|
|
col = pending_spaces = 0; |
|
|
i--; |
|
|
output_char (newline_character); |
|
|
} |
|
|
else if (c == space_character) |
|
|
pending_spaces++; |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
while (pending_spaces) |
|
|
{ |
|
|
output_char (space_character); |
|
|
--pending_spaces; |
|
|
} |
|
|
output_char (c); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
set_fd_flags (int fd, int add_flags, char const *name) |
|
|
{ |
|
|
|
|
|
add_flags &= ~ (O_NOCTTY | O_NOFOLLOW); |
|
|
|
|
|
if (add_flags) |
|
|
{ |
|
|
int old_flags = fcntl (fd, F_GETFL); |
|
|
int new_flags = old_flags | add_flags; |
|
|
bool ok = true; |
|
|
if (old_flags < 0) |
|
|
ok = false; |
|
|
else if (old_flags != new_flags) |
|
|
{ |
|
|
if (new_flags & (O_DIRECTORY | O_NOLINKS)) |
|
|
{ |
|
|
|
|
|
|
|
|
struct stat st; |
|
|
if (ifstat (fd, &st) != 0) |
|
|
ok = false; |
|
|
else if ((new_flags & O_DIRECTORY) && ! S_ISDIR (st.st_mode)) |
|
|
{ |
|
|
errno = ENOTDIR; |
|
|
ok = false; |
|
|
} |
|
|
else if ((new_flags & O_NOLINKS) && 1 < st.st_nlink) |
|
|
{ |
|
|
errno = EMLINK; |
|
|
ok = false; |
|
|
} |
|
|
new_flags &= ~ (O_DIRECTORY | O_NOLINKS); |
|
|
} |
|
|
|
|
|
if (ok && old_flags != new_flags |
|
|
&& fcntl (fd, F_SETFL, new_flags) == -1) |
|
|
ok = false; |
|
|
} |
|
|
|
|
|
if (!ok) |
|
|
error (EXIT_FAILURE, errno, _("setting flags for %s"), quoteaf (name)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
dd_copy (void) |
|
|
{ |
|
|
char *bufstart; |
|
|
ssize_t nread; |
|
|
|
|
|
|
|
|
|
|
|
idx_t partread = 0; |
|
|
|
|
|
int exit_status = EXIT_SUCCESS; |
|
|
idx_t n_bytes_read; |
|
|
|
|
|
if (skip_records != 0 || skip_bytes != 0) |
|
|
{ |
|
|
intmax_t us_bytes; |
|
|
bool us_bytes_overflow = |
|
|
(ckd_mul (&us_bytes, skip_records, input_blocksize) |
|
|
|| ckd_add (&us_bytes, skip_bytes, us_bytes)); |
|
|
off_t input_offset0 = input_offset; |
|
|
intmax_t us_blocks = skip (STDIN_FILENO, input_file, |
|
|
skip_records, input_blocksize, &skip_bytes); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((us_blocks |
|
|
|| (0 <= input_offset |
|
|
&& (us_bytes_overflow |
|
|
|| us_bytes != input_offset - input_offset0))) |
|
|
&& status_level != STATUS_NONE) |
|
|
{ |
|
|
diagnose (0, _("%s: cannot skip to specified offset"), |
|
|
quotef (input_file)); |
|
|
} |
|
|
} |
|
|
|
|
|
if (seek_records != 0 || seek_bytes != 0) |
|
|
{ |
|
|
idx_t bytes = seek_bytes; |
|
|
intmax_t write_records = skip (STDOUT_FILENO, output_file, |
|
|
seek_records, output_blocksize, &bytes); |
|
|
|
|
|
if (write_records != 0 || bytes != 0) |
|
|
{ |
|
|
memset (obuf, 0, write_records ? output_blocksize : bytes); |
|
|
|
|
|
do |
|
|
{ |
|
|
idx_t size = write_records ? output_blocksize : bytes; |
|
|
if (iwrite (STDOUT_FILENO, obuf, size) != size) |
|
|
{ |
|
|
diagnose (errno, _("writing to %s"), quoteaf (output_file)); |
|
|
quit (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (write_records != 0) |
|
|
write_records--; |
|
|
else |
|
|
bytes = 0; |
|
|
} |
|
|
while (write_records || bytes); |
|
|
} |
|
|
} |
|
|
|
|
|
if (max_records == 0 && max_bytes == 0) |
|
|
return exit_status; |
|
|
|
|
|
alloc_ibuf (); |
|
|
alloc_obuf (); |
|
|
int saved_byte = -1; |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
if (status_level == STATUS_PROGRESS) |
|
|
{ |
|
|
xtime_t progress_time = gethrxtime (); |
|
|
if (next_time <= progress_time) |
|
|
{ |
|
|
print_xfer_stats (progress_time); |
|
|
next_time += XTIME_PRECISION; |
|
|
} |
|
|
} |
|
|
|
|
|
if (r_partial + r_full >= max_records + !!max_bytes) |
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((conversions_mask & C_SYNC) && (conversions_mask & C_NOERROR)) |
|
|
memset (ibuf, |
|
|
(conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0', |
|
|
input_blocksize); |
|
|
|
|
|
if (r_partial + r_full >= max_records) |
|
|
nread = iread_fnc (STDIN_FILENO, ibuf, max_bytes); |
|
|
else |
|
|
nread = iread_fnc (STDIN_FILENO, ibuf, input_blocksize); |
|
|
|
|
|
if (nread > 0) |
|
|
{ |
|
|
advance_input_offset (nread); |
|
|
if (i_nocache) |
|
|
invalidate_cache (STDIN_FILENO, nread); |
|
|
} |
|
|
else if (nread == 0) |
|
|
{ |
|
|
i_nocache_eof |= i_nocache; |
|
|
o_nocache_eof |= o_nocache && ! (conversions_mask & C_NOTRUNC); |
|
|
break; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (!(conversions_mask & C_NOERROR) || status_level != STATUS_NONE) |
|
|
diagnose (errno, _("error reading %s"), quoteaf (input_file)); |
|
|
|
|
|
if (conversions_mask & C_NOERROR) |
|
|
{ |
|
|
print_stats (); |
|
|
idx_t bad_portion = input_blocksize - partread; |
|
|
|
|
|
|
|
|
|
|
|
invalidate_cache (STDIN_FILENO, bad_portion); |
|
|
|
|
|
|
|
|
if (!advance_input_after_read_error (bad_portion)) |
|
|
{ |
|
|
exit_status = EXIT_FAILURE; |
|
|
|
|
|
|
|
|
input_seekable = false; |
|
|
input_seek_errno = ESPIPE; |
|
|
} |
|
|
if ((conversions_mask & C_SYNC) && !partread) |
|
|
|
|
|
|
|
|
nread = 0; |
|
|
else |
|
|
continue; |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
exit_status = EXIT_FAILURE; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
n_bytes_read = nread; |
|
|
|
|
|
if (n_bytes_read < input_blocksize) |
|
|
{ |
|
|
r_partial++; |
|
|
partread = n_bytes_read; |
|
|
if (conversions_mask & C_SYNC) |
|
|
{ |
|
|
if (!(conversions_mask & C_NOERROR)) |
|
|
|
|
|
memset (ibuf + n_bytes_read, |
|
|
(conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0', |
|
|
input_blocksize - n_bytes_read); |
|
|
n_bytes_read = input_blocksize; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
r_full++; |
|
|
partread = 0; |
|
|
} |
|
|
|
|
|
if (ibuf == obuf) |
|
|
{ |
|
|
idx_t nwritten = iwrite (STDOUT_FILENO, obuf, n_bytes_read); |
|
|
w_bytes += nwritten; |
|
|
if (nwritten != n_bytes_read) |
|
|
{ |
|
|
diagnose (errno, _("error writing %s"), quoteaf (output_file)); |
|
|
return EXIT_FAILURE; |
|
|
} |
|
|
else if (n_bytes_read == input_blocksize) |
|
|
w_full++; |
|
|
else |
|
|
w_partial++; |
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (translation_needed) |
|
|
translate_buffer (ibuf, n_bytes_read); |
|
|
|
|
|
if (conversions_mask & C_SWAB) |
|
|
bufstart = swab_buffer (ibuf, &n_bytes_read, &saved_byte); |
|
|
else |
|
|
bufstart = ibuf; |
|
|
|
|
|
if (conversions_mask & C_BLOCK) |
|
|
copy_with_block (bufstart, n_bytes_read); |
|
|
else if (conversions_mask & C_UNBLOCK) |
|
|
copy_with_unblock (bufstart, n_bytes_read); |
|
|
else |
|
|
copy_simple (bufstart, n_bytes_read); |
|
|
} |
|
|
|
|
|
|
|
|
if (0 <= saved_byte) |
|
|
{ |
|
|
char saved_char = saved_byte; |
|
|
if (conversions_mask & C_BLOCK) |
|
|
copy_with_block (&saved_char, 1); |
|
|
else if (conversions_mask & C_UNBLOCK) |
|
|
copy_with_unblock (&saved_char, 1); |
|
|
else |
|
|
output_char (saved_char); |
|
|
} |
|
|
|
|
|
if ((conversions_mask & C_BLOCK) && col > 0) |
|
|
{ |
|
|
|
|
|
|
|
|
for (idx_t i = col; i < conversion_blocksize; i++) |
|
|
output_char (space_character); |
|
|
} |
|
|
|
|
|
if (col && (conversions_mask & C_UNBLOCK)) |
|
|
{ |
|
|
|
|
|
output_char (newline_character); |
|
|
} |
|
|
|
|
|
|
|
|
if (oc != 0) |
|
|
{ |
|
|
idx_t nwritten = iwrite (STDOUT_FILENO, obuf, oc); |
|
|
w_bytes += nwritten; |
|
|
if (nwritten != 0) |
|
|
w_partial++; |
|
|
if (nwritten != oc) |
|
|
{ |
|
|
diagnose (errno, _("error writing %s"), quoteaf (output_file)); |
|
|
return EXIT_FAILURE; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (final_op_was_seek) |
|
|
{ |
|
|
struct stat stdout_stat; |
|
|
if (ifstat (STDOUT_FILENO, &stdout_stat) != 0) |
|
|
{ |
|
|
diagnose (errno, _("cannot fstat %s"), quoteaf (output_file)); |
|
|
return EXIT_FAILURE; |
|
|
} |
|
|
if (S_ISREG (stdout_stat.st_mode) || S_TYPEISSHM (&stdout_stat)) |
|
|
{ |
|
|
off_t output_offset = lseek (STDOUT_FILENO, 0, SEEK_CUR); |
|
|
if (0 <= output_offset && stdout_stat.st_size < output_offset) |
|
|
{ |
|
|
if (iftruncate (STDOUT_FILENO, output_offset) != 0) |
|
|
{ |
|
|
diagnose (errno, _("failed to truncate to %jd bytes" |
|
|
" in output file %s"), |
|
|
(intmax_t) output_offset, quoteaf (output_file)); |
|
|
return EXIT_FAILURE; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (conversions_mask & (C_FDATASYNC | C_FSYNC) |
|
|
&& status_level == STATUS_PROGRESS |
|
|
&& 0 <= reported_w_bytes && reported_w_bytes < w_bytes) |
|
|
print_xfer_stats (0); |
|
|
|
|
|
return exit_status; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
synchronize_output (void) |
|
|
{ |
|
|
int exit_status = 0; |
|
|
int mask = conversions_mask; |
|
|
conversions_mask &= ~ (C_FDATASYNC | C_FSYNC); |
|
|
|
|
|
if ((mask & C_FDATASYNC) && ifdatasync (STDOUT_FILENO) != 0) |
|
|
{ |
|
|
if (errno != ENOSYS && errno != EINVAL) |
|
|
{ |
|
|
diagnose (errno, _("fdatasync failed for %s"), quoteaf (output_file)); |
|
|
exit_status = EXIT_FAILURE; |
|
|
} |
|
|
mask |= C_FSYNC; |
|
|
} |
|
|
|
|
|
if ((mask & C_FSYNC) && ifsync (STDOUT_FILENO) != 0) |
|
|
{ |
|
|
diagnose (errno, _("fsync failed for %s"), quoteaf (output_file)); |
|
|
return EXIT_FAILURE; |
|
|
} |
|
|
|
|
|
return exit_status; |
|
|
} |
|
|
|
|
|
int |
|
|
main (int argc, char **argv) |
|
|
{ |
|
|
int i; |
|
|
int exit_status; |
|
|
off_t offset; |
|
|
|
|
|
install_signal_handlers (); |
|
|
|
|
|
initialize_main (&argc, &argv); |
|
|
set_program_name (argv[0]); |
|
|
setlocale (LC_ALL, ""); |
|
|
bindtextdomain (PACKAGE, LOCALEDIR); |
|
|
textdomain (PACKAGE); |
|
|
|
|
|
|
|
|
atexit (maybe_close_stdout); |
|
|
|
|
|
page_size = getpagesize (); |
|
|
|
|
|
parse_gnu_standard_options_only (argc, argv, PROGRAM_NAME, PACKAGE, Version, |
|
|
true, usage, AUTHORS, |
|
|
(char const *) nullptr); |
|
|
close_stdout_required = false; |
|
|
|
|
|
|
|
|
for (i = 0; i < 256; i++) |
|
|
trans_table[i] = i; |
|
|
|
|
|
|
|
|
scanargs (argc, argv); |
|
|
|
|
|
apply_translations (); |
|
|
|
|
|
if (input_file == nullptr) |
|
|
{ |
|
|
input_file = _("standard input"); |
|
|
set_fd_flags (STDIN_FILENO, input_flags, input_file); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (ifd_reopen (STDIN_FILENO, input_file, O_RDONLY | input_flags, 0) < 0) |
|
|
error (EXIT_FAILURE, errno, _("failed to open %s"), |
|
|
quoteaf (input_file)); |
|
|
} |
|
|
|
|
|
offset = lseek (STDIN_FILENO, 0, SEEK_CUR); |
|
|
input_seekable = (0 <= offset); |
|
|
input_offset = MAX (0, offset); |
|
|
input_seek_errno = errno; |
|
|
|
|
|
if (output_file == nullptr) |
|
|
{ |
|
|
output_file = _("standard output"); |
|
|
set_fd_flags (STDOUT_FILENO, output_flags, output_file); |
|
|
} |
|
|
else |
|
|
{ |
|
|
mode_t perms = MODE_RW_UGO; |
|
|
int opts |
|
|
= (output_flags |
|
|
| (conversions_mask & C_NOCREAT ? 0 : O_CREAT) |
|
|
| (conversions_mask & C_EXCL ? O_EXCL : 0) |
|
|
| (seek_records || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC)); |
|
|
|
|
|
off_t size; |
|
|
if ((ckd_mul (&size, seek_records, output_blocksize) |
|
|
|| ckd_add (&size, seek_bytes, size)) |
|
|
&& !(conversions_mask & C_NOTRUNC)) |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("offset too large: " |
|
|
"cannot truncate to a length of seek=%jd" |
|
|
" (%td-byte) blocks"), |
|
|
seek_records, output_blocksize); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((! seek_records |
|
|
|| ifd_reopen (STDOUT_FILENO, output_file, O_RDWR | opts, perms) < 0) |
|
|
&& (ifd_reopen (STDOUT_FILENO, output_file, O_WRONLY | opts, perms) |
|
|
< 0)) |
|
|
error (EXIT_FAILURE, errno, _("failed to open %s"), |
|
|
quoteaf (output_file)); |
|
|
|
|
|
if (seek_records != 0 && !(conversions_mask & C_NOTRUNC)) |
|
|
{ |
|
|
if (iftruncate (STDOUT_FILENO, size) != 0) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int ftruncate_errno = errno; |
|
|
struct stat stdout_stat; |
|
|
if (ifstat (STDOUT_FILENO, &stdout_stat) != 0) |
|
|
{ |
|
|
diagnose (errno, _("cannot fstat %s"), quoteaf (output_file)); |
|
|
exit_status = EXIT_FAILURE; |
|
|
} |
|
|
else if (S_ISREG (stdout_stat.st_mode) |
|
|
|| S_ISDIR (stdout_stat.st_mode) |
|
|
|| S_TYPEISSHM (&stdout_stat)) |
|
|
{ |
|
|
intmax_t isize = size; |
|
|
diagnose (ftruncate_errno, |
|
|
_("failed to truncate to %jd bytes" |
|
|
" in output file %s"), |
|
|
isize, quoteaf (output_file)); |
|
|
exit_status = EXIT_FAILURE; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
start_time = gethrxtime (); |
|
|
next_time = start_time + XTIME_PRECISION; |
|
|
|
|
|
exit_status = dd_copy (); |
|
|
|
|
|
int sync_status = synchronize_output (); |
|
|
if (sync_status) |
|
|
exit_status = sync_status; |
|
|
|
|
|
if (max_records == 0 && max_bytes == 0) |
|
|
{ |
|
|
|
|
|
if (i_nocache && !invalidate_cache (STDIN_FILENO, 0)) |
|
|
{ |
|
|
diagnose (errno, _("failed to discard cache for: %s"), |
|
|
quotef (input_file)); |
|
|
exit_status = EXIT_FAILURE; |
|
|
} |
|
|
if (o_nocache && !invalidate_cache (STDOUT_FILENO, 0)) |
|
|
{ |
|
|
diagnose (errno, _("failed to discard cache for: %s"), |
|
|
quotef (output_file)); |
|
|
exit_status = EXIT_FAILURE; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
if (i_nocache || i_nocache_eof) |
|
|
invalidate_cache (STDIN_FILENO, 0); |
|
|
if (o_nocache || o_nocache_eof) |
|
|
invalidate_cache (STDOUT_FILENO, 0); |
|
|
} |
|
|
|
|
|
finish_up (); |
|
|
main_exit (exit_status); |
|
|
} |
|
|
|