]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/hexdecoct.c
b3ea05ce53e6d1b315edd429ee62027242d5aa6d
[thirdparty/systemd.git] / src / basic / hexdecoct.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <ctype.h>
9 #include <errno.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12
13 #include "alloc-util.h"
14 #include "hexdecoct.h"
15 #include "macro.h"
16 #include "string-util.h"
17 #include "util.h"
18
19 char octchar(int x) {
20 return '0' + (x & 7);
21 }
22
23 int unoctchar(char c) {
24
25 if (c >= '0' && c <= '7')
26 return c - '0';
27
28 return -EINVAL;
29 }
30
31 char decchar(int x) {
32 return '0' + (x % 10);
33 }
34
35 int undecchar(char c) {
36
37 if (c >= '0' && c <= '9')
38 return c - '0';
39
40 return -EINVAL;
41 }
42
43 char hexchar(int x) {
44 static const char table[16] = "0123456789abcdef";
45
46 return table[x & 15];
47 }
48
49 int unhexchar(char c) {
50
51 if (c >= '0' && c <= '9')
52 return c - '0';
53
54 if (c >= 'a' && c <= 'f')
55 return c - 'a' + 10;
56
57 if (c >= 'A' && c <= 'F')
58 return c - 'A' + 10;
59
60 return -EINVAL;
61 }
62
63 char *hexmem(const void *p, size_t l) {
64 const uint8_t *x;
65 char *r, *z;
66
67 z = r = new(char, l * 2 + 1);
68 if (!r)
69 return NULL;
70
71 for (x = p; x < (const uint8_t*) p + l; x++) {
72 *(z++) = hexchar(*x >> 4);
73 *(z++) = hexchar(*x & 15);
74 }
75
76 *z = 0;
77 return r;
78 }
79
80 static int unhex_next(const char **p, size_t *l) {
81 int r;
82
83 assert(p);
84 assert(l);
85
86 /* Find the next non-whitespace character, and decode it. We
87 * greedily skip all preceeding and all following whitespace. */
88
89 for (;;) {
90 if (*l == 0)
91 return -EPIPE;
92
93 if (!strchr(WHITESPACE, **p))
94 break;
95
96 /* Skip leading whitespace */
97 (*p)++, (*l)--;
98 }
99
100 r = unhexchar(**p);
101 if (r < 0)
102 return r;
103
104 for (;;) {
105 (*p)++, (*l)--;
106
107 if (*l == 0 || !strchr(WHITESPACE, **p))
108 break;
109
110 /* Skip following whitespace */
111 }
112
113 return r;
114 }
115
116 int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
117 _cleanup_free_ uint8_t *buf = NULL;
118 const char *x;
119 uint8_t *z;
120
121 assert(ret);
122 assert(ret_len);
123 assert(p || l == 0);
124
125 if (l == (size_t) -1)
126 l = strlen(p);
127
128 /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */
129 buf = malloc((l + 1) / 2 + 1);
130 if (!buf)
131 return -ENOMEM;
132
133 for (x = p, z = buf;;) {
134 int a, b;
135
136 a = unhex_next(&x, &l);
137 if (a == -EPIPE) /* End of string */
138 break;
139 if (a < 0)
140 return a;
141
142 b = unhex_next(&x, &l);
143 if (b < 0)
144 return b;
145
146 *(z++) = (uint8_t) a << 4 | (uint8_t) b;
147 }
148
149 *z = 0;
150
151 *ret_len = (size_t) (z - buf);
152 *ret = TAKE_PTR(buf);
153
154 return 0;
155 }
156
157 /* https://tools.ietf.org/html/rfc4648#section-6
158 * Notice that base32hex differs from base32 in the alphabet it uses.
159 * The distinction is that the base32hex representation preserves the
160 * order of the underlying data when compared as bytestrings, this is
161 * useful when representing NSEC3 hashes, as one can then verify the
162 * order of hashes directly from their representation. */
163 char base32hexchar(int x) {
164 static const char table[32] = "0123456789"
165 "ABCDEFGHIJKLMNOPQRSTUV";
166
167 return table[x & 31];
168 }
169
170 int unbase32hexchar(char c) {
171 unsigned offset;
172
173 if (c >= '0' && c <= '9')
174 return c - '0';
175
176 offset = '9' - '0' + 1;
177
178 if (c >= 'A' && c <= 'V')
179 return c - 'A' + offset;
180
181 return -EINVAL;
182 }
183
184 char *base32hexmem(const void *p, size_t l, bool padding) {
185 char *r, *z;
186 const uint8_t *x;
187 size_t len;
188
189 assert(p || l == 0);
190
191 if (padding)
192 /* five input bytes makes eight output bytes, padding is added so we must round up */
193 len = 8 * (l + 4) / 5;
194 else {
195 /* same, but round down as there is no padding */
196 len = 8 * l / 5;
197
198 switch (l % 5) {
199 case 4:
200 len += 7;
201 break;
202 case 3:
203 len += 5;
204 break;
205 case 2:
206 len += 4;
207 break;
208 case 1:
209 len += 2;
210 break;
211 }
212 }
213
214 z = r = malloc(len + 1);
215 if (!r)
216 return NULL;
217
218 for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) {
219 /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ
220 * x[3] == QQQQQQQQ; x[4] == WWWWWWWW */
221 *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */
222 *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
223 *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */
224 *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */
225 *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
226 *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */
227 *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */
228 *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */
229 }
230
231 switch (l % 5) {
232 case 4:
233 *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */
234 *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
235 *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */
236 *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */
237 *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
238 *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */
239 *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */
240 if (padding)
241 *(z++) = '=';
242
243 break;
244
245 case 3:
246 *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */
247 *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
248 *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */
249 *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */
250 *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */
251 if (padding) {
252 *(z++) = '=';
253 *(z++) = '=';
254 *(z++) = '=';
255 }
256
257 break;
258
259 case 2:
260 *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */
261 *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
262 *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */
263 *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */
264 if (padding) {
265 *(z++) = '=';
266 *(z++) = '=';
267 *(z++) = '=';
268 *(z++) = '=';
269 }
270
271 break;
272
273 case 1:
274 *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */
275 *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */
276 if (padding) {
277 *(z++) = '=';
278 *(z++) = '=';
279 *(z++) = '=';
280 *(z++) = '=';
281 *(z++) = '=';
282 *(z++) = '=';
283 }
284
285 break;
286 }
287
288 *z = 0;
289 return r;
290 }
291
292 int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) {
293 _cleanup_free_ uint8_t *r = NULL;
294 int a, b, c, d, e, f, g, h;
295 uint8_t *z;
296 const char *x;
297 size_t len;
298 unsigned pad = 0;
299
300 assert(p || l == 0);
301 assert(mem);
302 assert(_len);
303
304 if (l == (size_t) -1)
305 l = strlen(p);
306
307 /* padding ensures any base32hex input has input divisible by 8 */
308 if (padding && l % 8 != 0)
309 return -EINVAL;
310
311 if (padding) {
312 /* strip the padding */
313 while (l > 0 && p[l - 1] == '=' && pad < 7) {
314 pad++;
315 l--;
316 }
317 }
318
319 /* a group of eight input bytes needs five output bytes, in case of
320 * padding we need to add some extra bytes */
321 len = (l / 8) * 5;
322
323 switch (l % 8) {
324 case 7:
325 len += 4;
326 break;
327 case 5:
328 len += 3;
329 break;
330 case 4:
331 len += 2;
332 break;
333 case 2:
334 len += 1;
335 break;
336 case 0:
337 break;
338 default:
339 return -EINVAL;
340 }
341
342 z = r = malloc(len + 1);
343 if (!r)
344 return -ENOMEM;
345
346 for (x = p; x < p + (l / 8) * 8; x += 8) {
347 /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW
348 * e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */
349 a = unbase32hexchar(x[0]);
350 if (a < 0)
351 return -EINVAL;
352
353 b = unbase32hexchar(x[1]);
354 if (b < 0)
355 return -EINVAL;
356
357 c = unbase32hexchar(x[2]);
358 if (c < 0)
359 return -EINVAL;
360
361 d = unbase32hexchar(x[3]);
362 if (d < 0)
363 return -EINVAL;
364
365 e = unbase32hexchar(x[4]);
366 if (e < 0)
367 return -EINVAL;
368
369 f = unbase32hexchar(x[5]);
370 if (f < 0)
371 return -EINVAL;
372
373 g = unbase32hexchar(x[6]);
374 if (g < 0)
375 return -EINVAL;
376
377 h = unbase32hexchar(x[7]);
378 if (h < 0)
379 return -EINVAL;
380
381 *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
382 *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
383 *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */
384 *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
385 *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */
386 }
387
388 switch (l % 8) {
389 case 7:
390 a = unbase32hexchar(x[0]);
391 if (a < 0)
392 return -EINVAL;
393
394 b = unbase32hexchar(x[1]);
395 if (b < 0)
396 return -EINVAL;
397
398 c = unbase32hexchar(x[2]);
399 if (c < 0)
400 return -EINVAL;
401
402 d = unbase32hexchar(x[3]);
403 if (d < 0)
404 return -EINVAL;
405
406 e = unbase32hexchar(x[4]);
407 if (e < 0)
408 return -EINVAL;
409
410 f = unbase32hexchar(x[5]);
411 if (f < 0)
412 return -EINVAL;
413
414 g = unbase32hexchar(x[6]);
415 if (g < 0)
416 return -EINVAL;
417
418 /* g == 000VV000 */
419 if (g & 7)
420 return -EINVAL;
421
422 *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
423 *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
424 *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */
425 *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
426
427 break;
428 case 5:
429 a = unbase32hexchar(x[0]);
430 if (a < 0)
431 return -EINVAL;
432
433 b = unbase32hexchar(x[1]);
434 if (b < 0)
435 return -EINVAL;
436
437 c = unbase32hexchar(x[2]);
438 if (c < 0)
439 return -EINVAL;
440
441 d = unbase32hexchar(x[3]);
442 if (d < 0)
443 return -EINVAL;
444
445 e = unbase32hexchar(x[4]);
446 if (e < 0)
447 return -EINVAL;
448
449 /* e == 000SSSS0 */
450 if (e & 1)
451 return -EINVAL;
452
453 *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
454 *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
455 *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */
456
457 break;
458 case 4:
459 a = unbase32hexchar(x[0]);
460 if (a < 0)
461 return -EINVAL;
462
463 b = unbase32hexchar(x[1]);
464 if (b < 0)
465 return -EINVAL;
466
467 c = unbase32hexchar(x[2]);
468 if (c < 0)
469 return -EINVAL;
470
471 d = unbase32hexchar(x[3]);
472 if (d < 0)
473 return -EINVAL;
474
475 /* d == 000W0000 */
476 if (d & 15)
477 return -EINVAL;
478
479 *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
480 *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
481
482 break;
483 case 2:
484 a = unbase32hexchar(x[0]);
485 if (a < 0)
486 return -EINVAL;
487
488 b = unbase32hexchar(x[1]);
489 if (b < 0)
490 return -EINVAL;
491
492 /* b == 000YYY00 */
493 if (b & 3)
494 return -EINVAL;
495
496 *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
497
498 break;
499 case 0:
500 break;
501 default:
502 return -EINVAL;
503 }
504
505 *z = 0;
506
507 *mem = TAKE_PTR(r);
508 *_len = len;
509
510 return 0;
511 }
512
513 /* https://tools.ietf.org/html/rfc4648#section-4 */
514 char base64char(int x) {
515 static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
516 "abcdefghijklmnopqrstuvwxyz"
517 "0123456789+/";
518 return table[x & 63];
519 }
520
521 int unbase64char(char c) {
522 unsigned offset;
523
524 if (c >= 'A' && c <= 'Z')
525 return c - 'A';
526
527 offset = 'Z' - 'A' + 1;
528
529 if (c >= 'a' && c <= 'z')
530 return c - 'a' + offset;
531
532 offset += 'z' - 'a' + 1;
533
534 if (c >= '0' && c <= '9')
535 return c - '0' + offset;
536
537 offset += '9' - '0' + 1;
538
539 if (c == '+')
540 return offset;
541
542 offset++;
543
544 if (c == '/')
545 return offset;
546
547 return -EINVAL;
548 }
549
550 ssize_t base64mem(const void *p, size_t l, char **out) {
551 char *r, *z;
552 const uint8_t *x;
553
554 assert(p || l == 0);
555 assert(out);
556
557 /* three input bytes makes four output bytes, padding is added so we must round up */
558 z = r = malloc(4 * (l + 2) / 3 + 1);
559 if (!r)
560 return -ENOMEM;
561
562 for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
563 /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
564 *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
565 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
566 *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
567 *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */
568 }
569
570 switch (l % 3) {
571 case 2:
572 *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
573 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
574 *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */
575 *(z++) = '=';
576
577 break;
578 case 1:
579 *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
580 *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */
581 *(z++) = '=';
582 *(z++) = '=';
583
584 break;
585 }
586
587 *z = 0;
588 *out = r;
589 return z - r;
590 }
591
592 static int base64_append_width(
593 char **prefix, int plen,
594 const char *sep, int indent,
595 const void *p, size_t l,
596 int width) {
597
598 _cleanup_free_ char *x = NULL;
599 char *t, *s;
600 ssize_t slen, len, avail;
601 int line, lines;
602
603 len = base64mem(p, l, &x);
604 if (len <= 0)
605 return len;
606
607 lines = DIV_ROUND_UP(len, width);
608
609 slen = strlen_ptr(sep);
610 t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines);
611 if (!t)
612 return -ENOMEM;
613
614 memcpy_safe(t + plen, sep, slen);
615
616 for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) {
617 int act = MIN(width, avail);
618
619 if (line > 0 || sep) {
620 memset(s, ' ', indent);
621 s += indent;
622 }
623
624 memcpy(s, x + width * line, act);
625 s += act;
626 *(s++) = line < lines - 1 ? '\n' : '\0';
627 avail -= act;
628 }
629 assert(avail == 0);
630
631 *prefix = t;
632 return 0;
633 }
634
635 int base64_append(
636 char **prefix, int plen,
637 const void *p, size_t l,
638 int indent, int width) {
639
640 if (plen > width / 2 || plen + indent > width)
641 /* leave indent on the left, keep last column free */
642 return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1);
643 else
644 /* leave plen on the left, keep last column free */
645 return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1);
646 }
647
648 static int unbase64_next(const char **p, size_t *l) {
649 int ret;
650
651 assert(p);
652 assert(l);
653
654 /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We
655 * greedily skip all preceeding and all following whitespace. */
656
657 for (;;) {
658 if (*l == 0)
659 return -EPIPE;
660
661 if (!strchr(WHITESPACE, **p))
662 break;
663
664 /* Skip leading whitespace */
665 (*p)++, (*l)--;
666 }
667
668 if (**p == '=')
669 ret = INT_MAX; /* return padding as INT_MAX */
670 else {
671 ret = unbase64char(**p);
672 if (ret < 0)
673 return ret;
674 }
675
676 for (;;) {
677 (*p)++, (*l)--;
678
679 if (*l == 0)
680 break;
681 if (!strchr(WHITESPACE, **p))
682 break;
683
684 /* Skip following whitespace */
685 }
686
687 return ret;
688 }
689
690 int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) {
691 _cleanup_free_ uint8_t *buf = NULL;
692 const char *x;
693 uint8_t *z;
694 size_t len;
695
696 assert(p || l == 0);
697 assert(ret);
698 assert(ret_size);
699
700 if (l == (size_t) -1)
701 l = strlen(p);
702
703 /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra
704 * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */
705 len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0);
706
707 buf = malloc(len + 1);
708 if (!buf)
709 return -ENOMEM;
710
711 for (x = p, z = buf;;) {
712 int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
713
714 a = unbase64_next(&x, &l);
715 if (a == -EPIPE) /* End of string */
716 break;
717 if (a < 0)
718 return a;
719 if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
720 return -EINVAL;
721
722 b = unbase64_next(&x, &l);
723 if (b < 0)
724 return b;
725 if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
726 return -EINVAL;
727
728 c = unbase64_next(&x, &l);
729 if (c < 0)
730 return c;
731
732 d = unbase64_next(&x, &l);
733 if (d < 0)
734 return d;
735
736 if (c == INT_MAX) { /* Padding at the third character */
737
738 if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
739 return -EINVAL;
740
741 /* b == 00YY0000 */
742 if (b & 15)
743 return -EINVAL;
744
745 if (l > 0) /* Trailing rubbish? */
746 return -ENAMETOOLONG;
747
748 *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
749 break;
750 }
751
752 if (d == INT_MAX) {
753 /* c == 00ZZZZ00 */
754 if (c & 3)
755 return -EINVAL;
756
757 if (l > 0) /* Trailing rubbish? */
758 return -ENAMETOOLONG;
759
760 *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
761 *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
762 break;
763 }
764
765 *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
766 *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
767 *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */
768 }
769
770 *z = 0;
771
772 *ret_size = (size_t) (z - buf);
773 *ret = TAKE_PTR(buf);
774
775 return 0;
776 }
777
778 void hexdump(FILE *f, const void *p, size_t s) {
779 const uint8_t *b = p;
780 unsigned n = 0;
781
782 assert(b || s == 0);
783
784 if (!f)
785 f = stdout;
786
787 while (s > 0) {
788 size_t i;
789
790 fprintf(f, "%04x ", n);
791
792 for (i = 0; i < 16; i++) {
793
794 if (i >= s)
795 fputs(" ", f);
796 else
797 fprintf(f, "%02x ", b[i]);
798
799 if (i == 7)
800 fputc(' ', f);
801 }
802
803 fputc(' ', f);
804
805 for (i = 0; i < 16; i++) {
806
807 if (i >= s)
808 fputc(' ', f);
809 else
810 fputc(isprint(b[i]) ? (char) b[i] : '.', f);
811 }
812
813 fputc('\n', f);
814
815 if (s < 16)
816 break;
817
818 n += 16;
819 b += 16;
820 s -= 16;
821 }
822 }