Home | History | Annotate | Download | only in toolbox
      1 /*	$NetBSD: dd.c,v 1.37 2004/01/17 21:00:16 dbj Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1991, 1993, 1994
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * This code is derived from software contributed to Berkeley by
      8  * Keith Muller of the University of California, San Diego and Lance
      9  * Visser of Convex Computer Corporation.
     10  *
     11  * Redistribution and use in source and binary forms, with or without
     12  * modification, are permitted provided that the following conditions
     13  * are met:
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  * 2. Redistributions in binary form must reproduce the above copyright
     17  *    notice, this list of conditions and the following disclaimer in the
     18  *    documentation and/or other materials provided with the distribution.
     19  * 3. Neither the name of the University nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include <sys/cdefs.h>
     37 #ifndef lint
     38 __COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\n\
     39 	The Regents of the University of California.  All rights reserved.\n");
     40 #endif /* not lint */
     41 
     42 #ifndef lint
     43 #if 0
     44 static char sccsid[] = "@(#)dd.c	8.5 (Berkeley) 4/2/94";
     45 #else
     46 __RCSID("$NetBSD: dd.c,v 1.37 2004/01/17 21:00:16 dbj Exp $");
     47 #endif
     48 #endif /* not lint */
     49 
     50 #include <sys/param.h>
     51 #include <sys/stat.h>
     52 #include <sys/ioctl.h>
     53 #include <sys/time.h>
     54 
     55 #include <ctype.h>
     56 #include <err.h>
     57 #include <errno.h>
     58 #include <fcntl.h>
     59 #include <signal.h>
     60 #include <stdio.h>
     61 #include <stdlib.h>
     62 #include <string.h>
     63 #include <unistd.h>
     64 
     65 #include "dd.h"
     66 
     67 //#define NO_CONV
     68 
     69 //#include "extern.h"
     70 void block(void);
     71 void block_close(void);
     72 void dd_out(int);
     73 void def(void);
     74 void def_close(void);
     75 void jcl(char **);
     76 void pos_in(void);
     77 void pos_out(void);
     78 void summary(void);
     79 void summaryx(int);
     80 void terminate(int);
     81 void unblock(void);
     82 void unblock_close(void);
     83 ssize_t bwrite(int, const void *, size_t);
     84 
     85 extern IO		in, out;
     86 extern STAT		st;
     87 extern void		(*cfunc)(void);
     88 extern uint64_t		cpy_cnt;
     89 extern uint64_t		cbsz;
     90 extern u_int		ddflags;
     91 extern u_int		files_cnt;
     92 extern int		progress;
     93 extern const u_char	*ctab;
     94 
     95 
     96 #define MIN(a, b) ((a) < (b) ? (a) : (b))
     97 #define MAX(a, b) ((a) > (b) ? (a) : (b))
     98 #define DEFFILEMODE (S_IRUSR | S_IWUSR)
     99 
    100 static void dd_close(void);
    101 static void dd_in(void);
    102 static void getfdtype(IO *);
    103 static int redup_clean_fd(int);
    104 static void setup(void);
    105 
    106 
    107 IO		in, out;		/* input/output state */
    108 STAT		st;			/* statistics */
    109 void		(*cfunc)(void);		/* conversion function */
    110 uint64_t	cpy_cnt;		/* # of blocks to copy */
    111 static off_t	pending = 0;		/* pending seek if sparse */
    112 u_int		ddflags;		/* conversion options */
    113 uint64_t	cbsz;			/* conversion block size */
    114 u_int		files_cnt = 1;		/* # of files to copy */
    115 int		progress = 0;		/* display sign of life */
    116 const u_char	*ctab;			/* conversion table */
    117 sigset_t	infoset;		/* a set blocking SIGINFO */
    118 
    119 int
    120 dd_main(int argc, char *argv[])
    121 {
    122 	int ch;
    123 
    124 	while ((ch = getopt(argc, argv, "")) != -1) {
    125 		switch (ch) {
    126 		default:
    127 			fprintf(stderr, "usage: dd [operand ...]\n");
    128 			exit(1);
    129 			/* NOTREACHED */
    130 		}
    131 	}
    132 	argc -= (optind - 1);
    133 	argv += (optind - 1);
    134 
    135 	jcl(argv);
    136 	setup();
    137 
    138 //	(void)signal(SIGINFO, summaryx);
    139 	(void)signal(SIGINT, terminate);
    140 	(void)sigemptyset(&infoset);
    141 //	(void)sigaddset(&infoset, SIGINFO);
    142 
    143 	(void)atexit(summary);
    144 
    145 	while (files_cnt--)
    146 		dd_in();
    147 
    148 	dd_close();
    149 	exit(0);
    150 	/* NOTREACHED */
    151 }
    152 
    153 static void
    154 setup(void)
    155 {
    156 
    157 	if (in.name == NULL) {
    158 		in.name = "stdin";
    159 		in.fd = STDIN_FILENO;
    160 	} else {
    161 		in.fd = open(in.name, O_RDONLY, 0);
    162 		if (in.fd < 0) {
    163 			fprintf(stderr, "%s: cannot open for read: %s\n",
    164 				in.name, strerror(errno));
    165 			exit(1);
    166 			/* NOTREACHED */
    167 		}
    168 
    169 		/* Ensure in.fd is outside the stdio descriptor range */
    170 		in.fd = redup_clean_fd(in.fd);
    171 	}
    172 
    173 	getfdtype(&in);
    174 
    175 	if (files_cnt > 1 && !(in.flags & ISTAPE)) {
    176 		fprintf(stderr,
    177 			"files is not supported for non-tape devices\n");
    178 		exit(1);
    179 		/* NOTREACHED */
    180 	}
    181 
    182 	if (out.name == NULL) {
    183 		/* No way to check for read access here. */
    184 		out.fd = STDOUT_FILENO;
    185 		out.name = "stdout";
    186 	} else {
    187 #define	OFLAGS \
    188     (O_CREAT | (ddflags & (C_SEEK | C_NOTRUNC) ? 0 : O_TRUNC))
    189 		out.fd = open(out.name, O_RDWR | OFLAGS, DEFFILEMODE);
    190 		/*
    191 		 * May not have read access, so try again with write only.
    192 		 * Without read we may have a problem if output also does
    193 		 * not support seeks.
    194 		 */
    195 		if (out.fd < 0) {
    196 			out.fd = open(out.name, O_WRONLY | OFLAGS, DEFFILEMODE);
    197 			out.flags |= NOREAD;
    198 		}
    199 		if (out.fd < 0) {
    200 			fprintf(stderr, "%s: cannot open for write: %s\n",
    201 				out.name, strerror(errno));
    202 			exit(1);
    203 			/* NOTREACHED */
    204 		}
    205 
    206 		/* Ensure out.fd is outside the stdio descriptor range */
    207 		out.fd = redup_clean_fd(out.fd);
    208 	}
    209 
    210 	getfdtype(&out);
    211 
    212 	/*
    213 	 * Allocate space for the input and output buffers.  If not doing
    214 	 * record oriented I/O, only need a single buffer.
    215 	 */
    216 	if (!(ddflags & (C_BLOCK|C_UNBLOCK))) {
    217 		if ((in.db = malloc(out.dbsz + in.dbsz - 1)) == NULL) {
    218 			exit(1);
    219 			/* NOTREACHED */
    220 		}
    221 		out.db = in.db;
    222 	} else if ((in.db =
    223 	    malloc((u_int)(MAX(in.dbsz, cbsz) + cbsz))) == NULL ||
    224 	    (out.db = malloc((u_int)(out.dbsz + cbsz))) == NULL) {
    225 		exit(1);
    226 		/* NOTREACHED */
    227 	}
    228 	in.dbp = in.db;
    229 	out.dbp = out.db;
    230 
    231 	/* Position the input/output streams. */
    232 	if (in.offset)
    233 		pos_in();
    234 	if (out.offset)
    235 		pos_out();
    236 
    237 	/*
    238 	 * Truncate the output file; ignore errors because it fails on some
    239 	 * kinds of output files, tapes, for example.
    240 	 */
    241 	if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK))
    242 		(void)ftruncate(out.fd, (off_t)out.offset * out.dbsz);
    243 
    244 	(void)gettimeofday(&st.start, NULL);	/* Statistics timestamp. */
    245 }
    246 
    247 static void
    248 getfdtype(IO *io)
    249 {
    250 //	struct mtget mt;
    251 	struct stat sb;
    252 
    253 	if (fstat(io->fd, &sb)) {
    254 		fprintf(stderr, "%s: cannot fstat: %s\n",
    255 			io->name, strerror(errno));
    256 		exit(1);
    257 		/* NOTREACHED */
    258 	}
    259 	if (S_ISCHR(sb.st_mode))
    260 		io->flags |= /*ioctl(io->fd, MTIOCGET, &mt) ? ISCHR : ISTAPE; */ ISCHR;
    261 	else if (lseek(io->fd, (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE)
    262 		io->flags |= ISPIPE;		/* XXX fixed in 4.4BSD */
    263 }
    264 
    265 /*
    266  * Move the parameter file descriptor to a descriptor that is outside the
    267  * stdio descriptor range, if necessary.  This is required to avoid
    268  * accidentally outputting completion or error messages into the
    269  * output file that were intended for the tty.
    270  */
    271 static int
    272 redup_clean_fd(int fd)
    273 {
    274 	int newfd;
    275 
    276 	if (fd != STDIN_FILENO && fd != STDOUT_FILENO &&
    277 	    fd != STDERR_FILENO)
    278 		/* File descriptor is ok, return immediately. */
    279 		return fd;
    280 
    281 	/*
    282 	 * 3 is the first descriptor greater than STD*_FILENO.  Any
    283 	 * free descriptor valued 3 or above is acceptable...
    284 	 */
    285 	newfd = fcntl(fd, F_DUPFD, 3);
    286 	if (newfd < 0) {
    287 		fprintf(stderr, "dupfd IO: %s\n", strerror(errno));
    288 		exit(1);
    289 		/* NOTREACHED */
    290 	}
    291 
    292 	close(fd);
    293 
    294 	return newfd;
    295 }
    296 
    297 static void
    298 dd_in(void)
    299 {
    300 	int flags;
    301 	int64_t n;
    302 
    303 	for (flags = ddflags;;) {
    304 		if (cpy_cnt && (st.in_full + st.in_part) >= cpy_cnt)
    305 			return;
    306 
    307 		/*
    308 		 * Clear the buffer first if doing "sync" on input.
    309 		 * If doing block operations use spaces.  This will
    310 		 * affect not only the C_NOERROR case, but also the
    311 		 * last partial input block which should be padded
    312 		 * with zero and not garbage.
    313 		 */
    314 		if (flags & C_SYNC) {
    315 			if (flags & (C_BLOCK|C_UNBLOCK))
    316 				(void)memset(in.dbp, ' ', in.dbsz);
    317 			else
    318 				(void)memset(in.dbp, 0, in.dbsz);
    319 		}
    320 
    321 		n = read(in.fd, in.dbp, in.dbsz);
    322 		if (n == 0) {
    323 			in.dbrcnt = 0;
    324 			return;
    325 		}
    326 
    327 		/* Read error. */
    328 		if (n < 0) {
    329 
    330 			/*
    331 			 * If noerror not specified, die.  POSIX requires that
    332 			 * the warning message be followed by an I/O display.
    333 			 */
    334 			fprintf(stderr, "%s: read error: %s\n",
    335 				in.name, strerror(errno));
    336 			if (!(flags & C_NOERROR)) {
    337 				exit(1);
    338 				/* NOTREACHED */
    339 			}
    340 			summary();
    341 
    342 			/*
    343 			 * If it's not a tape drive or a pipe, seek past the
    344 			 * error.  If your OS doesn't do the right thing for
    345 			 * raw disks this section should be modified to re-read
    346 			 * in sector size chunks.
    347 			 */
    348 			if (!(in.flags & (ISPIPE|ISTAPE)) &&
    349 			    lseek(in.fd, (off_t)in.dbsz, SEEK_CUR))
    350 				fprintf(stderr, "%s: seek error: %s\n",
    351 					in.name, strerror(errno));
    352 
    353 			/* If sync not specified, omit block and continue. */
    354 			if (!(ddflags & C_SYNC))
    355 				continue;
    356 
    357 			/* Read errors count as full blocks. */
    358 			in.dbcnt += in.dbrcnt = in.dbsz;
    359 			++st.in_full;
    360 
    361 		/* Handle full input blocks. */
    362 		} else if (n == in.dbsz) {
    363 			in.dbcnt += in.dbrcnt = n;
    364 			++st.in_full;
    365 
    366 		/* Handle partial input blocks. */
    367 		} else {
    368 			/* If sync, use the entire block. */
    369 			if (ddflags & C_SYNC)
    370 				in.dbcnt += in.dbrcnt = in.dbsz;
    371 			else
    372 				in.dbcnt += in.dbrcnt = n;
    373 			++st.in_part;
    374 		}
    375 
    376 		/*
    377 		 * POSIX states that if bs is set and no other conversions
    378 		 * than noerror, notrunc or sync are specified, the block
    379 		 * is output without buffering as it is read.
    380 		 */
    381 		if (ddflags & C_BS) {
    382 			out.dbcnt = in.dbcnt;
    383 			dd_out(1);
    384 			in.dbcnt = 0;
    385 			continue;
    386 		}
    387 
    388 /*		if (ddflags & C_SWAB) {
    389 			if ((n = in.dbrcnt) & 1) {
    390 				++st.swab;
    391 				--n;
    392 			}
    393 			swab(in.dbp, in.dbp, n);
    394 		}
    395 */
    396 		in.dbp += in.dbrcnt;
    397 		(*cfunc)();
    398 	}
    399 }
    400 
    401 /*
    402  * Cleanup any remaining I/O and flush output.  If necesssary, output file
    403  * is truncated.
    404  */
    405 static void
    406 dd_close(void)
    407 {
    408 
    409 	if (cfunc == def)
    410 		def_close();
    411 	else if (cfunc == block)
    412 		block_close();
    413 	else if (cfunc == unblock)
    414 		unblock_close();
    415 	if (ddflags & C_OSYNC && out.dbcnt < out.dbsz) {
    416 		(void)memset(out.dbp, 0, out.dbsz - out.dbcnt);
    417 		out.dbcnt = out.dbsz;
    418 	}
    419 	/* If there are pending sparse blocks, make sure
    420 	 * to write out the final block un-sparse
    421 	 */
    422 	if ((out.dbcnt == 0) && pending) {
    423 		memset(out.db, 0, out.dbsz);
    424 		out.dbcnt = out.dbsz;
    425 		out.dbp = out.db + out.dbcnt;
    426 		pending -= out.dbsz;
    427 	}
    428 	if (out.dbcnt)
    429 		dd_out(1);
    430 
    431 	/*
    432 	 * Reporting nfs write error may be defered until next
    433 	 * write(2) or close(2) system call.  So, we need to do an
    434 	 * extra check.  If an output is stdout, the file structure
    435 	 * may be shared among with other processes and close(2) just
    436 	 * decreases the reference count.
    437 	 */
    438 	if (out.fd == STDOUT_FILENO && fsync(out.fd) == -1 && errno != EINVAL) {
    439 		fprintf(stderr, "fsync stdout: %s\n", strerror(errno));
    440 		exit(1);
    441 		/* NOTREACHED */
    442 	}
    443 	if (close(out.fd) == -1) {
    444 		fprintf(stderr, "close: %s\n", strerror(errno));
    445 		exit(1);
    446 		/* NOTREACHED */
    447 	}
    448 }
    449 
    450 void
    451 dd_out(int force)
    452 {
    453 	static int warned;
    454 	int64_t cnt, n, nw;
    455 	u_char *outp;
    456 
    457 	/*
    458 	 * Write one or more blocks out.  The common case is writing a full
    459 	 * output block in a single write; increment the full block stats.
    460 	 * Otherwise, we're into partial block writes.  If a partial write,
    461 	 * and it's a character device, just warn.  If a tape device, quit.
    462 	 *
    463 	 * The partial writes represent two cases.  1: Where the input block
    464 	 * was less than expected so the output block was less than expected.
    465 	 * 2: Where the input block was the right size but we were forced to
    466 	 * write the block in multiple chunks.  The original versions of dd(1)
    467 	 * never wrote a block in more than a single write, so the latter case
    468 	 * never happened.
    469 	 *
    470 	 * One special case is if we're forced to do the write -- in that case
    471 	 * we play games with the buffer size, and it's usually a partial write.
    472 	 */
    473 	outp = out.db;
    474 	for (n = force ? out.dbcnt : out.dbsz;; n = out.dbsz) {
    475 		for (cnt = n;; cnt -= nw) {
    476 
    477 			if (!force && ddflags & C_SPARSE) {
    478 				int sparse, i;
    479 				sparse = 1;	/* Is buffer sparse? */
    480 				for (i = 0; i < cnt; i++)
    481 					if (outp[i] != 0) {
    482 						sparse = 0;
    483 						break;
    484 					}
    485 				if (sparse) {
    486 					pending += cnt;
    487 					outp += cnt;
    488 					nw = 0;
    489 					break;
    490 				}
    491 			}
    492 			if (pending != 0) {
    493 				if (lseek(out.fd, pending, SEEK_CUR) ==
    494 				    -1) {
    495 					fprintf(stderr,
    496 						"%s: seek error creating "
    497 						"sparse file: %s\n",
    498 						out.name, strerror(errno));
    499 					exit(1);
    500 				}
    501 			}
    502 			nw = bwrite(out.fd, outp, cnt);
    503 			if (nw <= 0) {
    504 				if (nw == 0) {
    505 					fprintf(stderr, "%s: end of device\n",
    506 						out.name);
    507 					exit(1);
    508 					/* NOTREACHED */
    509 				}
    510 				if (errno != EINTR) {
    511 					fprintf(stderr, "%s: write error: %s\n",
    512 						out.name, strerror(errno));
    513 					/* NOTREACHED */
    514 					exit(1);
    515 				}
    516 				nw = 0;
    517 			}
    518 			if (pending) {
    519 				st.bytes += pending;
    520 				st.sparse += pending/out.dbsz;
    521 				st.out_full += pending/out.dbsz;
    522 				pending = 0;
    523 			}
    524 			outp += nw;
    525 			st.bytes += nw;
    526 			if (nw == n) {
    527 				if (n != out.dbsz)
    528 					++st.out_part;
    529 				else
    530 					++st.out_full;
    531 				break;
    532 			}
    533 			++st.out_part;
    534 			if (nw == cnt)
    535 				break;
    536 			if (out.flags & ISCHR && !warned) {
    537 				warned = 1;
    538 				fprintf(stderr, "%s: short write on character "
    539 					"device\n", out.name);
    540 			}
    541 			if (out.flags & ISTAPE) {
    542 				fprintf(stderr,
    543 					"%s: short write on tape device",
    544 					out.name);
    545 				exit(1);
    546 				/* NOTREACHED */
    547 			}
    548 		}
    549 		if ((out.dbcnt -= n) < out.dbsz)
    550 			break;
    551 	}
    552 
    553 	/* Reassemble the output block. */
    554 	if (out.dbcnt)
    555 		(void)memmove(out.db, out.dbp - out.dbcnt, out.dbcnt);
    556 	out.dbp = out.db + out.dbcnt;
    557 
    558 	if (progress)
    559 		(void)write(STDERR_FILENO, ".", 1);
    560 }
    561 
    562 /*
    563  * A protected against SIGINFO write
    564  */
    565 ssize_t
    566 bwrite(int fd, const void *buf, size_t len)
    567 {
    568 	sigset_t oset;
    569 	ssize_t rv;
    570 	int oerrno;
    571 
    572 	(void)sigprocmask(SIG_BLOCK, &infoset, &oset);
    573 	rv = write(fd, buf, len);
    574 	oerrno = errno;
    575 	(void)sigprocmask(SIG_SETMASK, &oset, NULL);
    576 	errno = oerrno;
    577 	return (rv);
    578 }
    579 
    580 /*
    581  * Position input/output data streams before starting the copy.  Device type
    582  * dependent.  Seekable devices use lseek, and the rest position by reading.
    583  * Seeking past the end of file can cause null blocks to be written to the
    584  * output.
    585  */
    586 void
    587 pos_in(void)
    588 {
    589 	int bcnt, cnt, nr, warned;
    590 
    591 	/* If not a pipe or tape device, try to seek on it. */
    592 	if (!(in.flags & (ISPIPE|ISTAPE))) {
    593 		if (lseek(in.fd,
    594 		    (off_t)in.offset * (off_t)in.dbsz, SEEK_CUR) == -1) {
    595 			fprintf(stderr, "%s: seek error: %s",
    596 				in.name, strerror(errno));
    597 			exit(1);
    598 			/* NOTREACHED */
    599 		}
    600 		return;
    601 		/* NOTREACHED */
    602 	}
    603 
    604 	/*
    605 	 * Read the data.  If a pipe, read until satisfy the number of bytes
    606 	 * being skipped.  No differentiation for reading complete and partial
    607 	 * blocks for other devices.
    608 	 */
    609 	for (bcnt = in.dbsz, cnt = in.offset, warned = 0; cnt;) {
    610 		if ((nr = read(in.fd, in.db, bcnt)) > 0) {
    611 			if (in.flags & ISPIPE) {
    612 				if (!(bcnt -= nr)) {
    613 					bcnt = in.dbsz;
    614 					--cnt;
    615 				}
    616 			} else
    617 				--cnt;
    618 			continue;
    619 		}
    620 
    621 		if (nr == 0) {
    622 			if (files_cnt > 1) {
    623 				--files_cnt;
    624 				continue;
    625 			}
    626 			fprintf(stderr, "skip reached end of input\n");
    627 			exit(1);
    628 			/* NOTREACHED */
    629 		}
    630 
    631 		/*
    632 		 * Input error -- either EOF with no more files, or I/O error.
    633 		 * If noerror not set die.  POSIX requires that the warning
    634 		 * message be followed by an I/O display.
    635 		 */
    636 		if (ddflags & C_NOERROR) {
    637 			if (!warned) {
    638 
    639 				fprintf(stderr, "%s: error occurred\n",
    640 					in.name);
    641 				warned = 1;
    642 				summary();
    643 			}
    644 			continue;
    645 		}
    646 		fprintf(stderr, "%s: read error: %s", in.name, strerror(errno));
    647 		exit(1);
    648 		/* NOTREACHED */
    649 	}
    650 }
    651 
    652 void
    653 pos_out(void)
    654 {
    655 //	struct mtop t_op;
    656 	int cnt, n;
    657 
    658 	/*
    659 	 * If not a tape, try seeking on the file.  Seeking on a pipe is
    660 	 * going to fail, but don't protect the user -- they shouldn't
    661 	 * have specified the seek operand.
    662 	 */
    663 	if (!(out.flags & ISTAPE)) {
    664 		if (lseek(out.fd,
    665 		    (off_t)out.offset * (off_t)out.dbsz, SEEK_SET) == -1) {
    666 			fprintf(stderr, "%s: seek error: %s\n",
    667 				out.name, strerror(errno));
    668 			exit(1);
    669 			/* NOTREACHED */
    670 		}
    671 		return;
    672 	}
    673 
    674 	/* If no read access, try using mtio. */
    675 	if (out.flags & NOREAD) {
    676 /*		t_op.mt_op = MTFSR;
    677 		t_op.mt_count = out.offset;
    678 
    679 		if (ioctl(out.fd, MTIOCTOP, &t_op) < 0)*/
    680 			fprintf(stderr, "%s: cannot read", out.name);
    681 			exit(1);
    682 			/* NOTREACHED */
    683 		return;
    684 	}
    685 
    686 	/* Read it. */
    687 	for (cnt = 0; cnt < out.offset; ++cnt) {
    688 		if ((n = read(out.fd, out.db, out.dbsz)) > 0)
    689 			continue;
    690 
    691 		if (n < 0) {
    692 			fprintf(stderr, "%s: cannot position by reading: %s\n",
    693 				out.name, strerror(errno));
    694 			exit(1);
    695 			/* NOTREACHED */
    696 		}
    697 
    698 		/*
    699 		 * If reach EOF, fill with NUL characters; first, back up over
    700 		 * the EOF mark.  Note, cnt has not yet been incremented, so
    701 		 * the EOF read does not count as a seek'd block.
    702 		 */
    703 /*		t_op.mt_op = MTBSR;
    704 		t_op.mt_count = 1;
    705 		if (ioctl(out.fd, MTIOCTOP, &t_op) == -1) */ {
    706 			fprintf(stderr, "%s: cannot position\n", out.name);
    707 			exit(1);
    708 			/* NOTREACHED */
    709 		}
    710 
    711 		while (cnt++ < out.offset)
    712 			if ((n = bwrite(out.fd, out.db, out.dbsz)) != out.dbsz) {
    713 				fprintf(stderr, "%s: cannot position "
    714 					"by writing: %s\n",
    715 					out.name, strerror(errno));
    716 				exit(1);
    717 				/* NOTREACHED */
    718 			}
    719 		break;
    720 	}
    721 }
    722 
    723 /*
    724  * def --
    725  * Copy input to output.  Input is buffered until reaches obs, and then
    726  * output until less than obs remains.  Only a single buffer is used.
    727  * Worst case buffer calculation is (ibs + obs - 1).
    728  */
    729 void
    730 def(void)
    731 {
    732 	uint64_t cnt;
    733 	u_char *inp;
    734 	const u_char *t;
    735 
    736 	if ((t = ctab) != NULL)
    737 		for (inp = in.dbp - (cnt = in.dbrcnt); cnt--; ++inp)
    738 			*inp = t[*inp];
    739 
    740 	/* Make the output buffer look right. */
    741 	out.dbp = in.dbp;
    742 	out.dbcnt = in.dbcnt;
    743 
    744 	if (in.dbcnt >= out.dbsz) {
    745 		/* If the output buffer is full, write it. */
    746 		dd_out(0);
    747 
    748 		/*
    749 		 * Ddout copies the leftover output to the beginning of
    750 		 * the buffer and resets the output buffer.  Reset the
    751 		 * input buffer to match it.
    752 	 	 */
    753 		in.dbp = out.dbp;
    754 		in.dbcnt = out.dbcnt;
    755 	}
    756 }
    757 
    758 void
    759 def_close(void)
    760 {
    761 	if (ddflags & C_FDATASYNC) {
    762 		fdatasync(out.fd);
    763 	}
    764 
    765 	/* Just update the count, everything is already in the buffer. */
    766 	if (in.dbcnt)
    767 		out.dbcnt = in.dbcnt;
    768 }
    769 
    770 #ifdef	NO_CONV
    771 /* Build a smaller version (i.e. for a miniroot) */
    772 /* These can not be called, but just in case...  */
    773 static const char no_block[] = "unblock and -DNO_CONV?\n";
    774 void block(void)		{ fprintf(stderr, "%s", no_block + 2); exit(1); }
    775 void block_close(void)		{ fprintf(stderr, "%s", no_block + 2); exit(1); }
    776 void unblock(void)		{ fprintf(stderr, "%s", no_block); exit(1); }
    777 void unblock_close(void)	{ fprintf(stderr, "%s", no_block); exit(1); }
    778 #else	/* NO_CONV */
    779 
    780 /*
    781  * Copy variable length newline terminated records with a max size cbsz
    782  * bytes to output.  Records less than cbs are padded with spaces.
    783  *
    784  * max in buffer:  MAX(ibs, cbsz)
    785  * max out buffer: obs + cbsz
    786  */
    787 void
    788 block(void)
    789 {
    790 	static int intrunc;
    791 	int ch = 0;	/* pacify gcc */
    792 	uint64_t cnt, maxlen;
    793 	u_char *inp, *outp;
    794 	const u_char *t;
    795 
    796 	/*
    797 	 * Record truncation can cross block boundaries.  If currently in a
    798 	 * truncation state, keep tossing characters until reach a newline.
    799 	 * Start at the beginning of the buffer, as the input buffer is always
    800 	 * left empty.
    801 	 */
    802 	if (intrunc) {
    803 		for (inp = in.db, cnt = in.dbrcnt;
    804 		    cnt && *inp++ != '\n'; --cnt);
    805 		if (!cnt) {
    806 			in.dbcnt = 0;
    807 			in.dbp = in.db;
    808 			return;
    809 		}
    810 		intrunc = 0;
    811 		/* Adjust the input buffer numbers. */
    812 		in.dbcnt = cnt - 1;
    813 		in.dbp = inp + cnt - 1;
    814 	}
    815 
    816 	/*
    817 	 * Copy records (max cbsz size chunks) into the output buffer.  The
    818 	 * translation is done as we copy into the output buffer.
    819 	 */
    820 	for (inp = in.dbp - in.dbcnt, outp = out.dbp; in.dbcnt;) {
    821 		maxlen = MIN(cbsz, in.dbcnt);
    822 		if ((t = ctab) != NULL)
    823 			for (cnt = 0;
    824 			    cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
    825 				*outp++ = t[ch];
    826 		else
    827 			for (cnt = 0;
    828 			    cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
    829 				*outp++ = ch;
    830 		/*
    831 		 * Check for short record without a newline.  Reassemble the
    832 		 * input block.
    833 		 */
    834 		if (ch != '\n' && in.dbcnt < cbsz) {
    835 			(void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
    836 			break;
    837 		}
    838 
    839 		/* Adjust the input buffer numbers. */
    840 		in.dbcnt -= cnt;
    841 		if (ch == '\n')
    842 			--in.dbcnt;
    843 
    844 		/* Pad short records with spaces. */
    845 		if (cnt < cbsz)
    846 			(void)memset(outp, ctab ? ctab[' '] : ' ', cbsz - cnt);
    847 		else {
    848 			/*
    849 			 * If the next character wouldn't have ended the
    850 			 * block, it's a truncation.
    851 			 */
    852 			if (!in.dbcnt || *inp != '\n')
    853 				++st.trunc;
    854 
    855 			/* Toss characters to a newline. */
    856 			for (; in.dbcnt && *inp++ != '\n'; --in.dbcnt);
    857 			if (!in.dbcnt)
    858 				intrunc = 1;
    859 			else
    860 				--in.dbcnt;
    861 		}
    862 
    863 		/* Adjust output buffer numbers. */
    864 		out.dbp += cbsz;
    865 		if ((out.dbcnt += cbsz) >= out.dbsz)
    866 			dd_out(0);
    867 		outp = out.dbp;
    868 	}
    869 	in.dbp = in.db + in.dbcnt;
    870 }
    871 
    872 void
    873 block_close(void)
    874 {
    875 
    876 	/*
    877 	 * Copy any remaining data into the output buffer and pad to a record.
    878 	 * Don't worry about truncation or translation, the input buffer is
    879 	 * always empty when truncating, and no characters have been added for
    880 	 * translation.  The bottom line is that anything left in the input
    881 	 * buffer is a truncated record.  Anything left in the output buffer
    882 	 * just wasn't big enough.
    883 	 */
    884 	if (in.dbcnt) {
    885 		++st.trunc;
    886 		(void)memmove(out.dbp, in.dbp - in.dbcnt, in.dbcnt);
    887 		(void)memset(out.dbp + in.dbcnt,
    888 		    ctab ? ctab[' '] : ' ', cbsz - in.dbcnt);
    889 		out.dbcnt += cbsz;
    890 	}
    891 }
    892 
    893 /*
    894  * Convert fixed length (cbsz) records to variable length.  Deletes any
    895  * trailing blanks and appends a newline.
    896  *
    897  * max in buffer:  MAX(ibs, cbsz) + cbsz
    898  * max out buffer: obs + cbsz
    899  */
    900 void
    901 unblock(void)
    902 {
    903 	uint64_t cnt;
    904 	u_char *inp;
    905 	const u_char *t;
    906 
    907 	/* Translation and case conversion. */
    908 	if ((t = ctab) != NULL)
    909 		for (cnt = in.dbrcnt, inp = in.dbp - 1; cnt--; inp--)
    910 			*inp = t[*inp];
    911 	/*
    912 	 * Copy records (max cbsz size chunks) into the output buffer.  The
    913 	 * translation has to already be done or we might not recognize the
    914 	 * spaces.
    915 	 */
    916 	for (inp = in.db; in.dbcnt >= cbsz; inp += cbsz, in.dbcnt -= cbsz) {
    917 		for (t = inp + cbsz - 1; t >= inp && *t == ' '; --t);
    918 		if (t >= inp) {
    919 			cnt = t - inp + 1;
    920 			(void)memmove(out.dbp, inp, cnt);
    921 			out.dbp += cnt;
    922 			out.dbcnt += cnt;
    923 		}
    924 		++out.dbcnt;
    925 		*out.dbp++ = '\n';
    926 		if (out.dbcnt >= out.dbsz)
    927 			dd_out(0);
    928 	}
    929 	if (in.dbcnt)
    930 		(void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
    931 	in.dbp = in.db + in.dbcnt;
    932 }
    933 
    934 void
    935 unblock_close(void)
    936 {
    937 	uint64_t cnt;
    938 	u_char *t;
    939 
    940 	if (in.dbcnt) {
    941 		warnx("%s: short input record", in.name);
    942 		for (t = in.db + in.dbcnt - 1; t >= in.db && *t == ' '; --t);
    943 		if (t >= in.db) {
    944 			cnt = t - in.db + 1;
    945 			(void)memmove(out.dbp, in.db, cnt);
    946 			out.dbp += cnt;
    947 			out.dbcnt += cnt;
    948 		}
    949 		++out.dbcnt;
    950 		*out.dbp++ = '\n';
    951 	}
    952 }
    953 
    954 #endif	/* NO_CONV */
    955 
    956 #define	tv2mS(tv) ((tv).tv_sec * 1000LL + ((tv).tv_usec + 500) / 1000)
    957 
    958 void
    959 summary(void)
    960 {
    961 	char buf[100];
    962 	int64_t mS;
    963 	struct timeval tv;
    964 
    965 	if (progress)
    966 		(void)write(STDERR_FILENO, "\n", 1);
    967 
    968 	(void)gettimeofday(&tv, NULL);
    969 	mS = tv2mS(tv) - tv2mS(st.start);
    970 	if (mS == 0)
    971 		mS = 1;
    972 	/* Use snprintf(3) so that we don't reenter stdio(3). */
    973 	(void)snprintf(buf, sizeof(buf),
    974 	    "%llu+%llu records in\n%llu+%llu records out\n",
    975 	    (unsigned long long)st.in_full,  (unsigned long long)st.in_part,
    976 	    (unsigned long long)st.out_full, (unsigned long long)st.out_part);
    977 	(void)write(STDERR_FILENO, buf, strlen(buf));
    978 	if (st.swab) {
    979 		(void)snprintf(buf, sizeof(buf), "%llu odd length swab %s\n",
    980 		    (unsigned long long)st.swab,
    981 		    (st.swab == 1) ? "block" : "blocks");
    982 		(void)write(STDERR_FILENO, buf, strlen(buf));
    983 	}
    984 	if (st.trunc) {
    985 		(void)snprintf(buf, sizeof(buf), "%llu truncated %s\n",
    986 		    (unsigned long long)st.trunc,
    987 		    (st.trunc == 1) ? "block" : "blocks");
    988 		(void)write(STDERR_FILENO, buf, strlen(buf));
    989 	}
    990 	if (st.sparse) {
    991 		(void)snprintf(buf, sizeof(buf), "%llu sparse output %s\n",
    992 		    (unsigned long long)st.sparse,
    993 		    (st.sparse == 1) ? "block" : "blocks");
    994 		(void)write(STDERR_FILENO, buf, strlen(buf));
    995 	}
    996 	(void)snprintf(buf, sizeof(buf),
    997 	    "%llu bytes transferred in %lu.%03d secs (%llu bytes/sec)\n",
    998 	    (unsigned long long) st.bytes,
    999 	    (long) (mS / 1000),
   1000 	    (int) (mS % 1000),
   1001 	    (unsigned long long) (st.bytes * 1000LL / mS));
   1002 	(void)write(STDERR_FILENO, buf, strlen(buf));
   1003 }
   1004 
   1005 void
   1006 terminate(int notused)
   1007 {
   1008 
   1009 	exit(0);
   1010 	/* NOTREACHED */
   1011 }
   1012 
   1013 static int	c_arg(const void *, const void *);
   1014 #ifndef	NO_CONV
   1015 static int	c_conv(const void *, const void *);
   1016 #endif
   1017 static void	f_bs(char *);
   1018 static void	f_cbs(char *);
   1019 static void	f_conv(char *);
   1020 static void	f_count(char *);
   1021 static void	f_files(char *);
   1022 static void	f_ibs(char *);
   1023 static void	f_if(char *);
   1024 static void	f_obs(char *);
   1025 static void	f_of(char *);
   1026 static void	f_seek(char *);
   1027 static void	f_skip(char *);
   1028 static void	f_progress(char *);
   1029 
   1030 static const struct arg {
   1031 	const char *name;
   1032 	void (*f)(char *);
   1033 	u_int set, noset;
   1034 } args[] = {
   1035      /* the array needs to be sorted by the first column so
   1036 	bsearch() can be used to find commands quickly */
   1037 	{ "bs",		f_bs,		C_BS,	 C_BS|C_IBS|C_OBS|C_OSYNC },
   1038 	{ "cbs",	f_cbs,		C_CBS,	 C_CBS },
   1039 	{ "conv",	f_conv,		0,	 0 },
   1040 	{ "count",	f_count,	C_COUNT, C_COUNT },
   1041 	{ "files",	f_files,	C_FILES, C_FILES },
   1042 	{ "ibs",	f_ibs,		C_IBS,	 C_BS|C_IBS },
   1043 	{ "if",		f_if,		C_IF,	 C_IF },
   1044 	{ "obs",	f_obs,		C_OBS,	 C_BS|C_OBS },
   1045 	{ "of",		f_of,		C_OF,	 C_OF },
   1046 	{ "progress",	f_progress,	0,	 0 },
   1047 	{ "seek",	f_seek,		C_SEEK,	 C_SEEK },
   1048 	{ "skip",	f_skip,		C_SKIP,	 C_SKIP },
   1049 };
   1050 
   1051 /*
   1052  * args -- parse JCL syntax of dd.
   1053  */
   1054 void
   1055 jcl(char **argv)
   1056 {
   1057 	struct arg *ap, tmp;
   1058 	char *oper, *arg;
   1059 
   1060 	in.dbsz = out.dbsz = 512;
   1061 
   1062 	while ((oper = *++argv) != NULL) {
   1063 		if ((arg = strchr(oper, '=')) == NULL) {
   1064 			fprintf(stderr, "unknown operand %s\n", oper);
   1065 			exit(1);
   1066 			/* NOTREACHED */
   1067 		}
   1068 		*arg++ = '\0';
   1069 		if (!*arg) {
   1070 			fprintf(stderr, "no value specified for %s\n", oper);
   1071 			exit(1);
   1072 			/* NOTREACHED */
   1073 		}
   1074 		tmp.name = oper;
   1075 		if (!(ap = (struct arg *)bsearch(&tmp, args,
   1076 		    sizeof(args)/sizeof(struct arg), sizeof(struct arg),
   1077 		    c_arg))) {
   1078 			fprintf(stderr, "unknown operand %s\n", tmp.name);
   1079 			exit(1);
   1080 			/* NOTREACHED */
   1081 		}
   1082 		if (ddflags & ap->noset) {
   1083 			fprintf(stderr,
   1084 			    "%s: illegal argument combination or already set\n",
   1085 			    tmp.name);
   1086 			exit(1);
   1087 			/* NOTREACHED */
   1088 		}
   1089 		ddflags |= ap->set;
   1090 		ap->f(arg);
   1091 	}
   1092 
   1093 	/* Final sanity checks. */
   1094 
   1095 	if (ddflags & C_BS) {
   1096 		/*
   1097 		 * Bs is turned off by any conversion -- we assume the user
   1098 		 * just wanted to set both the input and output block sizes
   1099 		 * and didn't want the bs semantics, so we don't warn.
   1100 		 */
   1101 		if (ddflags & (C_BLOCK | C_LCASE | C_SWAB | C_UCASE |
   1102 		    C_UNBLOCK | C_OSYNC | C_ASCII | C_EBCDIC | C_SPARSE)) {
   1103 			ddflags &= ~C_BS;
   1104 			ddflags |= C_IBS|C_OBS;
   1105 		}
   1106 
   1107 		/* Bs supersedes ibs and obs. */
   1108 		if (ddflags & C_BS && ddflags & (C_IBS|C_OBS))
   1109 			fprintf(stderr, "bs supersedes ibs and obs\n");
   1110 	}
   1111 
   1112 	/*
   1113 	 * Ascii/ebcdic and cbs implies block/unblock.
   1114 	 * Block/unblock requires cbs and vice-versa.
   1115 	 */
   1116 	if (ddflags & (C_BLOCK|C_UNBLOCK)) {
   1117 		if (!(ddflags & C_CBS)) {
   1118 			fprintf(stderr, "record operations require cbs\n");
   1119 			exit(1);
   1120 			/* NOTREACHED */
   1121 		}
   1122 		cfunc = ddflags & C_BLOCK ? block : unblock;
   1123 	} else if (ddflags & C_CBS) {
   1124 		if (ddflags & (C_ASCII|C_EBCDIC)) {
   1125 			if (ddflags & C_ASCII) {
   1126 				ddflags |= C_UNBLOCK;
   1127 				cfunc = unblock;
   1128 			} else {
   1129 				ddflags |= C_BLOCK;
   1130 				cfunc = block;
   1131 			}
   1132 		} else {
   1133 			fprintf(stderr,
   1134 			    "cbs meaningless if not doing record operations\n");
   1135 			exit(1);
   1136 			/* NOTREACHED */
   1137 		}
   1138 	} else
   1139 		cfunc = def;
   1140 
   1141 	/* Read, write and seek calls take off_t as arguments.
   1142 	 *
   1143 	 * The following check is not done because an off_t is a quad
   1144 	 *  for current NetBSD implementations.
   1145 	 *
   1146 	 * if (in.offset > INT_MAX/in.dbsz || out.offset > INT_MAX/out.dbsz)
   1147 	 *	errx(1, "seek offsets cannot be larger than %d", INT_MAX);
   1148 	 */
   1149 }
   1150 
   1151 static int
   1152 c_arg(const void *a, const void *b)
   1153 {
   1154 
   1155 	return (strcmp(((const struct arg *)a)->name,
   1156 	    ((const struct arg *)b)->name));
   1157 }
   1158 
   1159 static long long strsuftoll(const char* name, const char* arg, int def, unsigned int max)
   1160 {
   1161 	long long result;
   1162 
   1163 	if (sscanf(arg, "%lld", &result) == 0)
   1164 		result = def;
   1165 	return result;
   1166 }
   1167 
   1168 static void
   1169 f_bs(char *arg)
   1170 {
   1171 
   1172 	in.dbsz = out.dbsz = strsuftoll("block size", arg, 1, UINT_MAX);
   1173 }
   1174 
   1175 static void
   1176 f_cbs(char *arg)
   1177 {
   1178 
   1179 	cbsz = strsuftoll("conversion record size", arg, 1, UINT_MAX);
   1180 }
   1181 
   1182 static void
   1183 f_count(char *arg)
   1184 {
   1185 
   1186 	cpy_cnt = strsuftoll("block count", arg, 0, LLONG_MAX);
   1187 	if (!cpy_cnt)
   1188 		terminate(0);
   1189 }
   1190 
   1191 static void
   1192 f_files(char *arg)
   1193 {
   1194 
   1195 	files_cnt = (u_int)strsuftoll("file count", arg, 0, UINT_MAX);
   1196 	if (!files_cnt)
   1197 		terminate(0);
   1198 }
   1199 
   1200 static void
   1201 f_ibs(char *arg)
   1202 {
   1203 
   1204 	if (!(ddflags & C_BS))
   1205 		in.dbsz = strsuftoll("input block size", arg, 1, UINT_MAX);
   1206 }
   1207 
   1208 static void
   1209 f_if(char *arg)
   1210 {
   1211 
   1212 	in.name = arg;
   1213 }
   1214 
   1215 static void
   1216 f_obs(char *arg)
   1217 {
   1218 
   1219 	if (!(ddflags & C_BS))
   1220 		out.dbsz = strsuftoll("output block size", arg, 1, UINT_MAX);
   1221 }
   1222 
   1223 static void
   1224 f_of(char *arg)
   1225 {
   1226 
   1227 	out.name = arg;
   1228 }
   1229 
   1230 static void
   1231 f_seek(char *arg)
   1232 {
   1233 
   1234 	out.offset = strsuftoll("seek blocks", arg, 0, LLONG_MAX);
   1235 }
   1236 
   1237 static void
   1238 f_skip(char *arg)
   1239 {
   1240 
   1241 	in.offset = strsuftoll("skip blocks", arg, 0, LLONG_MAX);
   1242 }
   1243 
   1244 static void
   1245 f_progress(char *arg)
   1246 {
   1247 
   1248 	if (*arg != '0')
   1249 		progress = 1;
   1250 }
   1251 
   1252 #ifdef	NO_CONV
   1253 /* Build a small version (i.e. for a ramdisk root) */
   1254 static void
   1255 f_conv(char *arg)
   1256 {
   1257 
   1258 	fprintf(stderr, "conv option disabled\n");
   1259 	exit(1);
   1260 	/* NOTREACHED */
   1261 }
   1262 #else	/* NO_CONV */
   1263 
   1264 static const struct conv {
   1265 	const char *name;
   1266 	u_int set, noset;
   1267 	const u_char *ctab;
   1268 } clist[] = {
   1269 	{ "block",	C_BLOCK,	C_UNBLOCK,	NULL },
   1270 	{ "fdatasync",	C_FDATASYNC,	0,		NULL },
   1271 	{ "noerror",	C_NOERROR,	0,		NULL },
   1272 	{ "notrunc",	C_NOTRUNC,	0,		NULL },
   1273 	{ "osync",	C_OSYNC,	C_BS,		NULL },
   1274 	{ "sparse",	C_SPARSE,	0,		NULL },
   1275 	{ "swab",	C_SWAB,		0,		NULL },
   1276 	{ "sync",	C_SYNC,		0,		NULL },
   1277 	{ "unblock",	C_UNBLOCK,	C_BLOCK,	NULL },
   1278 	/* If you add items to this table, be sure to add the
   1279 	 * conversions to the C_BS check in the jcl routine above.
   1280 	 */
   1281 };
   1282 
   1283 static void
   1284 f_conv(char *arg)
   1285 {
   1286 	struct conv *cp, tmp;
   1287 
   1288 	while (arg != NULL) {
   1289 		tmp.name = strsep(&arg, ",");
   1290 		if (!(cp = (struct conv *)bsearch(&tmp, clist,
   1291 		    sizeof(clist)/sizeof(struct conv), sizeof(struct conv),
   1292 		    c_conv))) {
   1293 			errx(EXIT_FAILURE, "unknown conversion %s", tmp.name);
   1294 			/* NOTREACHED */
   1295 		}
   1296 		if (ddflags & cp->noset) {
   1297 			errx(EXIT_FAILURE, "%s: illegal conversion combination", tmp.name);
   1298 			/* NOTREACHED */
   1299 		}
   1300 		ddflags |= cp->set;
   1301 		if (cp->ctab)
   1302 			ctab = cp->ctab;
   1303 	}
   1304 }
   1305 
   1306 static int
   1307 c_conv(const void *a, const void *b)
   1308 {
   1309 
   1310 	return (strcmp(((const struct conv *)a)->name,
   1311 	    ((const struct conv *)b)->name));
   1312 }
   1313 
   1314 #endif	/* NO_CONV */
   1315 
   1316 
   1317