/* Copyright 2001 by the Massachusetts Institute of Technology.
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 */

/* This program checks that a file containing stat information,
 * as produced by the os-statfiles program, is correct with
 * respect to the corresponding master root directory (either
 * an Athena /os hierarchy, or a hierarchy of machine-dependent
 * files, as now found under /install/oscheck/mach).
 */

static const char rcsid[] = "$Id$";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include "array.h"

#define MAXLINE ((PATH_MAX * 2) + 256)

char *progname;

char *osroot = "/os";
Array *exceptlist = NULL;
int verbose = 0;

void usage();

void do_file(const char *path, mode_t mode, off_t size,
	     uid_t uid, gid_t gid, int flags);

void do_symlink(const char *from, const char *to);

void do_hardlink(const char *path, const char *to);

void do_directory(const char *path, mode_t mode, uid_t uid, gid_t gid);

Array *make_list(const char *file);

int in_list(const Array *list, const char *what);

int compare_string(const void *p1, const void *p2);

char *estrdup(const char *s);


void usage()
{
  fprintf(stderr, "Usage: %s [<options>] <statfile>\n", progname);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "        -o <osroot>      OS root, default is /os\n");
  fprintf(stderr, "        -v               Verbose output mode\n");
  fprintf(stderr, "        -x <file>        File containing exception list\n");
  exit(1);
}

int main(int argc, char **argv)
{
  const char *statfile = NULL;
  const char *exceptfile = NULL;
  FILE *statfp;
  char inbuf[MAXLINE];
  char *path;
  char linkpath[PATH_MAX+1];
  struct stat sb;
  unsigned long long mode, uid, gid, size;
  int c, len, flags;

  progname = argv[0];

  while ((c = getopt(argc, argv, "o:x:v")) != EOF)
    {
      switch(c)
	{
	case 'o':
	  osroot = optarg;
	  break;
	case 'x':
	  exceptfile = optarg;
	  break;
	case 'v':
	  verbose = 1;
	  break;
	case '?':
	  usage();
	  break;
	}
    }

  if (optind + 1 != argc)
    usage();

  statfile = argv[optind++];
  statfp = fopen(statfile, "r");
  if (statfp == NULL)
    {
      fprintf(stderr, "%s: Cannot open %s: %s\n", progname, statfile,
	      strerror(errno));
      exit(1);
    }

  if (stat(osroot, &sb) != 0 || !S_ISDIR(sb.st_mode))
    {
      fprintf(stderr, "%s: Invalid operating system root %s\n",
	      progname, osroot);
      exit(1);
    }

  if (exceptfile != NULL)
    exceptlist = make_list(exceptfile);

  /* Main loop -- read entries from the stat file. */
  while (fgets(inbuf, sizeof(inbuf), statfp) != NULL)
    {
      len = strlen(inbuf);
      if (inbuf[len-1] != '\n')
	{
	  fprintf(stderr, "%s: Invalid entry '%s'\n",
		  progname, inbuf);
	  continue;
	}
      inbuf[--len] = '\0';

      /* Get the entry path, always the last field in the line.
       * Skip it if it is in the exception list.
       */
      for (path = &inbuf[len-1]; path > &inbuf[0] && *path != ' '; --path);
      if (path <= &inbuf[0])
	{
	  fprintf(stderr, "%s: Invalid entry '%s'\n",
		  progname, inbuf);
	  continue;
	}
      ++path;

      if (in_list(exceptlist, path))
	{
	  if (verbose)
	    printf("Skipping exception %s\n", path);
	  continue;
	}

      switch (inbuf[0])
	{
	case 'l':
	  if (sscanf(&inbuf[2], "%s", linkpath) != 1)
	    {
	      fprintf(stderr,
		      "%s: Invalid symlink entry '%s'\n",
		      progname, inbuf);
	      continue;
	    }
	  do_symlink(path, linkpath);
	  break;
	case 'f':
	  if (sscanf(&inbuf[2], "%llo %llu %llu %llu %x", &mode, &size,
		     &uid, &gid, &flags) != 5)
	    {
	      fprintf(stderr,
		      "%s: Invalid file entry '%s'\n",
		      progname, inbuf);
	      continue;
	    }
	  do_file(path, mode, size, uid, gid, flags);
	  break;
	case 'h':
	  if (sscanf(&inbuf[2], "%s", linkpath) != 1)
	    {
	      fprintf(stderr,
		      "%s: Invalid hard link entry '%s'\n",
		      progname, inbuf);
	      continue;
	    }
	  do_hardlink(path, linkpath);
	  break;
	case 'd':
	  if (sscanf(&inbuf[2], "%llo %llu %llu", &mode, &uid, &gid) != 3)
	    {
	      fprintf(stderr,
		      "%s: Invalid directory entry '%s'\n",
		      progname, inbuf);
	      continue;
	    }
	  do_directory(path, mode, uid, gid);
	  break;
	default:
	  fprintf(stderr, "%s: Unrecognized type '%c'\n",
		  progname, inbuf[0]);
	  continue;
	}
    }
  exit(0);
}

void do_file(const char *path, mode_t mode, off_t size,
	     uid_t uid, gid_t gid, int flags)
{
  FILE *fp;
  struct stat sb;
  char ospath[PATH_MAX+1];

  /* Check that the file can be read from the os tree. */
  sprintf(ospath, "%s/%s", osroot, path);
  fp = fopen(ospath, "r");
  if (fp == NULL)
    {
      fprintf(stderr, "%s: Cannot open %s: %s\n",
	      progname, ospath, strerror(errno));
      return;
    }
  if (fstat(fileno(fp), &sb) == -1)
    {
      fprintf(stderr, "%s: Cannot stat %s: %s\n",
	      progname, ospath, strerror(errno));
      fclose(fp);
      return;
    }
  fclose(fp);
  if (sb.st_size != size)
    {
      fprintf(stderr, "%s: Size mismatch for %s (%llu, %llu)\n",
	      progname, ospath, (unsigned long long) size,
	      (unsigned long long) sb.st_size);
      return;
    }
  return;
}

void do_symlink(const char *from, const char *to)
{
  char ospath[PATH_MAX+1];
  struct stat sb;

  /* If the link is to /os, check if the linked-to file exists in the
   * os root.
   */
  if (strncmp(to, "/os/", 4) == 0)
    {
      sprintf(ospath, "%s/%s", osroot, to + 4);
      if (lstat(ospath, &sb) != 0)
	{
	  fprintf(stderr, "%s: Dangling symlink %s (link to %s)\n",
		  progname, from, to);
	  return;
	}
    }
  return;
}

/* Handle a hard link.  This is just a placeholder for now. */
void do_hardlink(const char *path, const char *to)
{
  return;
}

/* Check a directory entry.  This is just a placeholder for now; we
 * could add code to check that the directory exists in the /os tree.
 */
void do_directory(const char *path, mode_t mode, uid_t uid, gid_t gid)
{
  return;
}

/* Read a file into an array, one element per line. */
Array *make_list(const char *file)
{
  FILE *f;
  Array *list;
  char buffer[MAXLINE];

  f = fopen(file, "r");
  if (f == NULL)
    return NULL;

  list = array_new();
  while (fgets(buffer, sizeof(buffer), f))
    {
      buffer[strlen(buffer) - 1] = '\0';
      array_add(list, estrdup(buffer));
    }

  fclose(f);
  array_sort(list, compare_string);
  return list;
}

/* Check if the given string is an element in the given array.
 * Returns 1 if found in the array, 0 otherwise.
 */
int in_list(const Array *list, const char *what)
{
  if (list == NULL)
    return 0;

  return (array_search(list, what) != NULL);
}

int compare_string(const void *p1, const void *p2)
{
  return strcmp(*((char **)p1), *((char **)p2));
}

char *estrdup(const char *s)
{
  char *s2;

  s2 = strdup(s);
  if (s2 == NULL)
    {
      fprintf(stderr, "%s: Out of memory\n", progname);
      exit(1);
    }
  return s2;
}
