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