/*
 * title:       inspector.c
 *
 * author:      Craig E. Ward
 *
 * environment: GCC 3.1
 *
 * description:
 *              CMSI 587 Operating Systems, Spring 2003
 *              Assignment 7 -- File System
 *              Instructor: Tri Nguyen
 *              Due: April 9
 *
 *              Program accept one argument that can be either a file or a
 *              directory. If a file, reports (1) size; (2) timestamp(s); and
 *              (3) read/write/execute permissions. If a directory, reports (1)
 *              total number of files and directories in the directory (not
 *              recursively) and (2) read/write/execute permissions.
 *
 * build:       gcc -ansi -g -Wall inspector.c
 *
 * usage:       inpector file_or_directory_name
 *
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

/* constants */
#define DIRECTORY 0
#define REGULAR 1

/* static function prototypes */
static void inspect(const char *);
static void inspect_regular(struct stat *, const char *);
static void inspect_directory(struct stat *, const char *);
static char *permissions(mode_t, int);

/*  external data  */

/* initialized data */

/* uninitialized data */
char *progname;

int main(int argc, char *argv[])
{
  progname = strrchr(argv[0], '/');
  if (progname == NULL) { progname = argv[0]; } else { progname++; }

  if (argc != 2) {
    printf("usage:  %s file_to_inspect\n", progname);
    exit(EXIT_FAILURE);
  }

  inspect(argv[1]);

  exit(EXIT_SUCCESS);
}

static void inspect(const char *afile)
{
  int status = 0;
  struct stat stat_buf;

  printf("Inspecting %s\n", afile);

  status = stat(afile, &stat_buf);
  if (status == 0) {
    if (S_ISREG(stat_buf.st_mode)) {
      inspect_regular(&stat_buf, afile);
    } else if (S_ISDIR(stat_buf.st_mode)) {
      inspect_directory(&stat_buf, afile);
    } else {
      printf("Don't know what %s is!\n", afile);
    }
  } else {
    printf("status failed: %s\n", strerror(errno));
  }
}

/*
 * display data about a regular file
 */
static void inspect_regular(struct stat *st, const char *name)
{
  printf("%s is a regular file\n", name);
  printf("%s has %u bytes\n", name, (unsigned int)st->st_size);
  printf("%s has permissions: (%04o)\n%s\n", 
         name, (st->st_mode & 07777), permissions(st->st_mode, REGULAR));
  /* display the timestamps (ctime() includes a newline */
  if (st->st_atime != 0)
    printf("Last Accessed: %s", ctime(&st->st_atime));
  if (st->st_mtime != 0)
    printf("Last Modified: %s", ctime(&st->st_mtime));
  if (st->st_ctime != 0)
    printf("Last i-node change: %s", ctime(&st->st_ctime));
}

/*
 * display data about a directory file
 */
static void inspect_directory(struct stat *st, const char *name)
{
  DIR *dp;
  struct dirent *dirent_p;
  struct stat stat_buf;
  int status = 0;
  int count_files = 0;
  int count_directories = -2;   /* don't count . and .. */

  printf("%s is a directory\n", name);

  /* open directory stream */
  dp = opendir(name);

  /* change to there to allow stat to work */
  status = chdir(name);
  if (status == -1) {
    fprintf(stderr, "%s: chdir failed\n", strerror(errno));
    exit(EXIT_SUCCESS);
  }

  /* loop through directory and count directories and all other file types */
  if (dp != NULL) {
    while ((dirent_p = readdir(dp)) != NULL) {
#if 0
      fprintf(stderr,"\n\t-->looking for %s\n", dirent_p->d_name);
#endif
      status = stat(dirent_p->d_name, &stat_buf);
      if (status == 0) {
        if (S_ISDIR(stat_buf.st_mode))
          count_directories++;
        else
          count_files++;
      } else {
        fprintf(stderr, "%s: stat error: %s\n", 
                progname, strerror(errno));
        exit(EXIT_FAILURE);
      }
    } /* while */
    (void)closedir(dp);
  } else {
    fprintf(stderr, "%s: opendir error: %s\n", progname, strerror(errno));
    exit(EXIT_FAILURE);
  }

  /* print summary */
  printf("%s has %d %s and %d other %s in it\n", 
         name, count_files,
         (count_files == 1 ? "file" : "files"),
         count_directories,
         (count_directories == 1 ? "directory" : "directories"));
  printf("%s has permissions: (%04o)\n%s\n", 
         name, (st->st_mode & 07777), permissions(st->st_mode, DIRECTORY));
}

/*
 * build a verbose string about the file permissions
 */
static char *permissions(mode_t mode, int typeCode)
{
  static char perms[80];
  char *end_p;

  memset(perms, '\0', sizeof perms);
  strcpy(perms, "\tOwner: ");
  if ((S_IRUSR & mode) != 0) {
    strcat(perms, "read,");
  }
  if ((S_IWUSR & mode) != 0) {
    strcat(perms, "write,");
  }
  if ((S_IXUSR & mode) != 0) {
    strcat(perms, (typeCode == REGULAR ? "execute,": "passthru,"));
  }
  end_p = strrchr(perms,',');
  *end_p = '\n';

  strcat(perms, "\tGroup: ");

  if ((S_IRGRP & mode) != 0) {
    strcat(perms, "read,");
  }
  if ((S_IWGRP & mode) != 0) {
    strcat(perms, "write,");
  }
  if ((S_IXGRP & mode) != 0) {
    strcat(perms, (typeCode == REGULAR ? "execute,": "passthru,"));
  }
  end_p = strrchr(perms,',');
  *end_p = '\n';

  strcat(perms, "\tOther: ");

  if ((S_IROTH & mode) != 0) {
    strcat(perms, "read,");
  }
  if ((S_IWOTH & mode) != 0) {
    strcat(perms, "write,");
  }
  if ((S_IXOTH & mode) != 0) {
    strcat(perms, (typeCode == REGULAR ? "execute,": "passthru,"));
  }
  end_p = strrchr(perms,',');
  *end_p = '\0';

  return perms;
}
