]> git.refcnt.org Git - colorize.git/blob - colorize.c
Reuse file opening function
[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-2015 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 _BSD_SOURCE
23 #define _XOPEN_SOURCE 700
24 #define _FILE_OFFSET_BITS 64
25 #include <assert.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <getopt.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/time.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <time.h>
37 #include <unistd.h>
38
39 #ifndef DEBUG
40 # define DEBUG 0
41 #endif
42
43 #define str(arg) #arg
44 #define to_str(arg) str(arg)
45
46 #define streq(s1, s2) (strcmp (s1, s2) == 0)
47
48 #if !DEBUG
49 # define xmalloc(size) malloc_wrap(size)
50 # define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
51 # define xrealloc(ptr, size) realloc_wrap(ptr, size)
52 # define xstrdup(str) strdup_wrap(str, NULL, 0)
53 # define str_concat(str1, str2) str_concat_wrap(str1, str2, NULL, 0)
54 #else
55 # define xmalloc(size) malloc_wrap_debug(size, __FILE__, __LINE__)
56 # define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
57 # define xrealloc(ptr, size) realloc_wrap_debug(ptr, size, __FILE__, __LINE__)
58 # define xstrdup(str) strdup_wrap(str, __FILE__, __LINE__)
59 # define str_concat(str1, str2) str_concat_wrap(str1, str2, __FILE__, __LINE__)
60 #endif
61
62 #define free_null(ptr) free_wrap((void **)&ptr)
63
64 #if defined(BUF_SIZE) && (BUF_SIZE <= 0 || BUF_SIZE > 65536)
65 # undef BUF_SIZE
66 #endif
67 #ifndef BUF_SIZE
68 # define BUF_SIZE 4096
69 #endif
70
71 #define LF 0x01
72 #define CR 0x02
73
74 #define SKIP_LINE_ENDINGS(flags) (((flags) & CR) && ((flags) & LF) ? 2 : 1)
75
76 #define VALID_FILE_TYPE(mode) (S_ISREG (mode) || S_ISLNK (mode) || S_ISFIFO (mode))
77
78 #define STACK_VAR(ptr) do { \
79 stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
80 } while (false)
81
82 #define RELEASE_VAR(ptr) do { \
83 release_var (vars_list, stacked_vars, (void **)&ptr); \
84 } while (false)
85
86 #if !DEBUG
87 # define MEM_ALLOC_FAIL() do { \
88 fprintf (stderr, "%s: memory allocation failure\n", program_name); \
89 exit (EXIT_FAILURE); \
90 } while (false)
91 #else
92 # define MEM_ALLOC_FAIL_DEBUG(file, line) do { \
93 fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
94 exit (EXIT_FAILURE); \
95 } while (false)
96 #endif
97
98 #define ABORT_TRACE() \
99 fprintf (stderr, "Aborting in source file %s, line %u\n", __FILE__, __LINE__); \
100 abort (); \
101
102 #define CHECK_COLORS_RANDOM(color1, color2) \
103 streq (color_names[color1]->name, "random") \
104 && (streq (color_names[color2]->name, "none") \
105 || streq (color_names[color2]->name, "default")) \
106
107 #define COLOR_SEP_CHAR '/'
108
109 #define DEBUG_FILE "debug.txt"
110
111 #define VERSION "0.55"
112
113 typedef enum { false, true } bool;
114
115 struct color_name {
116 char *name;
117 char *orig;
118 };
119
120 static struct color_name *color_names[3] = { NULL, NULL, NULL };
121
122 struct color {
123 const char *name;
124 const char *code;
125 };
126
127 static const struct color fg_colors[] = {
128 { "none", NULL },
129 { "black", "30m" },
130 { "red", "31m" },
131 { "green", "32m" },
132 { "yellow", "33m" },
133 { "blue", "34m" },
134 { "magenta", "35m" },
135 { "cyan", "36m" },
136 { "white", "37m" },
137 { "default", "39m" },
138 };
139 static const struct color bg_colors[] = {
140 { "none", NULL },
141 { "black", "40m" },
142 { "red", "41m" },
143 { "green", "42m" },
144 { "yellow", "43m" },
145 { "blue", "44m" },
146 { "magenta", "45m" },
147 { "cyan", "46m" },
148 { "white", "47m" },
149 { "default", "49m" },
150 };
151
152 struct bytes_size {
153 unsigned int size;
154 char unit;
155 };
156
157 enum fmts {
158 FMT_GENERIC,
159 FMT_STRING,
160 FMT_QUOTE,
161 FMT_COLOR,
162 FMT_RANDOM,
163 FMT_ERROR,
164 FMT_FILE,
165 FMT_TYPE
166 };
167 static const char *formats[] = {
168 "%s", /* generic */
169 "%s '%s'", /* string */
170 "%s `%s' %s", /* quote */
171 "%s color '%s' %s", /* color */
172 "%s color '%s' %s '%s'", /* random */
173 "less than %u bytes %s", /* error */
174 "%s: %s", /* file */
175 "%s: %s: %s", /* type */
176 };
177
178 enum { FOREGROUND, BACKGROUND };
179
180 static const struct {
181 struct color const *entries;
182 unsigned int count;
183 const char *desc;
184 } tables[] = {
185 { fg_colors, sizeof (fg_colors) / sizeof (struct color), "foreground" },
186 { bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" },
187 };
188
189 static FILE *stream = NULL;
190 #if DEBUG
191 static FILE *log = NULL;
192 #endif
193
194 static unsigned int stacked_vars = 0;
195 static void **vars_list = NULL;
196
197 static bool clean = false;
198 static bool clean_all = false;
199
200 static char *exclude = NULL;
201
202 static const char *program_name;
203
204 static void print_hint (void);
205 static void print_help (void);
206 static void print_version (void);
207 static void cleanup (void);
208 static void free_color_names (struct color_name **);
209 static void process_args (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
210 static void process_file_arg (const char *, const char **, FILE **);
211 static void read_print_stream (bool, const struct color **, const char *, FILE *);
212 static void find_color_entries (struct color_name **, const struct color **);
213 static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
214 static void print_line (bool, const struct color **, const char * const, unsigned int);
215 static void print_clean (const char *);
216 static void print_free_offsets (const char *, char ***, unsigned int);
217 #if !DEBUG
218 static void *malloc_wrap (size_t);
219 static void *calloc_wrap (size_t, size_t);
220 static void *realloc_wrap (void *, size_t);
221 #else
222 static void *malloc_wrap_debug (size_t, const char *, unsigned int);
223 static void *calloc_wrap_debug (size_t, size_t, const char *, unsigned int);
224 static void *realloc_wrap_debug (void *, size_t, const char *, unsigned int);
225 #endif
226 static void free_wrap (void **);
227 static char *strdup_wrap (const char *, const char *, unsigned int);
228 static char *str_concat_wrap (const char *, const char *, const char *, unsigned int);
229 static bool get_bytes_size (unsigned long, struct bytes_size *);
230 static char *get_file_type (mode_t);
231 static bool has_color_name (const char *, const char *);
232 static FILE *open_file (const char *, const char *);
233 static void vfprintf_diag (const char *, ...);
234 static void vfprintf_fail (const char *, ...);
235 static void stack_var (void ***, unsigned int *, unsigned int, void *);
236 static void release_var (void **, unsigned int, void **);
237
238 #define SET_OPT_TYPE(type) \
239 opt_type = type; \
240 opt = 0; \
241 goto PARSE_OPT; \
242
243 extern char *optarg;
244 extern int optind;
245
246 static int opt_type = 0;
247
248 int
249 main (int argc, char **argv)
250 {
251 unsigned int arg_cnt = 0;
252
253 enum {
254 OPT_CLEAN = 1,
255 OPT_CLEAN_ALL,
256 OPT_EXCLUDE_RANDOM,
257 OPT_HELP,
258 OPT_VERSION
259 };
260
261 int opt;
262 struct option long_opts[] = {
263 { "clean", no_argument, &opt_type, OPT_CLEAN },
264 { "clean-all", no_argument, &opt_type, OPT_CLEAN_ALL },
265 { "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
266 { "help", no_argument, &opt_type, OPT_HELP },
267 { "version", no_argument, &opt_type, OPT_VERSION },
268 { NULL, 0, NULL, 0 },
269 };
270
271 bool bold = false;
272
273 const struct color *colors[2] = {
274 NULL, /* foreground */
275 NULL, /* background */
276 };
277
278 const char *file = NULL;
279
280 program_name = argv[0];
281 atexit (cleanup);
282
283 setvbuf (stdout, NULL, _IOLBF, 0);
284
285 #if DEBUG
286 log = open_file (DEBUG_FILE, "w");
287 #endif
288
289 while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
290 {
291 PARSE_OPT:
292 switch (opt)
293 {
294 case 0: /* long opts */
295 switch (opt_type)
296 {
297 case OPT_CLEAN:
298 clean = true;
299 break;
300 case OPT_CLEAN_ALL:
301 clean_all = true;
302 break;
303 case OPT_EXCLUDE_RANDOM: {
304 bool valid = false;
305 unsigned int i;
306 exclude = xstrdup (optarg);
307 STACK_VAR (exclude);
308 for (i = 1; i < tables[FOREGROUND].count - 1; i++) /* skip color none and default */
309 {
310 const struct color *entry = &tables[FOREGROUND].entries[i];
311 if (streq (exclude, entry->name))
312 {
313 valid = true;
314 break;
315 }
316 }
317 if (!valid)
318 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
319 break;
320 }
321 case OPT_HELP:
322 print_help ();
323 exit (EXIT_SUCCESS);
324 case OPT_VERSION:
325 print_version ();
326 exit (EXIT_SUCCESS);
327 default: /* never reached */
328 ABORT_TRACE ();
329 }
330 break;
331 case 'h':
332 SET_OPT_TYPE (OPT_HELP);
333 case 'v':
334 SET_OPT_TYPE (OPT_VERSION);
335 case '?':
336 print_hint ();
337 exit (EXIT_FAILURE);
338 default: /* never reached */
339 ABORT_TRACE ();
340 }
341 }
342
343 arg_cnt = argc - optind;
344
345 if (clean || clean_all)
346 {
347 if (clean && clean_all)
348 vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
349 if (arg_cnt > 1)
350 {
351 const char *format = "%s %s";
352 const char *message = "switch cannot be used with more than one file";
353 if (clean)
354 vfprintf_fail (format, "--clean", message);
355 else if (clean_all)
356 vfprintf_fail (format, "--clean-all", message);
357 }
358 }
359 else
360 {
361 if (arg_cnt == 0 || arg_cnt > 2)
362 {
363 vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
364 print_hint ();
365 exit (EXIT_FAILURE);
366 }
367 }
368
369 if (clean || clean_all)
370 process_file_arg (argv[optind], &file, &stream);
371 else
372 process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
373 read_print_stream (bold, colors, file, stream);
374
375 RELEASE_VAR (exclude);
376
377 exit (EXIT_SUCCESS);
378 }
379
380 static void
381 print_hint (void)
382 {
383 fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
384 }
385
386 static void
387 print_help (void)
388 {
389 unsigned int i;
390
391 printf ("Usage: %s (foreground) OR (foreground)%c(background) OR --clean[-all] [-|file]\n\n", program_name, COLOR_SEP_CHAR);
392 printf ("\tColors (foreground) (background)\n");
393 for (i = 0; i < tables[FOREGROUND].count; i++)
394 {
395 const struct color *entry = &tables[FOREGROUND].entries[i];
396 const char *name = entry->name;
397 const char *code = entry->code;
398 if (code)
399 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
400 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
401 else
402 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
403 }
404 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
405
406 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
407 printf ("\twhereas for lower case colors will be of normal intensity.\n");
408
409 printf ("\n\tOptions\n");
410 printf ("\t\t --clean\n");
411 printf ("\t\t --clean-all\n");
412 printf ("\t\t --exclude-random\n");
413 printf ("\t\t-h, --help\n");
414 printf ("\t\t-v, --version\n\n");
415 }
416
417 static void
418 print_version (void)
419 {
420 #ifdef HAVE_VERSION
421 # include "version.h"
422 #else
423 const char *version = NULL;
424 #endif
425 const char *version_prefix, *version_string;
426 const char *c_flags;
427 struct bytes_size bytes_size;
428 bool debug;
429 #ifdef CFLAGS
430 c_flags = to_str (CFLAGS);
431 #else
432 c_flags = "unknown";
433 #endif
434 #if DEBUG
435 debug = true;
436 #else
437 debug = false;
438 #endif
439 version_prefix = version ? "" : "v";
440 version_string = version ? version : VERSION;
441 printf ("colorize %s%s (compiled at %s, %s)\n", version_prefix, version_string, __DATE__, __TIME__);
442
443 printf ("Compiler flags: %s\n", c_flags);
444 if (get_bytes_size (BUF_SIZE, &bytes_size))
445 {
446 if (BUF_SIZE % 1024 == 0)
447 printf ("Buffer size: %u%c\n", bytes_size.size, bytes_size.unit);
448 else
449 printf ("Buffer size: %u%c, %u byte%s\n", bytes_size.size, bytes_size.unit,
450 BUF_SIZE % 1024, BUF_SIZE % 1024 > 1 ? "s" : "");
451 }
452 else
453 printf ("Buffer size: %lu byte%s\n", (unsigned long)BUF_SIZE, BUF_SIZE > 1 ? "s" : "");
454 printf ("Debugging: %s\n", debug ? "yes" : "no");
455 }
456
457 static void
458 cleanup (void)
459 {
460 free_color_names (color_names);
461
462 if (stream && fileno (stream) != STDIN_FILENO)
463 fclose (stream);
464 #if DEBUG
465 if (log)
466 fclose (log);
467 #endif
468
469 if (vars_list)
470 {
471 unsigned int i;
472 for (i = 0; i < stacked_vars; i++)
473 if (vars_list[i])
474 free_null (vars_list[i]);
475
476 free_null (vars_list);
477 }
478 }
479
480 static void
481 free_color_names (struct color_name **color_names)
482 {
483 unsigned int i;
484 for (i = 0; color_names[i]; i++)
485 {
486 free_null (color_names[i]->name);
487 free_null (color_names[i]->orig);
488 free_null (color_names[i]);
489 }
490 }
491
492 static void
493 process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
494 {
495 int ret;
496 unsigned int index;
497 char *color, *p, *str;
498 struct stat sb;
499
500 const char *color_string = arg_cnt >= 1 ? arg_strings[0] : NULL;
501 const char *file_string = arg_cnt == 2 ? arg_strings[1] : NULL;
502
503 assert (color_string);
504
505 if (streq (color_string, "-"))
506 {
507 if (file_string)
508 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
509 else
510 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
511 }
512
513 ret = lstat (color_string, &sb);
514
515 /* Ensure that we don't fail if there's a file with one or more
516 color names in its path. */
517 if (ret == 0) /* success */
518 {
519 bool have_file;
520 unsigned int c;
521 const char *color = color_string;
522 const mode_t mode = sb.st_mode;
523
524 for (c = 1; c <= 2 && *color; c++)
525 {
526 bool matched = false;
527 unsigned int i;
528 for (i = 0; i < tables[FOREGROUND].count; i++)
529 {
530 const struct color *entry = &tables[FOREGROUND].entries[i];
531 if (has_color_name (color, entry->name))
532 {
533 color += strlen (entry->name);
534 matched = true;
535 break;
536 }
537 }
538 if (!matched && has_color_name (color, "random"))
539 {
540 color += strlen ("random");
541 matched = true;
542 }
543 if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
544 color++;
545 else
546 break;
547 }
548
549 have_file = (*color != '\0');
550
551 if (have_file)
552 {
553 const char *file_exists = color_string;
554 if (file_string)
555 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "cannot be used as color string");
556 else
557 {
558 if (VALID_FILE_TYPE (mode))
559 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "must be preceeded by color string");
560 else
561 vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "is not a valid file type");
562 }
563 }
564 }
565
566 if ((p = strchr (color_string, COLOR_SEP_CHAR)))
567 {
568 if (p == color_string)
569 vfprintf_fail (formats[FMT_STRING], "foreground color missing in string", color_string);
570 else if (p == color_string + strlen (color_string) - 1)
571 vfprintf_fail (formats[FMT_STRING], "background color missing in string", color_string);
572 else if (strchr (++p, COLOR_SEP_CHAR))
573 vfprintf_fail (formats[FMT_STRING], "one color pair allowed only for string", color_string);
574 }
575
576 str = xstrdup (color_string);
577 STACK_VAR (str);
578
579 for (index = 0, color = str; *color; index++, color = p)
580 {
581 char *ch, *sep;
582
583 p = NULL;
584 if ((sep = strchr (color, COLOR_SEP_CHAR)))
585 {
586 *sep = '\0';
587 p = sep + 1;
588 }
589 else
590 p = color + strlen (color);
591 assert (p);
592
593 for (ch = color; *ch; ch++)
594 if (!isalpha (*ch))
595 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
596
597 for (ch = color + 1; *ch; ch++)
598 if (!islower (*ch))
599 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
600
601 if (streq (color, "None"))
602 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
603
604 if (isupper (*color))
605 {
606 switch (index)
607 {
608 case FOREGROUND:
609 *bold = true;
610 break;
611 case BACKGROUND:
612 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
613 break;
614 default: /* never reached */
615 ABORT_TRACE ();
616 }
617 }
618
619 color_names[index] = xcalloc (1, sizeof (struct color_name));
620
621 color_names[index]->orig = xstrdup (color);
622
623 for (ch = color; *ch; ch++)
624 *ch = tolower (*ch);
625
626 color_names[index]->name = xstrdup (color);
627 }
628
629 RELEASE_VAR (str);
630
631 assert (color_names[FOREGROUND]);
632
633 if (color_names[BACKGROUND])
634 {
635 unsigned int i;
636 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
637 for (i = 0; i < 2; i++)
638 {
639 unsigned int color1 = color_sets[i][0];
640 unsigned int color2 = color_sets[i][1];
641 if (CHECK_COLORS_RANDOM (color1, color2))
642 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
643 }
644 }
645
646 find_color_entries (color_names, colors);
647 free_color_names (color_names);
648
649 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
650 {
651 struct color_name color_name;
652 color_name.name = color_name.orig = "default";
653
654 find_color_entry (&color_name, FOREGROUND, colors);
655 }
656
657 process_file_arg (file_string, file, stream);
658 }
659
660 static void
661 process_file_arg (const char *file_string, const char **file, FILE **stream)
662 {
663 if (file_string)
664 {
665 if (streq (file_string, "-"))
666 *stream = stdin;
667 else
668 {
669 const char *file = file_string;
670 struct stat sb;
671 int ret;
672
673 errno = 0;
674 ret = lstat (file, &sb);
675
676 if (ret == -1)
677 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
678
679 if (!VALID_FILE_TYPE (sb.st_mode))
680 vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
681
682 *stream = open_file (file, "r");
683 }
684 *file = file_string;
685 }
686 else
687 {
688 *stream = stdin;
689 *file = "stdin";
690 }
691
692 assert (*stream);
693 assert (*file);
694 }
695
696 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
697 char *current_line, *merged_line = NULL; \
698 if (part_line) \
699 { \
700 merged_line = str_concat (part_line, line); \
701 free_null (part_line); \
702 } \
703 current_line = merged_line ? merged_line : (char *)line; \
704 if (!check_eof || *current_line != '\0') \
705 print_line (bold, colors, current_line, flags); \
706 free (merged_line); \
707 } while (false)
708
709 static void
710 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
711 {
712 char buf[BUF_SIZE + 1], *part_line = NULL;
713 unsigned int flags = 0;
714
715 while (!feof (stream))
716 {
717 size_t bytes_read;
718 char *eol;
719 const char *line;
720 memset (buf, '\0', BUF_SIZE + 1);
721 bytes_read = fread (buf, 1, BUF_SIZE, stream);
722 if (bytes_read != BUF_SIZE && ferror (stream))
723 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
724 line = buf;
725 while ((eol = strpbrk (line, "\n\r")))
726 {
727 char *p;
728 flags &= ~(CR|LF);
729 if (*eol == '\r')
730 {
731 flags |= CR;
732 if (*(eol + 1) == '\n')
733 flags |= LF;
734 }
735 else if (*eol == '\n')
736 flags |= LF;
737 else
738 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
739 p = eol + SKIP_LINE_ENDINGS (flags);
740 *eol = '\0';
741 MERGE_PRINT_LINE (part_line, line, flags, false);
742 line = p;
743 }
744 if (feof (stream)) {
745 MERGE_PRINT_LINE (part_line, line, 0, true);
746 }
747 else if (*line != '\0')
748 {
749 if (!clean && !clean_all) /* efficiency */
750 print_line (bold, colors, line, 0);
751 else if (!part_line)
752 part_line = xstrdup (line);
753 else
754 {
755 char *merged_line = str_concat (part_line, line);
756 free (part_line);
757 part_line = merged_line;
758 }
759 }
760 }
761 }
762
763 static void
764 find_color_entries (struct color_name **color_names, const struct color **colors)
765 {
766 struct timeval tv;
767 unsigned int index;
768
769 /* randomness */
770 gettimeofday (&tv, NULL);
771 srand (tv.tv_usec * tv.tv_sec);
772
773 for (index = 0; color_names[index]; index++)
774 {
775 const char *color_name = color_names[index]->name;
776
777 const unsigned int count = tables[index].count;
778 const struct color *const color_entries = tables[index].entries;
779
780 if (streq (color_name, "random"))
781 {
782 bool excludable;
783 unsigned int i;
784 do {
785 excludable = false;
786 i = rand() % (count - 2) + 1; /* omit color none and default */
787 switch (index)
788 {
789 case FOREGROUND:
790 /* --exclude-random */
791 if (exclude && streq (exclude, color_entries[i].name))
792 excludable = true;
793 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
794 excludable = true;
795 break;
796 case BACKGROUND:
797 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
798 excludable = true;
799 break;
800 default: /* never reached */
801 ABORT_TRACE ();
802 }
803 } while (excludable);
804 colors[index] = (struct color *)&color_entries[i];
805 }
806 else
807 find_color_entry (color_names[index], index, colors);
808 }
809 }
810
811 static void
812 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
813 {
814 bool found = false;
815 unsigned int i;
816
817 const unsigned int count = tables[index].count;
818 const struct color *const color_entries = tables[index].entries;
819
820 for (i = 0; i < count; i++)
821 if (streq (color_name->name, color_entries[i].name))
822 {
823 colors[index] = (struct color *)&color_entries[i];
824 found = true;
825 break;
826 }
827 if (!found)
828 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
829 }
830
831 static void
832 print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
833 {
834 /* --clean[-all] */
835 if (clean || clean_all)
836 print_clean (line);
837 else
838 {
839 /* Foreground color code is guaranteed to be set when background color code is present. */
840 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
841 printf ("\033[%s", colors[BACKGROUND]->code);
842 if (colors[FOREGROUND]->code)
843 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
844 else
845 printf (formats[FMT_GENERIC], line);
846 }
847 if (flags & CR)
848 putchar ('\r');
849 if (flags & LF)
850 putchar ('\n');
851 }
852
853 static void
854 print_clean (const char *line)
855 {
856 const char *p;
857 char ***offsets = NULL;
858 unsigned int count = 0, i = 0;
859
860 for (p = line; *p;)
861 {
862 /* ESC[ */
863 if (*p == 27 && *(p + 1) == '[')
864 {
865 const char *begin = p;
866 p += 2;
867 if (clean_all)
868 {
869 while (isdigit (*p) || *p == ';')
870 p++;
871 }
872 else if (clean)
873 {
874 bool check_values;
875 unsigned int iter = 0;
876 const char *digit;
877 do {
878 check_values = false;
879 iter++;
880 if (!isdigit (*p))
881 goto DISCARD;
882 digit = p;
883 while (isdigit (*p))
884 p++;
885 if (p - digit > 2)
886 goto DISCARD;
887 else /* check range */
888 {
889 char val[3];
890 int value;
891 unsigned int i;
892 const unsigned int digits = p - digit;
893 for (i = 0; i < digits; i++)
894 val[i] = *digit++;
895 val[i] = '\0';
896 value = atoi (val);
897 if (value == 0) /* reset */
898 {
899 if (iter > 1)
900 goto DISCARD;
901 goto END;
902 }
903 else if (value == 1) /* bold */
904 {
905 bool discard = false;
906 if (iter > 1)
907 discard = true;
908 else if (*p != ';')
909 discard = true;
910 if (discard)
911 goto DISCARD;
912 p++;
913 check_values = true;
914 }
915 else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
916 goto END;
917 else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
918 {
919 if (iter > 1)
920 goto DISCARD;
921 goto END;
922 }
923 else
924 goto DISCARD;
925 }
926 } while (iter == 1 && check_values);
927 }
928 END: if (*p == 'm')
929 {
930 const char *end = p++;
931 if (!offsets)
932 offsets = xmalloc (++count * sizeof (char **));
933 else
934 offsets = xrealloc (offsets, ++count * sizeof (char **));
935 offsets[i] = xmalloc (2 * sizeof (char *));
936 offsets[i][0] = (char *)begin; /* ESC */
937 offsets[i][1] = (char *)end; /* m */
938 i++;
939 continue;
940 }
941 DISCARD:
942 continue;
943 }
944 p++;
945 }
946
947 if (offsets)
948 print_free_offsets (line, offsets, count);
949 else
950 printf (formats[FMT_GENERIC], line);
951 }
952
953 #define SET_CHAR(offset, new, old) \
954 *old = *offset; \
955 *offset = new; \
956
957 #define RESTORE_CHAR(offset, old) \
958 *offset = old; \
959
960 static void
961 print_free_offsets (const char *line, char ***offsets, unsigned int count)
962 {
963 char ch;
964 unsigned int i;
965
966 SET_CHAR (offsets[0][0], '\0', &ch);
967 printf (formats[FMT_GENERIC], line);
968 RESTORE_CHAR (offsets[0][0], ch);
969
970 for (i = 0; i < count; i++)
971 {
972 char ch;
973 bool next_offset = false;
974 if (i + 1 < count)
975 {
976 SET_CHAR (offsets[i + 1][0], '\0', &ch);
977 next_offset = true;
978 }
979 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
980 if (next_offset)
981 RESTORE_CHAR (offsets[i + 1][0], ch);
982 }
983 for (i = 0; i < count; i++)
984 free_null (offsets[i]);
985 free_null (offsets);
986 }
987
988 #if !DEBUG
989 static void *
990 malloc_wrap (size_t size)
991 {
992 void *p = malloc (size);
993 if (!p)
994 MEM_ALLOC_FAIL ();
995 return p;
996 }
997
998 static void *
999 calloc_wrap (size_t nmemb, size_t size)
1000 {
1001 void *p = calloc (nmemb, size);
1002 if (!p)
1003 MEM_ALLOC_FAIL ();
1004 return p;
1005 }
1006
1007 static void *
1008 realloc_wrap (void *ptr, size_t size)
1009 {
1010 void *p = realloc (ptr, size);
1011 if (!p)
1012 MEM_ALLOC_FAIL ();
1013 return p;
1014 }
1015 #else
1016 static void *
1017 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
1018 {
1019 void *p = malloc (size);
1020 if (!p)
1021 MEM_ALLOC_FAIL_DEBUG (file, line);
1022 fprintf (log, "%s: malloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
1023 return p;
1024 }
1025
1026 static void *
1027 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
1028 {
1029 void *p = calloc (nmemb, size);
1030 if (!p)
1031 MEM_ALLOC_FAIL_DEBUG (file, line);
1032 fprintf (log, "%s: calloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)(nmemb * size), file, line);
1033 return p;
1034 }
1035
1036 static void *
1037 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
1038 {
1039 void *p = realloc (ptr, size);
1040 if (!p)
1041 MEM_ALLOC_FAIL_DEBUG (file, line);
1042 fprintf (log, "%s: realloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
1043 return p;
1044 }
1045 #endif /* !DEBUG */
1046
1047 static void
1048 free_wrap (void **ptr)
1049 {
1050 free (*ptr);
1051 *ptr = NULL;
1052 }
1053
1054 #if !DEBUG
1055 # define do_malloc(len, file, line) malloc_wrap(len)
1056 #else
1057 # define do_malloc(len, file, line) malloc_wrap_debug(len, file, line)
1058 #endif
1059
1060 static char *
1061 strdup_wrap (const char *str, const char *file, unsigned int line)
1062 {
1063 const size_t len = strlen (str) + 1;
1064 char *p = do_malloc (len, file, line);
1065 strncpy (p, str, len);
1066 return p;
1067 }
1068
1069 static char *
1070 str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned int line)
1071 {
1072 const size_t len = strlen (str1) + strlen (str2) + 1;
1073 char *p, *str;
1074
1075 p = str = do_malloc (len, file, line);
1076 strncpy (p, str1, strlen (str1));
1077 p += strlen (str1);
1078 strncpy (p, str2, strlen (str2));
1079 p += strlen (str2);
1080 *p = '\0';
1081
1082 return str;
1083 }
1084
1085 static bool
1086 get_bytes_size (unsigned long bytes, struct bytes_size *bytes_size)
1087 {
1088 const char *unit, units[] = { '0', 'K', 'M', 'G', '\0' };
1089 unsigned long size = bytes;
1090 if (bytes < 1024)
1091 return false;
1092 unit = units;
1093 while (size >= 1024 && *(unit + 1))
1094 {
1095 size /= 1024;
1096 unit++;
1097 }
1098 bytes_size->size = (unsigned int)size;
1099 bytes_size->unit = *unit;
1100 return true;
1101 }
1102
1103 static char *
1104 get_file_type (mode_t mode)
1105 {
1106 if (S_ISREG (mode))
1107 return "file";
1108 else if (S_ISDIR (mode))
1109 return "directory";
1110 else if (S_ISCHR (mode))
1111 return "character device";
1112 else if (S_ISBLK (mode))
1113 return "block device";
1114 else if (S_ISFIFO (mode))
1115 return "named pipe";
1116 else if (S_ISLNK (mode))
1117 return "symbolic link";
1118 else if (S_ISSOCK (mode))
1119 return "socket";
1120 else
1121 return "file";
1122 }
1123
1124 static bool
1125 has_color_name (const char *str, const char *name)
1126 {
1127 char *p;
1128
1129 assert (strlen (str));
1130 assert (strlen (name));
1131
1132 if (!(*str == *name || *str == toupper (*name)))
1133 return false;
1134 else if (*(name + 1) != '\0'
1135 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1136 return false;
1137
1138 return true;
1139 }
1140
1141 static FILE *
1142 open_file (const char *file, const char *mode)
1143 {
1144 FILE *stream;
1145
1146 errno = 0;
1147 stream = fopen (file, mode);
1148 if (!stream)
1149 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
1150
1151 return stream;
1152 }
1153
1154 #define DO_VFPRINTF(fmt) \
1155 va_list ap; \
1156 fprintf (stderr, "%s: ", program_name); \
1157 va_start (ap, fmt); \
1158 vfprintf (stderr, fmt, ap); \
1159 va_end (ap); \
1160 fprintf (stderr, "\n"); \
1161
1162 static void
1163 vfprintf_diag (const char *fmt, ...)
1164 {
1165 DO_VFPRINTF (fmt);
1166 }
1167
1168 static void
1169 vfprintf_fail (const char *fmt, ...)
1170 {
1171 DO_VFPRINTF (fmt);
1172 exit (EXIT_FAILURE);
1173 }
1174
1175 static void
1176 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1177 {
1178 /* nothing to stack */
1179 if (ptr == NULL)
1180 return;
1181 if (!*list)
1182 *list = xmalloc (sizeof (void *));
1183 else
1184 {
1185 unsigned int i;
1186 for (i = 0; i < *stacked; i++)
1187 if (!(*list)[i])
1188 {
1189 (*list)[i] = ptr;
1190 return; /* reused */
1191 }
1192 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1193 }
1194 (*list)[index] = ptr;
1195 (*stacked)++;
1196 }
1197
1198 static void
1199 release_var (void **list, unsigned int stacked, void **ptr)
1200 {
1201 unsigned int i;
1202 /* nothing to release */
1203 if (*ptr == NULL)
1204 return;
1205 for (i = 0; i < stacked; i++)
1206 if (list[i] == *ptr)
1207 {
1208 free (*ptr);
1209 *ptr = NULL;
1210 list[i] = NULL;
1211 return;
1212 }
1213 }