]> git.refcnt.org Git - colorize.git/blob - colorize.c
Print timestamp at top of debug output
[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-2018 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 _DEFAULT_SOURCE
23 #define _BSD_SOURCE
24 #define _XOPEN_SOURCE 700
25 #define _FILE_OFFSET_BITS 64
26 #include <assert.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <getopt.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <time.h>
38 #include <unistd.h>
39
40 #ifndef DEBUG
41 # define DEBUG 0
42 #endif
43
44 #define str(arg) #arg
45 #define to_str(arg) str(arg)
46
47 #define streq(s1, s2) (strcmp (s1, s2) == 0)
48 #define strneq(s1, s2, n) (strncmp (s1, s2, n) == 0)
49
50 #if !DEBUG
51 # define xmalloc(size) malloc_wrap(size)
52 # define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
53 # define xrealloc(ptr, size) realloc_wrap(ptr, size)
54 # define xstrdup(str) strdup_wrap(str, NULL, 0)
55 # define str_concat(str1, str2) str_concat_wrap(str1, str2, NULL, 0)
56 #else
57 # define xmalloc(size) malloc_wrap_debug(size, __FILE__, __LINE__)
58 # define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
59 # define xrealloc(ptr, size) realloc_wrap_debug(ptr, size, __FILE__, __LINE__)
60 # define xstrdup(str) strdup_wrap(str, __FILE__, __LINE__)
61 # define str_concat(str1, str2) str_concat_wrap(str1, str2, __FILE__, __LINE__)
62 #endif
63
64 #define free_null(ptr) free_wrap((void **)&ptr)
65
66 #if defined(BUF_SIZE) && (BUF_SIZE <= 0 || BUF_SIZE > 65536)
67 # undef BUF_SIZE
68 #endif
69 #ifndef BUF_SIZE
70 # define BUF_SIZE 4096
71 #endif
72
73 #define LF 0x01
74 #define CR 0x02
75
76 #define COUNT_OF(obj, type) (sizeof (obj) / sizeof (type))
77
78 #define SKIP_LINE_ENDINGS(flags) ((flags) == (CR|LF) ? 2 : 1)
79
80 #define VALID_FILE_TYPE(mode) (S_ISREG (mode) || S_ISLNK (mode) || S_ISFIFO (mode))
81
82 #define STACK_VAR(ptr) do { \
83 stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
84 } while (false)
85
86 #define RELEASE_VAR(ptr) do { \
87 release_var (vars_list, stacked_vars, (void **)&ptr); \
88 } while (false)
89
90 #if !DEBUG
91 # define MEM_ALLOC_FAIL() do { \
92 fprintf (stderr, "%s: memory allocation failure\n", program_name); \
93 exit (EXIT_FAILURE); \
94 } while (false)
95 #else
96 # define MEM_ALLOC_FAIL_DEBUG(file, line) do { \
97 fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
98 exit (EXIT_FAILURE); \
99 } while (false)
100 #endif
101
102 #define ABORT_TRACE() \
103 fprintf (stderr, "Aborting in source file %s, line %u\n", __FILE__, __LINE__); \
104 abort ();
105
106 #define CHECK_COLORS_RANDOM(color1, color2) \
107 streq (color_names[color1]->name, "random") \
108 && (streq (color_names[color2]->name, "none") \
109 || streq (color_names[color2]->name, "default"))
110
111 #define ALLOC_COMPLETE_PART_LINE 8
112
113 #if defined(COLOR_SEP_CHAR_COLON)
114 # define COLOR_SEP_CHAR ':'
115 #elif defined(COLOR_SEP_CHAR_SLASH)
116 # define COLOR_SEP_CHAR '/'
117 #else
118 # define COLOR_SEP_CHAR '/'
119 #endif
120
121 #if DEBUG
122 # define DEBUG_FILE "debug.txt"
123 #endif
124
125 #define MAX_ATTRIBUTE_CHARS (6 * 2)
126
127 #define PROGRAM_NAME "colorize"
128
129 #define VERSION "0.64"
130
131 typedef enum { false, true } bool;
132
133 struct color_name {
134 char *name;
135 char *orig;
136 };
137
138 struct color {
139 const char *name;
140 const char *code;
141 };
142
143 static const struct color fg_colors[] = {
144 { "none", NULL },
145 { "black", "30m" },
146 { "red", "31m" },
147 { "green", "32m" },
148 { "yellow", "33m" },
149 { "blue", "34m" },
150 { "magenta", "35m" },
151 { "cyan", "36m" },
152 { "white", "37m" },
153 { "default", "39m" },
154 };
155 static const struct color bg_colors[] = {
156 { "none", NULL },
157 { "black", "40m" },
158 { "red", "41m" },
159 { "green", "42m" },
160 { "yellow", "43m" },
161 { "blue", "44m" },
162 { "magenta", "45m" },
163 { "cyan", "46m" },
164 { "white", "47m" },
165 { "default", "49m" },
166 };
167
168 struct bytes_size {
169 unsigned int size;
170 char unit;
171 };
172
173 enum {
174 FMT_GENERIC,
175 FMT_STRING,
176 FMT_QUOTE,
177 FMT_COLOR,
178 FMT_RANDOM,
179 FMT_ERROR,
180 FMT_FILE,
181 FMT_TYPE
182 };
183 static const char *formats[] = {
184 "%s", /* generic */
185 "%s '%s'", /* string */
186 "%s `%s' %s", /* quote */
187 "%s color '%s' %s", /* color */
188 "%s color '%s' %s '%s'", /* random */
189 "less than %lu bytes %s", /* error */
190 "%s: %s", /* file */
191 "%s: %s: %s", /* type */
192 };
193
194 enum { GENERIC, FOREGROUND = 0, BACKGROUND };
195
196 static const struct {
197 const struct color *entries;
198 unsigned int count;
199 const char *desc;
200 } tables[] = {
201 { fg_colors, COUNT_OF (fg_colors, struct color), "foreground" },
202 { bg_colors, COUNT_OF (bg_colors, struct color), "background" },
203 };
204
205 enum {
206 OPT_ATTR = 1,
207 OPT_CLEAN,
208 OPT_CLEAN_ALL,
209 OPT_EXCLUDE_RANDOM,
210 OPT_OMIT_COLOR_EMPTY,
211 OPT_HELP,
212 OPT_VERSION
213 };
214 static int opt_type;
215 static const struct option long_opts[] = {
216 { "attr", required_argument, &opt_type, OPT_ATTR },
217 { "clean", no_argument, &opt_type, OPT_CLEAN },
218 { "clean-all", no_argument, &opt_type, OPT_CLEAN_ALL },
219 { "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
220 { "omit-color-empty", no_argument, &opt_type, OPT_OMIT_COLOR_EMPTY },
221 { "help", no_argument, &opt_type, OPT_HELP },
222 { "version", no_argument, &opt_type, OPT_VERSION },
223 { NULL, 0, NULL, 0 },
224 };
225
226 enum attr_type {
227 ATTR_BOLD = 0x01,
228 ATTR_UNDERSCORE = 0x02,
229 ATTR_BLINK = 0x04,
230 ATTR_REVERSE = 0x08,
231 ATTR_CONCEALED = 0x10
232 };
233 struct attr {
234 const char *name;
235 unsigned int val;
236 enum attr_type type;
237 };
238
239 static FILE *stream;
240 #if DEBUG
241 static FILE *log;
242 #endif
243
244 static unsigned int stacked_vars;
245 static void **vars_list;
246
247 static bool clean;
248 static bool clean_all;
249 static bool omit_color_empty;
250
251 static char attr[MAX_ATTRIBUTE_CHARS + 1];
252 static char *exclude;
253
254 static const char *program_name;
255
256 static void print_tstamp (FILE *);
257 static void process_opts (int, char **);
258 static void process_opt_attr (const char *);
259 static void write_attr (const struct attr *, unsigned int *);
260 static void print_hint (void);
261 static void print_help (void);
262 static void print_version (void);
263 static void cleanup (void);
264 static void free_color_names (struct color_name **);
265 static void process_args (unsigned int, char **, char *, const struct color **, const char **, FILE **);
266 static void process_file_arg (const char *, const char **, FILE **);
267 static void skip_path_colors (const char *, const char *, const struct stat *);
268 static void gather_color_names (const char *, char *, struct color_name **);
269 static void read_print_stream (const char *, const struct color **, const char *, FILE *);
270 static void merge_print_line (const char *, const char *, FILE *);
271 static void complete_part_line (const char *, char **, FILE *);
272 static bool get_next_char (char *, const char **, FILE *, bool *);
273 static void save_char (char, char **, size_t *, size_t *);
274 static void find_color_entries (struct color_name **, const struct color **);
275 static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
276 static void print_line (const char *, const struct color **, const char * const, unsigned int, bool);
277 static void print_clean (const char *);
278 static bool is_esc (const char *);
279 static const char *get_end_of_esc (const char *);
280 static const char *get_end_of_text (const char *);
281 static void print_text (const char *, size_t);
282 static bool gather_esc_offsets (const char *, const char **, const char **);
283 static bool validate_esc_clean_all (const char **);
284 static bool validate_esc_clean (int, unsigned int, unsigned int *, const char **, bool *);
285 static bool is_reset (int, unsigned int, const char **);
286 static bool is_attr (int, unsigned int, unsigned int, const char **);
287 static bool is_fg_color (int, const char **);
288 static bool is_bg_color (int, unsigned int, const char **);
289 #if !DEBUG
290 static void *malloc_wrap (size_t);
291 static void *calloc_wrap (size_t, size_t);
292 static void *realloc_wrap (void *, size_t);
293 #else
294 static void *malloc_wrap_debug (size_t, const char *, unsigned int);
295 static void *calloc_wrap_debug (size_t, size_t, const char *, unsigned int);
296 static void *realloc_wrap_debug (void *, size_t, const char *, unsigned int);
297 #endif
298 static void free_wrap (void **);
299 static char *strdup_wrap (const char *, const char *, unsigned int);
300 static char *str_concat_wrap (const char *, const char *, const char *, unsigned int);
301 static bool get_bytes_size (unsigned long, struct bytes_size *);
302 static char *get_file_type (mode_t);
303 static bool has_color_name (const char *, const char *);
304 static FILE *open_file (const char *, const char *);
305 static void vfprintf_diag (const char *, ...);
306 static void vfprintf_fail (const char *, ...);
307 static void stack_var (void ***, unsigned int *, unsigned int, void *);
308 static void release_var (void **, unsigned int, void **);
309
310 extern int optind;
311
312 int
313 main (int argc, char **argv)
314 {
315 unsigned int arg_cnt;
316
317 const struct color *colors[2] = {
318 NULL, /* foreground */
319 NULL, /* background */
320 };
321
322 const char *file = NULL;
323
324 program_name = argv[0];
325 atexit (cleanup);
326
327 setvbuf (stdout, NULL, _IOLBF, 0);
328
329 #if DEBUG
330 log = open_file (DEBUG_FILE, "w");
331 print_tstamp (log);
332 #endif
333
334 attr[0] = '\0';
335
336 process_opts (argc, argv);
337
338 arg_cnt = argc - optind;
339
340 if (clean || clean_all)
341 {
342 if (clean && clean_all)
343 vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
344 if (arg_cnt > 1)
345 {
346 const char *const format = "%s %s";
347 const char *const message = "switch cannot be used with more than one file";
348 if (clean)
349 vfprintf_fail (format, "--clean", message);
350 else if (clean_all)
351 vfprintf_fail (format, "--clean-all", message);
352 }
353 }
354 else
355 {
356 if (arg_cnt == 0 || arg_cnt > 2)
357 {
358 vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
359 print_hint ();
360 exit (EXIT_FAILURE);
361 }
362 }
363
364 if (clean || clean_all)
365 process_file_arg (argv[optind], &file, &stream);
366 else
367 process_args (arg_cnt, &argv[optind], &attr[0], colors, &file, &stream);
368 read_print_stream (&attr[0], colors, file, stream);
369
370 RELEASE_VAR (exclude);
371
372 exit (EXIT_SUCCESS);
373 }
374
375 static void
376 print_tstamp (FILE *log)
377 {
378 time_t t;
379 struct tm *tm;
380 char str[128];
381 size_t written;
382
383 t = time (NULL);
384 tm = localtime (&t);
385 if (tm == NULL)
386 {
387 perror ("localtime");
388 exit (EXIT_FAILURE);
389 }
390 written = strftime (str, sizeof (str), "%Y-%m-%d %H:%M:%S %Z", tm);
391 if (written == 0)
392 vfprintf_fail (formats[FMT_GENERIC], "strftime: 0 returned");
393
394 fprintf (log, "%s\n", str);
395 while (written--)
396 fprintf (log, "=");
397 fprintf (log, "\n");
398 }
399
400 #define PRINT_HELP_EXIT() \
401 print_help (); \
402 exit (EXIT_SUCCESS);
403
404 #define PRINT_VERSION_EXIT() \
405 print_version (); \
406 exit (EXIT_SUCCESS);
407
408 extern char *optarg;
409
410 static void
411 process_opts (int argc, char **argv)
412 {
413 int opt;
414 while ((opt = getopt_long (argc, argv, "hV", long_opts, NULL)) != -1)
415 {
416 switch (opt)
417 {
418 case 0: /* long opts */
419 switch (opt_type)
420 {
421 case OPT_ATTR:
422 process_opt_attr (optarg);
423 break;
424 case OPT_CLEAN:
425 clean = true;
426 break;
427 case OPT_CLEAN_ALL:
428 clean_all = true;
429 break;
430 case OPT_EXCLUDE_RANDOM: {
431 bool valid = false;
432 unsigned int i;
433 exclude = xstrdup (optarg);
434 STACK_VAR (exclude);
435 for (i = 1; i < tables[GENERIC].count - 1; i++) /* skip color none and default */
436 {
437 const struct color *entry = &tables[GENERIC].entries[i];
438 if (streq (exclude, entry->name))
439 {
440 valid = true;
441 break;
442 }
443 }
444 if (!valid)
445 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
446 break;
447 }
448 case OPT_OMIT_COLOR_EMPTY:
449 omit_color_empty = true;
450 break;
451 case OPT_HELP:
452 PRINT_HELP_EXIT ();
453 case OPT_VERSION:
454 PRINT_VERSION_EXIT ();
455 default: /* never reached */
456 ABORT_TRACE ();
457 }
458 break;
459 case 'h':
460 PRINT_HELP_EXIT ();
461 case 'V':
462 PRINT_VERSION_EXIT ();
463 case '?':
464 print_hint ();
465 exit (EXIT_FAILURE);
466 default: /* never reached */
467 ABORT_TRACE ();
468 }
469 }
470 }
471
472 static void
473 process_opt_attr (const char *p)
474 {
475 /* If attributes are added to this "list", also increase MAX_ATTRIBUTE_CHARS! */
476 const struct attr attrs[] = {
477 { "bold", 1, ATTR_BOLD },
478 { "underscore", 4, ATTR_UNDERSCORE },
479 { "blink", 5, ATTR_BLINK },
480 { "reverse", 7, ATTR_REVERSE },
481 { "concealed", 8, ATTR_CONCEALED },
482 };
483 unsigned int attr_types = 0;
484
485 while (*p)
486 {
487 const char *s;
488 if (!isalnum (*p))
489 vfprintf_fail (formats[FMT_GENERIC], "--attr switch must be provided a string");
490 s = p;
491 while (isalnum (*p))
492 p++;
493 if (*p != '\0' && *p != ',')
494 vfprintf_fail (formats[FMT_GENERIC], "--attr switch must have strings separated by ,");
495 else
496 {
497 bool valid_attr = false;
498 unsigned int i;
499 for (i = 0; i < COUNT_OF (attrs, struct attr); i++)
500 {
501 const size_t name_len = strlen (attrs[i].name);
502 if ((size_t)(p - s) == name_len && strneq (s, attrs[i].name, name_len))
503 {
504 write_attr (&attrs[i], &attr_types);
505 valid_attr = true;
506 break;
507 }
508 }
509 if (!valid_attr)
510 {
511 char *attr_invalid = xmalloc ((p - s) + 1);
512 STACK_VAR (attr_invalid);
513 strncpy (attr_invalid, s, p - s);
514 attr_invalid[p - s] = '\0';
515 vfprintf_fail ("--attr switch attribute '%s' is not valid", attr_invalid);
516 RELEASE_VAR (attr_invalid); /* never reached */
517 }
518 }
519 if (*p)
520 p++;
521 }
522 }
523
524 static void
525 write_attr (const struct attr *attr_i, unsigned int *attr_types)
526 {
527 const unsigned int val = attr_i->val;
528 const enum attr_type attr_type = attr_i->type;
529 const char *attr_name = attr_i->name;
530
531 if (*attr_types & attr_type)
532 vfprintf_fail ("--attr switch has attribute '%s' twice or more", attr_name);
533 snprintf (attr + strlen (attr), 3, "%u;", val);
534 *attr_types |= attr_type;
535 }
536
537 static void
538 print_hint (void)
539 {
540 fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
541 }
542
543 static void
544 print_help (void)
545 {
546 struct opt_data {
547 const char *name;
548 const char *short_opt;
549 const char *arg;
550 };
551 const struct opt_data opts_data[] = {
552 { "attr", NULL, "=ATTR1,ATTR2,..." },
553 { "exclude-random", NULL, "=COLOR" },
554 { "help", "h", NULL },
555 { "version", "V", NULL },
556 };
557 const struct option *opt = long_opts;
558 unsigned int i;
559
560 printf ("Usage: %s (foreground) OR (foreground)%c(background) OR --clean[-all] [-|file]\n\n", program_name, COLOR_SEP_CHAR);
561 printf ("\tColors (foreground) (background)\n");
562 for (i = 0; i < tables[FOREGROUND].count; i++)
563 {
564 const struct color *entry = &tables[FOREGROUND].entries[i];
565 const char *name = entry->name;
566 const char *code = entry->code;
567 if (code)
568 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
569 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
570 else
571 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
572 }
573 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
574
575 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
576 printf ("\twhereas for lower case colors will be of normal intensity.\n");
577
578 printf ("\n\tOptions\n");
579 for (; opt->name; opt++)
580 {
581 const struct opt_data *opt_data = NULL;
582 unsigned int i;
583 for (i = 0; i < COUNT_OF (opts_data, struct opt_data); i++)
584 if (streq (opt->name, opts_data[i].name))
585 {
586 opt_data = &opts_data[i];
587 break;
588 }
589 if (opt_data)
590 {
591 if (opt_data->short_opt)
592 printf ("\t\t-%s, --%s\n", opt_data->short_opt, opt->name);
593 else
594 printf ("\t\t --%s%s\n", opt->name, opt_data->arg);
595 }
596 else
597 printf ("\t\t --%s\n", opt->name);
598 }
599 printf ("\n");
600 }
601
602 static void
603 print_version (void)
604 {
605 #ifdef HAVE_VERSION
606 # include "version.h"
607 #else
608 const char *const version = NULL;
609 #endif
610 const char *version_prefix, *version_string;
611 const char *c_flags, *ld_flags, *cpp_flags;
612 const char *const desc_flags_unknown = "unknown";
613 struct bytes_size bytes_size;
614 bool debug;
615 #ifdef CFLAGS
616 c_flags = to_str (CFLAGS);
617 #else
618 c_flags = desc_flags_unknown;
619 #endif
620 #ifdef LDFLAGS
621 ld_flags = to_str (LDFLAGS);
622 #else
623 ld_flags = desc_flags_unknown;
624 #endif
625 #ifdef CPPFLAGS
626 cpp_flags = to_str (CPPFLAGS);
627 #else
628 cpp_flags = desc_flags_unknown;
629 #endif
630 #if DEBUG
631 debug = true;
632 #else
633 debug = false;
634 #endif
635 version_prefix = version ? "" : "v";
636 version_string = version ? version : VERSION;
637 printf ("%s %s%s (compiled at %s, %s)\n", PROGRAM_NAME, version_prefix, version_string, __DATE__, __TIME__);
638
639 printf ("Compiler flags: %s\n", c_flags);
640 printf ("Linker flags: %s\n", ld_flags);
641 printf ("Preprocessor flags: %s\n", cpp_flags);
642 if (get_bytes_size (BUF_SIZE, &bytes_size))
643 {
644 if (BUF_SIZE % 1024 == 0)
645 printf ("Buffer size: %u%c\n", bytes_size.size, bytes_size.unit);
646 else
647 printf ("Buffer size: %u%c, %u byte%s\n", bytes_size.size, bytes_size.unit,
648 BUF_SIZE % 1024, BUF_SIZE % 1024 > 1 ? "s" : "");
649 }
650 else
651 printf ("Buffer size: %lu byte%s\n", (unsigned long)BUF_SIZE, BUF_SIZE > 1 ? "s" : "");
652 printf ("Color separator: '%c'\n", COLOR_SEP_CHAR);
653 printf ("Debugging: %s\n", debug ? "yes" : "no");
654 }
655
656 static void
657 cleanup (void)
658 {
659 if (stream && fileno (stream) != STDIN_FILENO)
660 fclose (stream);
661 #if DEBUG
662 if (log)
663 fclose (log);
664 #endif
665
666 if (vars_list)
667 {
668 unsigned int i;
669 for (i = 0; i < stacked_vars; i++)
670 free (vars_list[i]);
671 free_null (vars_list);
672 }
673 }
674
675 static void
676 free_color_names (struct color_name **color_names)
677 {
678 unsigned int i;
679 for (i = 0; color_names[i]; i++)
680 {
681 RELEASE_VAR (color_names[i]->name);
682 RELEASE_VAR (color_names[i]->orig);
683 RELEASE_VAR (color_names[i]);
684 }
685 }
686
687 static void
688 process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct color **colors, const char **file, FILE **stream)
689 {
690 int ret;
691 char *p;
692 struct stat sb;
693 struct color_name *color_names[3] = {
694 NULL, /* foreground */
695 NULL, /* background */
696 NULL, /* sentinel value */
697 };
698
699 const char *color_string = arg_cnt >= 1 ? arg_strings[0] : NULL;
700 const char *file_string = arg_cnt == 2 ? arg_strings[1] : NULL;
701
702 assert (color_string != NULL);
703
704 if (streq (color_string, "-"))
705 {
706 if (file_string)
707 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
708 else
709 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceded by color string");
710 }
711
712 ret = lstat (color_string, &sb);
713
714 /* Ensure that we don't fail if there's a file with one or more
715 color names in its path. */
716 if (ret == 0) /* success */
717 skip_path_colors (color_string, file_string, &sb);
718
719 if ((p = strchr (color_string, COLOR_SEP_CHAR)))
720 {
721 if (p == color_string)
722 vfprintf_fail (formats[FMT_STRING], "foreground color missing in string", color_string);
723 else if (p == color_string + strlen (color_string) - 1)
724 vfprintf_fail (formats[FMT_STRING], "background color missing in string", color_string);
725 else if (strchr (++p, COLOR_SEP_CHAR))
726 vfprintf_fail (formats[FMT_STRING], "one color pair allowed only for string", color_string);
727 }
728
729 gather_color_names (color_string, attr, color_names);
730
731 assert (color_names[FOREGROUND] != NULL);
732
733 if (color_names[BACKGROUND])
734 {
735 unsigned int i;
736 const unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
737 for (i = 0; i < 2; i++)
738 {
739 const unsigned int color1 = color_sets[i][0];
740 const unsigned int color2 = color_sets[i][1];
741 if (CHECK_COLORS_RANDOM (color1, color2))
742 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
743 }
744 }
745
746 find_color_entries (color_names, colors);
747 assert (colors[FOREGROUND] != NULL);
748 free_color_names (color_names);
749
750 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
751 {
752 struct color_name color_name;
753 color_name.name = color_name.orig = "default";
754
755 find_color_entry (&color_name, FOREGROUND, colors);
756 assert (colors[FOREGROUND]->code != NULL);
757 }
758
759 process_file_arg (file_string, file, stream);
760 }
761
762 static void
763 process_file_arg (const char *file_string, const char **file, FILE **stream)
764 {
765 if (file_string)
766 {
767 if (streq (file_string, "-"))
768 *stream = stdin;
769 else
770 {
771 const char *file = file_string;
772 struct stat sb;
773 int ret;
774
775 errno = 0;
776 ret = stat (file, &sb);
777
778 if (ret == -1)
779 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
780
781 if (!VALID_FILE_TYPE (sb.st_mode))
782 vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
783
784 *stream = open_file (file, "r");
785 }
786 *file = file_string;
787 }
788 else
789 {
790 *stream = stdin;
791 *file = "stdin";
792 }
793
794 assert (*stream != NULL);
795 assert (*file != NULL);
796 }
797
798 static void
799 skip_path_colors (const char *color_string, const char *file_string, const struct stat *sb)
800 {
801 bool have_file;
802 unsigned int c;
803 const char *color = color_string;
804 const mode_t mode = sb->st_mode;
805
806 for (c = 1; c <= 2 && *color; c++)
807 {
808 bool matched = false;
809 unsigned int i;
810 for (i = 0; i < tables[GENERIC].count; i++)
811 {
812 const struct color *entry = &tables[GENERIC].entries[i];
813 if (has_color_name (color, entry->name))
814 {
815 color += strlen (entry->name);
816 matched = true;
817 break;
818 }
819 }
820 if (!matched && has_color_name (color, "random"))
821 {
822 color += strlen ("random");
823 matched = true;
824 }
825 if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
826 color++;
827 else
828 break;
829 }
830
831 have_file = (*color != '\0');
832
833 if (have_file)
834 {
835 const char *file_existing = color_string;
836 if (file_string)
837 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "cannot be used as color string");
838 else
839 {
840 if (VALID_FILE_TYPE (mode))
841 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "must be preceded by color string");
842 else
843 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "is not a valid file type");
844 }
845 }
846 }
847
848 static void
849 gather_color_names (const char *color_string, char *attr, struct color_name **color_names)
850 {
851 unsigned int index;
852 char *color, *p, *str;
853
854 str = xstrdup (color_string);
855 STACK_VAR (str);
856
857 for (index = 0, color = str; *color; index++, color = p)
858 {
859 char *ch, *sep;
860
861 p = NULL;
862 if ((sep = strchr (color, COLOR_SEP_CHAR)))
863 {
864 *sep = '\0';
865 p = sep + 1;
866 }
867 else
868 p = color + strlen (color);
869 assert (p != NULL);
870
871 for (ch = color; *ch; ch++)
872 if (!isalpha (*ch))
873 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
874
875 for (ch = color + 1; *ch; ch++)
876 if (!islower (*ch))
877 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
878
879 if (streq (color, "None"))
880 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
881
882 if (isupper (*color))
883 {
884 switch (index)
885 {
886 case FOREGROUND:
887 snprintf (attr + strlen (attr), 3, "1;");
888 break;
889 case BACKGROUND:
890 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
891 default: /* never reached */
892 ABORT_TRACE ();
893 }
894 }
895
896 color_names[index] = xcalloc (1, sizeof (struct color_name));
897 STACK_VAR (color_names[index]);
898
899 color_names[index]->orig = xstrdup (color);
900 STACK_VAR (color_names[index]->orig);
901
902 for (ch = color; *ch; ch++)
903 *ch = tolower (*ch);
904
905 color_names[index]->name = xstrdup (color);
906 STACK_VAR (color_names[index]->name);
907 }
908
909 RELEASE_VAR (str);
910 }
911
912 static void
913 read_print_stream (const char *attr, const struct color **colors, const char *file, FILE *stream)
914 {
915 char buf[BUF_SIZE + 1];
916 unsigned int flags = 0;
917
918 while (!feof (stream))
919 {
920 size_t bytes_read;
921 char *eol;
922 const char *line;
923 bytes_read = fread (buf, 1, BUF_SIZE, stream);
924 if (bytes_read != BUF_SIZE && ferror (stream))
925 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
926 buf[bytes_read] = '\0';
927 line = buf;
928 while ((eol = strpbrk (line, "\n\r")))
929 {
930 const bool has_text = (eol > line);
931 const char *p;
932 flags &= ~(CR|LF);
933 if (*eol == '\r')
934 {
935 flags |= CR;
936 if (*(eol + 1) == '\n')
937 flags |= LF;
938 }
939 else if (*eol == '\n')
940 flags |= LF;
941 else /* never reached */
942 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
943 p = eol + SKIP_LINE_ENDINGS (flags);
944 *eol = '\0';
945 print_line (attr, colors, line, flags,
946 omit_color_empty ? has_text : true);
947 line = p;
948 }
949 if (feof (stream))
950 {
951 if (*line != '\0')
952 print_line (attr, colors, line, 0, true);
953 }
954 else if (*line != '\0')
955 {
956 char *p;
957 if ((clean || clean_all) && (p = strrchr (line, '\033')))
958 merge_print_line (line, p, stream);
959 else
960 print_line (attr, colors, line, 0, true);
961 }
962 }
963 }
964
965 static void
966 merge_print_line (const char *line, const char *p, FILE *stream)
967 {
968 char *buf = NULL;
969 char *merged_esc = NULL;
970 const char *esc = "";
971 const char char_restore = *p;
972
973 complete_part_line (p + 1, &buf, stream);
974
975 if (buf)
976 {
977 /* form escape sequence */
978 esc = merged_esc = str_concat (p, buf);
979 /* shorten partial line accordingly */
980 *(char *)p = '\0';
981 free (buf);
982 }
983
984 #ifdef TEST_MERGE_PART_LINE
985 printf ("%s%s", line, esc);
986 fflush (stdout);
987 _exit (EXIT_SUCCESS);
988 #else
989 print_clean (line);
990 *(char *)p = char_restore;
991 print_clean (esc);
992 free (merged_esc);
993 #endif
994 }
995
996 static void
997 complete_part_line (const char *p, char **buf, FILE *stream)
998 {
999 bool got_next_char = false, read_from_stream;
1000 char ch;
1001 size_t i = 0, size;
1002
1003 if (get_next_char (&ch, &p, stream, &read_from_stream))
1004 {
1005 if (ch == '[')
1006 {
1007 if (read_from_stream)
1008 save_char (ch, buf, &i, &size);
1009 }
1010 else
1011 {
1012 if (read_from_stream)
1013 ungetc ((int)ch, stream);
1014 return; /* cancel */
1015 }
1016 }
1017 else
1018 return; /* cancel */
1019
1020 while (get_next_char (&ch, &p, stream, &read_from_stream))
1021 {
1022 if (isdigit (ch) || ch == ';')
1023 {
1024 if (read_from_stream)
1025 save_char (ch, buf, &i, &size);
1026 }
1027 else /* got next character */
1028 {
1029 got_next_char = true;
1030 break;
1031 }
1032 }
1033
1034 if (got_next_char)
1035 {
1036 if (ch == 'm')
1037 {
1038 if (read_from_stream)
1039 save_char (ch, buf, &i, &size);
1040 }
1041 else
1042 {
1043 if (read_from_stream)
1044 ungetc ((int)ch, stream);
1045 return; /* cancel */
1046 }
1047 }
1048 else
1049 return; /* cancel */
1050 }
1051
1052 static bool
1053 get_next_char (char *ch, const char **p, FILE *stream, bool *read_from_stream)
1054 {
1055 if (**p == '\0')
1056 {
1057 int c;
1058 if ((c = fgetc (stream)) != EOF)
1059 {
1060 *ch = (char)c;
1061 *read_from_stream = true;
1062 return true;
1063 }
1064 else
1065 {
1066 *read_from_stream = false;
1067 return false;
1068 }
1069 }
1070 else
1071 {
1072 *ch = **p;
1073 (*p)++;
1074 *read_from_stream = false;
1075 return true;
1076 }
1077 }
1078
1079 static void
1080 save_char (char ch, char **buf, size_t *i, size_t *size)
1081 {
1082 if (!*buf)
1083 {
1084 *size = ALLOC_COMPLETE_PART_LINE;
1085 *buf = xmalloc (*size);
1086 }
1087 /* +1: effective occupied size of buffer */
1088 else if ((*i + 1) == *size)
1089 {
1090 *size *= 2;
1091 *buf = xrealloc (*buf, *size);
1092 }
1093 (*buf)[*i] = ch;
1094 (*buf)[*i + 1] = '\0';
1095 (*i)++;
1096 }
1097
1098 static void
1099 find_color_entries (struct color_name **color_names, const struct color **colors)
1100 {
1101 struct timeval tv;
1102 unsigned int index;
1103
1104 /* randomness */
1105 gettimeofday (&tv, NULL);
1106 srand (tv.tv_usec * tv.tv_sec);
1107
1108 for (index = 0; color_names[index]; index++)
1109 {
1110 const char *color_name = color_names[index]->name;
1111
1112 const unsigned int count = tables[index].count;
1113 const struct color *const color_entries = tables[index].entries;
1114
1115 if (streq (color_name, "random"))
1116 {
1117 bool excludable;
1118 unsigned int i;
1119 do {
1120 excludable = false;
1121 i = rand() % (count - 2) + 1; /* omit color none and default */
1122 switch (index)
1123 {
1124 case FOREGROUND:
1125 /* --exclude-random */
1126 if (exclude && streq (exclude, color_entries[i].name))
1127 excludable = true;
1128 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
1129 excludable = true;
1130 break;
1131 case BACKGROUND:
1132 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
1133 excludable = true;
1134 break;
1135 default: /* never reached */
1136 ABORT_TRACE ();
1137 }
1138 } while (excludable);
1139 colors[index] = (struct color *)&color_entries[i];
1140 }
1141 else
1142 find_color_entry (color_names[index], index, colors);
1143 }
1144 }
1145
1146 static void
1147 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
1148 {
1149 bool found = false;
1150 unsigned int i;
1151
1152 const unsigned int count = tables[index].count;
1153 const struct color *const color_entries = tables[index].entries;
1154
1155 for (i = 0; i < count; i++)
1156 if (streq (color_name->name, color_entries[i].name))
1157 {
1158 colors[index] = (struct color *)&color_entries[i];
1159 found = true;
1160 break;
1161 }
1162 if (!found)
1163 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
1164 }
1165
1166 static void
1167 print_line (const char *attr, const struct color **colors, const char *const line, unsigned int flags, bool emit_colors)
1168 {
1169 /* --clean[-all] */
1170 if (clean || clean_all)
1171 print_clean (line);
1172 /* skip for --omit-color-empty? */
1173 else if (emit_colors)
1174 {
1175 /* Foreground color code is guaranteed to be set when background color code is present. */
1176 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
1177 printf ("\033[%s", colors[BACKGROUND]->code);
1178 if (colors[FOREGROUND]->code)
1179 printf ("\033[%s%s%s\033[0m", attr, colors[FOREGROUND]->code, line);
1180 else
1181 printf (formats[FMT_GENERIC], line);
1182 }
1183 if (flags & CR)
1184 putchar ('\r');
1185 if (flags & LF)
1186 putchar ('\n');
1187 }
1188
1189 static void
1190 print_clean (const char *line)
1191 {
1192 const char *p = line;
1193
1194 if (is_esc (p))
1195 p = get_end_of_esc (p);
1196
1197 while (*p != '\0')
1198 {
1199 const char *text_start = p;
1200 const char *text_end = get_end_of_text (p);
1201 print_text (text_start, text_end - text_start);
1202 p = get_end_of_esc (text_end);
1203 }
1204 }
1205
1206 static bool
1207 is_esc (const char *p)
1208 {
1209 return gather_esc_offsets (p, NULL, NULL);
1210 }
1211
1212 static const char *
1213 get_end_of_esc (const char *p)
1214 {
1215 const char *esc;
1216 const char *end = NULL;
1217 while ((esc = strchr (p, '\033')))
1218 {
1219 if (gather_esc_offsets (esc, NULL, &end))
1220 break;
1221 p = esc + 1;
1222 }
1223 return end ? end + 1 : p + strlen (p);
1224 }
1225
1226 static const char *
1227 get_end_of_text (const char *p)
1228 {
1229 const char *esc;
1230 const char *start = NULL;
1231 while ((esc = strchr (p, '\033')))
1232 {
1233 if (gather_esc_offsets (esc, &start, NULL))
1234 break;
1235 p = esc + 1;
1236 }
1237 return start ? start : p + strlen (p);
1238 }
1239
1240 static void
1241 print_text (const char *p, size_t len)
1242 {
1243 size_t bytes_written;
1244 bytes_written = fwrite (p, 1, len, stdout);
1245 if (bytes_written != len)
1246 vfprintf_fail (formats[FMT_ERROR], (unsigned long)len, "written");
1247 }
1248
1249 static bool
1250 gather_esc_offsets (const char *p, const char **start, const char **end)
1251 {
1252 /* ESC[ */
1253 if (*p == 27 && *(p + 1) == '[')
1254 {
1255 bool valid = false;
1256 const char *const begin = p;
1257 p += 2;
1258 if (clean_all)
1259 valid = validate_esc_clean_all (&p);
1260 else if (clean)
1261 {
1262 bool check_values;
1263 unsigned int prev_iter, iter;
1264 const char *digit;
1265 prev_iter = iter = 0;
1266 do {
1267 check_values = false;
1268 iter++;
1269 if (!isdigit (*p))
1270 break;
1271 digit = p;
1272 while (isdigit (*p))
1273 p++;
1274 if (p - digit > 2)
1275 break;
1276 else /* check range */
1277 {
1278 char val[3];
1279 int value;
1280 unsigned int i;
1281 const unsigned int digits = p - digit;
1282 for (i = 0; i < digits; i++)
1283 val[i] = *digit++;
1284 val[i] = '\0';
1285 value = atoi (val);
1286 valid = validate_esc_clean (value, iter, &prev_iter, &p, &check_values);
1287 }
1288 } while (check_values);
1289 }
1290 if (valid)
1291 {
1292 if (start)
1293 *start = begin;
1294 if (end)
1295 *end = p;
1296 return true;
1297 }
1298 }
1299 return false;
1300 }
1301
1302 static bool
1303 validate_esc_clean_all (const char **p)
1304 {
1305 while (isdigit (**p) || **p == ';')
1306 (*p)++;
1307 return (**p == 'm');
1308 }
1309
1310 static bool
1311 validate_esc_clean (int value, unsigned int iter, unsigned int *prev_iter, const char **p, bool *check_values)
1312 {
1313 if (is_reset (value, iter, p))
1314 return true;
1315 else if (is_attr (value, iter, *prev_iter, p))
1316 {
1317 (*p)++;
1318 *check_values = true;
1319 *prev_iter = iter;
1320 return false; /* partial escape sequence, need another valid value */
1321 }
1322 else if (is_fg_color (value, p))
1323 return true;
1324 else if (is_bg_color (value, iter, p))
1325 return true;
1326 else
1327 return false;
1328 }
1329
1330 static bool
1331 is_reset (int value, unsigned int iter, const char **p)
1332 {
1333 return (value == 0 && iter == 1 && **p == 'm');
1334 }
1335
1336 static bool
1337 is_attr (int value, unsigned int iter, unsigned int prev_iter, const char **p)
1338 {
1339 return ((value > 0 && value < 10) && (iter - prev_iter == 1) && **p == ';');
1340 }
1341
1342 static bool
1343 is_fg_color (int value, const char **p)
1344 {
1345 return (((value >= 30 && value <= 37) || value == 39) && **p == 'm');
1346 }
1347
1348 static bool
1349 is_bg_color (int value, unsigned int iter, const char **p)
1350 {
1351 return (((value >= 40 && value <= 47) || value == 49) && iter == 1 && **p == 'm');
1352 }
1353
1354 #if !DEBUG
1355 static void *
1356 malloc_wrap (size_t size)
1357 {
1358 void *p = malloc (size);
1359 if (!p)
1360 MEM_ALLOC_FAIL ();
1361 return p;
1362 }
1363
1364 static void *
1365 calloc_wrap (size_t nmemb, size_t size)
1366 {
1367 void *p = calloc (nmemb, size);
1368 if (!p)
1369 MEM_ALLOC_FAIL ();
1370 return p;
1371 }
1372
1373 static void *
1374 realloc_wrap (void *ptr, size_t size)
1375 {
1376 void *p = realloc (ptr, size);
1377 if (!p)
1378 MEM_ALLOC_FAIL ();
1379 return p;
1380 }
1381 #else
1382 static const char *const format_debug = "%s: %10s %7lu bytes [source file %s, line %5u]\n";
1383 static void *
1384 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
1385 {
1386 void *p = malloc (size);
1387 if (!p)
1388 MEM_ALLOC_FAIL_DEBUG (file, line);
1389 fprintf (log, format_debug, program_name, "malloc'ed", (unsigned long)size, file, line);
1390 return p;
1391 }
1392
1393 static void *
1394 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
1395 {
1396 void *p = calloc (nmemb, size);
1397 if (!p)
1398 MEM_ALLOC_FAIL_DEBUG (file, line);
1399 fprintf (log, format_debug, program_name, "calloc'ed", (unsigned long)(nmemb * size), file, line);
1400 return p;
1401 }
1402
1403 static void *
1404 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
1405 {
1406 void *p = realloc (ptr, size);
1407 if (!p)
1408 MEM_ALLOC_FAIL_DEBUG (file, line);
1409 fprintf (log, format_debug, program_name, "realloc'ed", (unsigned long)size, file, line);
1410 return p;
1411 }
1412 #endif /* !DEBUG */
1413
1414 static void
1415 free_wrap (void **ptr)
1416 {
1417 free (*ptr);
1418 *ptr = NULL;
1419 }
1420
1421 #if !DEBUG
1422 # define do_malloc(len, file, line) malloc_wrap(len)
1423 #else
1424 # define do_malloc(len, file, line) malloc_wrap_debug(len, file, line)
1425 #endif
1426
1427 static char *
1428 strdup_wrap (const char *str, const char *file, unsigned int line)
1429 {
1430 const size_t len = strlen (str) + 1;
1431 char *p = do_malloc (len, file, line);
1432 strncpy (p, str, len);
1433 return p;
1434 }
1435
1436 static char *
1437 str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned int line)
1438 {
1439 const size_t len = strlen (str1) + strlen (str2) + 1;
1440 char *p, *str;
1441
1442 p = str = do_malloc (len, file, line);
1443 strncpy (p, str1, strlen (str1));
1444 p += strlen (str1);
1445 strncpy (p, str2, strlen (str2));
1446 p += strlen (str2);
1447 *p = '\0';
1448
1449 return str;
1450 }
1451
1452 static bool
1453 get_bytes_size (unsigned long bytes, struct bytes_size *bytes_size)
1454 {
1455 const char *unit, units[] = { '0', 'K', 'M', 'G', '\0' };
1456 unsigned long size = bytes;
1457 if (bytes < 1024)
1458 return false;
1459 unit = units;
1460 while (size >= 1024 && *(unit + 1))
1461 {
1462 size /= 1024;
1463 unit++;
1464 }
1465 bytes_size->size = (unsigned int)size;
1466 bytes_size->unit = *unit;
1467 return true;
1468 }
1469
1470 static char *
1471 get_file_type (mode_t mode)
1472 {
1473 if (S_ISREG (mode))
1474 return "file";
1475 else if (S_ISDIR (mode))
1476 return "directory";
1477 else if (S_ISCHR (mode))
1478 return "character device";
1479 else if (S_ISBLK (mode))
1480 return "block device";
1481 else if (S_ISFIFO (mode))
1482 return "named pipe";
1483 else if (S_ISLNK (mode))
1484 return "symbolic link";
1485 else if (S_ISSOCK (mode))
1486 return "socket";
1487 else
1488 return "file";
1489 }
1490
1491 static bool
1492 has_color_name (const char *str, const char *name)
1493 {
1494 char *p;
1495
1496 assert (strlen (str) > 0);
1497 assert (strlen (name) > 0);
1498
1499 if (!(*str == *name || *str == toupper (*name)))
1500 return false;
1501 else if (*(name + 1) != '\0'
1502 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1503 return false;
1504 else
1505 return true;
1506 }
1507
1508 static FILE *
1509 open_file (const char *file, const char *mode)
1510 {
1511 FILE *stream;
1512
1513 errno = 0;
1514 stream = fopen (file, mode);
1515 if (!stream)
1516 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
1517
1518 return stream;
1519 }
1520
1521 #define DO_VFPRINTF(fmt) \
1522 va_list ap; \
1523 fprintf (stderr, "%s: ", program_name); \
1524 va_start (ap, fmt); \
1525 vfprintf (stderr, fmt, ap); \
1526 va_end (ap); \
1527 fprintf (stderr, "\n");
1528
1529 static void
1530 vfprintf_diag (const char *fmt, ...)
1531 {
1532 DO_VFPRINTF (fmt);
1533 }
1534
1535 static void
1536 vfprintf_fail (const char *fmt, ...)
1537 {
1538 DO_VFPRINTF (fmt);
1539 exit (EXIT_FAILURE);
1540 }
1541
1542 static void
1543 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1544 {
1545 /* nothing to stack */
1546 if (ptr == NULL)
1547 return;
1548 if (!*list)
1549 *list = xmalloc (sizeof (void *));
1550 else
1551 {
1552 unsigned int i;
1553 for (i = 0; i < *stacked; i++)
1554 if (!(*list)[i])
1555 {
1556 (*list)[i] = ptr;
1557 return; /* reused */
1558 }
1559 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1560 }
1561 (*list)[index] = ptr;
1562 (*stacked)++;
1563 }
1564
1565 static void
1566 release_var (void **list, unsigned int stacked, void **ptr)
1567 {
1568 unsigned int i;
1569 /* nothing to release */
1570 if (*ptr == NULL)
1571 return;
1572 for (i = 0; i < stacked; i++)
1573 if (list[i] == *ptr)
1574 {
1575 free (*ptr);
1576 *ptr = NULL;
1577 list[i] = NULL;
1578 return;
1579 }
1580 }