#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"
/* 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;
/* 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 \
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[] =
{"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)
{
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\
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
}
/* 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);
}
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;
/* 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
{
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 */
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)
}
}
/* 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. */
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;
}
if (print_grand_total)
- print_size (tot_size, _("total"));
+ print_size (&tot_dui, _("total"));
return ok;
}
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);
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;