]> git.refcnt.org Git - colorize.git/blob - colorize.c
Skip invalid sequences correctly
[colorize.git] / colorize.c
1 /*
2 * colorize - Read text from standard input stream or file and print
3 * it colorized through use of ANSI escape sequences
4 *
5 * Copyright (c) 2011-2013 Steven Schubiger
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #define _POSIX_SOURCE
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <getopt.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <time.h>
35 #include <unistd.h>
36
37 #define DEBUG 0
38
39 #define str(arg) #arg
40 #define to_str(arg) str(arg)
41
42 #define streq(s1, s2) (strcmp (s1, s2) == 0)
43
44 #define xmalloc(size) malloc_wrap(size, __FILE__, __LINE__)
45 #define xrealloc(ptr, size) realloc_wrap(ptr, size, __FILE__, __LINE__)
46 #define free_null(ptr) free_wrap((void **)&ptr )
47 #define xstrdup(str) strdup_wrap(str, __FILE__, __LINE__)
48
49 #if !defined BUF_SIZE || BUF_SIZE <= 0
50 # undef BUF_SIZE
51 # define BUF_SIZE 4096 + 1
52 #endif
53
54 #define LF 0x01
55 #define CR 0x02
56
57 #define SKIP_LINE_ENDINGS(flags) (((flags) & CR) && ((flags) & LF) ? 2 : 1)
58
59 #define STACK_VAR(ptr) do { \
60 stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
61 } while (false);
62
63 #define RELEASE_VAR(ptr) do { \
64 release_var (vars_list, stacked_vars, (void **)&ptr); \
65 } while (false);
66
67 #if DEBUG
68 # define MEM_ALLOC_FAIL(file, line) do { \
69 fprintf (stderr, "Memory allocation failure in source file %s, line %d\n", file, line); \
70 exit (2); \
71 } while (false);
72 #else
73 # define MEM_ALLOC_FAIL(file, line) do { \
74 fprintf (stderr, "%s: memory allocation failure\n", program_name); \
75 exit (2); \
76 } while (false);
77 #endif
78
79 #define ABORT_TRACE() \
80 fprintf (stderr, "Aborting in source file %s, line %d\n", __FILE__, __LINE__); \
81 abort (); \
82
83 #define CHECK_COLORS_RANDOM(color1, color2) \
84 streq (color_names[color1]->name, "random") \
85 && (streq (color_names[color2]->name, "none") \
86 || streq (color_names[color2]->name, "default")) \
87
88 #define VERSION "0.48"
89
90 typedef unsigned short bool;
91
92 enum { false, true };
93
94 struct color_name {
95 char *name;
96 char *orig;
97 };
98
99 static struct color_name *color_names[3] = { NULL, NULL, NULL };
100
101 struct color {
102 const char *name;
103 const char *code;
104 };
105
106 static const struct color fg_colors[] = {
107 { "none", NULL },
108 { "black", "30m" },
109 { "red", "31m" },
110 { "green", "32m" },
111 { "yellow", "33m" },
112 { "blue", "34m" },
113 { "cyan", "35m" },
114 { "magenta", "36m" },
115 { "white", "37m" },
116 { "default", "39m" },
117 };
118 static const struct color bg_colors[] = {
119 { "none", NULL },
120 { "black", "40m" },
121 { "red", "41m" },
122 { "green", "42m" },
123 { "yellow", "43m" },
124 { "blue", "44m" },
125 { "cyan", "45m" },
126 { "magenta", "46m" },
127 { "white", "47m" },
128 { "default", "49m" },
129 };
130
131 enum fmts {
132 FMT_GENERIC,
133 FMT_COLOR,
134 FMT_RANDOM,
135 FMT_ERROR,
136 FMT_FILE
137 };
138 static const char *formats[] = {
139 "%s", /* generic */
140 "%s color '%s' %s", /* color */
141 "%s color '%s' %s '%s'", /* random */
142 "less than %d bytes %s", /* error */
143 "%s: %s", /* file */
144 };
145
146 enum { FOREGROUND, BACKGROUND };
147
148 static const struct {
149 struct color const *entries;
150 unsigned int count;
151 const char *desc;
152 } tables[] = {
153 { fg_colors, sizeof (fg_colors) / sizeof (struct color), "foreground" },
154 { bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" },
155 };
156
157 enum stream_mode { SCAN_FIRST = 1, SCAN_ALWAYS };
158
159 struct ending {
160 unsigned int flags;
161 const char newline[3];
162 };
163
164 static const struct ending endings[] = {
165 { CR & LF, "\r\n" },
166 { CR, "\r" },
167 { LF, "\n" },
168 };
169
170 static FILE *stream = NULL;
171
172 static unsigned int stacked_vars = 0;
173 static void **vars_list = NULL;
174
175 static bool clean = false;
176 static char *exclude = NULL;
177
178 static const char *program_name;
179
180 static void print_help (void);
181 static void print_version (void);
182 static void cleanup (void);
183 static void free_color_names (struct color_name **);
184 static void process_options (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
185 static void process_file_option (const char *, const char **, FILE **);
186 static void read_print_stream (bool, const struct color **, const char *, FILE *, enum stream_mode);
187 static void find_color_entries (struct color_name **, const struct color **);
188 static void find_color_entry (const char *const, unsigned int, const struct color **);
189 static void print_line (const struct color **, bool, const char * const, unsigned int);
190 static void print_clean (const char *);
191 static void print_free_offsets (const char *, char ***, unsigned int);
192 static void *malloc_wrap (size_t, const char *, unsigned int);
193 static void *realloc_wrap (void *, size_t, const char *, unsigned int);
194 static void free_wrap (void **);
195 static char *strdup_wrap (const char *, const char *, unsigned int);
196 static char *str_concat (const char *, const char *);
197 static void vfprintf_fail (const char *, ...);
198 static void stack_var (void ***, unsigned int *, unsigned int, void *);
199 static void release_var (void **, unsigned int, void **);
200
201 extern char *optarg;
202 extern int optind;
203
204 int
205 main (int argc, char **argv)
206 {
207 unsigned int arg_cnt = 0;
208
209 int opt;
210 struct option long_opts[] = {
211 { "clean", no_argument, NULL, 'c' },
212 { "exclude-random", required_argument, NULL, 'e' },
213 { "help", no_argument, NULL, 'h' },
214 { "version", no_argument, NULL, 'v' },
215 { 0, 0, 0, 0 },
216 };
217
218 bool bold = false;
219
220 const struct color *colors[2] = {
221 NULL, /* foreground */
222 NULL, /* background */
223 };
224
225 const char *file;
226
227 enum stream_mode mode = SCAN_FIRST;
228
229 program_name = argv[0];
230 atexit (cleanup);
231
232 setvbuf (stdout, NULL, _IOLBF, 0);
233
234 while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
235 {
236 switch (opt)
237 {
238 case 'c':
239 clean = true;
240 break;
241 case 'e': {
242 char *p;
243 exclude = xstrdup (optarg);
244 STACK_VAR (exclude);
245 for (p = exclude; *p; p++)
246 *p = tolower (*p);
247 if (streq (exclude, "random"))
248 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a color");
249 break;
250 }
251 case 'h':
252 print_help ();
253 exit (EXIT_SUCCESS);
254 case 'v':
255 print_version ();
256 exit (EXIT_SUCCESS);
257 case '?':
258 print_help ();
259 exit (EXIT_FAILURE);
260 default: /* never reached */
261 ABORT_TRACE ();
262 }
263 }
264
265 arg_cnt = argc - optind;
266
267 if (clean)
268 {
269 if (arg_cnt > 1)
270 vfprintf_fail (formats[FMT_GENERIC], "--clean switch cannot be used with more than one file");
271 }
272 else
273 {
274 if (arg_cnt == 0 || arg_cnt > 2)
275 {
276 print_help ();
277 exit (EXIT_FAILURE);
278 }
279 }
280
281 if (clean)
282 process_file_option (argv[optind], &file, &stream);
283 else
284 process_options (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
285 read_print_stream (bold, colors, file, stream, mode);
286
287 RELEASE_VAR (exclude);
288
289 exit (EXIT_SUCCESS);
290 }
291
292 static void
293 print_help (void)
294 {
295 unsigned int i;
296
297 printf ("Usage: %s (foreground) OR (foreground)/(background) OR --clean [-|file]\n\n", program_name);
298 printf ("\tColors (foreground) (background)\n");
299 for (i = 0; i < tables[FOREGROUND].count; i++)
300 {
301 const struct color *entry = &tables[FOREGROUND].entries[i];
302 const char *name = entry->name;
303 const char *code = entry->code;
304 if (code)
305 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
306 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
307 else
308 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
309 }
310 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
311
312 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
313 printf ("\twhereas for lower case colors will be of normal intensity.\n");
314
315 printf ("\n\tOptions\n");
316 printf ("\t\t --clean\n");
317 printf ("\t\t --exclude-random\n");
318 printf ("\t\t-h, --help\n");
319 printf ("\t\t-v, --version\n\n");
320 }
321
322 static void
323 print_version (void)
324 {
325 const char *c_flags;
326 printf ("%s v%s (compiled at %s, %s)\n", "colorize", VERSION, __DATE__, __TIME__);
327 #ifdef CFLAGS
328 c_flags = to_str (CFLAGS);
329 #else
330 c_flags = "unknown";
331 #endif
332 printf ("Compiler flags: %s\n", c_flags);
333 printf ("Buffer size: %d bytes\n", BUF_SIZE - 1);
334 }
335
336 static void
337 cleanup (void)
338 {
339 free_color_names (color_names);
340
341 if (stream && fileno (stream) != STDIN_FILENO)
342 fclose (stream);
343
344 if (vars_list)
345 {
346 unsigned int i;
347 for (i = 0; i < stacked_vars; i++)
348 if (vars_list[i])
349 free_null (vars_list[i]);
350 free_null (vars_list);
351 }
352 }
353
354 static void
355 free_color_names (struct color_name **color_names)
356 {
357 unsigned int i;
358 for (i = 0; color_names[i]; i++)
359 {
360 free_null (color_names[i]->name);
361 free_null (color_names[i]->orig);
362 free_null (color_names[i]);
363 }
364 }
365
366 static void
367 process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
368 {
369 int ret;
370 unsigned int index;
371 char *color, *p, *str;
372 struct stat sb;
373
374 const char *color_string = arg_cnt >= 1 ? option_strings[0] : NULL;
375 const char *file_string = arg_cnt == 2 ? option_strings[1] : NULL;
376
377 assert (color_string);
378
379 if (streq (color_string, "-"))
380 {
381 if (file_string)
382 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
383 else
384 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
385 }
386
387 ret = stat (color_string, &sb);
388
389 /* Ensure that we don't fail if there's a file with one or more
390 color names in its path. */
391 if (ret != -1)
392 {
393 bool have_file;
394 unsigned int c;
395 const char *color = color_string;
396
397 for (c = 1; c <= 2 && *color; c++)
398 {
399 bool matched = false;
400 unsigned int i;
401 for (i = 0; i < tables[FOREGROUND].count; i++)
402 {
403 const struct color *entry = &tables[FOREGROUND].entries[i];
404 char *p;
405 if ((p = strstr (color, entry->name)) && p == color)
406 {
407 color = p + strlen (entry->name);
408 matched = true;
409 break;
410 }
411 }
412 if (matched && *color == '/' && *(color + 1))
413 color++;
414 else
415 break;
416 }
417
418 have_file = (*color != '\0');
419
420 if (have_file)
421 {
422 if (file_string)
423 vfprintf_fail (formats[FMT_GENERIC], "file cannot be used as color string");
424 else
425 vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
426 }
427 }
428
429 if ((p = strchr (color_string, '/')))
430 {
431 if (p == color_string)
432 vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
433 else if (p == color_string + strlen (color_string) - 1)
434 vfprintf_fail (formats[FMT_GENERIC], "background color missing");
435 else if (strchr (++p, '/'))
436 vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
437 }
438
439 str = xstrdup (color_string);
440 STACK_VAR (str);
441
442 for (index = 0, color = str; *color; index++, color = p)
443 {
444 char *ch, *sep;
445 if ((sep = strchr (color, '/')))
446 {
447 *sep = '\0';
448 p = sep + 1;
449 }
450 else
451 p = color + strlen (color);
452
453 for (ch = color; *ch; ch++)
454 if (!isalpha (*ch))
455 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
456
457 for (ch = color + 1; *ch; ch++)
458 if (!islower (*ch))
459 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
460
461 if (streq (color, "None"))
462 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
463
464 if (isupper (*color))
465 {
466 switch (index)
467 {
468 case FOREGROUND:
469 *bold = true;
470 break;
471 case BACKGROUND:
472 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
473 break;
474 default: /* never reached */
475 ABORT_TRACE ();
476 }
477 }
478
479 color_names[index] = xmalloc (sizeof (struct color_name));
480
481 color_names[index]->orig = xstrdup (color);
482
483 for (ch = color; *ch; ch++)
484 *ch = tolower (*ch);
485
486 color_names[index]->name = xstrdup (color);
487 }
488
489 RELEASE_VAR (str);
490
491 assert (color_names[FOREGROUND]);
492
493 if (color_names[BACKGROUND])
494 {
495 unsigned int i;
496 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
497 for (i = 0; i < 2; i++)
498 {
499 unsigned int color1 = color_sets[i][0];
500 unsigned int color2 = color_sets[i][1];
501 if (CHECK_COLORS_RANDOM (color1, color2))
502 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
503 }
504 }
505
506 find_color_entries (color_names, colors);
507 free_color_names (color_names);
508
509 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
510 find_color_entry ("default", FOREGROUND, colors);
511
512 process_file_option (file_string, file, stream);
513 }
514
515 static void
516 process_file_option (const char *file_string, const char **file, FILE **stream)
517 {
518 if (file_string)
519 {
520 if (streq (file_string, "-"))
521 *stream = stdin;
522 else
523 {
524 FILE *s;
525 const char *file = file_string;
526 struct stat sb;
527 int errno, ret;
528
529 errno = 0;
530 ret = stat (file, &sb);
531
532 if (ret == -1)
533 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
534
535 if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
536 vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
537
538 errno = 0;
539
540 s = fopen (file, "r");
541 if (!s)
542 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
543 *stream = s;
544 }
545 *file = file_string;
546 }
547 else
548 {
549 *stream = stdin;
550 *file = "stdin";
551 }
552
553 assert (*stream);
554 }
555
556 #define MERGE_PRINT_LINE(part_line, line, flags) do { \
557 char *merged_line = NULL; \
558 if (part_line) \
559 { \
560 merged_line = str_concat (part_line, line); \
561 free_null (part_line); \
562 } \
563 print_line (colors, bold, merged_line ? merged_line : line, flags); \
564 free (merged_line); \
565 } while (false);
566
567 static void
568 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream, enum stream_mode mode)
569 {
570 char buf[BUF_SIZE], *part_line = NULL;
571 unsigned int flags = 0;
572 bool first = false, always = false;
573
574 switch (mode)
575 {
576 case SCAN_FIRST:
577 first = true;
578 break;
579 case SCAN_ALWAYS:
580 always = true;
581 break;
582 default: /* never reached */
583 ABORT_TRACE ();
584 }
585
586 while (!feof (stream))
587 {
588 size_t bytes_read;
589 char *eol;
590 const char *line;
591 memset (buf, '\0', BUF_SIZE);
592 bytes_read = fread (buf, 1, BUF_SIZE - 1, stream);
593 if (bytes_read != (BUF_SIZE - 1) && ferror (stream))
594 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE - 1, "read");
595 line = buf;
596 LOOP: while ((eol = strpbrk (line, "\n\r")))
597 {
598 char *p;
599 if (first || always)
600 {
601 first = false;
602 flags &= ~(CR|LF);
603 if (*eol == '\r')
604 {
605 flags |= CR;
606 if (*(eol + 1) == '\n')
607 flags |= LF;
608 }
609 else if (*eol == '\n')
610 flags |= LF;
611 else
612 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
613 }
614 if (always)
615 p = eol + SKIP_LINE_ENDINGS (flags);
616 else /* first */
617 {
618 unsigned int i;
619 unsigned int count = sizeof (endings) / sizeof (struct ending);
620 for (i = 0; i < count; i++)
621 {
622 if (flags & endings[i].flags)
623 {
624 char *p;
625 if ((p = strstr (eol, endings[i].newline)) && p == eol)
626 break;
627 else
628 {
629 always = true;
630 goto LOOP;
631 }
632 }
633 }
634 p = eol + SKIP_LINE_ENDINGS (flags);
635 }
636 *eol = '\0';
637 MERGE_PRINT_LINE (part_line, line, flags);
638 line = p;
639 }
640 if (feof (stream)) {
641 MERGE_PRINT_LINE (part_line, line, 0);
642 }
643 else
644 {
645 if (!clean) /* efficiency */
646 print_line (colors, bold, line, 0);
647 else if (!part_line)
648 part_line = xstrdup (line);
649 else
650 {
651 char *merged_line = str_concat (part_line, line);
652 free (part_line);
653 part_line = merged_line;
654 }
655 }
656 }
657 }
658
659 static void
660 find_color_entries (struct color_name **color_names, const struct color **colors)
661 {
662 struct timeval tv;
663 unsigned int index;
664
665 /* randomness */
666 gettimeofday (&tv, NULL);
667 srand (tv.tv_usec * tv.tv_sec);
668
669 for (index = 0; color_names[index]; index++)
670 {
671 const char *color_name = color_names[index]->name;
672
673 const unsigned int count = tables[index].count;
674 const struct color *const color_entries = tables[index].entries;
675
676 if (streq (color_name, "random"))
677 {
678 bool excludable;
679 unsigned int i;
680 do {
681 excludable = false;
682 i = rand() % (count - 2) + 1; /* omit color none and default */
683 switch (index)
684 {
685 case FOREGROUND:
686 /* --exclude-random */
687 if (exclude && streq (exclude, color_entries[i].name))
688 excludable = true;
689 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
690 excludable = true;
691 break;
692 case BACKGROUND:
693 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
694 excludable = true;
695 break;
696 default: /* never reached */
697 ABORT_TRACE ();
698 }
699 } while (excludable);
700 colors[index] = (struct color *)&color_entries[i];
701 }
702 else
703 find_color_entry (color_name, index, colors);
704 }
705 }
706
707 static void
708 find_color_entry (const char *const color_name, unsigned int index, const struct color **colors)
709 {
710 bool found = false;
711 unsigned int i;
712
713 const unsigned int count = tables[index].count;
714 const struct color *const color_entries = tables[index].entries;
715
716 for (i = 0; i < count; i++)
717 if (streq (color_name, color_entries[i].name))
718 {
719 colors[index] = (struct color *)&color_entries[i];
720 found = true;
721 break;
722 }
723 if (!found)
724 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name, "not recognized");
725 }
726
727 static void
728 print_line (const struct color **colors, bool bold, const char *const line, unsigned int flags)
729 {
730 /* --clean */
731 if (clean)
732 print_clean (line);
733 else
734 {
735 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
736 printf ("\033[%s", colors[BACKGROUND]->code);
737 if (colors[FOREGROUND]->code)
738 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
739 else
740 printf (formats[FMT_GENERIC], line);
741 }
742 if (flags & CR)
743 putchar ('\r');
744 if (flags & LF)
745 putchar ('\n');
746 }
747
748 static void
749 print_clean (const char *line)
750 {
751 const char *p;
752 char ***offsets = NULL;
753 unsigned int count = 0, i = 0;
754
755 for (p = line; *p;)
756 {
757 /* ESC[ */
758 if (*p == 27 && *(p + 1) == '[')
759 {
760 bool check_values, first = true;
761 const char *begin = p;
762 p += 2;
763 if (!isdigit (*p))
764 goto END;
765 do {
766 const char *digit;
767 check_values = false;
768 if (!first && !isdigit (*p))
769 goto DISCARD;
770 digit = p;
771 while (isdigit (*p))
772 p++;
773 if (p - digit > 2)
774 goto DISCARD;
775 else /* check range */
776 {
777 char val[3];
778 int value;
779 unsigned int i;
780 const unsigned int digits = p - digit;
781 for (i = 0; i < digits; i++)
782 val[i] = *digit++;
783 val[i] = '\0';
784 value = atoi (val);
785 if (!((value >= 0 && value <= 8) /* attributes */
786 || (value >= 30 && value <= 37) /* foreground colors */
787 || (value >= 40 && value <= 47) /* background colors */
788 || (value == 39 || value == 49))) /* default colors */
789 goto DISCARD;
790 }
791 if (*p == ';')
792 {
793 p++;
794 check_values = true;
795 }
796 first = false;
797 } while (check_values);
798 END: if (*p == 'm')
799 {
800 const char *end = p;
801 if (!offsets)
802 offsets = xmalloc (++count * sizeof (char **));
803 else
804 offsets = xrealloc (offsets, ++count * sizeof (char **));
805 offsets[i] = xmalloc (2 * sizeof (char *));
806 offsets[i][0] = (char *)begin; /* ESC */
807 offsets[i][1] = (char *)end; /* m */
808 i++;
809 }
810 DISCARD:
811 continue;
812 }
813 p++;
814 }
815
816 if (offsets)
817 print_free_offsets (line, offsets, count);
818 else
819 printf (formats[FMT_GENERIC], line);
820 }
821
822 #define SET_CHAR(offset, new, old) \
823 *old = *offset; \
824 *offset = new; \
825
826 #define RESTORE_CHAR(offset, old) \
827 *offset = old; \
828
829 static void
830 print_free_offsets (const char *line, char ***offsets, unsigned int count)
831 {
832 char ch;
833 unsigned int i;
834
835 SET_CHAR (offsets[0][0], '\0', &ch);
836 printf (formats[FMT_GENERIC], line);
837 RESTORE_CHAR (offsets[0][0], ch);
838
839 for (i = 0; i < count; i++)
840 {
841 char ch;
842 bool next_offset = false;
843 if (i + 1 < count)
844 {
845 SET_CHAR (offsets[i + 1][0], '\0', &ch);
846 next_offset = true;
847 }
848 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
849 if (next_offset)
850 RESTORE_CHAR (offsets[i + 1][0], ch);
851 }
852 for (i = 0; i < count; i++)
853 free_null (offsets[i]);
854 free_null (offsets);
855 }
856
857 static void *
858 malloc_wrap (size_t size, const char *file, unsigned int line)
859 {
860 void *p = malloc (size);
861 if (!p)
862 MEM_ALLOC_FAIL (file, line);
863 return p;
864 }
865
866 static void *
867 realloc_wrap (void *ptr, size_t size, const char *file, unsigned int line)
868 {
869 void *p = realloc (ptr, size);
870 if (!p)
871 MEM_ALLOC_FAIL (file, line);
872 return p;
873 }
874
875 static void
876 free_wrap (void **ptr)
877 {
878 free (*ptr);
879 *ptr = NULL;
880 }
881
882 static char *
883 strdup_wrap (const char *str, const char *file, unsigned int line)
884 {
885 const unsigned int len = strlen (str) + 1;
886 char *p = malloc (len);
887 if (!p)
888 MEM_ALLOC_FAIL (file, line);
889 strncpy (p, str, len);
890 return p;
891 }
892
893 static char *
894 str_concat (const char *str1, const char *str2)
895 {
896 const unsigned long len = strlen (str1) + strlen (str2) + 1;
897 char *p, *str;
898
899 p = str = xmalloc (len);
900 strncpy (p, str1, strlen (str1));
901 p += strlen (str1);
902 strncpy (p, str2, strlen (str2));
903 p += strlen (str2);
904 *p = '\0';
905
906 return str;
907 }
908
909 static void
910 vfprintf_fail (const char *fmt, ...)
911 {
912 va_list ap;
913 fprintf (stderr, "%s: ", program_name);
914 va_start (ap, fmt);
915 vfprintf (stderr, fmt, ap);
916 va_end (ap);
917 fprintf (stderr, "\n");
918 exit (EXIT_FAILURE);
919 }
920
921 static void
922 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
923 {
924 /* nothing to stack */
925 if (ptr == NULL)
926 return;
927 if (!*list)
928 *list = xmalloc (sizeof (void *));
929 else
930 {
931 unsigned int i;
932 for (i = 0; i < *stacked; i++)
933 if (!(*list)[i])
934 {
935 (*list)[i] = ptr;
936 return; /* reused */
937 }
938 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
939 }
940 (*list)[index] = ptr;
941 (*stacked)++;
942 }
943
944 static void
945 release_var (void **list, unsigned int stacked, void **ptr)
946 {
947 unsigned int i;
948 /* nothing to release */
949 if (*ptr == NULL)
950 return;
951 for (i = 0; i < stacked; i++)
952 if (list[i] == *ptr)
953 {
954 free (*ptr);
955 *ptr = NULL;
956 list[i] = NULL;
957 return;
958 }
959 }