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