platform_system_core/toolbox/dd.c
Jongrak Kwon c05aae4ac4 use lseek64 to resolve offset oveflow
The offset variable in lseek is 32 bit and get easily overflow
when accessing with large offset in dd command.
Use lseek64 to resolve it.

Change-Id: Ib75d9dcb587004a6851365ab5bb8584ce1010b57
2013-04-17 14:05:55 -07:00

1316 lines
30 KiB
C

/* $NetBSD: dd.c,v 1.37 2004/01/17 21:00:16 dbj Exp $ */
/*-
* 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.
* 3. 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.
*/
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n");
#endif /* not lint */
#ifndef lint
#if 0
static char sccsid[] = "@(#)dd.c 8.5 (Berkeley) 4/2/94";
#else
__RCSID("$NetBSD: dd.c,v 1.37 2004/01/17 21:00:16 dbj Exp $");
#endif
#endif /* not lint */
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "dd.h"
//#define NO_CONV
//#include "extern.h"
void block(void);
void block_close(void);
void dd_out(int);
void def(void);
void def_close(void);
void jcl(char **);
void pos_in(void);
void pos_out(void);
void summary(void);
void summaryx(int);
void terminate(int);
void unblock(void);
void unblock_close(void);
ssize_t bwrite(int, const void *, size_t);
extern IO in, out;
extern STAT st;
extern void (*cfunc)(void);
extern uint64_t cpy_cnt;
extern uint64_t cbsz;
extern u_int ddflags;
extern u_int files_cnt;
extern int progress;
extern const u_char *ctab;
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define DEFFILEMODE (S_IRUSR | S_IWUSR)
static void dd_close(void);
static void dd_in(void);
static void getfdtype(IO *);
static int redup_clean_fd(int);
static void setup(void);
IO in, out; /* input/output state */
STAT st; /* statistics */
void (*cfunc)(void); /* conversion function */
uint64_t cpy_cnt; /* # of blocks to copy */
static off_t pending = 0; /* pending seek if sparse */
u_int ddflags; /* conversion options */
uint64_t cbsz; /* conversion block size */
u_int files_cnt = 1; /* # of files to copy */
int progress = 0; /* display sign of life */
const u_char *ctab; /* conversion table */
sigset_t infoset; /* a set blocking SIGINFO */
int
dd_main(int argc, char *argv[])
{
int ch;
while ((ch = getopt(argc, argv, "")) != -1) {
switch (ch) {
default:
fprintf(stderr, "usage: dd [operand ...]\n");
exit(1);
/* NOTREACHED */
}
}
argc -= (optind - 1);
argv += (optind - 1);
jcl(argv);
setup();
// (void)signal(SIGINFO, summaryx);
(void)signal(SIGINT, terminate);
(void)sigemptyset(&infoset);
// (void)sigaddset(&infoset, SIGINFO);
(void)atexit(summary);
while (files_cnt--)
dd_in();
dd_close();
exit(0);
/* NOTREACHED */
}
static void
setup(void)
{
if (in.name == NULL) {
in.name = "stdin";
in.fd = STDIN_FILENO;
} else {
in.fd = open(in.name, O_RDONLY, 0);
if (in.fd < 0) {
fprintf(stderr, "%s: cannot open for read: %s\n",
in.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
/* Ensure in.fd is outside the stdio descriptor range */
in.fd = redup_clean_fd(in.fd);
}
getfdtype(&in);
if (files_cnt > 1 && !(in.flags & ISTAPE)) {
fprintf(stderr,
"files is not supported for non-tape devices\n");
exit(1);
/* NOTREACHED */
}
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 < 0) {
out.fd = open(out.name, O_WRONLY | OFLAGS, DEFFILEMODE);
out.flags |= NOREAD;
}
if (out.fd < 0) {
fprintf(stderr, "%s: cannot open for write: %s\n",
out.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
/* Ensure out.fd is outside the stdio descriptor range */
out.fd = redup_clean_fd(out.fd);
}
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) {
exit(1);
/* NOTREACHED */
}
out.db = in.db;
} else if ((in.db =
malloc((u_int)(MAX(in.dbsz, cbsz) + cbsz))) == NULL ||
(out.db = malloc((u_int)(out.dbsz + cbsz))) == NULL) {
exit(1);
/* NOTREACHED */
}
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; ignore errors because it fails on some
* kinds of output files, tapes, for example.
*/
if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK))
(void)ftruncate(out.fd, (off_t)out.offset * out.dbsz);
(void)gettimeofday(&st.start, NULL); /* Statistics timestamp. */
}
static void
getfdtype(IO *io)
{
// struct mtget mt;
struct stat sb;
if (fstat(io->fd, &sb)) {
fprintf(stderr, "%s: cannot fstat: %s\n",
io->name, strerror(errno));
exit(1);
/* NOTREACHED */
}
if (S_ISCHR(sb.st_mode))
io->flags |= /*ioctl(io->fd, MTIOCGET, &mt) ? ISCHR : ISTAPE; */ ISCHR;
else if (lseek(io->fd, (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE)
io->flags |= ISPIPE; /* XXX fixed in 4.4BSD */
}
/*
* Move the parameter file descriptor to a descriptor that is outside the
* stdio descriptor range, if necessary. This is required to avoid
* accidentally outputting completion or error messages into the
* output file that were intended for the tty.
*/
static int
redup_clean_fd(int fd)
{
int newfd;
if (fd != STDIN_FILENO && fd != STDOUT_FILENO &&
fd != STDERR_FILENO)
/* File descriptor is ok, return immediately. */
return fd;
/*
* 3 is the first descriptor greater than STD*_FILENO. Any
* free descriptor valued 3 or above is acceptable...
*/
newfd = fcntl(fd, F_DUPFD, 3);
if (newfd < 0) {
fprintf(stderr, "dupfd IO: %s\n", strerror(errno));
exit(1);
/* NOTREACHED */
}
close(fd);
return newfd;
}
static void
dd_in(void)
{
int flags;
int64_t n;
for (flags = ddflags;;) {
if (cpy_cnt && (st.in_full + st.in_part) >= cpy_cnt)
return;
/*
* Clear the buffer first if doing "sync" on input.
* If doing block operations use spaces. This will
* affect not only the C_NOERROR case, but also the
* last partial input block which should be padded
* with zero and not garbage.
*/
if (flags & C_SYNC) {
if (flags & (C_BLOCK|C_UNBLOCK))
(void)memset(in.dbp, ' ', in.dbsz);
else
(void)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 < 0) {
/*
* If noerror not specified, die. POSIX requires that
* the warning message be followed by an I/O display.
*/
fprintf(stderr, "%s: read error: %s\n",
in.name, strerror(errno));
if (!(flags & C_NOERROR)) {
exit(1);
/* NOTREACHED */
}
summary();
/*
* If it's not a tape drive or a pipe, 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 & (ISPIPE|ISTAPE)) &&
lseek(in.fd, (off_t)in.dbsz, SEEK_CUR))
fprintf(stderr, "%s: seek error: %s\n",
in.name, strerror(errno));
/* 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 (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_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, n);
}
*/
in.dbp += in.dbrcnt;
(*cfunc)();
}
}
/*
* Cleanup any remaining I/O and flush output. If necesssary, 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.dbsz) {
(void)memset(out.dbp, 0, out.dbsz - out.dbcnt);
out.dbcnt = out.dbsz;
}
/* If there are pending sparse blocks, make sure
* to write out the final block un-sparse
*/
if ((out.dbcnt == 0) && pending) {
memset(out.db, 0, out.dbsz);
out.dbcnt = out.dbsz;
out.dbp = out.db + out.dbcnt;
pending -= out.dbsz;
}
if (out.dbcnt)
dd_out(1);
/*
* Reporting nfs write error may be defered until next
* write(2) or close(2) system call. So, we need to do an
* extra check. If an output is stdout, the file structure
* may be shared among with other processes and close(2) just
* decreases the reference count.
*/
if (out.fd == STDOUT_FILENO && fsync(out.fd) == -1 && errno != EINVAL) {
fprintf(stderr, "fsync stdout: %s\n", strerror(errno));
exit(1);
/* NOTREACHED */
}
if (close(out.fd) == -1) {
fprintf(stderr, "close: %s\n", strerror(errno));
exit(1);
/* NOTREACHED */
}
}
void
dd_out(int force)
{
static int warned;
int64_t cnt, n, nw;
u_char *outp;
/*
* 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;
for (n = force ? out.dbcnt : out.dbsz;; n = out.dbsz) {
for (cnt = n;; cnt -= nw) {
if (!force && ddflags & C_SPARSE) {
int sparse, i;
sparse = 1; /* Is buffer sparse? */
for (i = 0; i < cnt; i++)
if (outp[i] != 0) {
sparse = 0;
break;
}
if (sparse) {
pending += cnt;
outp += cnt;
nw = 0;
break;
}
}
if (pending != 0) {
if (lseek(out.fd, pending, SEEK_CUR) ==
-1) {
fprintf(stderr,
"%s: seek error creating "
"sparse file: %s\n",
out.name, strerror(errno));
exit(1);
}
}
nw = bwrite(out.fd, outp, cnt);
if (nw <= 0) {
if (nw == 0) {
fprintf(stderr, "%s: end of device\n",
out.name);
exit(1);
/* NOTREACHED */
}
if (errno != EINTR) {
fprintf(stderr, "%s: write error: %s\n",
out.name, strerror(errno));
/* NOTREACHED */
exit(1);
}
nw = 0;
}
if (pending) {
st.bytes += pending;
st.sparse += pending/out.dbsz;
st.out_full += pending/out.dbsz;
pending = 0;
}
outp += nw;
st.bytes += nw;
if (nw == n) {
if (n != out.dbsz)
++st.out_part;
else
++st.out_full;
break;
}
++st.out_part;
if (nw == cnt)
break;
if (out.flags & ISCHR && !warned) {
warned = 1;
fprintf(stderr, "%s: short write on character "
"device\n", out.name);
}
if (out.flags & ISTAPE) {
fprintf(stderr,
"%s: short write on tape device",
out.name);
exit(1);
/* NOTREACHED */
}
}
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;
if (progress)
(void)write(STDERR_FILENO, ".", 1);
}
/*
* A protected against SIGINFO write
*/
ssize_t
bwrite(int fd, const void *buf, size_t len)
{
sigset_t oset;
ssize_t rv;
int oerrno;
(void)sigprocmask(SIG_BLOCK, &infoset, &oset);
rv = write(fd, buf, len);
oerrno = errno;
(void)sigprocmask(SIG_SETMASK, &oset, NULL);
errno = oerrno;
return (rv);
}
/*
* Position input/output data streams before starting the copy. Device type
* dependent. Seekable devices use lseek, and the rest position by reading.
* Seeking past the end of file can cause null blocks to be written to the
* output.
*/
void
pos_in(void)
{
int bcnt, cnt, nr, warned;
/* If not a pipe or tape device, try to seek on it. */
if (!(in.flags & (ISPIPE|ISTAPE))) {
if (lseek64(in.fd,
(off64_t)in.offset * (off64_t)in.dbsz, SEEK_CUR) == -1) {
fprintf(stderr, "%s: seek error: %s",
in.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
return;
/* NOTREACHED */
}
/*
* Read the data. If a pipe, read until satisfy the number of bytes
* being skipped. No differentiation for reading complete and partial
* blocks for other devices.
*/
for (bcnt = in.dbsz, cnt = in.offset, warned = 0; cnt;) {
if ((nr = read(in.fd, in.db, bcnt)) > 0) {
if (in.flags & ISPIPE) {
if (!(bcnt -= nr)) {
bcnt = in.dbsz;
--cnt;
}
} else
--cnt;
continue;
}
if (nr == 0) {
if (files_cnt > 1) {
--files_cnt;
continue;
}
fprintf(stderr, "skip reached end of input\n");
exit(1);
/* NOTREACHED */
}
/*
* Input error -- either EOF with no more files, or I/O error.
* If noerror not set die. POSIX requires that the warning
* message be followed by an I/O display.
*/
if (ddflags & C_NOERROR) {
if (!warned) {
fprintf(stderr, "%s: error occurred\n",
in.name);
warned = 1;
summary();
}
continue;
}
fprintf(stderr, "%s: read error: %s", in.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
}
void
pos_out(void)
{
// struct mtop t_op;
int cnt, n;
/*
* If not a tape, try seeking on the file. Seeking on a pipe is
* going to fail, but don't protect the user -- they shouldn't
* have specified the seek operand.
*/
if (!(out.flags & ISTAPE)) {
if (lseek64(out.fd,
(off64_t)out.offset * (off64_t)out.dbsz, SEEK_SET) == -1) {
fprintf(stderr, "%s: seek error: %s\n",
out.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
return;
}
/* If no read access, try using mtio. */
if (out.flags & NOREAD) {
/* t_op.mt_op = MTFSR;
t_op.mt_count = out.offset;
if (ioctl(out.fd, MTIOCTOP, &t_op) < 0)*/
fprintf(stderr, "%s: cannot read", out.name);
exit(1);
/* NOTREACHED */
return;
}
/* Read it. */
for (cnt = 0; cnt < out.offset; ++cnt) {
if ((n = read(out.fd, out.db, out.dbsz)) > 0)
continue;
if (n < 0) {
fprintf(stderr, "%s: cannot position by reading: %s\n",
out.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
/*
* If reach EOF, fill with NUL characters; first, back up over
* the EOF mark. Note, cnt has not yet been incremented, so
* the EOF read does not count as a seek'd block.
*/
/* t_op.mt_op = MTBSR;
t_op.mt_count = 1;
if (ioctl(out.fd, MTIOCTOP, &t_op) == -1) */ {
fprintf(stderr, "%s: cannot position\n", out.name);
exit(1);
/* NOTREACHED */
}
while (cnt++ < out.offset)
if ((n = bwrite(out.fd, out.db, out.dbsz)) != out.dbsz) {
fprintf(stderr, "%s: cannot position "
"by writing: %s\n",
out.name, strerror(errno));
exit(1);
/* NOTREACHED */
}
break;
}
}
/*
* def --
* Copy input to output. Input is buffered until reaches obs, and then
* output until less than obs remains. Only a single buffer is used.
* Worst case buffer calculation is (ibs + obs - 1).
*/
void
def(void)
{
uint64_t cnt;
u_char *inp;
const u_char *t;
if ((t = ctab) != NULL)
for (inp = in.dbp - (cnt = in.dbrcnt); cnt--; ++inp)
*inp = t[*inp];
/* Make the output buffer look right. */
out.dbp = in.dbp;
out.dbcnt = in.dbcnt;
if (in.dbcnt >= out.dbsz) {
/* If the output buffer is full, write it. */
dd_out(0);
/*
* Ddout copies the leftover output to the beginning of
* the buffer and resets the output buffer. Reset the
* input buffer to match it.
*/
in.dbp = out.dbp;
in.dbcnt = out.dbcnt;
}
}
void
def_close(void)
{
if (ddflags & C_FDATASYNC) {
fdatasync(out.fd);
}
/* Just update the count, everything is already in the buffer. */
if (in.dbcnt)
out.dbcnt = in.dbcnt;
}
#ifdef NO_CONV
/* Build a smaller version (i.e. for a miniroot) */
/* These can not be called, but just in case... */
static const char no_block[] = "unblock and -DNO_CONV?\n";
void block(void) { fprintf(stderr, "%s", no_block + 2); exit(1); }
void block_close(void) { fprintf(stderr, "%s", no_block + 2); exit(1); }
void unblock(void) { fprintf(stderr, "%s", no_block); exit(1); }
void unblock_close(void) { fprintf(stderr, "%s", no_block); exit(1); }
#else /* NO_CONV */
/*
* Copy variable length newline terminated records with a max size cbsz
* bytes to output. Records less than cbs are padded with spaces.
*
* max in buffer: MAX(ibs, cbsz)
* max out buffer: obs + cbsz
*/
void
block(void)
{
static int intrunc;
int ch = 0; /* pacify gcc */
uint64_t cnt, maxlen;
u_char *inp, *outp;
const u_char *t;
/*
* Record truncation can cross block boundaries. If currently in a
* truncation state, keep tossing characters until reach a newline.
* Start at the beginning of the buffer, as the input buffer is always
* left empty.
*/
if (intrunc) {
for (inp = in.db, cnt = in.dbrcnt;
cnt && *inp++ != '\n'; --cnt);
if (!cnt) {
in.dbcnt = 0;
in.dbp = in.db;
return;
}
intrunc = 0;
/* Adjust the input buffer numbers. */
in.dbcnt = cnt - 1;
in.dbp = inp + cnt - 1;
}
/*
* Copy records (max cbsz size chunks) into the output buffer. The
* translation is done as we copy into the output buffer.
*/
for (inp = in.dbp - in.dbcnt, outp = out.dbp; in.dbcnt;) {
maxlen = MIN(cbsz, in.dbcnt);
if ((t = ctab) != NULL)
for (cnt = 0;
cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
*outp++ = t[ch];
else
for (cnt = 0;
cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
*outp++ = ch;
/*
* Check for short record without a newline. Reassemble the
* input block.
*/
if (ch != '\n' && in.dbcnt < cbsz) {
(void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
break;
}
/* Adjust the input buffer numbers. */
in.dbcnt -= cnt;
if (ch == '\n')
--in.dbcnt;
/* Pad short records with spaces. */
if (cnt < cbsz)
(void)memset(outp, ctab ? ctab[' '] : ' ', cbsz - cnt);
else {
/*
* If the next character wouldn't have ended the
* block, it's a truncation.
*/
if (!in.dbcnt || *inp != '\n')
++st.trunc;
/* Toss characters to a newline. */
for (; in.dbcnt && *inp++ != '\n'; --in.dbcnt);
if (!in.dbcnt)
intrunc = 1;
else
--in.dbcnt;
}
/* Adjust output buffer numbers. */
out.dbp += cbsz;
if ((out.dbcnt += cbsz) >= out.dbsz)
dd_out(0);
outp = out.dbp;
}
in.dbp = in.db + in.dbcnt;
}
void
block_close(void)
{
/*
* Copy any remaining data into the output buffer and pad to a record.
* Don't worry about truncation or translation, the input buffer is
* always empty when truncating, and no characters have been added for
* translation. The bottom line is that anything left in the input
* buffer is a truncated record. Anything left in the output buffer
* just wasn't big enough.
*/
if (in.dbcnt) {
++st.trunc;
(void)memmove(out.dbp, in.dbp - in.dbcnt, in.dbcnt);
(void)memset(out.dbp + in.dbcnt,
ctab ? ctab[' '] : ' ', cbsz - in.dbcnt);
out.dbcnt += cbsz;
}
}
/*
* Convert fixed length (cbsz) records to variable length. Deletes any
* trailing blanks and appends a newline.
*
* max in buffer: MAX(ibs, cbsz) + cbsz
* max out buffer: obs + cbsz
*/
void
unblock(void)
{
uint64_t cnt;
u_char *inp;
const u_char *t;
/* Translation and case conversion. */
if ((t = ctab) != NULL)
for (cnt = in.dbrcnt, inp = in.dbp - 1; cnt--; inp--)
*inp = t[*inp];
/*
* Copy records (max cbsz size chunks) into the output buffer. The
* translation has to already be done or we might not recognize the
* spaces.
*/
for (inp = in.db; in.dbcnt >= cbsz; inp += cbsz, in.dbcnt -= cbsz) {
for (t = inp + cbsz - 1; t >= inp && *t == ' '; --t);
if (t >= inp) {
cnt = t - inp + 1;
(void)memmove(out.dbp, inp, cnt);
out.dbp += cnt;
out.dbcnt += cnt;
}
++out.dbcnt;
*out.dbp++ = '\n';
if (out.dbcnt >= out.dbsz)
dd_out(0);
}
if (in.dbcnt)
(void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
in.dbp = in.db + in.dbcnt;
}
void
unblock_close(void)
{
uint64_t cnt;
u_char *t;
if (in.dbcnt) {
warnx("%s: short input record", in.name);
for (t = in.db + in.dbcnt - 1; t >= in.db && *t == ' '; --t);
if (t >= in.db) {
cnt = t - in.db + 1;
(void)memmove(out.dbp, in.db, cnt);
out.dbp += cnt;
out.dbcnt += cnt;
}
++out.dbcnt;
*out.dbp++ = '\n';
}
}
#endif /* NO_CONV */
#define tv2mS(tv) ((tv).tv_sec * 1000LL + ((tv).tv_usec + 500) / 1000)
void
summary(void)
{
char buf[100];
int64_t mS;
struct timeval tv;
if (progress)
(void)write(STDERR_FILENO, "\n", 1);
(void)gettimeofday(&tv, NULL);
mS = tv2mS(tv) - tv2mS(st.start);
if (mS == 0)
mS = 1;
/* Use snprintf(3) so that we don't reenter stdio(3). */
(void)snprintf(buf, sizeof(buf),
"%llu+%llu records in\n%llu+%llu records out\n",
(unsigned long long)st.in_full, (unsigned long long)st.in_part,
(unsigned long long)st.out_full, (unsigned long long)st.out_part);
(void)write(STDERR_FILENO, buf, strlen(buf));
if (st.swab) {
(void)snprintf(buf, sizeof(buf), "%llu odd length swab %s\n",
(unsigned long long)st.swab,
(st.swab == 1) ? "block" : "blocks");
(void)write(STDERR_FILENO, buf, strlen(buf));
}
if (st.trunc) {
(void)snprintf(buf, sizeof(buf), "%llu truncated %s\n",
(unsigned long long)st.trunc,
(st.trunc == 1) ? "block" : "blocks");
(void)write(STDERR_FILENO, buf, strlen(buf));
}
if (st.sparse) {
(void)snprintf(buf, sizeof(buf), "%llu sparse output %s\n",
(unsigned long long)st.sparse,
(st.sparse == 1) ? "block" : "blocks");
(void)write(STDERR_FILENO, buf, strlen(buf));
}
(void)snprintf(buf, sizeof(buf),
"%llu bytes transferred in %lu.%03d secs (%llu bytes/sec)\n",
(unsigned long long) st.bytes,
(long) (mS / 1000),
(int) (mS % 1000),
(unsigned long long) (st.bytes * 1000LL / mS));
(void)write(STDERR_FILENO, buf, strlen(buf));
}
void
terminate(int notused)
{
exit(0);
/* NOTREACHED */
}
static int c_arg(const void *, const void *);
#ifndef NO_CONV
static int c_conv(const void *, const void *);
#endif
static void f_bs(char *);
static void f_cbs(char *);
static void f_conv(char *);
static void f_count(char *);
static void f_files(char *);
static void f_ibs(char *);
static void f_if(char *);
static void f_obs(char *);
static void f_of(char *);
static void f_seek(char *);
static void f_skip(char *);
static void f_progress(char *);
static const struct arg {
const char *name;
void (*f)(char *);
u_int set, noset;
} args[] = {
/* the array needs to be sorted by the first column so
bsearch() can be used to find commands quickly */
{ "bs", f_bs, C_BS, C_BS|C_IBS|C_OBS|C_OSYNC },
{ "cbs", f_cbs, C_CBS, C_CBS },
{ "conv", f_conv, 0, 0 },
{ "count", f_count, C_COUNT, C_COUNT },
{ "files", f_files, C_FILES, C_FILES },
{ "ibs", f_ibs, C_IBS, C_BS|C_IBS },
{ "if", f_if, C_IF, C_IF },
{ "obs", f_obs, C_OBS, C_BS|C_OBS },
{ "of", f_of, C_OF, C_OF },
{ "progress", f_progress, 0, 0 },
{ "seek", f_seek, C_SEEK, C_SEEK },
{ "skip", f_skip, C_SKIP, C_SKIP },
};
/*
* args -- parse JCL syntax of dd.
*/
void
jcl(char **argv)
{
struct arg *ap, tmp;
char *oper, *arg;
in.dbsz = out.dbsz = 512;
while ((oper = *++argv) != NULL) {
if ((arg = strchr(oper, '=')) == NULL) {
fprintf(stderr, "unknown operand %s\n", oper);
exit(1);
/* NOTREACHED */
}
*arg++ = '\0';
if (!*arg) {
fprintf(stderr, "no value specified for %s\n", oper);
exit(1);
/* NOTREACHED */
}
tmp.name = oper;
if (!(ap = (struct arg *)bsearch(&tmp, args,
sizeof(args)/sizeof(struct arg), sizeof(struct arg),
c_arg))) {
fprintf(stderr, "unknown operand %s\n", tmp.name);
exit(1);
/* NOTREACHED */
}
if (ddflags & ap->noset) {
fprintf(stderr,
"%s: illegal argument combination or already set\n",
tmp.name);
exit(1);
/* NOTREACHED */
}
ddflags |= ap->set;
ap->f(arg);
}
/* Final sanity checks. */
if (ddflags & C_BS) {
/*
* Bs is turned off by any conversion -- we assume the user
* just wanted to set both the input and output block sizes
* and didn't want the bs semantics, so we don't warn.
*/
if (ddflags & (C_BLOCK | C_LCASE | C_SWAB | C_UCASE |
C_UNBLOCK | C_OSYNC | C_ASCII | C_EBCDIC | C_SPARSE)) {
ddflags &= ~C_BS;
ddflags |= C_IBS|C_OBS;
}
/* Bs supersedes ibs and obs. */
if (ddflags & C_BS && ddflags & (C_IBS|C_OBS))
fprintf(stderr, "bs supersedes ibs and obs\n");
}
/*
* Ascii/ebcdic and cbs implies block/unblock.
* Block/unblock requires cbs and vice-versa.
*/
if (ddflags & (C_BLOCK|C_UNBLOCK)) {
if (!(ddflags & C_CBS)) {
fprintf(stderr, "record operations require cbs\n");
exit(1);
/* NOTREACHED */
}
cfunc = ddflags & C_BLOCK ? block : unblock;
} else if (ddflags & C_CBS) {
if (ddflags & (C_ASCII|C_EBCDIC)) {
if (ddflags & C_ASCII) {
ddflags |= C_UNBLOCK;
cfunc = unblock;
} else {
ddflags |= C_BLOCK;
cfunc = block;
}
} else {
fprintf(stderr,
"cbs meaningless if not doing record operations\n");
exit(1);
/* NOTREACHED */
}
} else
cfunc = def;
/* Read, write and seek calls take off_t as arguments.
*
* The following check is not done because an off_t is a quad
* for current NetBSD implementations.
*
* if (in.offset > INT_MAX/in.dbsz || out.offset > INT_MAX/out.dbsz)
* errx(1, "seek offsets cannot be larger than %d", INT_MAX);
*/
}
static int
c_arg(const void *a, const void *b)
{
return (strcmp(((const struct arg *)a)->name,
((const struct arg *)b)->name));
}
static long long strsuftoll(const char* name, const char* arg, int def, unsigned int max)
{
long long result;
if (sscanf(arg, "%lld", &result) == 0)
result = def;
return result;
}
static void
f_bs(char *arg)
{
in.dbsz = out.dbsz = strsuftoll("block size", arg, 1, UINT_MAX);
}
static void
f_cbs(char *arg)
{
cbsz = strsuftoll("conversion record size", arg, 1, UINT_MAX);
}
static void
f_count(char *arg)
{
cpy_cnt = strsuftoll("block count", arg, 0, LLONG_MAX);
if (!cpy_cnt)
terminate(0);
}
static void
f_files(char *arg)
{
files_cnt = (u_int)strsuftoll("file count", arg, 0, UINT_MAX);
if (!files_cnt)
terminate(0);
}
static void
f_ibs(char *arg)
{
if (!(ddflags & C_BS))
in.dbsz = strsuftoll("input block size", arg, 1, UINT_MAX);
}
static void
f_if(char *arg)
{
in.name = arg;
}
static void
f_obs(char *arg)
{
if (!(ddflags & C_BS))
out.dbsz = strsuftoll("output block size", arg, 1, UINT_MAX);
}
static void
f_of(char *arg)
{
out.name = arg;
}
static void
f_seek(char *arg)
{
out.offset = strsuftoll("seek blocks", arg, 0, LLONG_MAX);
}
static void
f_skip(char *arg)
{
in.offset = strsuftoll("skip blocks", arg, 0, LLONG_MAX);
}
static void
f_progress(char *arg)
{
if (*arg != '0')
progress = 1;
}
#ifdef NO_CONV
/* Build a small version (i.e. for a ramdisk root) */
static void
f_conv(char *arg)
{
fprintf(stderr, "conv option disabled\n");
exit(1);
/* NOTREACHED */
}
#else /* NO_CONV */
static const struct conv {
const char *name;
u_int set, noset;
const u_char *ctab;
} clist[] = {
{ "block", C_BLOCK, C_UNBLOCK, NULL },
{ "fdatasync", C_FDATASYNC, 0, NULL },
{ "noerror", C_NOERROR, 0, NULL },
{ "notrunc", C_NOTRUNC, 0, NULL },
{ "osync", C_OSYNC, C_BS, NULL },
{ "sparse", C_SPARSE, 0, NULL },
{ "swab", C_SWAB, 0, NULL },
{ "sync", C_SYNC, 0, NULL },
{ "unblock", C_UNBLOCK, C_BLOCK, NULL },
/* If you add items to this table, be sure to add the
* conversions to the C_BS check in the jcl routine above.
*/
};
static void
f_conv(char *arg)
{
struct conv *cp, tmp;
while (arg != NULL) {
tmp.name = strsep(&arg, ",");
if (!(cp = (struct conv *)bsearch(&tmp, clist,
sizeof(clist)/sizeof(struct conv), sizeof(struct conv),
c_conv))) {
errx(EXIT_FAILURE, "unknown conversion %s", tmp.name);
/* NOTREACHED */
}
if (ddflags & cp->noset) {
errx(EXIT_FAILURE, "%s: illegal conversion combination", tmp.name);
/* NOTREACHED */
}
ddflags |= cp->set;
if (cp->ctab)
ctab = cp->ctab;
}
}
static int
c_conv(const void *a, const void *b)
{
return (strcmp(((const struct conv *)a)->name,
((const struct conv *)b)->name));
}
#endif /* NO_CONV */