| /* $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> |
| |
| #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" |
| |
| #ifdef __APPLE__ |
| #include <AvailabilityMacros.h> |
| #if defined MAC_OS_X_VERSION_MIN_REQUIRED && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 |
| #define fdatasync fsync |
| #else |
| extern int fdatasync(int); |
| #endif |
| #endif |
| |
| #ifdef __INTERIX |
| #define fdatasync fsync |
| #endif |
| |
| //#define NO_CONV |
| |
| static void block(void); |
| static void block_close(void); |
| static void dd_out(int); |
| static void def(void); |
| static void def_close(void); |
| static void jcl(char **); |
| static void pos_in(void); |
| static void pos_out(void); |
| static void summary(void); |
| static void terminate(int); |
| static void unblock(void); |
| static void unblock_close(void); |
| static 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 unsigned int ddflags; |
| extern unsigned int files_cnt; |
| extern int progress; |
| extern const uint8_t *ctab; |
| |
| #ifndef MIN |
| #define MIN(a, u_charb) ((a) < (b) ? (a) : (b)) |
| #endif |
| #ifndef MAX |
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| #endif |
| #ifndef DEFFILEMODE |
| #define DEFFILEMODE (S_IRUSR | S_IWUSR) |
| #endif |
| |
| 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 */ |
| unsigned int ddflags; /* conversion options */ |
| uint64_t cbsz; /* conversion block size */ |
| unsigned int files_cnt = 1; /* # of files to copy */ |
| int progress = 0; /* display sign of life */ |
| const uint8_t *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); |
| |
| atexit(summary); |
| |
| while(files_cnt--) dd_in(); |
| |
| dd_close(); |
| return 0; |
| } |
| |
| static void |
| setup(void) |
| { |
| |
| if(!in.name) { |
| in.name = "stdin"; |
| in.fd = STDIN_FILENO; |
| } else { |
| in.fd = open(in.name, O_RDONLY, 0); |
| if(in.fd == -1) { |
| 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) { |
| /* 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); |
| out.db = in.db; |
| } else if((in.db = |
| malloc((unsigned int)(MAX(in.dbsz, cbsz) + cbsz))) == NULL || |
| (out.db = malloc((unsigned 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)) { |
| ftruncate(out.fd, (off_t)out.offset * out.dbsz); |
| } |
| |
| 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)) 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 < 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); |
| 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; |
| } |
| |
| 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) { |
| 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 */ |
| } |
| } |
| |
| static void dd_out(int force) { |
| static int warned; |
| int64_t cnt, n, nw; |
| uint8_t *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) memmove(out.db, out.dbp - out.dbcnt, out.dbcnt); |
| out.dbp = out.db + out.dbcnt; |
| |
| if(progress) write(STDERR_FILENO, ".", 1); |
| } |
| |
| /* |
| * A protected against SIGINFO write |
| */ |
| static ssize_t bwrite(int fd, const void *buf, size_t len) { |
| sigset_t oset; |
| ssize_t rv; |
| int oerrno; |
| |
| sigprocmask(SIG_BLOCK, &infoset, &oset); |
| rv = write(fd, buf, len); |
| oerrno = errno; |
| 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. |
| */ |
| static 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 (lseek(in.fd, (off_t)in.offset * (off_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 */ |
| } |
| } |
| |
| static 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 (lseek(out.fd, (off_t)out.offset * (off_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). |
| */ |
| static void def(void) { |
| uint64_t cnt; |
| uint8_t *inp; |
| const uint8_t *t; |
| |
| if((t = ctab)) 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; |
| } |
| } |
| |
| static 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"; |
| static void block(void) { fprintf(stderr, "%s", no_block + 2); exit(1); } |
| static void block_close(void) { fprintf(stderr, "%s", no_block + 2); exit(1); } |
| static void unblock(void) { fprintf(stderr, "%s", no_block); exit(1); } |
| static 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 |
| */ |
| static void block(void) { |
| static int intrunc; |
| int ch = 0; /* pacify gcc */ |
| uint64_t cnt, maxlen; |
| uint8_t *inp, *outp; |
| const uint8_t *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) { |
| 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) 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; |
| } |
| |
| static 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++; |
| memmove(out.dbp, in.dbp - in.dbcnt, in.dbcnt); |
| 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 |
| */ |
| static void unblock(void) { |
| uint64_t cnt; |
| uint8_t *inp; |
| const uint8_t *t; |
| |
| /* Translation and case conversion. */ |
| if((t = ctab)) 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; |
| 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) memmove(in.db, in.dbp - in.dbcnt, in.dbcnt); |
| in.dbp = in.db + in.dbcnt; |
| } |
| |
| static void unblock_close(void) { |
| uint64_t cnt; |
| uint8_t *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) |
| |
| static void summary(void) { |
| char buf[100]; |
| int64_t mS; |
| struct timeval tv; |
| |
| if(progress) write(STDERR_FILENO, "\n", 1); |
| |
| 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). */ |
| 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); |
| write(STDERR_FILENO, buf, strlen(buf)); |
| if(st.swab) { |
| snprintf(buf, sizeof(buf), "%llu odd length swab %s\n", |
| (unsigned long long)st.swab, |
| (st.swab == 1) ? "block" : "blocks"); |
| write(STDERR_FILENO, buf, strlen(buf)); |
| } |
| if(st.trunc) { |
| snprintf(buf, sizeof(buf), "%llu truncated %s\n", |
| (unsigned long long)st.trunc, |
| (st.trunc == 1) ? "block" : "blocks"); |
| write(STDERR_FILENO, buf, strlen(buf)); |
| } |
| if(st.sparse) { |
| snprintf(buf, sizeof(buf), "%llu sparse output %s\n", |
| (unsigned long long)st.sparse, |
| (st.sparse == 1) ? "block" : "blocks"); |
| write(STDERR_FILENO, buf, strlen(buf)); |
| } |
| 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)); |
| write(STDERR_FILENO, buf, strlen(buf)); |
| } |
| |
| static 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 *); |
| unsigned 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. |
| */ |
| static 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 long long 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 = (unsigned 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; |
| unsigned int set, noset; |
| const uint8_t *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 */ |