#include <sys/fcntl.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include "dumpscan.h"

char *argv0;
static char *input_path = 0;
static int quiet = 0, showpaths = 0, searchcount = 1;
static int error_count = 0, bad_count = 0;
static path_hashinfo phi;
static dump_parser dp;

/* Print a usage message and exit */
static void usage(int status, char *msg)
{
  if (msg) fprintf(stderr, "%s: %s\n", argv0, msg);
  fprintf(stderr, "Usage: %s [options] [file]\n", argv0);
  fprintf(stderr, "  -h     Print this help message\n");
  fprintf(stderr, "  -p     Print paths of bad vnodes\n");
  fprintf(stderr, "  -q     Quiet mode (don't print errors)\n");
  exit(status);
}


/* Parse the command-line options */
static void parse_options(int argc, char **argv)
{
  int c;

  if (argv0 = strrchr(argv[0], '/')) argv0++;
  else argv0 = argv[0];

  /* Parse the options */
  while ((c = getopt(argc, argv, "n:hpq")) != EOF) {
    switch (c) {
      case 'n': searchcount  = atoi(optarg); continue;
      case 'p': showpaths    = 1;            continue;
      case 'q': quiet        = 1;            continue;
      case 'h': usage(0, 0);
      default:  usage(1, "Invalid option!");
    }
  }

  if (argc - optind > 1) usage(1, "Too many arguments!");
  input_path = (argc == optind) ? "-" : argv[optind];
}


/* A callback to count and print errors */
static afs_uint32 my_error_cb(afs_uint32 code, int fatal, void *ref, char *msg, ...)
{
  va_list alist;

  error_count++;
  if (!quiet) {
    va_start(alist, msg);
    com_err_va(argv0, code, msg, alist);
    va_end(alist);
  }
}


/* A callback to process file vnodes */
static afs_uint32 my_file_cb(afs_vnode *v, XFILE *X, void *refcon)
{
  static char buf[1024];
  afs_uint32 size, nulls, cnulls, maxcnulls, n, r;
  char *name = 0;
  int i;

  nulls = cnulls = maxcnulls = 0;
  size = v->size;
  if ((r = xfseek(X, &v->d_offset))) return r;
  while (size) {
    n = (size > 1024) ? 1024 : size;
    if (r = xfread(X, buf, n)) return r;
    for (i = 0; i < n; i++) {
      if (buf[i]) {
        if (cnulls > maxcnulls) maxcnulls = cnulls;
        cnulls = 0;
      } else {
        nulls++;
        cnulls++;
      }
    }
    size -= n;
  }
  if (maxcnulls >= searchcount) {
    bad_count++;
    if (showpaths) Path_Build(X, &phi, v->vnode, &name, 0);
    if (name) {
      printf("*** BAD %d (%s) - %d nulls, %d consecutive\n",
             v->vnode, name, nulls, maxcnulls);
      free(name);
    } else {
      printf("*** BAD %d - %d nulls, %d consecutive\n",
             v->vnode, nulls, maxcnulls);
    }
  }
  return r;
}


int main(int argc, char **argv)
{
  XFILE input_file;
  afs_uint32 r;

  parse_options(argc, argv);
  initialize_acfg_error_table();
  initialize_AVds_error_table();
  initialize_rxk_error_table();
  initialize_u_error_table();
  initialize_vl_error_table();
  initialize_vols_error_table();
  initialize_xFil_error_table();
  r = xfopen(&input_file, O_RDONLY, input_path);
  if (r) {
    com_err(argv0, r, "opening %s", input_path);
    exit(2);
  }

  memset(&dp, 0, sizeof(dp));
  dp.cb_error      = my_error_cb;
  if (input_file.is_seekable) dp.flags |= DSFLAG_SEEK;
  if (showpaths) {
    u_int64 where;

    memset(&phi, 0, sizeof(phi));
    phi.p = &dp;

    if ((r = xftell(&input_file, &where))
    ||  (r = Path_PreScan(&input_file, &phi, 0))
    ||  (r = xfseek(&input_file, &where))) {
      com_err(argv0, r, "- path initialization failed");
      xfclose(&input_file);
      exit(2);
    }
  }

  dp.cb_vnode_file = my_file_cb;
  r = ParseDumpFile(&input_file, &dp);
  xfclose(&input_file);

  if (error_count) printf("*** %d errors\n", error_count);
  if (bad_count)   printf("*** %d bad files\n", bad_count);
  if (r && !quiet) printf("*** FAILED: %s\n", error_message(r));
}
