freebsd-dev/bin/dd/dd.c
Thomas Quinot aa7a161b40 (dd_out): Fix handling of all-zeroes block at end of input with
conv=sparse.

This change fixes two separate issues observed when the last output
block is all zeroes, and conv=sparse is in use. In this case, care
must be taken to roll back the last seek and write the entire last zero
block at the original offset where it should have occurred: when the
destination file is a block device, it is not possible to roll back
by just one character as the write would then not be properly aligned.

Furthermore, the buffer used to write this last all-zeroes block
needs to be properly zeroed-out. This was not the case previously,
resulting in a junk data byte appearing instead of a zero in the
output stream.

PR:		bin/189174
PR:		bin/189284
Reviewed by:	kib
MFC after:	2 weeks
2014-05-07 19:33:29 +00:00

527 lines
13 KiB
C

/*-
* Copyright (c) 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Keith Muller of the University of California, San Diego and Lance
* Visser of Convex Computer Corporation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#if 0
#ifndef lint
static char const copyright[] =
"@(#) Copyright (c) 1991, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)dd.c 8.5 (Berkeley) 4/2/94";
#endif /* not lint */
#endif
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/disklabel.h>
#include <sys/filio.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include "dd.h"
#include "extern.h"
static void dd_close(void);
static void dd_in(void);
static void getfdtype(IO *);
static void setup(void);
IO in, out; /* input/output state */
STAT st; /* statistics */
void (*cfunc)(void); /* conversion function */
uintmax_t cpy_cnt; /* # of blocks to copy */
static off_t pending = 0; /* pending seek if sparse */
static off_t last_sp = 0; /* size of last added sparse block */
u_int ddflags = 0; /* conversion options */
size_t cbsz; /* conversion block size */
uintmax_t files_cnt = 1; /* # of files to copy */
const u_char *ctab; /* conversion table */
char fill_char; /* Character to fill with if defined */
volatile sig_atomic_t need_summary;
int
main(int argc __unused, char *argv[])
{
(void)setlocale(LC_CTYPE, "");
jcl(argv);
setup();
(void)signal(SIGINFO, siginfo_handler);
(void)signal(SIGINT, terminate);
atexit(summary);
while (files_cnt--)
dd_in();
dd_close();
/*
* Some devices such as cfi(4) may perform significant amounts
* of work when a write descriptor is closed. Close the out
* descriptor explicitly so that the summary handler (called
* from an atexit() hook) includes this work.
*/
close(out.fd);
exit(0);
}
static int
parity(u_char c)
{
int i;
i = c ^ (c >> 1) ^ (c >> 2) ^ (c >> 3) ^
(c >> 4) ^ (c >> 5) ^ (c >> 6) ^ (c >> 7);
return (i & 1);
}
static void
setup(void)
{
u_int cnt;
struct timespec tv;
if (in.name == NULL) {
in.name = "stdin";
in.fd = STDIN_FILENO;
} else {
in.fd = open(in.name, O_RDONLY, 0);
if (in.fd == -1)
err(1, "%s", in.name);
}
getfdtype(&in);
if (files_cnt > 1 && !(in.flags & ISTAPE))
errx(1, "files is not supported for non-tape devices");
if (out.name == NULL) {
/* No way to check for read access here. */
out.fd = STDOUT_FILENO;
out.name = "stdout";
} else {
#define OFLAGS \
(O_CREAT | (ddflags & (C_SEEK | C_NOTRUNC) ? 0 : O_TRUNC))
out.fd = open(out.name, O_RDWR | OFLAGS, DEFFILEMODE);
/*
* May not have read access, so try again with write only.
* Without read we may have a problem if output also does
* not support seeks.
*/
if (out.fd == -1) {
out.fd = open(out.name, O_WRONLY | OFLAGS, DEFFILEMODE);
out.flags |= NOREAD;
}
if (out.fd == -1)
err(1, "%s", out.name);
}
getfdtype(&out);
/*
* Allocate space for the input and output buffers. If not doing
* record oriented I/O, only need a single buffer.
*/
if (!(ddflags & (C_BLOCK | C_UNBLOCK))) {
if ((in.db = malloc(out.dbsz + in.dbsz - 1)) == NULL)
err(1, "input buffer");
out.db = in.db;
} else if ((in.db = malloc(MAX(in.dbsz, cbsz) + cbsz)) == NULL ||
(out.db = malloc(out.dbsz + cbsz)) == NULL)
err(1, "output buffer");
/* dbp is the first free position in each buffer. */
in.dbp = in.db;
out.dbp = out.db;
/* Position the input/output streams. */
if (in.offset)
pos_in();
if (out.offset)
pos_out();
/*
* Truncate the output file. If it fails on a type of output file
* that it should _not_ fail on, error out.
*/
if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK) &&
out.flags & ISTRUNC)
if (ftruncate(out.fd, out.offset * out.dbsz) == -1)
err(1, "truncating %s", out.name);
if (ddflags & (C_LCASE | C_UCASE | C_ASCII | C_EBCDIC | C_PARITY)) {
if (ctab != NULL) {
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = ctab[cnt];
} else {
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = cnt;
}
if ((ddflags & C_PARITY) && !(ddflags & C_ASCII)) {
/*
* If the input is not EBCDIC, and we do parity
* processing, strip input parity.
*/
for (cnt = 200; cnt <= 0377; ++cnt)
casetab[cnt] = casetab[cnt & 0x7f];
}
if (ddflags & C_LCASE) {
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = tolower(casetab[cnt]);
} else if (ddflags & C_UCASE) {
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = toupper(casetab[cnt]);
}
if ((ddflags & C_PARITY)) {
/*
* This should strictly speaking be a no-op, but I
* wonder what funny LANG settings could get us.
*/
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = casetab[cnt] & 0x7f;
}
if ((ddflags & C_PARSET)) {
for (cnt = 0; cnt <= 0377; ++cnt)
casetab[cnt] = casetab[cnt] | 0x80;
}
if ((ddflags & C_PAREVEN)) {
for (cnt = 0; cnt <= 0377; ++cnt)
if (parity(casetab[cnt]))
casetab[cnt] = casetab[cnt] | 0x80;
}
if ((ddflags & C_PARODD)) {
for (cnt = 0; cnt <= 0377; ++cnt)
if (!parity(casetab[cnt]))
casetab[cnt] = casetab[cnt] | 0x80;
}
ctab = casetab;
}
if (clock_gettime(CLOCK_MONOTONIC_PRECISE, &tv))
err(EX_OSERR, "clock_gettime");
st.start = tv.tv_sec + tv.tv_nsec * 1.0e-9;
}
static void
getfdtype(IO *io)
{
struct stat sb;
int type;
if (fstat(io->fd, &sb) == -1)
err(1, "%s", io->name);
if (S_ISREG(sb.st_mode))
io->flags |= ISTRUNC;
if (S_ISCHR(sb.st_mode) || S_ISBLK(sb.st_mode)) {
if (ioctl(io->fd, FIODTYPE, &type) == -1) {
err(1, "%s", io->name);
} else {
if (type & D_TAPE)
io->flags |= ISTAPE;
else if (type & (D_DISK | D_MEM))
io->flags |= ISSEEK;
if (S_ISCHR(sb.st_mode) && (type & D_TAPE) == 0)
io->flags |= ISCHR;
}
return;
}
errno = 0;
if (lseek(io->fd, (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE)
io->flags |= ISPIPE;
else
io->flags |= ISSEEK;
}
static void
dd_in(void)
{
ssize_t n;
for (;;) {
switch (cpy_cnt) {
case -1: /* count=0 was specified */
return;
case 0:
break;
default:
if (st.in_full + st.in_part >= (uintmax_t)cpy_cnt)
return;
break;
}
/*
* Zero the buffer first if sync; if doing block operations,
* use spaces.
*/
if (ddflags & C_SYNC) {
if (ddflags & C_FILL)
memset(in.dbp, fill_char, in.dbsz);
else if (ddflags & (C_BLOCK | C_UNBLOCK))
memset(in.dbp, ' ', in.dbsz);
else
memset(in.dbp, 0, in.dbsz);
}
n = read(in.fd, in.dbp, in.dbsz);
if (n == 0) {
in.dbrcnt = 0;
return;
}
/* Read error. */
if (n == -1) {
/*
* If noerror not specified, die. POSIX requires that
* the warning message be followed by an I/O display.
*/
if (!(ddflags & C_NOERROR))
err(1, "%s", in.name);
warn("%s", in.name);
summary();
/*
* If it's a seekable file descriptor, seek past the
* error. If your OS doesn't do the right thing for
* raw disks this section should be modified to re-read
* in sector size chunks.
*/
if (in.flags & ISSEEK &&
lseek(in.fd, (off_t)in.dbsz, SEEK_CUR))
warn("%s", in.name);
/* If sync not specified, omit block and continue. */
if (!(ddflags & C_SYNC))
continue;
/* Read errors count as full blocks. */
in.dbcnt += in.dbrcnt = in.dbsz;
++st.in_full;
/* Handle full input blocks. */
} else if ((size_t)n == in.dbsz) {
in.dbcnt += in.dbrcnt = n;
++st.in_full;
/* Handle partial input blocks. */
} else {
/* If sync, use the entire block. */
if (ddflags & C_SYNC)
in.dbcnt += in.dbrcnt = in.dbsz;
else
in.dbcnt += in.dbrcnt = n;
++st.in_part;
}
/*
* POSIX states that if bs is set and no other conversions
* than noerror, notrunc or sync are specified, the block
* is output without buffering as it is read.
*/
if ((ddflags & ~(C_NOERROR | C_NOTRUNC | C_SYNC)) == C_BS) {
out.dbcnt = in.dbcnt;
dd_out(1);
in.dbcnt = 0;
continue;
}
if (ddflags & C_SWAB) {
if ((n = in.dbrcnt) & 1) {
++st.swab;
--n;
}
swab(in.dbp, in.dbp, (size_t)n);
}
in.dbp += in.dbrcnt;
(*cfunc)();
if (need_summary) {
summary();
}
}
}
/*
* Clean up any remaining I/O and flush output. If necessary, the output file
* is truncated.
*/
static void
dd_close(void)
{
if (cfunc == def)
def_close();
else if (cfunc == block)
block_close();
else if (cfunc == unblock)
unblock_close();
if (ddflags & C_OSYNC && out.dbcnt && out.dbcnt < out.dbsz) {
if (ddflags & C_FILL)
memset(out.dbp, fill_char, out.dbsz - out.dbcnt);
else if (ddflags & (C_BLOCK | C_UNBLOCK))
memset(out.dbp, ' ', out.dbsz - out.dbcnt);
else
memset(out.dbp, 0, out.dbsz - out.dbcnt);
out.dbcnt = out.dbsz;
}
if (out.dbcnt || pending)
dd_out(1);
}
void
dd_out(int force)
{
u_char *outp;
size_t cnt, i, n;
ssize_t nw;
static int warned;
int sparse;
/*
* Write one or more blocks out. The common case is writing a full
* output block in a single write; increment the full block stats.
* Otherwise, we're into partial block writes. If a partial write,
* and it's a character device, just warn. If a tape device, quit.
*
* The partial writes represent two cases. 1: Where the input block
* was less than expected so the output block was less than expected.
* 2: Where the input block was the right size but we were forced to
* write the block in multiple chunks. The original versions of dd(1)
* never wrote a block in more than a single write, so the latter case
* never happened.
*
* One special case is if we're forced to do the write -- in that case
* we play games with the buffer size, and it's usually a partial write.
*/
outp = out.db;
/*
* If force, first try to write all pending data, else try to write
* just one block. Subsequently always write data one full block at
* a time at most.
*/
for (n = force ? out.dbcnt : out.dbsz;; n = out.dbsz) {
cnt = n;
do {
sparse = 0;
if (ddflags & C_SPARSE) {
sparse = 1; /* Is buffer sparse? */
for (i = 0; i < cnt; i++)
if (outp[i] != 0) {
sparse = 0;
break;
}
}
if (sparse && !force) {
pending += cnt;
last_sp = cnt;
nw = cnt;
} else {
if (pending != 0) {
/* If forced to write, and we have no
* data left, we need to write the last
* sparse block explicitly.
*/
if (force && cnt == 0) {
pending -= last_sp;
assert(outp == out.db);
memset(outp, 0, cnt);
}
if (lseek(out.fd, pending, SEEK_CUR) ==
-1)
err(2, "%s: seek error creating sparse file",
out.name);
pending = last_sp = 0;
}
if (cnt)
nw = write(out.fd, outp, cnt);
else
return;
}
if (nw <= 0) {
if (nw == 0)
errx(1, "%s: end of device", out.name);
if (errno != EINTR)
err(1, "%s", out.name);
nw = 0;
}
outp += nw;
st.bytes += nw;
if ((size_t)nw == n && n == out.dbsz)
++st.out_full;
else
++st.out_part;
if ((size_t) nw != cnt) {
if (out.flags & ISTAPE)
errx(1, "%s: short write on tape device",
out.name);
if (out.flags & ISCHR && !warned) {
warned = 1;
warnx("%s: short write on character device",
out.name);
}
}
cnt -= nw;
} while (cnt != 0);
if ((out.dbcnt -= n) < out.dbsz)
break;
}
/* Reassemble the output block. */
if (out.dbcnt)
(void)memmove(out.db, out.dbp - out.dbcnt, out.dbcnt);
out.dbp = out.db + out.dbcnt;
}