diff options
Diffstat (limited to 'src/dd.c')
-rw-r--r-- | src/dd.c | 1744 |
1 files changed, 1744 insertions, 0 deletions
diff --git a/src/dd.c b/src/dd.c new file mode 100644 index 0000000..27a4a08 --- /dev/null +++ b/src/dd.c @@ -0,0 +1,1744 @@ +/* dd -- convert a file while copying it. + Copyright (C) 85, 90, 91, 1995-2007 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin, David MacKenzie, and Stuart Kemp. */ + +#include <config.h> + +#define SWAB_ALIGN_OFFSET 2 + +#include <sys/types.h> +#include <signal.h> +#include <getopt.h> + +#include "system.h" +#include "error.h" +#include "fd-reopen.h" +#include "gethrxtime.h" +#include "getpagesize.h" +#include "human.h" +#include "long-options.h" +#include "quote.h" +#include "xstrtol.h" +#include "xtime.h" + +static void process_signals (void); + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "dd" + +#define AUTHORS "Paul Rubin", "David MacKenzie", "Stuart Kemp" + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. SA_NODEFER and SA_RESETHAND are XSI extensions. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif +#ifndef SA_NODEFER +# define SA_NODEFER 0 +#endif +#ifndef SA_RESETHAND +# define SA_RESETHAND 0 +#endif + +#ifndef SIGINFO +# define SIGINFO SIGUSR1 +#endif + +#if ! HAVE_FDATASYNC +# define fdatasync(fd) (errno = ENOSYS, -1) +#endif + +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define output_char(c) \ + do \ + { \ + obuf[oc++] = (c); \ + if (oc >= output_blocksize) \ + write_output (); \ + } \ + while (0) + +/* Default input and output blocksize. */ +#define DEFAULT_BLOCKSIZE 512 + +/* How many bytes to add to the input and output block sizes before invoking + malloc. See dd_copy for details. INPUT_BLOCK_SLOP must be no less than + OUTPUT_BLOCK_SLOP. */ +#define INPUT_BLOCK_SLOP (2 * SWAB_ALIGN_OFFSET + 2 * page_size - 1) +#define OUTPUT_BLOCK_SLOP (page_size - 1) + +/* Maximum blocksize for the given SLOP. + Keep it smaller than SIZE_MAX - SLOP, so that we can + allocate buffers that size. Keep it smaller than SSIZE_MAX, for + the benefit of system calls like "read". And keep it smaller than + OFF_T_MAX, for the benefit of the large-offset seek code. */ +#define MAX_BLOCKSIZE(slop) MIN (SIZE_MAX - (slop), MIN (SSIZE_MAX, OFF_T_MAX)) + +/* Conversions bit masks. */ +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, + + /* Use separate input and output buffers, and combine partial + input blocks. */ + C_TWOBUFS = 04000, + + C_NOCREAT = 010000, + C_EXCL = 020000, + C_FDATASYNC = 040000, + C_FSYNC = 0100000 + }; + +/* Status bit masks. */ +enum + { + STATUS_NOXFER = 01 + }; + +/* The name this program was run with. */ +char *program_name; + +/* The name of the input file, or NULL for the standard input. */ +static char const *input_file = NULL; + +/* The name of the output file, or NULL for the standard output. */ +static char const *output_file = NULL; + +/* The page size on this host. */ +static size_t page_size; + +/* The number of bytes in which atomic reads are done. */ +static size_t input_blocksize = 0; + +/* The number of bytes in which atomic writes are done. */ +static size_t output_blocksize = 0; + +/* Conversion buffer size, in bytes. 0 prevents conversions. */ +static size_t conversion_blocksize = 0; + +/* Skip this many records of `input_blocksize' bytes before input. */ +static uintmax_t skip_records = 0; + +/* Skip this many records of `output_blocksize' bytes before output. */ +static uintmax_t seek_records = 0; + +/* Copy only this many records. The default is effectively infinity. */ +static uintmax_t max_records = (uintmax_t) -1; + +/* Bit vector of conversions to apply. */ +static int conversions_mask = 0; + +/* Open flags for the input and output files. */ +static int input_flags = 0; +static int output_flags = 0; + +/* Status flags for what is printed to stderr. */ +static int status_flags = 0; + +/* If nonzero, filter characters through the translation table. */ +static bool translation_needed = false; + +/* Number of partial blocks written. */ +static uintmax_t w_partial = 0; + +/* Number of full blocks written. */ +static uintmax_t w_full = 0; + +/* Number of partial blocks read. */ +static uintmax_t r_partial = 0; + +/* Number of full blocks read. */ +static uintmax_t r_full = 0; + +/* Number of bytes written. */ +static uintmax_t w_bytes = 0; + +/* Time that dd started. */ +static xtime_t start_time; + +/* True if input is seekable. */ +static bool input_seekable; + +/* Error number corresponding to initial attempt to lseek input. + If ESPIPE, do not issue any more diagnostics about it. */ +static int input_seek_errno; + +/* File offset of the input, in bytes, along with a flag recording + whether it overflowed. The offset is valid only if the input is + seekable and if the offset has not overflowed. */ +static uintmax_t input_offset; +static bool input_offset_overflow; + +/* Records truncated by conv=block. */ +static uintmax_t r_truncate = 0; + +/* Output representation of newline and space characters. + They change if we're converting to EBCDIC. */ +static char newline_character = '\n'; +static char space_character = ' '; + +/* Output buffer. */ +static char *obuf; + +/* Current index into `obuf'. */ +static size_t oc = 0; + +/* Index into current line, for `conv=block' and `conv=unblock'. */ +static size_t col = 0; + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +/* If nonzero, the value of the pending fatal signal. */ +static sig_atomic_t volatile interrupt_signal; + +/* A count of the number of pending info signals that have been received. */ +static sig_atomic_t volatile info_signal_count; + +/* A longest symbol in the struct symbol_values tables below. */ +#define LONGEST_SYMBOL "fdatasync" + +/* A symbol and the corresponding integer value. */ +struct symbol_value +{ + char symbol[sizeof LONGEST_SYMBOL]; + int value; +}; + +/* Conversion symbols, for conv="...". */ +static struct symbol_value const conversions[] = +{ + {"ascii", C_ASCII | C_TWOBUFS}, /* EBCDIC to ASCII. */ + {"ebcdic", C_EBCDIC | C_TWOBUFS}, /* ASCII to EBCDIC. */ + {"ibm", C_IBM | C_TWOBUFS}, /* Slightly different ASCII to EBCDIC. */ + {"block", C_BLOCK | C_TWOBUFS}, /* Variable to fixed length records. */ + {"unblock", C_UNBLOCK | C_TWOBUFS}, /* Fixed to variable length records. */ + {"lcase", C_LCASE | C_TWOBUFS}, /* Translate upper to lower case. */ + {"ucase", C_UCASE | C_TWOBUFS}, /* Translate lower to upper case. */ + {"swab", C_SWAB | C_TWOBUFS}, /* Swap bytes of input. */ + {"noerror", C_NOERROR}, /* Ignore i/o errors. */ + {"nocreat", C_NOCREAT}, /* Do not create output file. */ + {"excl", C_EXCL}, /* Fail if the output file already exists. */ + {"notrunc", C_NOTRUNC}, /* Do not truncate output file. */ + {"sync", C_SYNC}, /* Pad input records to ibs with NULs. */ + {"fdatasync", C_FDATASYNC}, /* Synchronize output data before finishing. */ + {"fsync", C_FSYNC}, /* Also synchronize output metadata. */ + {"", 0} +}; + +/* Flags, for iflag="..." and oflag="...". */ +static struct symbol_value const flags[] = +{ + {"append", O_APPEND}, + {"binary", O_BINARY}, + {"direct", O_DIRECT}, + {"directory", O_DIRECTORY}, + {"dsync", O_DSYNC}, + {"noatime", O_NOATIME}, + {"noctty", O_NOCTTY}, + {"nofollow", HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0}, + {"nolinks", O_NOLINKS}, + {"nonblock", O_NONBLOCK}, + {"sync", O_SYNC}, + {"text", O_TEXT}, + {"", 0} +}; + +/* Status, for status="...". */ +static struct symbol_value const statuses[] = +{ + {"noxfer", STATUS_NOXFER}, + {"", 0} +}; + +/* Translation table formed by applying successive transformations. */ +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', '\117', '\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', '\112', '\340', '\132', '\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', '\152', '\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 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', '\133', '\056', '\074', '\050', '\053', '\041', + '\046', '\251', '\252', '\253', '\254', '\255', '\256', '\257', + '\260', '\261', '\135', '\044', '\052', '\051', '\073', '\136', + '\055', '\057', '\262', '\263', '\264', '\265', '\266', '\267', + '\270', '\271', '\174', '\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', '\313', '\314', '\315', '\316', '\317', '\320', + '\321', '\176', '\163', '\164', '\165', '\166', '\167', '\170', + '\171', '\172', '\322', '\323', '\324', '\325', '\326', '\327', + '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337', + '\340', '\341', '\342', '\343', '\344', '\345', '\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' +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + 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 force ibs=BYTES and obs=BYTES\n\ + cbs=BYTES convert BYTES bytes at a time\n\ + conv=CONVS convert the file as per the comma separated symbol list\n\ + count=BLOCKS copy only BLOCKS input blocks\n\ + ibs=BYTES read BYTES bytes at a time\n\ +"), stdout); + fputs (_("\ + if=FILE read from FILE instead of stdin\n\ + iflag=FLAGS read as per the comma separated symbol list\n\ + obs=BYTES write BYTES bytes at a time\n\ + of=FILE write to FILE instead of stdout\n\ + oflag=FLAGS write as per the comma separated symbol list\n\ + seek=BLOCKS skip BLOCKS obs-sized blocks at start of output\n\ + skip=BLOCKS skip BLOCKS ibs-sized blocks at start of input\n\ + status=noxfer suppress transfer statistics\n\ +"), stdout); + fputs (_("\ +\n\ +BLOCKS and BYTES may be followed by the following multiplicative suffixes:\n\ +xM M, c 1, w 2, b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,\n\ +GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.\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\ +"), stdout); + fputs (_("\ + nocreat do not create the output file\n\ + excl fail if the output file already exists\n\ + notrunc do not truncate the output file\n\ + ucase change lower case to upper case\n\ + swab swap every pair of input bytes\n\ +"), stdout); + fputs (_("\ + noerror continue after read errors\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\ + 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_DIRECT) + fputs (_(" direct use direct I/O for data\n"), stdout); + if (O_DIRECTORY) + 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); + 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 (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); + + { + char const *siginfo_name = (SIGINFO == SIGUSR1 ? "USR1" : "INFO"); + 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\ + $ dd if=/dev/zero of=/dev/null& pid=$!\n\ + $ kill -%s $pid; sleep 1; kill $pid\n\ + 18335302+0 records in\n\ + 18335302+0 records out\n\ + 9387674624 bytes (9.4 GB) copied, 34.6279 seconds, 271 MB/s\n\ +\n\ +Options are:\n\ +\n\ +"), + siginfo_name, siginfo_name); + } + + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +translate_charset (char const *new_trans) +{ + int i; + + for (i = 0; i < 256; i++) + trans_table[i] = new_trans[trans_table[i]]; + translation_needed = true; +} + +/* Return true if I has more than one bit set. I must be nonnegative. */ + +static inline bool +multiple_bits_set (int i) +{ + return (i & (i - 1)) != 0; +} + +/* Print transfer statistics. */ + +static void +print_stats (void) +{ + xtime_t now = gethrxtime (); + char hbuf[LONGEST_HUMAN_READABLE + 1]; + int human_opts = + (human_autoscale | human_round_to_nearest + | human_space_before_unit | human_SI | human_B); + double delta_s; + char const *bytes_per_second; + + fprintf (stderr, + _("%"PRIuMAX"+%"PRIuMAX" records in\n" + "%"PRIuMAX"+%"PRIuMAX" records out\n"), + r_full, r_partial, w_full, w_partial); + + if (r_truncate != 0) + fprintf (stderr, + ngettext ("%"PRIuMAX" truncated record\n", + "%"PRIuMAX" truncated records\n", + select_plural (r_truncate)), + r_truncate); + + if (status_flags & STATUS_NOXFER) + return; + + /* Use integer arithmetic to compute the transfer rate, + since that makes it easy to use SI abbreviations. */ + + fprintf (stderr, + ngettext ("%"PRIuMAX" byte (%s) copied", + "%"PRIuMAX" bytes (%s) copied", + select_plural (w_bytes)), + w_bytes, + human_readable (w_bytes, hbuf, human_opts, 1, 1)); + + if (start_time < now) + { + double XTIME_PRECISIONe0 = XTIME_PRECISION; + uintmax_t delta_xtime = now; + delta_xtime -= start_time; + delta_s = delta_xtime / XTIME_PRECISIONe0; + bytes_per_second = human_readable (w_bytes, hbuf, human_opts, + XTIME_PRECISION, delta_xtime); + } + else + { + delta_s = 0; + bytes_per_second = _("Infinity B"); + } + + /* TRANSLATORS: The two instances of "s" in this string are the SI + symbol "s" (meaning second), and should not be translated. + + This format used to be: + + ngettext (", %g second, %s/s\n", ", %g seconds, %s/s\n", delta_s == 1) + + but that was incorrect for languages like Polish. To fix this + bug we now use SI symbols even though they're a bit more + confusing in English. */ + fprintf (stderr, _(", %g s, %s/s\n"), delta_s, bytes_per_second); +} + +static void +cleanup (void) +{ + if (close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, + _("closing input file %s"), quote (input_file)); + + /* Don't remove this call to close, even though close_stdout + closes standard output. This close is necessary when cleanup + is called as part of a signal handler. */ + if (close (STDOUT_FILENO) < 0) + error (EXIT_FAILURE, errno, + _("closing output file %s"), quote (output_file)); +} + +static inline void ATTRIBUTE_NORETURN +quit (int code) +{ + cleanup (); + print_stats (); + process_signals (); + exit (code); +} + +/* An ordinary signal was received; arrange for the program to exit. */ + +static void +interrupt_handler (int sig) +{ + if (! SA_RESETHAND) + signal (sig, SIG_DFL); + interrupt_signal = sig; +} + +/* An info signal was received; arrange for the program to print status. */ + +static void +siginfo_handler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, siginfo_handler); + info_signal_count++; +} + +/* Install the signal handlers. */ + +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) + { + sigaction (SIGINFO, NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, SIGINFO); + } + sigaction (SIGINT, NULL, &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, NULL); + } + + if (sigismember (&caught_signals, SIGINT)) + { + /* POSIX 1003.1-2001 says SA_RESETHAND implies SA_NODEFER, + but this is not true on Solaris 8 at least. It doesn't + hurt to use SA_NODEFER here, so leave it in. */ + act.sa_handler = interrupt_handler; + act.sa_flags = SA_NODEFER | SA_RESETHAND; + sigaction (SIGINT, &act, NULL); + } + +#else + + if (catch_siginfo && signal (SIGINFO, SIG_IGN) != SIG_IGN) + { + signal (SIGINFO, siginfo_handler); + siginterrupt (SIGINFO, 1); + } + if (signal (SIGINT, SIG_IGN) != SIG_IGN) + { + signal (SIGINT, interrupt_handler); + siginterrupt (SIGINT, 1); + } +#endif +} + +/* Process any pending signals. If signals are caught, this function + should be called periodically. Ideally there should never be an + unbounded amount of time when signals are not being processed. */ + +static void +process_signals (void) +{ + while (interrupt_signal | info_signal_count) + { + int interrupt; + int infos; + sigset_t oldset; + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + /* Reload interrupt_signal and info_signal_count, in case a new + signal was handled before sigprocmask took effect. */ + interrupt = interrupt_signal; + infos = info_signal_count; + + if (infos) + info_signal_count = infos - 1; + + sigprocmask (SIG_SETMASK, &oldset, NULL); + + if (interrupt) + cleanup (); + print_stats (); + if (interrupt) + raise (interrupt); + } +} + +/* Read from FD into the buffer BUF of size SIZE, processing any + signals that arrive before bytes are read. Return the number of + bytes read if successful, -1 (setting errno) on failure. */ + +static ssize_t +iread (int fd, char *buf, size_t size) +{ + for (;;) + { + ssize_t nread; + process_signals (); + nread = read (fd, buf, size); + if (! (nread < 0 && errno == EINTR)) + return nread; + } +} + +/* Write to FD the buffer BUF of size SIZE, processing any signals + that arrive. Return the number of bytes written, setting errno if + this is less than SIZE. Keep trying if there are partial + writes. */ + +static size_t +iwrite (int fd, char const *buf, size_t size) +{ + size_t total_written = 0; + + while (total_written < size) + { + ssize_t nwritten; + process_signals (); + nwritten = write (fd, buf + total_written, size - total_written); + if (nwritten < 0) + { + if (errno != EINTR) + break; + } + else if (nwritten == 0) + { + /* Some buggy drivers return 0 when one tries to write beyond + a device's end. (Example: Linux 1.2.13 on /dev/fd0.) + Set errno to ENOSPC so they get a sensible diagnostic. */ + errno = ENOSPC; + break; + } + else + total_written += nwritten; + } + + return total_written; +} + +/* Write, then empty, the output buffer `obuf'. */ + +static void +write_output (void) +{ + size_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize); + w_bytes += nwritten; + if (nwritten != output_blocksize) + { + error (0, errno, _("writing to %s"), quote (output_file)); + if (nwritten != 0) + w_partial++; + quit (EXIT_FAILURE); + } + else + w_full++; + oc = 0; +} + +/* Interpret one "conv=..." or similar operand STR according to the + symbols in TABLE, returning the flags specified. If the operand + cannot be parsed, use ERROR_MSGID to generate a diagnostic. + As a by product, this function replaces each `,' in STR with a NUL byte. */ + +static int +parse_symbols (char *str, struct symbol_value const *table, + char const *error_msgid) +{ + int value = 0; + + do + { + struct symbol_value const *entry; + char *new = strchr (str, ','); + if (new != NULL) + *new++ = '\0'; + for (entry = table; ; entry++) + { + if (! entry->symbol[0]) + { + error (0, 0, _(error_msgid), quote (str)); + usage (EXIT_FAILURE); + } + if (STREQ (entry->symbol, str)) + { + if (! entry->value) + error (EXIT_FAILURE, 0, _(error_msgid), quote (str)); + value |= entry->value; + break; + } + } + str = new; + } + while (str); + + return value; +} + +/* Return the value of STR, interpreted as a non-negative decimal integer, + optionally multiplied by various values. + Set *INVALID if STR does not represent a number in this format. */ + +static uintmax_t +parse_integer (const char *str, bool *invalid) +{ + uintmax_t n; + char *suffix; + enum strtol_error e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0"); + + if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x') + { + uintmax_t multiplier = parse_integer (suffix + 1, invalid); + + if (multiplier != 0 && n * multiplier / multiplier != n) + { + *invalid = true; + return 0; + } + + n *= multiplier; + } + else if (e != LONGINT_OK) + { + *invalid = true; + return 0; + } + + return n; +} + +static void +scanargs (int argc, char **argv) +{ + int i; + size_t blocksize = 0; + + for (i = optind; i < argc; i++) + { + char *name, *val; + + name = argv[i]; + val = strchr (name, '='); + if (val == NULL) + { + error (0, 0, _("unrecognized operand %s"), quote (name)); + usage (EXIT_FAILURE); + } + *val++ = '\0'; + + if (STREQ (name, "if")) + input_file = val; + else if (STREQ (name, "of")) + output_file = val; + else if (STREQ (name, "conv")) + conversions_mask |= parse_symbols (val, conversions, + N_("invalid conversion: %s")); + else if (STREQ (name, "iflag")) + input_flags |= parse_symbols (val, flags, + N_("invalid input flag: %s")); + else if (STREQ (name, "oflag")) + output_flags |= parse_symbols (val, flags, + N_("invalid output flag: %s")); + else if (STREQ (name, "status")) + status_flags |= parse_symbols (val, statuses, + N_("invalid status flag: %s")); + else + { + bool invalid = false; + uintmax_t n = parse_integer (val, &invalid); + + if (STREQ (name, "ibs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (INPUT_BLOCK_SLOP)); + input_blocksize = n; + conversions_mask |= C_TWOBUFS; + } + else if (STREQ (name, "obs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (OUTPUT_BLOCK_SLOP)); + output_blocksize = n; + conversions_mask |= C_TWOBUFS; + } + else if (STREQ (name, "bs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (INPUT_BLOCK_SLOP)); + blocksize = n; + } + else if (STREQ (name, "cbs")) + { + invalid |= ! (0 < n && n <= SIZE_MAX); + conversion_blocksize = n; + } + else if (STREQ (name, "skip")) + skip_records = n; + else if (STREQ (name, "seek")) + seek_records = n; + else if (STREQ (name, "count")) + max_records = n; + else + { + error (0, 0, _("unrecognized operand %s=%s"), + quote_n (0, name), quote_n (1, val)); + usage (EXIT_FAILURE); + } + + if (invalid) + error (EXIT_FAILURE, 0, _("invalid number %s"), quote (val)); + } + } + + if (blocksize) + input_blocksize = output_blocksize = blocksize; + + /* If bs= was given, both `input_blocksize' and `output_blocksize' will + have been set to positive values. If either has not been set, + bs= was not given, so make sure two buffers are used. */ + if (input_blocksize == 0 || output_blocksize == 0) + 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 (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")); +} + +/* Fix up translation table. */ + +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[' ']; + } +} + +/* Apply the character-set translations specified by the user + to the NREAD bytes in BUF. */ + +static void +translate_buffer (char *buf, size_t nread) +{ + char *cp; + size_t i; + + for (i = nread, cp = buf; i; i--, cp++) + *cp = trans_table[to_uchar (*cp)]; +} + +/* If true, the last char from the previous call to `swab_buffer' + is saved in `saved_char'. */ +static bool char_is_saved = false; + +/* Odd char from previous call. */ +static char saved_char; + +/* Swap NREAD bytes in BUF, plus possibly an initial char from the + previous call. If NREAD is odd, save the last char for the + next call. Return the new start of the BUF buffer. */ + +static char * +swab_buffer (char *buf, size_t *nread) +{ + char *bufstart = buf; + char *cp; + size_t i; + + /* Is a char left from last time? */ + if (char_is_saved) + { + *--bufstart = saved_char; + (*nread)++; + char_is_saved = false; + } + + if (*nread & 1) + { + /* An odd number of chars are in the buffer. */ + saved_char = bufstart[--*nread]; + char_is_saved = true; + } + + /* Do the byte-swapping by moving every second character two + positions toward the end, working from the end of the buffer + toward the beginning. This way we only move half of the data. */ + + cp = bufstart + *nread; /* Start one char past the last. */ + for (i = *nread / 2; i; i--, cp -= 2) + *cp = *(cp - 2); + + return ++bufstart; +} + +/* Add OFFSET to the input offset, setting the overflow flag if + necessary. */ + +static void +advance_input_offset (uintmax_t offset) +{ + input_offset += offset; + if (input_offset < offset) + input_offset_overflow = true; +} + +/* This is a wrapper for lseek. It detects and warns about a kernel + bug that makes lseek a no-op for tape devices, even though the kernel + lseek return value suggests that the function succeeded. + + The parameters are the same as those of the lseek function, but + with the addition of FILENAME, the name of the file associated with + descriptor FDESC. The file name is used solely in the warning that's + printed when the bug is detected. Return the same value that lseek + would have returned, but when the lseek bug is detected, return -1 + to indicate that lseek failed. + + The offending behavior has been confirmed with an Exabyte SCSI tape + drive accessed via /dev/nst0 on both Linux-2.2.17 and Linux-2.4.16. */ + +#ifdef __linux__ + +# include <sys/mtio.h> + +# define MT_SAME_POSITION(P, Q) \ + ((P).mt_resid == (Q).mt_resid \ + && (P).mt_fileno == (Q).mt_fileno \ + && (P).mt_blkno == (Q).mt_blkno) + +static off_t +skip_via_lseek (char const *filename, int fdesc, off_t offset, int whence) +{ + struct mtget s1; + struct mtget s2; + bool got_original_tape_position = (ioctl (fdesc, MTIOCGET, &s1) == 0); + /* known bad device type */ + /* && s.mt_type == MT_ISSCSI2 */ + + off_t new_position = lseek (fdesc, offset, whence); + if (0 <= new_position + && got_original_tape_position + && ioctl (fdesc, MTIOCGET, &s2) == 0 + && MT_SAME_POSITION (s1, s2)) + { + error (0, 0, _("warning: working around lseek kernel bug for file (%s)\n\ + of mt_type=0x%0lx -- see <sys/mtio.h> for the list of types"), + filename, s2.mt_type); + errno = 0; + new_position = -1; + } + + return new_position; +} +#else +# define skip_via_lseek(Filename, Fd, Offset, Whence) lseek (Fd, Offset, Whence) +#endif + +/* Throw away RECORDS blocks of BLOCKSIZE bytes on file descriptor FDESC, + which is open with read permission for FILE. Store up to BLOCKSIZE + bytes of the data at a time in BUF, if necessary. RECORDS must be + nonzero. If fdesc is STDIN_FILENO, advance the input offset. + Return the number of records remaining, i.e., that were not skipped + because EOF was reached. */ + +static uintmax_t +skip (int fdesc, char const *file, uintmax_t records, size_t blocksize, + char *buf) +{ + uintmax_t offset = records * blocksize; + + /* Try lseek and if an error indicates it was an inappropriate operation -- + or if the file offset is not representable as an off_t -- + fall back on using read. */ + + errno = 0; + if (records <= OFF_T_MAX / blocksize + && 0 <= skip_via_lseek (file, fdesc, offset, SEEK_CUR)) + { + if (fdesc == STDIN_FILENO) + advance_input_offset (offset); + return 0; + } + else + { + int lseek_errno = errno; + + do + { + ssize_t nread = iread (fdesc, buf, blocksize); + if (nread < 0) + { + if (fdesc == STDIN_FILENO) + { + error (0, errno, _("reading %s"), quote (file)); + if (conversions_mask & C_NOERROR) + { + print_stats (); + continue; + } + } + else + error (0, lseek_errno, _("%s: cannot seek"), quote (file)); + quit (EXIT_FAILURE); + } + + if (nread == 0) + break; + if (fdesc == STDIN_FILENO) + advance_input_offset (nread); + } + while (--records != 0); + + return records; + } +} + +/* Advance the input by NBYTES if possible, after a read error. + The input file offset may or may not have advanced after the failed + read; adjust it to point just after the bad record regardless. + Return true if successful, or if the input is already known to not + be seekable. */ + +static bool +advance_input_after_read_error (size_t nbytes) +{ + if (! input_seekable) + { + if (input_seek_errno == ESPIPE) + return true; + errno = input_seek_errno; + } + else + { + off_t offset; + advance_input_offset (nbytes); + input_offset_overflow |= (OFF_T_MAX < input_offset); + if (input_offset_overflow) + { + error (0, 0, _("offset overflow while reading file %s"), + quote (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)) + error (0, 0, _("warning: invalid file offset after failed read")); + if (0 <= skip_via_lseek (input_file, STDIN_FILENO, diff, SEEK_CUR)) + return true; + if (errno == 0) + error (0, 0, _("cannot work around kernel bug after all")); + } + } + + error (0, errno, _("%s: cannot seek"), quote (input_file)); + return false; +} + +/* Copy NREAD bytes of BUF, with no conversions. */ + +static void +copy_simple (char const *buf, size_t nread) +{ + const char *start = buf; /* First uncopied char in BUF. */ + + do + { + size_t nfree = MIN (nread, output_blocksize - oc); + + memcpy (obuf + oc, start, nfree); + + nread -= nfree; /* Update the number of bytes left to copy. */ + start += nfree; + oc += nfree; + if (oc >= output_blocksize) + write_output (); + } + while (nread != 0); +} + +/* Copy NREAD bytes of BUF, doing conv=block + (pad newline-terminated records to `conversion_blocksize', + replacing the newline with trailing spaces). */ + +static void +copy_with_block (char const *buf, size_t nread) +{ + size_t i; + + for (i = nread; i; i--, buf++) + { + if (*buf == newline_character) + { + if (col < conversion_blocksize) + { + size_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++; + } + } +} + +/* Copy NREAD bytes of BUF, doing conv=unblock + (replace trailing spaces in `conversion_blocksize'-sized records + with a newline). */ + +static void +copy_with_unblock (char const *buf, size_t nread) +{ + size_t i; + char c; + static size_t pending_spaces = 0; + + for (i = 0; i < nread; i++) + { + c = buf[i]; + + if (col++ >= conversion_blocksize) + { + col = pending_spaces = 0; /* Wipe out any pending spaces. */ + i--; /* Push the char back; get it later. */ + output_char (newline_character); + } + else if (c == space_character) + pending_spaces++; + else + { + /* `c' is the character after a run of spaces that were not + at the end of the conversion buffer. Output them. */ + while (pending_spaces) + { + output_char (space_character); + --pending_spaces; + } + output_char (c); + } + } +} + +/* Set the file descriptor flags for FD that correspond to the nonzero bits + in ADD_FLAGS. The file's name is NAME. */ + +static void +set_fd_flags (int fd, int add_flags, char const *name) +{ + /* Ignore file creation flags that are no-ops on file descriptors. */ + 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)) + { + /* NEW_FLAGS contains at least one file creation flag that + requires some checking of the open file descriptor. */ + struct stat st; + if (fstat (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"), quote (name)); + } +} + +/* The main loop. */ + +static int +dd_copy (void) +{ + char *ibuf, *bufstart; /* Input buffer. */ + /* These are declared static so that even though we don't free the + buffers, valgrind will recognize that there is no "real" leak. */ + static char *real_buf; /* real buffer address before alignment */ + static char *real_obuf; + ssize_t nread; /* Bytes read in the current block. */ + + /* If nonzero, then the previously read block was partial and + PARTREAD was its size. */ + size_t partread = 0; + + int exit_status = EXIT_SUCCESS; + size_t n_bytes_read; + + /* Leave at least one extra byte at the beginning and end of `ibuf' + for conv=swab, but keep the buffer address even. But some peculiar + device drivers work only with word-aligned buffers, so leave an + extra two bytes. */ + + /* Some devices require alignment on a sector or page boundary + (e.g. character disk devices). Align the input buffer to a + page boundary to cover all bases. Note that due to the swab + algorithm, we must have at least one byte in the page before + the input buffer; thus we allocate 2 pages of slop in the + real buffer. 8k above the blocksize shouldn't bother anyone. + + The page alignment is necessary on any linux system that supports + either the SGI raw I/O patch or Steven Tweedies raw I/O patch. + It is necessary when accessing raw (i.e. character special) disk + devices on Unixware or other SVR4-derived system. */ + + real_buf = xmalloc (input_blocksize + INPUT_BLOCK_SLOP); + ibuf = real_buf; + ibuf += SWAB_ALIGN_OFFSET; /* allow space for swab */ + + ibuf = ptr_align (ibuf, page_size); + + if (conversions_mask & C_TWOBUFS) + { + /* Page-align the output buffer, too. */ + real_obuf = xmalloc (output_blocksize + OUTPUT_BLOCK_SLOP); + obuf = ptr_align (real_obuf, page_size); + } + else + { + real_obuf = NULL; + obuf = ibuf; + } + + if (skip_records != 0) + { + skip (STDIN_FILENO, input_file, skip_records, input_blocksize, ibuf); + /* POSIX doesn't say what to do when dd detects it has been + asked to skip past EOF, so I assume it's non-fatal if the + call to 'skip' returns nonzero. FIXME: maybe give a warning. */ + } + + if (seek_records != 0) + { + uintmax_t write_records = skip (STDOUT_FILENO, output_file, + seek_records, output_blocksize, obuf); + + if (write_records != 0) + { + memset (obuf, 0, output_blocksize); + + do + if (iwrite (STDOUT_FILENO, obuf, output_blocksize) + != output_blocksize) + { + error (0, errno, _("writing to %s"), quote (output_file)); + quit (EXIT_FAILURE); + } + while (--write_records != 0); + } + } + + if (max_records == 0) + return exit_status; + + while (1) + { + if (r_partial + r_full >= max_records) + break; + + /* Zero the buffer before reading, so that if we get a read error, + whatever data we are able to read is followed by zeros. + This minimizes data loss. */ + if ((conversions_mask & C_SYNC) && (conversions_mask & C_NOERROR)) + memset (ibuf, + (conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0', + input_blocksize); + + nread = iread (STDIN_FILENO, ibuf, input_blocksize); + + if (nread == 0) + break; /* EOF. */ + + if (nread < 0) + { + error (0, errno, _("reading %s"), quote (input_file)); + if (conversions_mask & C_NOERROR) + { + print_stats (); + /* Seek past the bad block if possible. */ + if (!advance_input_after_read_error (input_blocksize - partread)) + { + exit_status = EXIT_FAILURE; + + /* Suppress duplicate diagnostics. */ + input_seekable = false; + input_seek_errno = ESPIPE; + } + if ((conversions_mask & C_SYNC) && !partread) + /* Replace the missing input with null bytes and + proceed normally. */ + nread = 0; + else + continue; + } + else + { + /* Write any partial block. */ + exit_status = EXIT_FAILURE; + break; + } + } + + n_bytes_read = nread; + advance_input_offset (nread); + + if (n_bytes_read < input_blocksize) + { + r_partial++; + partread = n_bytes_read; + if (conversions_mask & C_SYNC) + { + if (!(conversions_mask & C_NOERROR)) + /* If C_NOERROR, we zeroed the block before reading. */ + 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) /* If not C_TWOBUFS. */ + { + size_t nwritten = iwrite (STDOUT_FILENO, obuf, n_bytes_read); + w_bytes += nwritten; + if (nwritten != n_bytes_read) + { + error (0, errno, _("writing %s"), quote (output_file)); + return EXIT_FAILURE; + } + else if (n_bytes_read == input_blocksize) + w_full++; + else + w_partial++; + continue; + } + + /* Do any translations on the whole buffer at once. */ + + if (translation_needed) + translate_buffer (ibuf, n_bytes_read); + + if (conversions_mask & C_SWAB) + bufstart = swab_buffer (ibuf, &n_bytes_read); + 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 we have a char left as a result of conv=swab, output it. */ + if (char_is_saved) + { + 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) + { + /* If the final input line didn't end with a '\n', pad + the output block to `conversion_blocksize' chars. */ + size_t i; + for (i = col; i < conversion_blocksize; i++) + output_char (space_character); + } + + if ((conversions_mask & C_UNBLOCK) && col == conversion_blocksize) + /* Add a final '\n' if there are exactly `conversion_blocksize' + characters in the final record. */ + output_char (newline_character); + + /* Write out the last block. */ + if (oc != 0) + { + size_t nwritten = iwrite (STDOUT_FILENO, obuf, oc); + w_bytes += nwritten; + if (nwritten != 0) + w_partial++; + if (nwritten != oc) + { + error (0, errno, _("writing %s"), quote (output_file)); + return EXIT_FAILURE; + } + } + + if ((conversions_mask & C_FDATASYNC) && fdatasync (STDOUT_FILENO) != 0) + { + if (errno != ENOSYS && errno != EINVAL) + { + error (0, errno, _("fdatasync failed for %s"), quote (output_file)); + exit_status = EXIT_FAILURE; + } + conversions_mask |= C_FSYNC; + } + + if (conversions_mask & C_FSYNC) + while (fsync (STDOUT_FILENO) != 0) + if (errno != EINTR) + { + error (0, errno, _("fsync failed for %s"), quote (output_file)); + return EXIT_FAILURE; + } + + return exit_status; +} + +int +main (int argc, char **argv) +{ + int i; + int exit_status; + off_t offset; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Arrange to close stdout if parse_long_options exits. */ + atexit (close_stdout); + + page_size = getpagesize (); + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + /* Initialize translation table to identity translation. */ + for (i = 0; i < 256; i++) + trans_table[i] = i; + + /* Decode arguments. */ + scanargs (argc, argv); + + apply_translations (); + + if (input_file == NULL) + { + input_file = _("standard input"); + set_fd_flags (STDIN_FILENO, input_flags, input_file); + } + else + { + if (fd_reopen (STDIN_FILENO, input_file, O_RDONLY | input_flags, 0) < 0) + error (EXIT_FAILURE, errno, _("opening %s"), quote (input_file)); + } + + offset = lseek (STDIN_FILENO, 0, SEEK_CUR); + input_seekable = (0 <= offset); + input_offset = offset; + input_seek_errno = errno; + + if (output_file == NULL) + { + output_file = _("standard output"); + set_fd_flags (STDOUT_FILENO, output_flags, output_file); + } + else + { + mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + 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)); + + /* Open the output file with *read* access only if we might + need to read to satisfy a `seek=' request. If we can't read + the file, go ahead with write-only access; it might work. */ + if ((! seek_records + || fd_reopen (STDOUT_FILENO, output_file, O_RDWR | opts, perms) < 0) + && (fd_reopen (STDOUT_FILENO, output_file, O_WRONLY | opts, perms) + < 0)) + error (EXIT_FAILURE, errno, _("opening %s"), quote (output_file)); + +#if HAVE_FTRUNCATE + if (seek_records != 0 && !(conversions_mask & C_NOTRUNC)) + { + uintmax_t size = seek_records * output_blocksize; + unsigned long int obs = output_blocksize; + + if (OFF_T_MAX / output_blocksize < seek_records) + error (EXIT_FAILURE, 0, + _("offset too large: " + "cannot truncate to a length of seek=%"PRIuMAX"" + " (%lu-byte) blocks"), + seek_records, obs); + + if (ftruncate (STDOUT_FILENO, size) != 0) + { + /* Complain only when ftruncate fails on a regular file, a + directory, or a shared memory object, as POSIX 1003.1-2004 + specifies ftruncate's behavior only for these file types. + For example, do not complain when Linux 2.4 ftruncate + fails on /dev/fd0. */ + int ftruncate_errno = errno; + struct stat stdout_stat; + if (fstat (STDOUT_FILENO, &stdout_stat) != 0) + error (EXIT_FAILURE, errno, _("cannot fstat %s"), + quote (output_file)); + if (S_ISREG (stdout_stat.st_mode) + || S_ISDIR (stdout_stat.st_mode) + || S_TYPEISSHM (&stdout_stat)) + error (EXIT_FAILURE, ftruncate_errno, + _("truncating at %"PRIuMAX" bytes in output file %s"), + size, quote (output_file)); + } + } +#endif + } + + install_signal_handlers (); + + start_time = gethrxtime (); + + exit_status = dd_copy (); + + quit (exit_status); +} |