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