#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>

static int noexec=0, verbose=0;

static char rcsid[] = "$Id: f2d.c,v 1.13 2007/05/31 01:06:20 yuuji Exp yuuji $";

void memout()
{
  fprintf(stderr, "Out of memory");
  exit(2);
}

/* Oops, SunOS5 w/o language package does not have scandir()... */
int alpsort(const void *d1, const void *d2)
{
  struct dirent **x = (struct dirent**)d1;
  struct dirent **y = (struct dirent**)d2;
  return strcmp((*x)->d_name, (*y)->d_name);
}
int _scdir_true(const struct dirent *d)
{
  return 1;
}
int skip2(const struct dirent *d)
{
  if (!strcmp(".", d->d_name) || !strcmp("..", d->d_name))
    return 0;
  return 1;
}
/* scdir() is a function compatible with scandir() except error handling */
int scdir (char *dir, struct dirent ***namelist,
           int (*select)(const struct dirent *),
           int (*compar)(const void *, const void *))
{
  DIR *d;
  int n=0;
  int (*sel)() = select;
  int sz = sizeof(struct dirent **);
  int realsize;		/* For system whose dirent d_name has char[1] */
  struct dirent *f;
  if (NULL == sel)
    sel = _scdir_true;
  if (NULL == (d=opendir(dir)))
    return -1;
  while (NULL != (f=readdir(d))) {
    if (!sel(f)) continue;
    *namelist = (struct dirent**)realloc(*namelist, ++n * sz);
    if (NULL == (*namelist)) {
      fprintf(stderr, "(scdir)");
      memout();
    }
    realsize = (sizeof *f)-(sizeof f->d_name)+strlen(f->d_name)+1;
    (*namelist)[n-1] = (struct dirent*)malloc(realsize);
    if (NULL == (*namelist)[n-1]) {
      fprintf(stderr, "(scdir)");
      memout();
    }
    memcpy((*namelist)[n-1], f, realsize);
  }
  if (n && NULL != compar)
    qsort(*namelist, n, sizeof(struct dirent*), compar);
  return n;
}


int alphacmp(const void *x, const void *y)
{
  char **a = (char**)x;
  char **b = (char**)y;
  return strcmp(*a, *b);
}
void strlwr(char *p)
{
  while (*p) {
    if (isalpha((int)*p))
      *p = tolower((int)*p);
    p++;
  }
}
int readf(char *file, char ***ptr, char ***ukptr)
{
  char buf[MAXPATHLEN+1], *p, *q;
  FILE *fp;
  int n=0, ukn=0;
  *ukptr = NULL;
  *ptr = NULL;
  if (0 == strcmp("-", file))
    fp = stdin;
  else if (NULL == (fp=fopen(file, "r")))
    return 0;
  while (NULL != fgets(buf, sizeof buf, fp)) {
    for (p=buf; *p && isspace((int)*p); p++)
      ;
    for (q=p+strlen(p)-1; p<q && isspace((int)*q); q--)
      *q = '\0';
    if (*p == '#' || p>q)
      continue;
    strlwr(p);
    if ((q=strstr(p, ":unknown"))) {
      *q = '\0';
      if (NULL == (*ukptr = (char**)realloc(*ukptr, ++ukn * sizeof(char**))))
        memout();
      if (NULL == ((*ukptr)[ukn-1]=(char*)malloc(strlen(p)+1)))
        memout();
      strncpy((*ukptr)[ukn-1], p, strlen(p)+1);
    } else {
      if (NULL == (*ptr = (char**)realloc(*ptr, ++n * sizeof(char**))))
        memout();
      if (NULL == ((*ptr)[n-1]=(char*)malloc(strlen(p)+1)))
        memout();
      strncpy((*ptr)[n-1], p, strlen(p)+1);
    }
  }
  if (fp != stdin) fclose(fp);
  qsort(*ptr, n, sizeof(char*), alphacmp);
  if (NULL == (*ptr = (char**)realloc(*ptr, ++n * sizeof(char**))))
    memout();
  (*ptr)[n-1] = NULL;
  qsort(*ukptr, ukn, sizeof(char*), alphacmp);
  if (NULL == (*ukptr = (char**)realloc(*ukptr, ++ukn * sizeof(char**))))
    memout();
  (*ukptr)[ukn-1] = NULL;
  return n;
}

void
syncf2d(char **item, struct dirent **entry, int nfiles, char *path)
{
  int i=0, j=0;
  int dirxp = 0;
  char file[MAXPATHLEN+1];
  char *fn;
  struct stat s;
  if (0==stat(path, &s) && (s.st_mode & S_IFDIR)) {
    dirxp = 1;
  }
  while (1) {
    int r;
    fn = 0;
    if (item[i] == NULL && j>=nfiles)
      break;
    if (item[i] == NULL)
      r = 1;
    else if (j>=nfiles)
      r = -1;
    else {
      fn = entry[j]->d_name;
      r=strcmp(item[i], fn);
    }
    if (fn && !strcmp("unknown", fn)) {
      j++;
      continue;
    }
    if (r == 0) {
      i++, j++;
      continue;
    } else if (r < 0) {
      if (!dirxp) {
        if (!noexec)
          if (0 > mkdir(path, 0777)) {
            fprintf(stderr, "Please make sure %s exists as directory\n", path);
            exit(2);
          }
        dirxp=1;
      }
      if (noexec || verbose)
        printf("touch %s/%s\n", path, item[i]);
      if (!noexec) {
        FILE *fp;
        snprintf(file, MAXPATHLEN+1, "%s/%s", path, item[i]);
        if ((fp=fopen(file, "a")))
          fclose(fp);
      }
      i++;
      continue;
    } else {
      snprintf(file, MAXPATHLEN+1, "%s/%s", path, entry[j]->d_name);
      if (noexec || verbose)
        printf("rm %s\n", file);
      if (!noexec) {
        if (0 > unlink(file)) {
          fprintf(stderr, "Remove failure on %s\n", file);
        }
      }
      j++;
      continue;
    }
  }
}

void usage(char *myname)
{
  fprintf(stderr, "%s\n\nUsage:\n", rcsid);
  fprintf(stderr, " %s -d BADDBDIR dbfile\n", myname);
  exit(1);
}
int main(int argc, char *argv[])
{
  struct dirent **entry = NULL, **ukentry = NULL;
  char *dir = ".", ukdir[MAXPATHLEN];
  char *file = "";
  char **item = NULL, **ukitem = NULL;
  int nfiles, uknfiles;
  int i, j;
  for (i=1; i<argc; i++) {
    if ('-' == argv[i][0] && argv[i][1]) {
      for (j=1; argv[i][j]; j++) {
        if ('d' == argv[i][j]) {
          dir = argv[++i];
          break;
        } else if ('n' == argv[i][j]) {
          noexec = 1;
        } else if ('v' == argv[i][j]) {
          verbose = 1;
        }
      }
    } else {
      file = argv[i];
      break;
    }
  }
  if (!*dir || !*file) {
    usage(argv[0]);
  }
  snprintf(ukdir, sizeof ukdir, "%s/unknown", dir);
  //nfiles = scandir(dir, &entry, NULL, alphasort);
  nfiles = scdir(dir, &entry, skip2, alpsort);
  uknfiles = scdir(ukdir, &ukentry, skip2, alpsort);
  if (!readf(file, &item, &ukitem))
    usage(argv[0]);
#ifdef DEBUGent
  for (i=0; item[i]; i++) {
    fprintf(stderr, "%d: %s\n", i, item[i]);
  }
  for (i=0; i<nfiles; i++)
    printf("%d: %s\n", i, entry[i]->d_name);
#endif
  syncf2d(item, entry, nfiles, dir);
  syncf2d(ukitem, ukentry, uknfiles, ukdir);
  return 0;
}
