/* filedate:  print out the modification time for the specified files

   Copyright (C) 2004-2026 by Brian Lindholm.  This file is part of the
   littleutils utility set.

   The filedate utility is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3, or (at your option) any later
   version.

   The filedate utility is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.

   You should have received a copy of the GNU General Public License along with
   the littleutils.  If not, see <https://www.gnu.org/licenses/>. */

#include <config.h>

#include <limits.h>
#ifdef HAVE_STDIO_H
# include <stdio.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <time.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
# define OPTEND -1
#else
# define OPTEND EOF
#endif
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif

#ifdef __MINGW32__
extern int getopt(int argc, char * const *argv, const char *optstring);
extern char *optarg;
extern int optind;
#endif

#ifdef DJGPP
/* speed up stat command for DJGPP */
unsigned short _djstat_flags = 63;
#endif

#ifndef PATH_MAX
# define PATH_MAX 256
#endif

#define FORMAT_DEFAULT 0
#define FORMAT_LOCALE 1
#define FORMAT_TOUCH 2

/* help function */
static void help(FILE *where)
{
  fprintf(where, "filedate " PACKAGE_VERSION "\n"
    "usage: filedate [-a(ccess_time)] [-f file_list] [-h(elp)] [-l(ocale_format)]\n"
    "         [-p(ipe)] [-q(uiet)] [-t(ouch_format)] [-U(TC_time)] [-v(erbose)]\n"
    "         file...\n");
}

/* print filedate function */
static void printdate(char *filename, int verbose, int use_access_time, int format, int use_UTC)
{
  struct stat file_stats;
  char date_string[256], nano_string[16], *format_string;
  time_t time_val;
  int use_nsec;

  if (stat(filename, &file_stats))
    fprintf(stderr, "filedate error: can't determine date of %s\n", filename);
  else {
    if (format == FORMAT_TOUCH) {
      format_string = "%Y%m%d%H%M.%S";
      use_nsec = 0;
    }
    else if (format == FORMAT_LOCALE) {
      format_string = "%c";
      use_nsec = 0;
    }
    else {
      format_string = "%Y-%m-%d %H:%M:%S";
#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
      use_nsec = 1;
#elif HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
      use_nsec = 1;
#elif HAVE_STRUCT_STAT_ST_MTIMENSEC
      use_nsec = 1;
#else
      use_nsec = 0;
#endif
    }
    if (use_access_time)
      time_val = file_stats.st_atime;
    else
      time_val = file_stats.st_mtime;
    if (use_UTC)
      (void) strftime(date_string, 255, format_string, gmtime(&time_val));
    else
      (void) strftime(date_string, 255, format_string, localtime(&time_val));
    if (use_nsec == 0)
      nano_string[0] = '\0';
    else if (use_access_time)
#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.st_atim.tv_nsec);
#elif HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.st_atimespec.tv_nsec);
#elif HAVE_STRUCT_STAT_ST_MTIMENSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.st_atimensec);
#else
      nano_string[0] = '\0';
#endif
    else
#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.st_mtim.tv_nsec);
#elif HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.st_mtimespec.tv_nsec);
#elif HAVE_STRUCT_STAT_ST_MTIMENSEC
      (void) sprintf(nano_string, ".%09u", (unsigned int) file_stats.stat.st_mtimensec);
#else
      nano_string[0] = '\0';
#endif
    if (verbose == 1)
      fprintf(stdout, "%s\t%s%s\n", filename, date_string, nano_string);
    else
      fprintf(stdout, "%s%s\n", date_string, nano_string);
  }
}

/* main program */
int main(int argc, char *argv[])
{
  FILE *infile;
  char filename[PATH_MAX], *listname, *newline, *rc;
  int argn, opt, use_access_time, use_file, use_format, use_pipe, use_UTC, verbose;

  /* parse options */
  listname = "";
  verbose = 0;
  use_access_time = 0;
  use_file = 0;
  use_format = FORMAT_DEFAULT;
  use_pipe = 0;
  use_UTC = 0;
  while ((opt = getopt(argc, argv, "af:hlpqtUv")) != OPTEND) switch (opt) {
    case 'a':
      use_access_time = 1;
      break;
    case 'f':
      use_file = 1;
      listname = optarg;
      break;
    case 'h':
      help(stdout);
      return(0);
    case 'l':
      use_format = FORMAT_LOCALE;
      break;
    case 'p':
      use_pipe = 1;
      break;
    case 'q':
      verbose = -1;
      break;
    case 't':
      use_format = FORMAT_TOUCH;
      break;
    case 'U':
      use_UTC = 1;
      break;
    case 'v':
      verbose = 1;
      break;
    case '?':
      help(stderr);
      return(1);
  }
  /* finalize options */
  if ((optind == argc) && (use_file == 0) && (use_pipe == 0)) {
    help(stdout);
    return(0);
  }
  if (verbose == 0) {
    if (((argc - optind) != 1) || use_file || use_pipe)
      verbose = 1;
    else
      verbose = -1;
  }
  /* process files in listed in file specified by -f option */
  if (use_file) {
    infile = fopen(listname, "r");
    if (infile == NULL)
      fprintf(stderr, "filedate error: can't open %s!\n", listname);
    else {
      while (!feof(infile)) {
        rc = fgets(filename, PATH_MAX - 1, infile);
        if (rc != NULL) {
          newline = strchr(filename, '\n');
          if (newline != NULL) *newline = '\0';
          if (strlen(filename) != 0) printdate(filename, verbose, use_access_time, use_format, use_UTC);
        }
      }
      (void) fclose(infile);
    }
  }
  /* process files listed on stdin (i.e., the -p option) */
  if (use_pipe) while (!feof(stdin)) {
    rc = fgets(filename, PATH_MAX - 1, stdin);
    if (rc != NULL) {
      newline = strchr(filename, '\n');
      if (newline != NULL) *newline = '\0';
      if (strlen(filename) != 0) printdate(filename, verbose, use_access_time, use_format, use_UTC);
    }
  }
  /* process files given in the argument list */
  for (argn = optind; argn < argc; argn++) printdate(argv[argn], verbose, use_access_time, use_format, use_UTC);
  /* indicate successful finish */
  return(0);
}
