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