bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Suggested enhancement to du command - show last modified date.


From: William Brendling
Subject: Suggested enhancement to du command - show last modified date.
Date: Thu, 23 Jun 2005 13:38:12 +0100

The second iteration of my patch to "du" to show last modified date follows.

> Internally, file time stamps should be maintained to nanosecond
> resolution, not just 1-second resolution.

This should be working. However my old system does not return nsec
time stamps, so I have not fully tested this.

I now use a class (well a struct and a few macros) to collect file /
directory information. This includes the nsec time stamp. I also no
longer assume that the minimum date is zero (1/1/1970). The class
should be extensible if anybody wants to collect additional
iinformation.

A second struct is used to optimise memory allocation. There is now
only one calloc/realloc.

> For consistency the time stamp format option should use the same
> option syntax as 'ls'.  E.g.,
>
> du --last-time='modify' --time-style='+%Y-%m-%d'

I have adopted this (and a lot of code from "ls"). I have not bothered
with the two date formats (more or less than 6 months old), but
everything else "ls" recognises for time style should also be
recognised by "du". The TIME_STYLE environment variable is recognised,
taking only the first style if two are defined.

I have attempted to update the --help text and info file to reflect
the enhancements.

I will not have any further free time to work on this in the next few
months, so if my patch is thought worthwhile, I would appreciate it if
someone else takes up the batton at this point.

Patch follows:

Index: coreutils/ChangeLog
===================================================================
RCS file: /cvsroot/coreutils/coreutils/ChangeLog,v
retrieving revision 1.1314
diff -u -r1.1314 ChangeLog
--- coreutils/ChangeLog 2 Jun 2005 10:04:32 -0000       1.1314
+++ coreutils/ChangeLog 23 Jun 2005 11:37:47 -0000
@@ -1,3 +1,7 @@
+2005-06-14  William Brendling  <address@hidden>
+
+       * src/du.c: Added --last-time and --time-style switches.
+
 2005-06-02  Jim Meyering  <address@hidden>

        * Version 5.3.1.
Index: coreutils/doc/ChangeLog
===================================================================
RCS file: /cvsroot/coreutils/coreutils/doc/ChangeLog,v
retrieving revision 1.237
diff -u -r1.237 ChangeLog
--- coreutils/doc/ChangeLog     2 Jun 2005 05:00:50 -0000       1.237
+++ coreutils/doc/ChangeLog     23 Jun 2005 11:38:43 -0000
@@ -1,3 +1,7 @@
+2005-06-14  William Brendling  <address@hidden>
+
+       * coreutils.texi (du invocation): New options --last-time and
--time-style.
+
 2005-06-01  Paul Eggert  <address@hidden>

        Use "file name" when talking about file names, instead of "filename"
Index: coreutils/doc/coreutils.texi
===================================================================
RCS file: /cvsroot/coreutils/coreutils/doc/coreutils.texi,v
retrieving revision 1.259
diff -u -r1.259 coreutils.texi
--- coreutils/doc/coreutils.texi        2 Jun 2005 05:00:24 -0000       1.259
+++ coreutils/doc/coreutils.texi        23 Jun 2005 11:39:00 -0000
@@ -9006,6 +9006,30 @@
 or directory that the link points to instead of the space used by
 the link).

address@hidden --last-time
address@hidden --last-time
address@hidden last modified dates, displaying in @command{du}
+Show time of the most recent modification of any file in the directory,
+or any of its subdirectories.
+
address@hidden --last-time=ctime
address@hidden --last-time=status
address@hidden --last-time=use
address@hidden --last-time
address@hidden address@hidden, show the most recent}
address@hidden status address@hidden, show the most recent}
address@hidden use address@hidden, show the most recent}
+Show the most recent status change time (the @samp{ctime} in the inode) of
+any file in the directory, instead of the modification time.
+
address@hidden --last-time=atime
address@hidden --last-time=access
address@hidden --last-time
address@hidden address@hidden, show the most recent}
address@hidden access address@hidden, show the most recent}
+Show the most recent access time (the @samp{atime} in the inode) of
+any file in the directory, instead of the modification time.
+
 @item -P
 @itemx --no-dereference
 @opindex -P
@@ -9051,6 +9075,78 @@
 Report the size of each directory separately, not including the sizes
 of subdirectories.

address@hidden address@hidden
address@hidden --time-style
address@hidden time style
+Implicity selects the option @option{--last-time} to show last modified
+date if an explicit @option{--last-time} option has not been selected.
+List timestamps in style @var{style}.  The @var{style} should
+be one of the following:
+
address@hidden @samp
address@hidden address@hidden
address@hidden LC_TIME
+List timestamps using @var{format}, where @var{format} is interpreted
+like the format argument of @command{date} (@pxref{date invocation}).
+For example, @option{--time-style="+%Y-%m-%d %H:%M:%S"} causes
address@hidden to list timestamps like @samp{2002-03-30 23:45:56}.  As
+with @command{date}, @var{format}'s interpretation is affected by the
address@hidden locale category.
+
address@hidden full-iso
+List timestamps in full using @acronym{ISO} 8601 date, time, and time zone
+format with nanosecond precision, e.g., @samp{2002-03-30
+23:45:56.477817180 -0700}.  This style is equivalent to
address@hidden %H:%M:%S.%N %z}.
+
address@hidden long-iso
+List @acronym{ISO} 8601 date and time in minutes, e.g.,
address@hidden 23:45}.  These timestamps are shorter than
address@hidden timestamps, and are usually good enough for everyday
+work.  This style is equivalent to @samp{%Y-%m-%d %H:%M}.
+
address@hidden iso
+List @acronym{ISO} 8601 dates for timestamps
+
address@hidden locale
address@hidden LC_TIME
+List timestamps in a locale-dependent form.  For example, a Finnish
+locale might list timestamps like @samp{maalis 30@ @ 2002}.
+Locale-dependent timestamps typically consume more space than @samp{iso}
+timestamps and are harder for programs to parse because locale
+conventions vary so widely, but they are easier for many people to read.
+
+The @env{LC_TIME} locale category specifies the timestamp format.  The
+default @acronym{POSIX} locale uses timestamps like @samp{Mar 30@
+@ 2002}; in this locale, the following two @command{du} invocations are
+equivalent:
+
address@hidden
+du --last-time --time-style="+%b %e %Y"
+du --last-time --time-style="locale"
address@hidden example
+
+Other locales behave differently.  For example, in a German locale,
address@hidden"locale"} might be equivalent to
address@hidden"+%e. %b %Y"}
+and might generate timestamps like @samp{30. M@"ar 2002@ }.
+
address@hidden address@hidden
address@hidden LC_TIME
+List @acronym{POSIX}-locale timestamps if the @env{LC_TIME} locale
+category is @acronym{POSIX}, @var{style} timestamps otherwise.  For
+example, the default style, which is @samp{posix-long-iso}, lists
+timestamps like @samp{Mar 30@ @ 2002} when in
+the @acronym{POSIX} locale, and like @samp{2002-03-30 23:45} otherwise.
address@hidden table
+
address@hidden TIME_STYLE
+You can specify the default value of the @option{--time-style} option
+with the environment variable @env{TIME_STYLE}; if @env{TIME_STYLE} is not set
+the default style is @samp{posix-long-iso}. If @env{TIME_STYLE} contains two
+styles, separated by a newline (for @command{ls}), the @command{du} uses only
+the first of the two styles.
+
 @item -x
 @itemx --one-file-system
 @opindex -x
Index: coreutils/src/du.c
===================================================================
RCS file: /cvsroot/coreutils/coreutils/src/du.c,v
retrieving revision 1.203
diff -u -r1.203 du.c
--- coreutils/src/du.c  2 Jun 2005 05:17:24 -0000       1.203
+++ coreutils/src/du.c  23 Jun 2005 11:39:06 -0000
@@ -29,13 +29,14 @@
 #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 @@
 /* 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 @@
 /* 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 @@
   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 @@
   {"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 @@
                           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 @@
     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 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 @@
 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 @@
       /* 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 @@

          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 @@
             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 @@
   /* 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 @@
   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 @@
     }

   if (print_grand_total)
-    print_size (tot_size, _("total"));
+    print_size (&tot_dui, _("total"));

   return ok;
 }
@@ -681,6 +851,17 @@
          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 @@
   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;




reply via email to

[Prev in Thread] Current Thread [Next in Thread]