xref: /illumos-gate/usr/src/cmd/od/od.c (revision d15bebc723be0341b7bc9fbd45a2e5e18fdfc9ae)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
14  */
15 
16 /*
17  * od - octal dump.  Not really just octal anymore; read the POSIX
18  * specification for it -- its more complex than you think!
19  *
20  * NB: We followed the POSIX semantics fairly strictly, where the
21  * legacy code's behavior was in conflict.  In many cases the legacy
22  * Solaris code was so completely broken as to be completely unusable.
23  * (For example, the long double support was broken beyond
24  * imagination!)  Note that GNU coreutils violates POSIX in a few
25  * interesting ways, such as changing the numbering of the addresses
26  * when skipping.  (Address starts should always be at 0, according to
27  * the sample output in the Open Group man page.)
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <string.h>
34 #include <err.h>
35 #include <wchar.h>
36 #include <locale.h>
37 #include <unistd.h>
38 #include <sys/stat.h>
39 
40 #define	_(x)	gettext(x)
41 
42 
43 #ifndef TEXT_DOMAIN
44 #define	TEXT_DOMAIN	"SYS_TEST"
45 #endif
46 
47 /* address format */
48 static char *afmt  =	"%07llo";
49 static char *cfmt  =    "       ";
50 
51 static FILE *input = NULL;
52 static size_t lcm = 1;
53 static size_t blocksize = 16;
54 static int numfiles = 0;
55 static int curfile = 0;
56 static char **files = NULL;
57 static off_t limit = -1;
58 
59 /*
60  * This structure describes our ring buffer.  Its always a power of 2
61  * in size to make wrap around calculations fast using a mask instead
62  * of doing modulo.
63  *
64  * The size is calculated thusly: We need three "blocks" of data, as
65  * we process a block at a time (one block == one line of od output.)
66  *
67  * We need lookahead of an extra block to support multibyte chars.  We
68  * also have a look behind so that we can avoid printing lines that
69  * are identical to what we've already printed.  Finally, we need the
70  * current block.
71  *
72  * The block size is determined by the least common multiple of the
73  * data items being displayed.  Usually it will be 16, but sometimes
74  * it is 24 (when 12-byte long doubles are presented.)
75  *
76  * The data buffer is allocaed via memalign to make sure it is
77  * properly aligned.
78  */
79 typedef struct buffer {
80 	char	*data;		/* data buffer */
81 	int	prod;		/* producer index */
82 	int	cons;		/* consumer index */
83 	int	mask;		/* buffer size - 1, wraparound index */
84 	int	navail;		/* total bytes avail */
85 } buffer_t;
86 
87 /*
88  * This structure is used to provide information on a specific output
89  * format.  We link them together in a list representing the output
90  * formats that the user has selected.
91  */
92 typedef struct output {
93 	int	width;				/* bytes consumed per call */
94 	void	(*func)(buffer_t *, int);	/* output function */
95 	struct output	*next;			/* link node */
96 } output_t;
97 
98 /*
99  * Specifiers
100  */
101 
102 typedef unsigned char		u8;
103 typedef unsigned short		u16;
104 typedef unsigned int		u32;
105 typedef unsigned long long	u64;
106 typedef char			s8;
107 typedef short			s16;
108 typedef int			s32;
109 typedef long long		s64;
110 typedef float			fF;
111 typedef	double			fD;
112 typedef long double		fL;
113 
114 static void
115 usage(void)
116 {
117 	(void) fprintf(stderr, _("usage: od [-bcCdDfFoOsSvxX] "
118 	    "[-t types ]... [-A base] [-j skip] [-N count] [file]...\n"));
119 	exit(1);
120 }
121 
122 #define	DECL_GET(typ)							\
123 static typ								\
124 get_ ## typ(buffer_t *b, int index)					\
125 {									\
126 	typ val = *(typ *)(void *)(b->data + index);			\
127 	return (val);							\
128 }
129 DECL_GET(u8)
130 DECL_GET(u16)
131 DECL_GET(u32)
132 DECL_GET(u64)
133 DECL_GET(s8)
134 DECL_GET(s16)
135 DECL_GET(s32)
136 DECL_GET(s64)
137 DECL_GET(fF)
138 DECL_GET(fD)
139 DECL_GET(fL)
140 
141 #define	DECL_OUT(nm, typ, fmt)					\
142 static void							\
143 do_ ## nm(buffer_t *buf, int index)				\
144 {								\
145 	typ v = get_ ## typ(buf, index);			\
146 	(void) printf(fmt, v);					\
147 }								\
148 								\
149 static output_t output_ ## nm =  {				\
150 	sizeof (typ), do_ ## nm					\
151 };
152 
153 DECL_OUT(oct_b, u8, " %03o")
154 DECL_OUT(oct_w, u16, " %06ho")
155 DECL_OUT(oct_d, u32, " %011o")
156 DECL_OUT(oct_q, u64, " %022llo")
157 DECL_OUT(dec_b, u8, " %03u")
158 DECL_OUT(dec_w, u16, " %05hu")
159 DECL_OUT(dec_d, u32, " %010u")
160 DECL_OUT(dec_q, u64, " %020llu")
161 DECL_OUT(sig_b, s8, " %03d")
162 DECL_OUT(sig_w, s16, " %6.05hd")
163 DECL_OUT(sig_d, s32, " %11.010d")
164 DECL_OUT(sig_q, s64, " %20.019lld")
165 DECL_OUT(hex_b, u8, " %02x")
166 DECL_OUT(hex_w, u16, " %04hx")
167 DECL_OUT(hex_d, s32, " %08x")
168 DECL_OUT(hex_q, s64, " %016llx")
169 DECL_OUT(float, fF, " %14.7e")
170 DECL_OUT(double, fD, " %21.14e")
171 DECL_OUT(ldouble, fL, " %24.14Le")
172 
173 static char *ascii[] = {
174 	"nul", "soh", "stx", "etx", "eot", "enq", "ack", " be",
175 	" bs", " ht", " lf", " vt", " ff", " cr", " so", " si",
176 	"dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
177 	"can", " em", "sub", "esc", " fs", " gs", " rs", " us",
178 	" sp", "  !", "  \"", "  #", "  $", "  %", "  &", "  '",
179 	"  (", "  )", "  *", "  +", "  ,", "  -", "  .", "  /",
180 	"  0", "  1", "  2", "  3", "  4", "  5", "  6", "  7",
181 	"  8", "  9", "  :", "  ;", "  <", "  =", "  >", "  ?",
182 	"  @", "  A", "  B", "  C", "  D", "  E", "  F", "  G",
183 	"  H", "  I", "  J", "  K", "  L", "  M", "  N", "  O",
184 	"  P", "  Q", "  R", "  S", "  T", "  U", "  V", "  W",
185 	"  X", "  Y", "  Z", "  [", "  \\", "  ]", "  ^", "  _",
186 	"  `", "  a", "  b", "  c", "  d", "  e", "  f", "  g",
187 	"  h", "  i", "  j", "  k", "  l", "  m", "  n", "  o",
188 	"  p", "  q", "  r", "  s", "  t", "  u", "  v", "  w",
189 	"  x", "  y", "  z", "  {", "  |", "  }", "  ~", "del"
190 };
191 
192 static void
193 do_ascii(buffer_t *buf, int index)
194 {
195 	uint8_t v = get_u8(buf, index);
196 
197 	(void) fputc(' ', stdout);
198 	(void) fputs(ascii[v & 0x7f], stdout);
199 }
200 
201 static output_t output_ascii = {
202 	1, do_ascii,
203 };
204 
205 static void
206 do_char(buffer_t *buf, int index)
207 {
208 	static int	nresid = 0;
209 	static int	printable = 0;
210 	int		cnt;
211 	int		avail;
212 	int		nb;
213 	char		scratch[10];
214 	wchar_t		wc;
215 	int		which;
216 
217 	uint8_t v = get_u8(buf, index);
218 
219 	/*
220 	 * If there were residual bytes from an earlier
221 	 * character, then just display the ** continuation
222 	 * indication.
223 	 */
224 	if (nresid) {
225 		if (printable) {
226 			(void) fputs("  **", stdout);
227 		} else {
228 			(void) printf(" %03o", v);
229 		}
230 		nresid--;
231 		return;
232 	}
233 
234 	/*
235 	 * Peek ahead up to MB_CUR_MAX characters.  This has to be
236 	 * done carefully because we might need to look into the next
237 	 * block to really know for sure.
238 	 */
239 	scratch[0] = v;
240 	avail = buf->navail;
241 	if (avail > MB_CUR_MAX)
242 		avail = MB_CUR_MAX;
243 	for (cnt = 1, which = index + 1; cnt < avail; cnt++, which++) {
244 		scratch[cnt] = buf->data[which & buf->mask];
245 	}
246 
247 	/* now see if the value is a real character */
248 	nresid = 0;
249 	wc = 0;
250 	nb = mbtowc(&wc, scratch, avail);
251 	if (nb < 0) {
252 		(void) printf(" %03o", v);
253 		return;
254 	}
255 	if (nb == 0) {
256 		(void) fputs("  \\0", stdout);
257 		return;
258 	}
259 	nresid = nb - 1;
260 	if (nb && iswprint(wc)) {
261 		scratch[nb] = 0;
262 		(void) fputs("   ", stdout);
263 		(void) fputs(scratch, stdout);
264 		printable = 1;
265 		return;
266 	}
267 	printable = 0;
268 	if (wc == 0) {
269 		(void) fputs("  \\0", stdout);
270 	} else if (wc == '\b') {
271 		(void) fputs("  \\b", stdout);
272 	} else if (wc == '\f') {
273 		(void) fputs("  \\f", stdout);
274 	} else if (wc == '\n') {
275 		(void) fputs("  \\n", stdout);
276 	} else if (wc == '\r') {
277 		(void) fputs("  \\r", stdout);
278 	} else if (wc == '\t') {
279 		(void) fputs("  \\t", stdout);
280 	} else {
281 		(void) printf(" %03o", v);
282 	}
283 }
284 
285 static output_t output_char = {
286 	1, do_char,
287 };
288 
289 /*
290  * List of output formatting structures.
291  */
292 static output_t *head = NULL;
293 static output_t **tailp = &head;
294 
295 static void
296 add_out(output_t *src)
297 {
298 	output_t	*out;
299 	int		m;
300 
301 	if ((out = calloc(1, sizeof (*src))) == NULL) {
302 		err(1, "malloc");
303 	}
304 
305 	m = lcm;
306 	while ((m % src->width) != 0) {
307 		m += lcm;
308 	}
309 	lcm = m;
310 	blocksize = lcm;
311 	while (blocksize < 16)
312 		blocksize *= 2;
313 
314 	(void) memcpy(out, src, sizeof (*src));
315 	*tailp = out;
316 	tailp = &out->next;
317 }
318 
319 static FILE *
320 next_input(void)
321 {
322 	for (;;) {
323 		if (curfile >= numfiles)
324 			return (NULL);
325 
326 		if (input != NULL) {
327 			if ((input = freopen(files[curfile], "r", input)) !=
328 			    NULL) {
329 				curfile++;
330 				return (input);
331 			}
332 		} else {
333 			if ((input = fopen(files[curfile], "r")) != NULL) {
334 				curfile++;
335 				return (input);
336 			}
337 		}
338 		warn("open: %s", files[curfile]);
339 		curfile++;
340 	}
341 }
342 
343 static void
344 refill(buffer_t *b)
345 {
346 	int	n;
347 	int	want;
348 	int	zero;
349 
350 	/*
351 	 * If we have 2 blocks of bytes available, we're done.  Note
352 	 * that each iteration usually loads up 16 bytes, unless we
353 	 * run out of data.
354 	 */
355 	while ((input != NULL) && (b->navail < (2 * blocksize))) {
356 
357 		/* we preload the next one in advance */
358 
359 		if (limit == 0) {
360 			(void) fclose(input);
361 			input = NULL;
362 			continue;
363 		}
364 
365 		/* we want to read a whole block if possible */
366 		want = blocksize;
367 		if ((limit >= 0) && (want > limit)) {
368 			want = limit;
369 		}
370 		zero = blocksize;
371 
372 		while (want && input) {
373 			int	c;
374 			b->prod &= b->mask;
375 			c = (b->prod + want > (b->mask + 1)) ?
376 			    b->mask - b->prod :
377 			    want;
378 
379 			n = fread(b->data + b->prod, 1, c, input);
380 			if (n < 0) {
381 				warn("read: %s",
382 				    files ? files[curfile-1] : "stdin");
383 				input = next_input();
384 				continue;
385 			}
386 			if (n == 0) {
387 				input = next_input();
388 				continue;
389 			}
390 			if (limit >= 0)
391 				limit -= n;
392 			b->navail += n;
393 			b->prod += n;
394 			want -= n;
395 			zero -= n;
396 		}
397 
398 		while (zero) {
399 			b->data[b->prod & b->mask] = 0;
400 			b->prod++;
401 			b->prod &= b->mask;
402 			zero--;
403 		}
404 	}
405 }
406 
407 #define	STR1	"C1"
408 #define	STR2	"S2"
409 #ifdef	_LP64
410 #define	STR8	"L8"
411 #define	STR4	"I4"
412 #else
413 #define	STR8	"8"
414 #define	STR4	"IL4"
415 #endif
416 
417 static void
418 do_type_string(char *typestr)
419 {
420 	if (*typestr == 0) {
421 		errx(1, _("missing type string"));
422 	}
423 	while (*typestr) {
424 		switch (*typestr) {
425 		case 'a':
426 			typestr++;
427 			add_out(&output_ascii);
428 			break;
429 		case 'c':
430 			add_out(&output_char);
431 			typestr++;
432 			break;
433 		case 'f':
434 			typestr++;
435 			switch (*typestr) {
436 			case 'F':
437 			case '4':
438 				add_out(&output_float);
439 				typestr++;
440 				break;
441 			case '8':
442 			case 'D':
443 				add_out(&output_double);
444 				typestr++;
445 				break;
446 			case 'L':
447 				add_out(&output_ldouble);
448 				typestr++;
449 				break;
450 			default:
451 				add_out(&output_float);
452 				break;
453 			}
454 			break;
455 
456 
457 		case 'd':
458 			typestr++;
459 			if (strchr(STR1, *typestr)) {
460 				typestr++;
461 				add_out(&output_sig_b);
462 			} else if (strchr(STR2, *typestr)) {
463 				typestr++;
464 				add_out(&output_sig_w);
465 			} else if (strchr(STR4, *typestr)) {
466 				typestr++;
467 				add_out(&output_sig_d);
468 			} else if (strchr(STR8, *typestr)) {
469 				typestr++;
470 				add_out(&output_sig_q);
471 			} else {
472 				add_out(&output_sig_d);
473 			}
474 			break;
475 
476 		case 'u':
477 			typestr++;
478 			if (strchr(STR1, *typestr)) {
479 				typestr++;
480 				add_out(&output_dec_b);
481 			} else if (strchr(STR2, *typestr)) {
482 				typestr++;
483 				add_out(&output_dec_w);
484 			} else if (strchr(STR4, *typestr)) {
485 				typestr++;
486 				add_out(&output_dec_d);
487 			} else if (strchr(STR8, *typestr)) {
488 				typestr++;
489 				add_out(&output_dec_q);
490 			} else {
491 				add_out(&output_dec_d);
492 			}
493 			break;
494 
495 		case 'o':
496 			typestr++;
497 			if (strchr(STR1, *typestr)) {
498 				typestr++;
499 				add_out(&output_oct_b);
500 			} else if (strchr(STR2, *typestr)) {
501 				typestr++;
502 				add_out(&output_oct_w);
503 			} else if (strchr(STR4, *typestr)) {
504 				typestr++;
505 				add_out(&output_oct_d);
506 			} else if (strchr(STR8, *typestr)) {
507 				typestr++;
508 				add_out(&output_oct_q);
509 			} else {
510 				add_out(&output_oct_d);
511 			}
512 			break;
513 
514 		case 'x':
515 			typestr++;
516 			if (strchr(STR1, *typestr)) {
517 				typestr++;
518 				add_out(&output_hex_b);
519 			} else if (strchr(STR2, *typestr)) {
520 				typestr++;
521 				add_out(&output_hex_w);
522 			} else if (strchr(STR4, *typestr)) {
523 				typestr++;
524 				add_out(&output_hex_d);
525 			} else if (strchr(STR8, *typestr)) {
526 				typestr++;
527 				add_out(&output_hex_q);
528 			} else {
529 				add_out(&output_hex_d);
530 			}
531 			break;
532 
533 		default:
534 			errx(1, _("unrecognized type string character: %c"),
535 			    *typestr);
536 			exit(1);
537 		}
538 	}
539 }
540 
541 int
542 main(int argc, char **argv)
543 {
544 	int		c;
545 	int		i;
546 	buffer_t	buffer;
547 	boolean_t	first = B_TRUE;
548 	boolean_t	doall = B_FALSE;
549 	boolean_t	same = B_FALSE;
550 	boolean_t	newarg = B_FALSE;
551 	off_t		offset = 0;
552 	off_t		skip = 0;
553 	char		*eptr;
554 	char		*offstr = 0;
555 
556 	input = stdin;
557 
558 	(void) setlocale(LC_ALL, "");
559 	(void) textdomain(TEXT_DOMAIN);
560 
561 	while ((c = getopt(argc, argv, "A:bCcdDfFj:N:oOsSxXvt:")) != EOF) {
562 		switch (c) {
563 		case 'A':
564 			newarg = B_TRUE;
565 			if (strlen(optarg) > 1) {
566 				afmt = NULL;
567 			}
568 			switch (*optarg) {
569 			case 'o':
570 				afmt = "%07llo";
571 				cfmt = "       ";
572 				break;
573 			case 'd':
574 				afmt = "%07lld";
575 				cfmt = "       ";
576 				break;
577 			case 'x':
578 				afmt = "%07llx";
579 				cfmt = "       ";
580 				break;
581 			case 'n':
582 				/*
583 				 * You could argue that the code should
584 				 * use the same 7 spaces.  Legacy uses 8
585 				 * though.  Oh well.  Better to avoid
586 				 * gratuitous change.
587 				 */
588 				afmt = "        ";
589 				cfmt = "        ";
590 				break;
591 			default:
592 				afmt = NULL;
593 				break;
594 			}
595 			if (strlen(optarg) != 1) {
596 				afmt = NULL;
597 			}
598 			if (afmt == NULL)
599 				warnx(_("invalid address base, "
600 				    "must be o, d, x, or n"));
601 			break;
602 
603 		case 'b':
604 			add_out(&output_oct_b);
605 			break;
606 
607 		case 'c':
608 		case 'C':
609 			add_out(&output_char);
610 			break;
611 
612 		case 'f':
613 			add_out(&output_float);
614 			break;
615 
616 		case 'F':
617 			add_out(&output_double);
618 			break;
619 
620 		case 'd':
621 			add_out(&output_dec_w);
622 			break;
623 
624 		case 'D':
625 			add_out(&output_dec_d);
626 			break;
627 
628 		case 't':
629 			newarg = B_TRUE;
630 			do_type_string(optarg);
631 			break;
632 
633 		case 'o':
634 			add_out(&output_oct_w);
635 			break;
636 
637 		case 'O':
638 			add_out(&output_oct_d);
639 			break;
640 
641 		case 's':
642 			add_out(&output_sig_w);
643 			break;
644 
645 		case 'S':
646 			add_out(&output_sig_d);
647 			break;
648 
649 		case 'x':
650 			add_out(&output_hex_w);
651 			break;
652 
653 		case 'X':
654 			add_out(&output_hex_d);
655 			break;
656 
657 		case 'v':
658 			doall = B_TRUE;
659 			break;
660 
661 		case 'j':
662 			newarg = B_TRUE;
663 			skip = strtoll(optarg, &eptr, 0);
664 			if (*eptr == 'b') {
665 				skip <<= 9;	/* 512 bytes */
666 				eptr++;
667 			} else if (*eptr == 'k') {
668 				skip <<= 10;	/* 1k */
669 				eptr++;
670 			} else if (*eptr == 'm') {
671 				skip <<= 20;	/* 1m */
672 				eptr++;
673 			} else if (*eptr == 'g') {
674 				skip <<= 30;	/* 1g */
675 				eptr++;
676 			}
677 			if ((skip < 0) || (eptr[0] != 0)) {
678 				warnx(_("invalid skip count '%s' specified"),
679 				    optarg);
680 				exit(1);
681 			}
682 			break;
683 
684 		case 'N':
685 			newarg = B_TRUE;
686 			limit = strtoll(optarg, &eptr, 0);
687 			/*
688 			 * POSIX doesn't specify this, but I think these
689 			 * may be helpful.
690 			 */
691 			if (*eptr == 'b') {
692 				limit <<= 9;
693 				eptr++;
694 			} else if (*eptr == 'k') {
695 				limit <<= 10;
696 				eptr++;
697 			} else if (*eptr == 'm') {
698 				limit <<= 20;
699 				eptr++;
700 			} else if (*eptr == 'g') {
701 				limit <<= 30;
702 				eptr++;
703 			}
704 			if ((limit < 0) || (eptr[0] != 0)) {
705 				warnx(_("invalid byte count '%s' specified"),
706 				    optarg);
707 				exit(1);
708 			}
709 			break;
710 
711 		default:
712 			usage();
713 			break;
714 		}
715 	}
716 
717 	/* this finds the smallest power of two size we can use */
718 	buffer.mask = (1 << (ffs(blocksize * 3) + 1)) - 1;
719 	buffer.data = memalign(16, buffer.mask + 1);
720 	if (buffer.data == NULL) {
721 		err(1, "memalign");
722 	}
723 
724 
725 	/*
726 	 * Wow.  This option parsing is hideous.
727 	 *
728 	 * If the we've not seen a new option, and there is just one
729 	 * operand, if it starts with a "+", then treat it as an
730 	 * offset.  Otherwise if two operands, and the second operand
731 	 * starts with + or a digit, then it is an offset.
732 	 */
733 	if (!newarg) {
734 		if (((argc - optind) == 1) && (argv[optind][0] == '+')) {
735 			offstr = argv[optind];
736 			argc--;
737 		} else if (((argc - optind) == 2) &&
738 		    (strchr("+0123456789", (argv[optind + 1][0])) != NULL)) {
739 			offstr = argv[optind + 1];
740 			argc--;
741 		}
742 	}
743 	if (offstr) {
744 		int base = 0;
745 		int mult = 1;
746 		int l;
747 		if (*offstr == '+') {
748 			offstr++;
749 		}
750 		l = strlen(offstr);
751 		if ((strncmp(offstr, "0x", 2) == 0)) {
752 			afmt = "%07llx";
753 			base = 16;
754 			offstr += 2;
755 			if (offstr[l - 1] == 'B') {
756 				offstr[l - 1] = 0;
757 				l--;
758 				mult = 512;
759 			}
760 		} else {
761 			base = 8;
762 			afmt = "%07llo";
763 			if ((offstr[l - 1] == 'B') || (offstr[l - 1] == 'b')) {
764 				offstr[l - 1] = 0;
765 				l--;
766 				mult = 512;
767 			}
768 			if (offstr[l - 1] == '.') {
769 				offstr[l - 1] = 0;
770 				base = 10;
771 				afmt = "%07lld";
772 			}
773 		}
774 		skip = strtoll(offstr, &eptr, base);
775 		if (*eptr != '\0') {
776 			errx(1, _("invalid offset string specified"));
777 		}
778 		skip *= mult;
779 		offset += skip;
780 	}
781 
782 	/*
783 	 * Allocate an array for all the input files.
784 	 */
785 	if (argc > optind) {
786 		files = calloc(sizeof (char *), argc - optind);
787 		for (i = 0; i < argc - optind; i++) {
788 			files[i] = argv[optind + i];
789 			numfiles++;
790 		}
791 		input = next_input();
792 	} else {
793 		input = stdin;
794 	}
795 
796 	/*
797 	 * We need to seek ahead.  fseek would be faster.
798 	 */
799 	while (skip && (input != NULL)) {
800 		struct stat sbuf;
801 
802 		/*
803 		 * Only fseek() on regular files.  (Others
804 		 * we have to read().
805 		 */
806 		if (fstat(fileno(input), &sbuf) < 0) {
807 			warn("fstat: %s", files[curfile-1]);
808 			input = next_input();
809 			continue;
810 		}
811 		if (S_ISREG(sbuf.st_mode)) {
812 			/*
813 			 * No point in seeking a file that is too
814 			 * short to begin with.
815 			 */
816 			if (sbuf.st_size < skip) {
817 				skip -= sbuf.st_size;
818 				input = next_input();
819 				continue;
820 			}
821 			if (fseeko(input, skip, SEEK_SET) < 0) {
822 				err(1, "fseek:%s", files[curfile-1]);
823 			}
824 			/* Done seeking. */
825 			skip = 0;
826 			break;
827 		}
828 
829 		/*
830 		 * fgetc seems like it would be slow, but it uses
831 		 * buffered I/O, so it should be fast enough.
832 		 */
833 		flockfile(input);
834 		while (skip) {
835 			if (getc_unlocked(input) == EOF) {
836 				funlockfile(input);
837 				if (ferror(input)) {
838 					warn("read: %s", files[curfile-1]);
839 				}
840 				input = next_input();
841 				if (input != NULL) {
842 					flockfile(input);
843 				}
844 				break;
845 			}
846 			skip--;
847 		}
848 		if (input != NULL)
849 			funlockfile(input);
850 	}
851 
852 	if (head == NULL) {
853 		add_out(&output_oct_w);
854 	}
855 
856 	buffer.navail = 0;
857 	buffer.prod = 0;
858 	buffer.cons = 0;
859 
860 	for (refill(&buffer); buffer.navail > 0; refill(&buffer)) {
861 		output_t *out;
862 		int	mx;
863 		int	j, k;
864 
865 		/*
866 		 * If this buffer was the same as last, then just
867 		 * dump an asterisk.
868 		 */
869 		if ((!first) && (buffer.navail >= blocksize) && (!doall)) {
870 			j = buffer.cons;
871 			k = j - blocksize;
872 			for (i = 0; i < blocksize; i++) {
873 				if (buffer.data[j & buffer.mask] !=
874 				    buffer.data[k & buffer.mask]) {
875 					break;
876 				}
877 				j++;
878 				k++;
879 			}
880 			if (i == blocksize) {
881 				if (!same) {
882 					(void) fputs("*\n", stdout);
883 					same = B_TRUE;
884 				}
885 				buffer.navail -= blocksize;
886 				offset += blocksize;
887 				buffer.cons += blocksize;
888 				buffer.cons &= buffer.mask;
889 				continue;
890 			}
891 		}
892 
893 		first = B_FALSE;
894 		same = B_FALSE;
895 		mx = (buffer.navail > blocksize) ? blocksize : buffer.navail;
896 
897 		for (out = head; out != NULL; out = out->next) {
898 
899 			if (out == head) {
900 				/*LINTED E_SEC_PRINTF_VAR_FMT*/
901 				(void) printf(afmt, offset);
902 			} else {
903 				(void) fputs(cfmt, stdout);
904 			}
905 			for (i = 0, j = buffer.cons; i < mx; i += out->width) {
906 				out->func(&buffer, j);
907 				j += out->width;
908 				j &= buffer.mask;
909 			}
910 			(void) fputs("\n", stdout);
911 		}
912 		buffer.cons += mx;
913 		buffer.cons &= buffer.mask;
914 		offset += mx;
915 		buffer.navail -= mx;
916 	}
917 	/*LINTED E_SEC_PRINTF_VAR_FMT*/
918 	(void) printf(afmt, offset);
919 	(void) fputs("\n", stdout);
920 	return (0);
921 }
922