]> git.refcnt.org Git - colorize.git/blob - colorize.c
colorize 0.55
[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 FILE *s;
670 const char *file = file_string;
671 struct stat sb;
672 int ret;
673
674 errno = 0;
675 ret = lstat (file, &sb);
676
677 if (ret == -1)
678 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
679
680 if (!VALID_FILE_TYPE (sb.st_mode))
681 vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
682
683 errno = 0;
684
685 s = fopen (file, "r");
686 if (!s)
687 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
688 *stream = s;
689 }
690 *file = file_string;
691 }
692 else
693 {
694 *stream = stdin;
695 *file = "stdin";
696 }
697
698 assert (*stream);
699 assert (*file);
700 }
701
702 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
703 char *current_line, *merged_line = NULL; \
704 if (part_line) \
705 { \
706 merged_line = str_concat (part_line, line); \
707 free_null (part_line); \
708 } \
709 current_line = merged_line ? merged_line : (char *)line; \
710 if (!check_eof || *current_line != '\0') \
711 print_line (bold, colors, current_line, flags); \
712 free (merged_line); \
713 } while (false)
714
715 static void
716 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
717 {
718 char buf[BUF_SIZE + 1], *part_line = NULL;
719 unsigned int flags = 0;
720
721 while (!feof (stream))
722 {
723 size_t bytes_read;
724 char *eol;
725 const char *line;
726 memset (buf, '\0', BUF_SIZE + 1);
727 bytes_read = fread (buf, 1, BUF_SIZE, stream);
728 if (bytes_read != BUF_SIZE && ferror (stream))
729 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
730 line = buf;
731 while ((eol = strpbrk (line, "\n\r")))
732 {
733 char *p;
734 flags &= ~(CR|LF);
735 if (*eol == '\r')
736 {
737 flags |= CR;
738 if (*(eol + 1) == '\n')
739 flags |= LF;
740 }
741 else if (*eol == '\n')
742 flags |= LF;
743 else
744 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
745 p = eol + SKIP_LINE_ENDINGS (flags);
746 *eol = '\0';
747 MERGE_PRINT_LINE (part_line, line, flags, false);
748 line = p;
749 }
750 if (feof (stream)) {
751 MERGE_PRINT_LINE (part_line, line, 0, true);
752 }
753 else if (*line != '\0')
754 {
755 if (!clean && !clean_all) /* efficiency */
756 print_line (bold, colors, line, 0);
757 else if (!part_line)
758 part_line = xstrdup (line);
759 else
760 {
761 char *merged_line = str_concat (part_line, line);
762 free (part_line);
763 part_line = merged_line;
764 }
765 }
766 }
767 }
768
769 static void
770 find_color_entries (struct color_name **color_names, const struct color **colors)
771 {
772 struct timeval tv;
773 unsigned int index;
774
775 /* randomness */
776 gettimeofday (&tv, NULL);
777 srand (tv.tv_usec * tv.tv_sec);
778
779 for (index = 0; color_names[index]; index++)
780 {
781 const char *color_name = color_names[index]->name;
782
783 const unsigned int count = tables[index].count;
784 const struct color *const color_entries = tables[index].entries;
785
786 if (streq (color_name, "random"))
787 {
788 bool excludable;
789 unsigned int i;
790 do {
791 excludable = false;
792 i = rand() % (count - 2) + 1; /* omit color none and default */
793 switch (index)
794 {
795 case FOREGROUND:
796 /* --exclude-random */
797 if (exclude && streq (exclude, color_entries[i].name))
798 excludable = true;
799 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
800 excludable = true;
801 break;
802 case BACKGROUND:
803 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
804 excludable = true;
805 break;
806 default: /* never reached */
807 ABORT_TRACE ();
808 }
809 } while (excludable);
810 colors[index] = (struct color *)&color_entries[i];
811 }
812 else
813 find_color_entry (color_names[index], index, colors);
814 }
815 }
816
817 static void
818 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
819 {
820 bool found = false;
821 unsigned int i;
822
823 const unsigned int count = tables[index].count;
824 const struct color *const color_entries = tables[index].entries;
825
826 for (i = 0; i < count; i++)
827 if (streq (color_name->name, color_entries[i].name))
828 {
829 colors[index] = (struct color *)&color_entries[i];
830 found = true;
831 break;
832 }
833 if (!found)
834 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
835 }
836
837 static void
838 print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
839 {
840 /* --clean[-all] */
841 if (clean || clean_all)
842 print_clean (line);
843 else
844 {
845 /* Foreground color code is guaranteed to be set when background color code is present. */
846 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
847 printf ("\033[%s", colors[BACKGROUND]->code);
848 if (colors[FOREGROUND]->code)
849 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
850 else
851 printf (formats[FMT_GENERIC], line);
852 }
853 if (flags & CR)
854 putchar ('\r');
855 if (flags & LF)
856 putchar ('\n');
857 }
858
859 static void
860 print_clean (const char *line)
861 {
862 const char *p;
863 char ***offsets = NULL;
864 unsigned int count = 0, i = 0;
865
866 for (p = line; *p;)
867 {
868 /* ESC[ */
869 if (*p == 27 && *(p + 1) == '[')
870 {
871 const char *begin = p;
872 p += 2;
873 if (clean_all)
874 {
875 while (isdigit (*p) || *p == ';')
876 p++;
877 }
878 else if (clean)
879 {
880 bool check_values;
881 unsigned int iter = 0;
882 const char *digit;
883 do {
884 check_values = false;
885 iter++;
886 if (!isdigit (*p))
887 goto DISCARD;
888 digit = p;
889 while (isdigit (*p))
890 p++;
891 if (p - digit > 2)
892 goto DISCARD;
893 else /* check range */
894 {
895 char val[3];
896 int value;
897 unsigned int i;
898 const unsigned int digits = p - digit;
899 for (i = 0; i < digits; i++)
900 val[i] = *digit++;
901 val[i] = '\0';
902 value = atoi (val);
903 if (value == 0) /* reset */
904 {
905 if (iter > 1)
906 goto DISCARD;
907 goto END;
908 }
909 else if (value == 1) /* bold */
910 {
911 bool discard = false;
912 if (iter > 1)
913 discard = true;
914 else if (*p != ';')
915 discard = true;
916 if (discard)
917 goto DISCARD;
918 p++;
919 check_values = true;
920 }
921 else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
922 goto END;
923 else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
924 {
925 if (iter > 1)
926 goto DISCARD;
927 goto END;
928 }
929 else
930 goto DISCARD;
931 }
932 } while (iter == 1 && check_values);
933 }
934 END: if (*p == 'm')
935 {
936 const char *end = p++;
937 if (!offsets)
938 offsets = xmalloc (++count * sizeof (char **));
939 else
940 offsets = xrealloc (offsets, ++count * sizeof (char **));
941 offsets[i] = xmalloc (2 * sizeof (char *));
942 offsets[i][0] = (char *)begin; /* ESC */
943 offsets[i][1] = (char *)end; /* m */
944 i++;
945 continue;
946 }
947 DISCARD:
948 continue;
949 }
950 p++;
951 }
952
953 if (offsets)
954 print_free_offsets (line, offsets, count);
955 else
956 printf (formats[FMT_GENERIC], line);
957 }
958
959 #define SET_CHAR(offset, new, old) \
960 *old = *offset; \
961 *offset = new; \
962
963 #define RESTORE_CHAR(offset, old) \
964 *offset = old; \
965
966 static void
967 print_free_offsets (const char *line, char ***offsets, unsigned int count)
968 {
969 char ch;
970 unsigned int i;
971
972 SET_CHAR (offsets[0][0], '\0', &ch);
973 printf (formats[FMT_GENERIC], line);
974 RESTORE_CHAR (offsets[0][0], ch);
975
976 for (i = 0; i < count; i++)
977 {
978 char ch;
979 bool next_offset = false;
980 if (i + 1 < count)
981 {
982 SET_CHAR (offsets[i + 1][0], '\0', &ch);
983 next_offset = true;
984 }
985 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
986 if (next_offset)
987 RESTORE_CHAR (offsets[i + 1][0], ch);
988 }
989 for (i = 0; i < count; i++)
990 free_null (offsets[i]);
991 free_null (offsets);
992 }
993
994 #if !DEBUG
995 static void *
996 malloc_wrap (size_t size)
997 {
998 void *p = malloc (size);
999 if (!p)
1000 MEM_ALLOC_FAIL ();
1001 return p;
1002 }
1003
1004 static void *
1005 calloc_wrap (size_t nmemb, size_t size)
1006 {
1007 void *p = calloc (nmemb, size);
1008 if (!p)
1009 MEM_ALLOC_FAIL ();
1010 return p;
1011 }
1012
1013 static void *
1014 realloc_wrap (void *ptr, size_t size)
1015 {
1016 void *p = realloc (ptr, size);
1017 if (!p)
1018 MEM_ALLOC_FAIL ();
1019 return p;
1020 }
1021 #else
1022 static void *
1023 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
1024 {
1025 void *p = malloc (size);
1026 if (!p)
1027 MEM_ALLOC_FAIL_DEBUG (file, line);
1028 fprintf (log, "%s: malloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
1029 return p;
1030 }
1031
1032 static void *
1033 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
1034 {
1035 void *p = calloc (nmemb, size);
1036 if (!p)
1037 MEM_ALLOC_FAIL_DEBUG (file, line);
1038 fprintf (log, "%s: calloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)(nmemb * size), file, line);
1039 return p;
1040 }
1041
1042 static void *
1043 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
1044 {
1045 void *p = realloc (ptr, size);
1046 if (!p)
1047 MEM_ALLOC_FAIL_DEBUG (file, line);
1048 fprintf (log, "%s: realloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
1049 return p;
1050 }
1051 #endif /* !DEBUG */
1052
1053 static void
1054 free_wrap (void **ptr)
1055 {
1056 free (*ptr);
1057 *ptr = NULL;
1058 }
1059
1060 #if !DEBUG
1061 # define do_malloc(len, file, line) malloc_wrap(len)
1062 #else
1063 # define do_malloc(len, file, line) malloc_wrap_debug(len, file, line)
1064 #endif
1065
1066 static char *
1067 strdup_wrap (const char *str, const char *file, unsigned int line)
1068 {
1069 const size_t len = strlen (str) + 1;
1070 char *p = do_malloc (len, file, line);
1071 strncpy (p, str, len);
1072 return p;
1073 }
1074
1075 static char *
1076 str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned int line)
1077 {
1078 const size_t len = strlen (str1) + strlen (str2) + 1;
1079 char *p, *str;
1080
1081 p = str = do_malloc (len, file, line);
1082 strncpy (p, str1, strlen (str1));
1083 p += strlen (str1);
1084 strncpy (p, str2, strlen (str2));
1085 p += strlen (str2);
1086 *p = '\0';
1087
1088 return str;
1089 }
1090
1091 static bool
1092 get_bytes_size (unsigned long bytes, struct bytes_size *bytes_size)
1093 {
1094 const char *unit, units[] = { '0', 'K', 'M', 'G', '\0' };
1095 unsigned long size = bytes;
1096 if (bytes < 1024)
1097 return false;
1098 unit = units;
1099 while (size >= 1024 && *(unit + 1))
1100 {
1101 size /= 1024;
1102 unit++;
1103 }
1104 bytes_size->size = (unsigned int)size;
1105 bytes_size->unit = *unit;
1106 return true;
1107 }
1108
1109 static char *
1110 get_file_type (mode_t mode)
1111 {
1112 if (S_ISREG (mode))
1113 return "file";
1114 else if (S_ISDIR (mode))
1115 return "directory";
1116 else if (S_ISCHR (mode))
1117 return "character device";
1118 else if (S_ISBLK (mode))
1119 return "block device";
1120 else if (S_ISFIFO (mode))
1121 return "named pipe";
1122 else if (S_ISLNK (mode))
1123 return "symbolic link";
1124 else if (S_ISSOCK (mode))
1125 return "socket";
1126 else
1127 return "file";
1128 }
1129
1130 static bool
1131 has_color_name (const char *str, const char *name)
1132 {
1133 char *p;
1134
1135 assert (strlen (str));
1136 assert (strlen (name));
1137
1138 if (!(*str == *name || *str == toupper (*name)))
1139 return false;
1140 else if (*(name + 1) != '\0'
1141 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1142 return false;
1143
1144 return true;
1145 }
1146
1147 static FILE *
1148 open_file (const char *file, const char *mode)
1149 {
1150 FILE *stream;
1151
1152 errno = 0;
1153 stream = fopen (file, mode);
1154 if (!stream)
1155 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
1156
1157 return stream;
1158 }
1159
1160 #define DO_VFPRINTF(fmt) \
1161 va_list ap; \
1162 fprintf (stderr, "%s: ", program_name); \
1163 va_start (ap, fmt); \
1164 vfprintf (stderr, fmt, ap); \
1165 va_end (ap); \
1166 fprintf (stderr, "\n"); \
1167
1168 static void
1169 vfprintf_diag (const char *fmt, ...)
1170 {
1171 DO_VFPRINTF (fmt);
1172 }
1173
1174 static void
1175 vfprintf_fail (const char *fmt, ...)
1176 {
1177 DO_VFPRINTF (fmt);
1178 exit (EXIT_FAILURE);
1179 }
1180
1181 static void
1182 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1183 {
1184 /* nothing to stack */
1185 if (ptr == NULL)
1186 return;
1187 if (!*list)
1188 *list = xmalloc (sizeof (void *));
1189 else
1190 {
1191 unsigned int i;
1192 for (i = 0; i < *stacked; i++)
1193 if (!(*list)[i])
1194 {
1195 (*list)[i] = ptr;
1196 return; /* reused */
1197 }
1198 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1199 }
1200 (*list)[index] = ptr;
1201 (*stacked)++;
1202 }
1203
1204 static void
1205 release_var (void **list, unsigned int stacked, void **ptr)
1206 {
1207 unsigned int i;
1208 /* nothing to release */
1209 if (*ptr == NULL)
1210 return;
1211 for (i = 0; i < stacked; i++)
1212 if (list[i] == *ptr)
1213 {
1214 free (*ptr);
1215 *ptr = NULL;
1216 list[i] = NULL;
1217 return;
1218 }
1219 }