]> git.refcnt.org Git - colorize.git/blobdiff - colorize.c
colorize 0.55
[colorize.git] / colorize.c
index 6cd51f773db27fe494f6f1ed4aefaed206799258..1325fce1f10326f432fa55d0edf2c5cde168dec1 100644 (file)
@@ -2,7 +2,7 @@
  * colorize - Read text from standard input stream or file and print
  *            it colorized through use of ANSI escape sequences
  *
- * Copyright (c) 2011-2013 Steven Schubiger
+ * Copyright (c) 2011-2015 Steven Schubiger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,9 @@
  *
  */
 
-#define _POSIX_SOURCE
+#define _BSD_SOURCE
+#define _XOPEN_SOURCE 700
+#define _FILE_OFFSET_BITS 64
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
 
 #define streq(s1, s2) (strcmp (s1, s2) == 0)
 
-#define vfprintf_fail(fmt, ...) vfprintf_name(true,  fmt, __VA_ARGS__)
-#define vfprintf_diag(fmt, ...) vfprintf_name(false, fmt, __VA_ARGS__)
-
-#if DEBUG
-# define xmalloc(size)        malloc_wrap_debug(size,        __FILE__, __LINE__)
-# define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
-# define xrealloc(ptr, size)  realloc_wrap_debug(ptr, size,  __FILE__, __LINE__)
+#if !DEBUG
+# define xmalloc(size)          malloc_wrap(size)
+# define xcalloc(nmemb, size)   calloc_wrap(nmemb, size)
+# define xrealloc(ptr, size)    realloc_wrap(ptr, size)
+# define xstrdup(str)           strdup_wrap(str,            NULL, 0)
+# define str_concat(str1, str2) str_concat_wrap(str1, str2, NULL, 0)
 #else
-# define xmalloc(size)        malloc_wrap(size)
-# define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
-# define xrealloc(ptr, size)  realloc_wrap(ptr, size)
+# define xmalloc(size)          malloc_wrap_debug(size,        __FILE__, __LINE__)
+# define xcalloc(nmemb, size)   calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
+# define xrealloc(ptr, size)    realloc_wrap_debug(ptr, size,  __FILE__, __LINE__)
+# define xstrdup(str)           strdup_wrap(str,               __FILE__, __LINE__)
+# define str_concat(str1, str2) str_concat_wrap(str1, str2,    __FILE__, __LINE__)
 #endif
 
 #define free_null(ptr) free_wrap((void **)&ptr)
-#define xstrdup(str)   strdup_wrap(str)
 
-#if !defined BUF_SIZE || BUF_SIZE <= 0
+#if defined(BUF_SIZE) && (BUF_SIZE <= 0 || BUF_SIZE > 65536)
 # undef BUF_SIZE
-# define BUF_SIZE 4096 + 1
+#endif
+#ifndef BUF_SIZE
+# define BUF_SIZE 4096
 #endif
 
 #define LF 0x01
 
 #define SKIP_LINE_ENDINGS(flags) (((flags) & CR) && ((flags) & LF) ? 2 : 1)
 
+#define VALID_FILE_TYPE(mode) (S_ISREG (mode) || S_ISLNK (mode) || S_ISFIFO (mode))
+
 #define STACK_VAR(ptr) do {                                   \
     stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
-} while (false);
+} while (false)
 
 #define RELEASE_VAR(ptr) do {                             \
     release_var (vars_list, stacked_vars, (void **)&ptr); \
-} while (false);
+} while (false)
 
-#define MEM_ALLOC_FAIL_DEBUG(file, line) do {                                               \
-    fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
-    exit (2);                                                                               \
-} while (false);
-#define MEM_ALLOC_FAIL() do {                                          \
+#if !DEBUG
+# define MEM_ALLOC_FAIL() do {                                         \
     fprintf (stderr, "%s: memory allocation failure\n", program_name); \
-    exit (2);                                                          \
-} while (false);
+    exit (EXIT_FAILURE);                                               \
+} while (false)
+#else
+# define MEM_ALLOC_FAIL_DEBUG(file, line) do {                                              \
+    fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
+    exit (EXIT_FAILURE);                                                                    \
+} while (false)
+#endif
 
 #define ABORT_TRACE()                                                              \
     fprintf (stderr, "Aborting in source file %s, line %u\n", __FILE__, __LINE__); \
 
 #define COLOR_SEP_CHAR '/'
 
-#define VERSION "0.50"
+#define DEBUG_FILE "debug.txt"
 
-typedef unsigned short bool;
+#define VERSION "0.55"
 
-enum { false, true };
+typedef enum { false, true } bool;
 
 struct color_name {
     char *name;
@@ -122,8 +131,8 @@ static const struct color fg_colors[] = {
     { "green",   "32m" },
     { "yellow",  "33m" },
     { "blue",    "34m" },
-    { "cyan",    "35m" },
-    { "magenta", "36m" },
+    { "magenta", "35m" },
+    { "cyan",    "36m" },
     { "white",   "37m" },
     { "default", "39m" },
 };
@@ -134,25 +143,36 @@ static const struct color bg_colors[] = {
     { "green",   "42m" },
     { "yellow",  "43m" },
     { "blue",    "44m" },
-    { "cyan",    "45m" },
-    { "magenta", "46m" },
+    { "magenta", "45m" },
+    { "cyan",    "46m" },
     { "white",   "47m" },
     { "default", "49m" },
 };
 
+struct bytes_size {
+    unsigned int size;
+    char unit;
+};
+
 enum fmts {
     FMT_GENERIC,
+    FMT_STRING,
+    FMT_QUOTE,
     FMT_COLOR,
     FMT_RANDOM,
     FMT_ERROR,
-    FMT_FILE
+    FMT_FILE,
+    FMT_TYPE
 };
 static const char *formats[] = {
     "%s",                    /* generic */
+    "%s '%s'",               /* string  */
+    "%s `%s' %s",            /* quote   */
     "%s color '%s' %s",      /* color   */
     "%s color '%s' %s '%s'", /* random  */
     "less than %u bytes %s", /* error   */
     "%s: %s",                /* file    */
+    "%s: %s: %s",            /* type    */
 };
 
 enum { FOREGROUND, BACKGROUND };
@@ -167,6 +187,9 @@ static const struct {
 };
 
 static FILE *stream = NULL;
+#if DEBUG
+static FILE *log = NULL;
+#endif
 
 static unsigned int stacked_vars = 0;
 static void **vars_list = NULL;
@@ -178,28 +201,37 @@ static char *exclude = NULL;
 
 static const char *program_name;
 
+static void print_hint (void);
 static void print_help (void);
 static void print_version (void);
 static void cleanup (void);
 static void free_color_names (struct color_name **);
-static void process_options (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
-static void process_file_option (const char *, const char **, FILE **);
+static void process_args (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
+static void process_file_arg (const char *, const char **, FILE **);
 static void read_print_stream (bool, const struct color **, const char *, FILE *);
 static void find_color_entries (struct color_name **, const struct color **);
-static void find_color_entry (const char *const, unsigned int, const struct color **);
-static void print_line (const struct color **, bool, const char * const, unsigned int);
+static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
+static void print_line (bool, const struct color **, const char * const, unsigned int);
 static void print_clean (const char *);
 static void print_free_offsets (const char *, char ***, unsigned int);
+#if !DEBUG
 static void *malloc_wrap (size_t);
 static void *calloc_wrap (size_t, size_t);
 static void *realloc_wrap (void *, size_t);
+#else
 static void *malloc_wrap_debug (size_t, const char *, unsigned int);
 static void *calloc_wrap_debug (size_t, size_t, const char *, unsigned int);
 static void *realloc_wrap_debug (void *, size_t, const char *, unsigned int);
+#endif
 static void free_wrap (void **);
-static char *strdup_wrap (const char *);
-static char *str_concat (const char *, const char *);
-static void vfprintf_name (bool, const char *, ...);
+static char *strdup_wrap (const char *, const char *, unsigned int);
+static char *str_concat_wrap (const char *, const char *, const char *, unsigned int);
+static bool get_bytes_size (unsigned long, struct bytes_size *);
+static char *get_file_type (mode_t);
+static bool has_color_name (const char *, const char *);
+static FILE *open_file (const char *, const char *);
+static void vfprintf_diag (const char *, ...);
+static void vfprintf_fail (const char *, ...);
 static void stack_var (void ***, unsigned int *, unsigned int, void *);
 static void release_var (void **, unsigned int, void **);
 
@@ -211,6 +243,8 @@ static void release_var (void **, unsigned int, void **);
 extern char *optarg;
 extern int optind;
 
+static int opt_type = 0;
+
 int
 main (int argc, char **argv)
 {
@@ -224,14 +258,14 @@ main (int argc, char **argv)
         OPT_VERSION
     };
 
-    int opt, opt_type = 0;
+    int opt;
     struct option long_opts[] = {
         { "clean",          no_argument,       &opt_type, OPT_CLEAN          },
         { "clean-all",      no_argument,       &opt_type, OPT_CLEAN_ALL      },
         { "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
         { "help",           no_argument,       &opt_type, OPT_HELP           },
         { "version",        no_argument,       &opt_type, OPT_VERSION        },
-        {  0,               0,                 0,         0                  },
+        {  NULL,            0,                 NULL,      0                  },
     };
 
     bool bold = false;
@@ -241,13 +275,17 @@ main (int argc, char **argv)
         NULL, /* background */
     };
 
-    const char *file;
+    const char *file = NULL;
 
     program_name = argv[0];
     atexit (cleanup);
 
     setvbuf (stdout, NULL, _IOLBF, 0);
 
+#if DEBUG
+    log = open_file (DEBUG_FILE, "w");
+#endif
+
     while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
       {
         PARSE_OPT:
@@ -263,13 +301,21 @@ main (int argc, char **argv)
                     clean_all = true;
                     break;
                   case OPT_EXCLUDE_RANDOM: {
-                    char *p;
+                    bool valid = false;
+                    unsigned int i;
                     exclude = xstrdup (optarg);
                     STACK_VAR (exclude);
-                    for (p = exclude; *p; p++)
-                      *p = tolower (*p);
-                    if (streq (exclude, "random"))
-                      vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a color");
+                    for (i = 1; i < tables[FOREGROUND].count - 1; i++) /* skip color none and default */
+                      {
+                        const struct color *entry = &tables[FOREGROUND].entries[i];
+                        if (streq (exclude, entry->name))
+                          {
+                            valid = true;
+                            break;
+                          }
+                      }
+                    if (!valid)
+                      vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
                     break;
                   }
                   case OPT_HELP:
@@ -287,7 +333,7 @@ main (int argc, char **argv)
             case 'v':
               SET_OPT_TYPE (OPT_VERSION);
             case '?':
-              print_help ();
+              print_hint ();
               exit (EXIT_FAILURE);
             default: /* never reached */
               ABORT_TRACE ();
@@ -314,15 +360,16 @@ main (int argc, char **argv)
       {
         if (arg_cnt == 0 || arg_cnt > 2)
           {
-            print_help ();
+            vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
+            print_hint ();
             exit (EXIT_FAILURE);
           }
       }
 
     if (clean || clean_all)
-      process_file_option (argv[optind], &file, &stream);
+      process_file_arg (argv[optind], &file, &stream);
     else
-      process_options (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
+      process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
     read_print_stream (bold, colors, file, stream);
 
     RELEASE_VAR (exclude);
@@ -330,6 +377,12 @@ main (int argc, char **argv)
     exit (EXIT_SUCCESS);
 }
 
+static void
+print_hint (void)
+{
+    fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
+}
+
 static void
 print_help (void)
 {
@@ -364,15 +417,41 @@ print_help (void)
 static void
 print_version (void)
 {
+#ifdef HAVE_VERSION
+# include "version.h"
+#else
+    const char *version = NULL;
+#endif
+    const char *version_prefix, *version_string;
     const char *c_flags;
-    printf ("%s v%s (compiled at %s, %s)\n", "colorize", VERSION, __DATE__, __TIME__);
+    struct bytes_size bytes_size;
+    bool debug;
 #ifdef CFLAGS
     c_flags = to_str (CFLAGS);
 #else
     c_flags = "unknown";
 #endif
+#if DEBUG
+    debug = true;
+#else
+    debug = false;
+#endif
+    version_prefix = version ? "" : "v";
+    version_string = version ? version : VERSION;
+    printf ("colorize %s%s (compiled at %s, %s)\n", version_prefix, version_string, __DATE__, __TIME__);
+
     printf ("Compiler flags: %s\n", c_flags);
-    printf ("Buffer size: %u bytes\n", BUF_SIZE - 1);
+    if (get_bytes_size (BUF_SIZE, &bytes_size))
+      {
+        if (BUF_SIZE % 1024 == 0)
+          printf ("Buffer size: %u%c\n", bytes_size.size, bytes_size.unit);
+        else
+          printf ("Buffer size: %u%c, %u byte%s\n", bytes_size.size, bytes_size.unit,
+                   BUF_SIZE % 1024, BUF_SIZE % 1024 > 1 ? "s" : "");
+      }
+    else
+      printf ("Buffer size: %lu byte%s\n", (unsigned long)BUF_SIZE, BUF_SIZE > 1 ? "s" : "");
+    printf ("Debugging: %s\n", debug ? "yes" : "no");
 }
 
 static void
@@ -382,6 +461,10 @@ cleanup (void)
 
     if (stream && fileno (stream) != STDIN_FILENO)
       fclose (stream);
+#if DEBUG
+    if (log)
+      fclose (log);
+#endif
 
     if (vars_list)
       {
@@ -407,15 +490,15 @@ free_color_names (struct color_name **color_names)
 }
 
 static void
-process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
+process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
 {
     int ret;
     unsigned int index;
     char *color, *p, *str;
     struct stat sb;
 
-    const char *color_string = arg_cnt >= 1 ? option_strings[0] : NULL;
-    const char *file_string  = arg_cnt == 2 ? option_strings[1] : NULL;
+    const char *color_string = arg_cnt >= 1 ? arg_strings[0] : NULL;
+    const char *file_string  = arg_cnt == 2 ? arg_strings[1] : NULL;
 
     assert (color_string);
 
@@ -427,15 +510,16 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
           vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
       }
 
-    ret = stat (color_string, &sb);
+    ret = lstat (color_string, &sb);
 
     /* Ensure that we don't fail if there's a file with one or more
        color names in its path.  */
-    if (ret != -1)
+    if (ret == 0) /* success */
       {
         bool have_file;
         unsigned int c;
         const char *color = color_string;
+        const mode_t mode = sb.st_mode;
 
         for (c = 1; c <= 2 && *color; c++)
           {
@@ -444,14 +528,18 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
             for (i = 0; i < tables[FOREGROUND].count; i++)
               {
                 const struct color *entry = &tables[FOREGROUND].entries[i];
-                char *p;
-                if ((p = strstr (color, entry->name)) && p == color)
+                if (has_color_name (color, entry->name))
                   {
-                    color = p + strlen (entry->name);
+                    color += strlen (entry->name);
                     matched = true;
                     break;
                   }
               }
+            if (!matched && has_color_name (color, "random"))
+              {
+                color += strlen ("random");
+                matched = true;
+              }
             if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
               color++;
             else
@@ -462,21 +550,27 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
 
         if (have_file)
           {
+            const char *file_exists = color_string;
             if (file_string)
-              vfprintf_fail (formats[FMT_GENERIC], "file cannot be used as color string");
+              vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "cannot be used as color string");
             else
-              vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
+              {
+                if (VALID_FILE_TYPE (mode))
+                  vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "must be preceeded by color string");
+                else
+                  vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_exists, "is not a valid file type");
+              }
           }
       }
 
     if ((p = strchr (color_string, COLOR_SEP_CHAR)))
       {
         if (p == color_string)
-          vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
+          vfprintf_fail (formats[FMT_STRING], "foreground color missing in string", color_string);
         else if (p == color_string + strlen (color_string) - 1)
-          vfprintf_fail (formats[FMT_GENERIC], "background color missing");
+          vfprintf_fail (formats[FMT_STRING], "background color missing in string", color_string);
         else if (strchr (++p, COLOR_SEP_CHAR))
-          vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
+          vfprintf_fail (formats[FMT_STRING], "one color pair allowed only for string", color_string);
       }
 
     str = xstrdup (color_string);
@@ -485,6 +579,8 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
     for (index = 0, color = str; *color; index++, color = p)
       {
         char *ch, *sep;
+
+        p = NULL;
         if ((sep = strchr (color, COLOR_SEP_CHAR)))
           {
             *sep = '\0';
@@ -492,6 +588,7 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
           }
         else
           p = color + strlen (color);
+        assert (p);
 
         for (ch = color; *ch; ch++)
           if (!isalpha (*ch))
@@ -550,13 +647,18 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
     free_color_names (color_names);
 
     if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
-      find_color_entry ("default", FOREGROUND, colors);
+      {
+        struct color_name color_name;
+        color_name.name = color_name.orig = "default";
+
+        find_color_entry (&color_name, FOREGROUND, colors);
+      }
 
-    process_file_option (file_string, file, stream);
+    process_file_arg (file_string, file, stream);
 }
 
 static void
-process_file_option (const char *file_string, const char **file, FILE **stream)
+process_file_arg (const char *file_string, const char **file, FILE **stream)
 {
     if (file_string)
       {
@@ -567,16 +669,16 @@ process_file_option (const char *file_string, const char **file, FILE **stream)
             FILE *s;
             const char *file = file_string;
             struct stat sb;
-            int errno, ret;
+            int ret;
 
             errno = 0;
-            ret = stat (file, &sb);
+            ret = lstat (file, &sb);
 
             if (ret == -1)
               vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
 
-            if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
-              vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
+            if (!VALID_FILE_TYPE (sb.st_mode))
+              vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
 
             errno = 0;
 
@@ -594,6 +696,7 @@ process_file_option (const char *file_string, const char **file, FILE **stream)
       }
 
     assert (*stream);
+    assert (*file);
 }
 
 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
@@ -605,14 +708,14 @@ process_file_option (const char *file_string, const char **file, FILE **stream)
       }                                                          \
     current_line = merged_line ? merged_line : (char *)line;     \
     if (!check_eof || *current_line != '\0')                     \
-      print_line (colors, bold, current_line, flags);            \
+      print_line (bold, colors, current_line, flags);            \
     free (merged_line);                                          \
-} while (false);
+} while (false)
 
 static void
 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
 {
-    char buf[BUF_SIZE], *part_line = NULL;
+    char buf[BUF_SIZE + 1], *part_line = NULL;
     unsigned int flags = 0;
 
     while (!feof (stream))
@@ -620,10 +723,10 @@ read_print_stream (bool bold, const struct color **colors, const char *file, FIL
         size_t bytes_read;
         char *eol;
         const char *line;
-        memset (buf, '\0', BUF_SIZE);
-        bytes_read = fread (buf, 1, BUF_SIZE - 1, stream);
-        if (bytes_read != (BUF_SIZE - 1) && ferror (stream))
-          vfprintf_fail (formats[FMT_ERROR], BUF_SIZE - 1, "read");
+        memset (buf, '\0', BUF_SIZE + 1);
+        bytes_read = fread (buf, 1, BUF_SIZE, stream);
+        if (bytes_read != BUF_SIZE && ferror (stream))
+          vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
         line = buf;
         while ((eol = strpbrk (line, "\n\r")))
           {
@@ -650,7 +753,7 @@ read_print_stream (bool bold, const struct color **colors, const char *file, FIL
         else if (*line != '\0')
           {
             if (!clean && !clean_all) /* efficiency */
-              print_line (colors, bold, line, 0);
+              print_line (bold, colors, line, 0);
             else if (!part_line)
               part_line = xstrdup (line);
             else
@@ -707,12 +810,12 @@ find_color_entries (struct color_name **color_names, const struct color **colors
             colors[index] = (struct color *)&color_entries[i];
           }
         else
-          find_color_entry (color_name, index, colors);
+          find_color_entry (color_names[index], index, colors);
       }
 }
 
 static void
-find_color_entry (const char *const color_name, unsigned int index, const struct color **colors)
+find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
 {
     bool found = false;
     unsigned int i;
@@ -721,18 +824,18 @@ find_color_entry (const char *const color_name, unsigned int index, const struct
     const struct color *const color_entries = tables[index].entries;
 
     for (i = 0; i < count; i++)
-      if (streq (color_name, color_entries[i].name))
+      if (streq (color_name->name, color_entries[i].name))
         {
           colors[index] = (struct color *)&color_entries[i];
           found = true;
           break;
         }
     if (!found)
-      vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name, "not recognized");
+      vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
 }
 
 static void
-print_line (const struct color **colors, bool bold, const char *const line, unsigned int flags)
+print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
 {
     /* --clean[-all] */
     if (clean || clean_all)
@@ -888,6 +991,7 @@ print_free_offsets (const char *line, char ***offsets, unsigned int count)
     free_null (offsets);
 }
 
+#if !DEBUG
 static void *
 malloc_wrap (size_t size)
 {
@@ -914,13 +1018,14 @@ realloc_wrap (void *ptr, size_t size)
       MEM_ALLOC_FAIL ();
     return p;
 }
-
+#else
 static void *
 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
 {
     void *p = malloc (size);
     if (!p)
       MEM_ALLOC_FAIL_DEBUG (file, line);
+    fprintf (log, "%s: malloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
     return p;
 }
 
@@ -930,6 +1035,7 @@ calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int lin
     void *p = calloc (nmemb, size);
     if (!p)
       MEM_ALLOC_FAIL_DEBUG (file, line);
+    fprintf (log, "%s: calloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)(nmemb * size), file, line);
     return p;
 }
 
@@ -939,8 +1045,10 @@ realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
     void *p = realloc (ptr, size);
     if (!p)
       MEM_ALLOC_FAIL_DEBUG (file, line);
+    fprintf (log, "%s: realloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
     return p;
 }
+#endif /* !DEBUG */
 
 static void
 free_wrap (void **ptr)
@@ -949,22 +1057,28 @@ free_wrap (void **ptr)
     *ptr = NULL;
 }
 
+#if !DEBUG
+# define do_malloc(len, file, line) malloc_wrap(len)
+#else
+# define do_malloc(len, file, line) malloc_wrap_debug(len, file, line)
+#endif
+
 static char *
-strdup_wrap (const char *str)
+strdup_wrap (const char *str, const char *file, unsigned int line)
 {
     const size_t len = strlen (str) + 1;
-    char *p = xmalloc (len);
+    char *p = do_malloc (len, file, line);
     strncpy (p, str, len);
     return p;
 }
 
 static char *
-str_concat (const char *str1, const char *str2)
+str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned int line)
 {
     const size_t len = strlen (str1) + strlen (str2) + 1;
     char *p, *str;
 
-    p = str = xmalloc (len);
+    p = str = do_malloc (len, file, line);
     strncpy (p, str1, strlen (str1));
     p += strlen (str1);
     strncpy (p, str2, strlen (str2));
@@ -974,17 +1088,94 @@ str_concat (const char *str1, const char *str2)
     return str;
 }
 
+static bool
+get_bytes_size (unsigned long bytes, struct bytes_size *bytes_size)
+{
+    const char *unit, units[] = { '0', 'K', 'M', 'G', '\0' };
+    unsigned long size = bytes;
+    if (bytes < 1024)
+      return false;
+    unit = units;
+    while (size >= 1024 && *(unit + 1))
+      {
+        size /= 1024;
+        unit++;
+      }
+    bytes_size->size = (unsigned int)size;
+    bytes_size->unit = *unit;
+    return true;
+}
+
+static char *
+get_file_type (mode_t mode)
+{
+    if (S_ISREG (mode))
+      return "file";
+    else if (S_ISDIR (mode))
+      return "directory";
+    else if (S_ISCHR (mode))
+      return "character device";
+    else if (S_ISBLK (mode))
+      return "block device";
+    else if (S_ISFIFO (mode))
+      return "named pipe";
+    else if (S_ISLNK (mode))
+      return "symbolic link";
+    else if (S_ISSOCK (mode))
+      return "socket";
+    else
+      return "file";
+}
+
+static bool
+has_color_name (const char *str, const char *name)
+{
+    char *p;
+
+    assert (strlen (str));
+    assert (strlen (name));
+
+    if (!(*str == *name || *str == toupper (*name)))
+      return false;
+    else if (*(name + 1) != '\0'
+     && !((p = strstr (str + 1, name + 1)) && p == str + 1))
+      return false;
+
+    return true;
+}
+
+static FILE *
+open_file (const char *file, const char *mode)
+{
+    FILE *stream;
+
+    errno = 0;
+    stream = fopen (file, mode);
+    if (!stream)
+      vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
+
+    return stream;
+}
+
+#define DO_VFPRINTF(fmt)                    \
+    va_list ap;                             \
+    fprintf (stderr, "%s: ", program_name); \
+    va_start (ap, fmt);                     \
+    vfprintf (stderr, fmt, ap);             \
+    va_end (ap);                            \
+    fprintf (stderr, "\n");                 \
+
+static void
+vfprintf_diag (const char *fmt, ...)
+{
+    DO_VFPRINTF (fmt);
+}
+
 static void
-vfprintf_name (bool fatal, const char *fmt, ...)
+vfprintf_fail (const char *fmt, ...)
 {
-    va_list ap;
-    fprintf (stderr, "%s: ", program_name);
-    va_start (ap, fmt);
-    vfprintf (stderr, fmt, ap);
-    va_end (ap);
-    fprintf (stderr, "\n");
-    if (fatal)
-      exit (EXIT_FAILURE);
+    DO_VFPRINTF (fmt);
+    exit (EXIT_FAILURE);
 }
 
 static void