2005-06-14 William Brendling <wbrendling@gmail.com>
authorJim Meyering <jim@meyering.net>
Thu, 23 Jun 2005 14:20:16 +0000 (14:20 +0000)
committerJim Meyering <jim@meyering.net>
Thu, 23 Jun 2005 14:20:16 +0000 (14:20 +0000)
* src/du.c: Add --last-time and --time-style options.

src/du.c

index 67018ba..92c4fd4 100644 (file)
--- a/src/du.c
+++ b/src/du.c
 #include <getopt.h>
 #include <sys/types.h>
 #include <assert.h>
-
 #include "system.h"
+#include "argmatch.h"
 #include "dirname.h" /* for strip_trailing_slashes */
 #include "error.h"
 #include "exclude.h"
 #include "hash.h"
 #include "human.h"
+#include "inttostr.h"
 #include "quote.h"
 #include "quotearg.h"
 #include "readtokens0.h"
@@ -74,6 +75,48 @@ struct entry
 /* A set of dev/ino pairs.  */
 static Hash_table *htab;
 
+/* Define a class for collecting directory information. */
+
+struct duinfo
+{
+  uintmax_t size;    /* Size of files in directory */
+  time_t    dmax;    /* Last modified date */
+  int       nsec;    /* Nanoseconds part of date */
+  int       valid;   /* Indicates that date is valid */
+};
+
+/* DUINFO_INI (struct duinfo a); - Initialise duinfo structure. */
+#define DUINFO_INI(a)  { (a).size = 0; (a).dmax = 0; (a).nsec = 0; (a).valid = 0; }
+
+/* DUINFO_SET (struct duinfo a, uintmax_t size, time_t date, int nsec) - Set structure data. */
+#define DUINFO_SET(a, fsize, fdmax, fnsec)  \
+  { (a).size = (fsize); (a).dmax = (fdmax); (a).nsec = fnsec; (a).valid = 1; }
+
+/* DUINFO_ADD (struct duinfo a, const struct duinfo b) - Accumulate directory data. */
+#define DUINFO_ADD(a, b)  \
+  {  if ( (b).valid ) \
+       { \
+         (a).size += (b).size; \
+         if ( ( ! (a).valid ) || ( (b).dmax > (a).dmax ) ) \
+           { \
+             (a).dmax = (b).dmax; \
+             (a).nsec = (b).nsec; \
+             (a).valid = 1; \
+           } \
+         else if ( ( (b).dmax == (a).dmax ) && ( (b).nsec > (a).nsec ) ) \
+           { \
+             (a).nsec = (b).nsec; \
+           } \
+       } \
+   }
+
+/* A structure for per-directory level information */
+struct dulevel
+{
+  struct duinfo  ent;         /* Entries in this directory */
+  struct duinfo  subdir;      /* Total for subdirectories */
+};
+
 /* Name under which this program was invoked.  */
 char *program_name;
 
@@ -104,14 +147,34 @@ static size_t max_depth = SIZE_MAX;
 /* Human-readable options for output.  */
 static int human_output_opts;
 
+/* If option non-zero, print most recently modified date, using the specified format */
+static int opt_last_time = 0;
+
+/* Type of time to display. controlled by --last-time */
+
+enum time_type
+  {
+    time_mtime,                        /* default */
+    time_ctime,
+    time_atime
+  };
+
+static enum time_type time_type = time_mtime;
+
+/* User specified date / time style */
+static char *time_style = NULL;
+
+/* Format used to display date / time. Controlled by --time-style */
+static char *time_format = NULL;
+
 /* The units to use when printing sizes.  */
 static uintmax_t output_block_size;
 
 /* File name patterns to exclude.  */
 static struct exclude *exclude;
 
-/* Grand total size of all args, in bytes. */
-static uintmax_t tot_size = 0;
+/* Grand total size of all args, in bytes. Also latest modified date. */
+static struct duinfo tot_dui = { 0, 0, 0, 0 };
 
 #define IS_DIR_TYPE(Type)      \
   ((Type) == FTS_DP            \
@@ -125,7 +188,9 @@ enum
   EXCLUDE_OPTION,
   FILES0_FROM_OPTION,
   HUMAN_SI_OPTION,
-  MAX_DEPTH_OPTION
+  MAX_DEPTH_OPTION,
+  LAST_TIME_OPTION,
+  TIME_STYLE_OPTION
 };
 
 static struct option const long_options[] =
@@ -151,11 +216,47 @@ static struct option const long_options[] =
   {"separate-dirs", no_argument, NULL, 'S'},
   {"summarize", no_argument, NULL, 's'},
   {"total", no_argument, NULL, 'c'},
+  {"last-time", optional_argument, NULL, LAST_TIME_OPTION},
+  {"time-style", required_argument, NULL, TIME_STYLE_OPTION},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
 };
 
+static char const *const time_args[] =
+{
+  "atime", "access", "use", "ctime", "status", 0
+};
+
+static enum time_type const time_types[] =
+{
+  time_atime, time_atime, time_atime, time_ctime, time_ctime
+};
+
+/* `full-iso' uses full ISO-style dates and times.  `long-iso' uses longer
+   ISO-style time stamps, though shorter than `full-iso'.  `iso' uses shorter
+   ISO-style time stamps.  `locale' uses locale-dependent time stamps.  */
+enum time_style
+  {
+    full_iso_time_style,       /* --time-style=full-iso */
+    long_iso_time_style,       /* --time-style=long-iso */
+    iso_time_style,            /* --time-style=iso */
+    locale_time_style          /* --time-style=locale */
+  };
+
+static char const *const time_style_args[] =
+{
+  "full-iso", "long-iso", "iso", "locale", 0
+};
+
+static enum time_style const time_style_types[] =
+{
+  full_iso_time_style, long_iso_time_style, iso_time_style,
+  locale_time_style, 0
+};
+
+static char const posix_prefix[] = "posix-";
+
 void
 usage (int status)
 {
@@ -212,6 +313,17 @@ Mandatory arguments to long options are mandatory for short options too.\n\
                           line argument;  --max-depth=0 is the same as\n\
                           --summarize\n\
 "), stdout);
+      fputs (_("\
+      --last-time            show time of the most recent modification of any\n\
+                             file in the directory, or any of its\n\
+                             subdirectories\n\
+      --last-time=WORD       show time as WORD instead of modification time:\n\
+                               atime, access, use, ctime or status; use\n\
+      --time-style=STYLE     show times using style STYLE:\n\
+                               full-iso, long-iso, iso, locale, +FORMAT\n\
+                             FORMAT is interpreted like `date';\n\
+                             implicity implies --last-time\n\
+"), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       fputs (_("\n\
@@ -285,6 +397,57 @@ hash_init (void)
     xalloc_die ();
 }
 
+/* Display the date and time in PDUI according to the format specified
+   in TIME_FORMAT.  If TIME_FORMAT is NULL, use the standard output format.
+   Return zero if successful.
+*/
+
+static int
+show_date (const char *time_format, time_t when, int nsec)
+{
+  struct tm *tm;
+  char *out = NULL;
+  size_t out_length = 0;
+
+  if ((time_format == NULL)||(*time_format == '\0'))
+    {
+      time_format = "%Y-%m-%d %H:%M";
+    }
+
+  tm = localtime (&when);
+  if (! tm)
+    {
+      char buf[INT_BUFSIZE_BOUND (intmax_t)];
+      error (0, 0, _("time %s is out of range"),
+            (TYPE_SIGNED (time_t)
+             ? imaxtostr (when, buf)
+             : umaxtostr (when, buf)));
+      fputs (buf, stdout);
+      return 1;
+    }
+
+  while (1)
+    {
+      int done;
+      out = x2nrealloc (out, &out_length, sizeof *out);
+
+      /* Mark the first byte of the buffer so we can detect the case
+         of nstrftime producing an empty string.  Otherwise, this loop
+         would not terminate when date was invoked like this
+         `LANG=de date +%p' on a system with good language support.  */
+      out[0] = '\1';
+
+      done = (nstrftime (out, out_length, time_format, tm, 0, nsec)
+             || out[0] == '\0');
+
+      if (done) break;
+    }
+
+  fputs (out, stdout);
+  free (out);
+  return 0;
+}
+
 /* Print N_BYTES.  Convert it to a readable value before printing.  */
 
 static void
@@ -296,12 +459,18 @@ print_only_size (uintmax_t n_bytes)
 }
 
 /* Print N_BYTES followed by STRING on a line.
+   Optionally include last modified date.
    Convert N_BYTES to a readable value before printing.  */
 
 static void
-print_size (uintmax_t n_bytes, const char *string)
+print_size (const struct duinfo *pdui, const char *string)
 {
-  print_only_size (n_bytes);
+  print_only_size (pdui->size);
+  if ( opt_last_time )
+    {
+      putchar ('\t');
+      show_date (time_format, pdui->dmax, pdui->nsec);
+    }
   printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
   fflush (stdout);
 }
@@ -315,20 +484,20 @@ static bool
 process_file (FTS *fts, FTSENT *ent)
 {
   bool ok;
-  uintmax_t size;
-  uintmax_t size_to_print;
+  struct duinfo dui;
+  struct duinfo dui_to_print;
   size_t level;
   static size_t prev_level;
   static size_t n_alloc;
-  /* The sum of the st_size values of all entries in the single directory
-     at the corresponding level.  Although this does include the st_size
-     corresponding to each subdirectory, it does not include the size of
-     any file in a subdirectory.  */
-  static uintmax_t *sum_ent;
-
-  /* The sum of the sizes of all entries in the hierarchy at or below the
-     directory at the specified level.  */
-  static uintmax_t *sum_subdir;
+  /* First element of the structure contains:
+       The sum of the st_size values of all entries in the single directory
+       at the corresponding level.  Although this does include the st_size
+       corresponding to each subdirectory, it does not include the size of
+       any file in a subdirectory. Also corresponding last modified date.
+     Second element of the structure contains:
+       The sum of the sizes of all entries in the hierarchy at or below the
+       directory at the specified level.  */
+  static struct dulevel *dulvl;
   bool print = true;
 
   const char *file = ent->fts_path;
@@ -380,24 +549,30 @@ process_file (FTS *fts, FTSENT *ent)
       /* Note that we must not simply return here.
         We still have to update prev_level and maybe propagate
         some sums up the hierarchy.  */
-      size = 0;
+      DUINFO_INI (dui)
       print = false;
     }
   else
     {
-      size = (apparent_size
-             ? sb->st_size
-             : ST_NBLOCKS (*sb) * ST_NBLOCKSIZE);
-    }
+      DUINFO_SET (dui,
+                   (apparent_size
+                      ? sb->st_size
+                      : ST_NBLOCKS (*sb) * ST_NBLOCKSIZE),
+                   ( time_type == time_ctime ) ? sb->st_ctime :
+                     ( time_type == time_atime ) ? sb->st_atime :
+                       sb->st_mtime,
+                   ( time_type == time_ctime ) ? TIMESPEC_NS (sb->st_ctim) :
+                     ( time_type == time_atime ) ? TIMESPEC_NS (sb->st_atim) :
+                       TIMESPEC_NS (sb->st_mtim))
+   }
 
   level = ent->fts_level;
-  size_to_print = size;
+  dui_to_print = dui;
 
   if (n_alloc == 0)
     {
       n_alloc = level + 10;
-      sum_ent = xcalloc (n_alloc, sizeof *sum_ent);
-      sum_subdir = xcalloc (n_alloc, sizeof *sum_subdir);
+      dulvl = xcalloc (n_alloc, sizeof *dulvl);
     }
   else
     {
@@ -415,16 +590,14 @@ process_file (FTS *fts, FTSENT *ent)
 
          if (n_alloc <= level)
            {
-             sum_ent = xnrealloc (sum_ent, level, 2 * sizeof *sum_ent);
-             sum_subdir = xnrealloc (sum_subdir, level,
-                                     2 * sizeof *sum_subdir);
+             dulvl = xnrealloc (dulvl, level, 2 * sizeof *dulvl);
              n_alloc = level * 2;
            }
 
          for (i = prev_level + 1; i <= level; i++)
-           {
-             sum_ent[i] = 0;
-             sum_subdir[i] = 0;
+       {
+         DUINFO_INI (dulvl[i].ent)
+         DUINFO_INI (dulvl[i].subdir)
            }
        }
       else /* level < prev_level */
@@ -435,11 +608,12 @@ process_file (FTS *fts, FTSENT *ent)
             propagate sums from the children (prev_level) to the parent.
             Here, the current level is always one smaller than the
             previous one.  */
-         assert (level == prev_level - 1);
-         size_to_print += sum_ent[prev_level];
+     assert (level == prev_level - 1);
+     DUINFO_ADD (dui_to_print, dulvl[prev_level].ent)
          if (!opt_separate_dirs)
-           size_to_print += sum_subdir[prev_level];
-         sum_subdir[level] += sum_ent[prev_level] + sum_subdir[prev_level];
+       DUINFO_ADD (dui_to_print, dulvl[prev_level].subdir)
+     DUINFO_ADD (dulvl[level].subdir, dulvl[prev_level].ent)
+     DUINFO_ADD (dulvl[level].subdir, dulvl[prev_level].subdir)
        }
     }
 
@@ -448,11 +622,11 @@ process_file (FTS *fts, FTSENT *ent)
   /* Let the size of a directory entry contribute to the total for the
      containing directory, unless --separate-dirs (-S) is specified.  */
   if ( ! (opt_separate_dirs && IS_DIR_TYPE (ent->fts_info)))
-    sum_ent[level] += size;
+    DUINFO_ADD (dulvl[level].ent, dui)
 
   /* Even if this directory is unreadable or we can't chdir into it,
      do let its size contribute to the total, ... */
-  tot_size += size;
+  DUINFO_ADD (tot_dui, dui)
 
   /* ... but don't print out a total for it, since without the size(s)
      of any potential entries, it could be very misleading.  */
@@ -468,11 +642,7 @@ process_file (FTS *fts, FTSENT *ent)
   if ((IS_DIR_TYPE (ent->fts_info) && level <= max_depth)
       || ((opt_all && level <= max_depth) || level == 0))
     {
-      print_only_size (size_to_print);
-      fputc ('\t', stdout);
-      fputs (file, stdout);
-      fputc (opt_nul_terminate_output ? '\0' : '\n', stdout);
-      fflush (stdout);
+      print_size (&dui_to_print, file);
     }
 
   return ok;
@@ -519,7 +689,7 @@ du_files (char **files, int bit_flags)
     }
 
   if (print_grand_total)
-    print_size (tot_size, _("total"));
+    print_size (&tot_dui, _("total"));
 
   return ok;
 }
@@ -681,6 +851,17 @@ main (int argc, char **argv)
          add_exclude (exclude, optarg, EXCLUDE_WILDCARDS);
          break;
 
+   case LAST_TIME_OPTION:
+     opt_last_time = 1;
+     if ( optarg )
+       time_type = XARGMATCH ("--last-time", optarg, time_args, time_types);
+     break;
+
+   case TIME_STYLE_OPTION:
+     opt_last_time = 1;
+     time_style = optarg;
+         break;
+
        case_GETOPT_HELP_CHAR;
 
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
@@ -715,6 +896,47 @@ main (int argc, char **argv)
   if (opt_summarize_only)
     max_depth = 0;
 
+  /* Process time style if printing last times */
+  if ( opt_last_time )
+    {
+      if (! time_style )
+        if (! (time_style = getenv ("TIME_STYLE")))
+          time_style = "posix-long-iso";
+
+      while (strncmp (time_style, posix_prefix, sizeof posix_prefix - 1) == 0)
+        {
+          time_style += sizeof posix_prefix - 1;
+        }
+
+      if (*time_style == '+')
+        {
+          time_format = time_style + 1;
+        }
+      else
+        {
+          switch (XARGMATCH ("time style", time_style,
+                             time_style_args, time_style_types))
+            {
+              case full_iso_time_style:
+                 time_format = "%Y-%m-%d %H:%M:%S.%N %z";
+                 break;
+
+              case long_iso_time_style:
+                time_format = "%Y-%m-%d %H:%M";
+                break;
+
+              case iso_time_style:
+                time_format = "%Y-%m-%d ";
+                break;
+
+              case locale_time_style:
+                if (hard_locale (LC_TIME))
+                  time_format =
+                     dcgettext (NULL, time_format, LC_TIME);
+            }
+        }
+    }
+
   if (files_from)
     {
       FILE *istream;