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