/*
 * getsdir.c
 *
 *	Get and return a sorted directory listing
 *
 *	Copyright (c) 1998 by James S. Seymour (jseymour@jimsun.LinxNet.com)
 *
 *	This code 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
 *	2 of the License, or (at your option) any later version.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *	Note: this code uses "wildmat.c", which has different copyright
 *	and licensing conditions.  See the source, Luke.
 *
 *
 *  2011: getsdir() has been simplified wrt memory management by
 *        Adam Lackorzynski
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#include "getsdir.h"
#include "intl.h"
#include "minicom.h"


/* locally defined constants */

#define MAX_CNT 100		/* number of entries to hold in holding buf */

typedef struct dat_buf {		/* structure of input buffers */
  struct dat_buf *nxt;			/* pointer to next buffer */
  unsigned cnt;				/* data count in present buffer */
  GETSDIR_ENTRY data[MAX_CNT];		/* data in present buffer */
} DAT_BUF;

static int g_sortflags;			/* sort flags */

/* sort compare routines */

/*
 * name:	namecmpr
 *
 * purpose:	return stat to qsort on comparison between name fields in
 *		directory entry.
 *
 * synopsis:	static in namecmpr(d1, d2)
 *		GETSDIR_ENTRY *d1;
 *		GETSDIR_ENTRY *d2;
 *
 * input:	See explanation of qsort
 *
 * process:	See explanation of qsort
 *
 * output:	See explanation of qsort
 *
 * notes:	See explanation of qsort
 */
static int namecmpr(GETSDIR_ENTRY *d1, GETSDIR_ENTRY *d2)
{
  if (g_sortflags & (GETSDIR_DIRSF | GETSDIR_DIRSL)) {
    if (S_ISDIR((d1->mode)) && !S_ISDIR((d2->mode)))
      return (g_sortflags & GETSDIR_DIRSF) ? -1 : 1;
    else if (S_ISDIR((d2->mode)) && ! S_ISDIR((d1->mode)))
      return (g_sortflags & GETSDIR_DIRSF) ? 1 : -1;
  }

  return (g_sortflags & GETSDIR_RSORT)
          ? strcmp(d2->fname, d1->fname) : strcmp(d1->fname, d2->fname);

} /* namecmpr */


/*
 * name:	timecmpr
 *
 * purpose:	return stat to qsort on comparison between time fields in
 *		directory entry.
 *
 * synopsis:	static in timecmpr(d1, d2)
 *		GETSDIR_ENTRY *d1;
 *		GETSDIR_ENTRY *d2;
 *
 * input:	See explanation of qsort
 *
 * process:	See explanation of qsort
 *
 * output:	See explanation of qsort
 *
 * notes:	See explanation of qsort
 */
static int timecmpr(GETSDIR_ENTRY *d1, GETSDIR_ENTRY *d2)
{
  if (g_sortflags & (GETSDIR_DIRSF | GETSDIR_DIRSL)) {
    if (S_ISDIR((d1->mode)) && !S_ISDIR((d2->mode)))
      return (g_sortflags & GETSDIR_DIRSF) ? -1 : 1;
    else if (S_ISDIR((d2->mode)) && ! S_ISDIR((d1->mode)))
      return (g_sortflags & GETSDIR_DIRSF) ? 1 : -1;
  }

  return (g_sortflags & GETSDIR_RSORT)
         ? (d2->time - d1->time) : (d1->time - d2->time);
} /* timecmpr */

/*
 * name:	getsdir
 *
 * purpose:	To return a directory listing - possibly sorted
 *
 * synopsis:	#include <dirent.h>
 *
 *		int getsdir(dirpath, pattern, sortflags, modemask, datptr, len)
 *		const char *dirpath;
 *		const char *pattern;
 *		int sortflags;
 *		mode_t modemask;
 *		GETSDIR_ENTRY **datptr;
 *		int *len;
 *
 * input:	 *dirpath - pointer to path to directory to get list of
 *			    files from
 *		 *pattern - pointer to optional wildmat pattern
 *		sortflags - specification flags of how to sort the
 *			    resulting list.  See descriptions below.
 *		 modemask - caller-supplied mode mask.  Bits in this will
 *			    be ANDed against directory entries to determine
 *			    whether to return them.  Ignored if 0.
 *		 **datptr - pointer to a destination pointer variable.
 *			    getsdir will allocate the required amount
 *			    of memory for the results and will return a
 *			    pointer to the returned data in this variable.
 *
 *			    The data will be in the form:
 *				typedef struct dirEntry {
 *				    char fname[MAXNAMLEN + 1];
 *				    time_t time;
 *				    mode_t mode;
 *				} GETSDIR_ENTRY;
 *		     *len - pointer to int to contain length of longest
 *			    string in returned array.
 *
 * process:	For each 0..MAX_CNT values read from specified directory,
 *		allocates a temporary buffer to store the entries into.
 *		When end of directory is detected, merges the buffers into
 *		a single array of data and sorts into order based on key.
 *
 * output:	Count of number of data items pointed to by datptr or
 *		-1 if error.  errno may or may not be valid, based on
 *		type of error encountered.
 *
 * notes:	If there is any error, -1 is returned.
 *
 *		It is the caller's responsibility to free the memory
 *		block pointed to by datptr on return when done with the
 *		data, except in case of error return.
 *
 *		See also: opendir(3C), readdir(3C), closedir(3C), qsort(3C)
 *
 *		The pattern parameter is optional and may be 0-length or
 *		a NULL pointer.
 *
 *		The sort flags affect the output as follows:
 *
 *		    GETSDIR_PARNT - include parent dir (..)
 *		    GETSDIR_NSORT - sort by name
 *		    GETSDIR_TSORT - sort by time (NSORT wins if both)
 *
 *		    The following are only meaningful if GETSDIR_NSORT or
 *		    GETSDIR_TSORT are specified:
 *
 *			GETSDIR_DIRSF - dirs first
 *			GETSDIR_DIRSL - dirs last
 *			GETSDIR_RSORT - reverse sort (does not affect
 *					GETSDIR_DIRSF/GETSDIR_DIRSL)
 *
 *		So-called "hidden" files (those beginning with a ".") are
 *		not returned unless a pattern like ".*" is specified.
 *
 *		The present directory (".") is never returned.
 */

int getsdir(const char *dirpath, const char *pattern, int sortflags,
            mode_t modemask, GETSDIR_ENTRY **datptr, int *len)
{
  unsigned cnt = 0;		/* data count */

  DIR *dirp;			/* point to open dir */
  struct dirent *dp;		/* structure of dir as per system */
  struct stat statbuf;		/* structure of file stat as per system */
  char fpath[BUFSIZ];		/* filename with dir path prepended */
  int cmprstat;

  g_sortflags = sortflags;	/* for sort funcs */
  *len = 0;			/* longest name */

  /* open the specified directory */
  if ((dirp = opendir(dirpath)) == NULL)
    return -1;

  while ((dp = readdir(dirp)))
    {
      if (!strcmp(dp->d_name, "."))
        continue;

      if ((sortflags & GETSDIR_PARNT) && !strcmp(dp->d_name, ".."))
        cmprstat = 1;
      else if (pattern && *pattern)
        cmprstat = wildmat(dp->d_name, pattern);
      else
        cmprstat = 1;

      if (cmprstat)
        {			/* matching name? */
          *datptr = realloc(*datptr, sizeof(**datptr) * (cnt + 1));
          if (!*datptr)
            {
              free(*datptr);
              closedir(dirp);
              return -1;
            }

          /* copy the filename */
          strncpy((*datptr)[cnt].fname, dp->d_name, MAXNAMLEN);

          /* get information about the directory entry */
          snprintf(fpath, sizeof(fpath), "%s/%s", dirpath, dp->d_name);
          if (stat(fpath, &statbuf))	/* if error getting stat... */
            continue;

          if (modemask && !(S_IFMT & modemask & statbuf.st_mode))
            continue;

          int l;
          if ((l = strlen(dp->d_name)) > *len)
            *len = l;

          (*datptr)[cnt].time   = statbuf.st_mtime;
          (*datptr)[cnt].mode   = statbuf.st_mode;
          (*datptr)[cnt].cflags = 0;

          cnt++;
        }
    }

  closedir(dirp);		/* close file pointer */

  /* post-process array by option */
  if (cnt && sortflags) {
    if (sortflags & GETSDIR_NSORT)
      qsort(*datptr, cnt, sizeof(GETSDIR_ENTRY),
            (int (*)(const void *, const void *))namecmpr);
    else if (sortflags & GETSDIR_TSORT)
      qsort(*datptr, cnt, sizeof(GETSDIR_ENTRY),
            (int (*)(const void *, const void *))timecmpr);
  }

  return cnt;
} /* getsdir */



#ifdef GETSDIR_STANDALONE_TEST
/*
 * debug for getsdir()
 *
 * usage: getsdir <dirpath>
 *
 */
extern char *ctime(void);

void main(int argc, char **argv)
{
  GETSDIR_ENTRY *dirdat;
  int cnt, index;
  int sortflags = 0;
  mode_t modemask = (mode_t) 0;
  int len;

  if (argc != 4) {
    fprintf(stderr,"usage: %s <dirpath> <pattern> <sortflags>\n", argv[0]);
    exit(1);
  }

  switch (argv[3][0]) {
    case 'n': sortflags = GETSDIR_NSORT;
              break;
    case 't': sortflags = GETSDIR_TSORT;
              break;
  }

  /* sortflags |= GETSDIR_DIRSL | GETSDIR_RSORT; */
  sortflags |= GETSDIR_DIRSF;
  /* sortflags |= GETSDIR_PARNT; */
  /* modemask = S_IFDIR | S_IFREG; */

  printf("modemask==%x\n", modemask);

  if ((cnt = getsdir(argv[1], argv[2], sortflags, modemask, &dirdat, &len)) == -1) {
    fprintf(stderr, "%s: error getting directory\n", argv[0]);
    exit(1);
  }

  printf(_("%d files:\n"), cnt);
  for (index = 1; index <= cnt; ++index, ++dirdat)
    printf("%2d: %-20s%s", index, dirdat->fname, ctime(&dirdat->time));

  free(dirdat);
  return 0;
}
#endif
