#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#if defined(sgi) || defined(sun)
#include <sys/sysmacros.h>
#endif
#include <sys/stat.h>

#ifdef sun
#ifdef __svr4__
#define SOLARIS2
#else
#define SOLARIS1
#endif
#endif


#ifdef SOLARIS2
#define HAS_STATVFS
#endif

#ifdef sgi
#define HAS_ST_FSTYPE
#endif


#ifdef HAS_STATVFS
#include <sys/statvfs.h>
#endif


int opt_L = 0, opt_i = 0, opt_d = 0, opt_r = 0, opt_l = 0, opt_s = 0,
    opt_p = 0, opt_k = 0, opt_u = 0, opt_g = 0, opt_c = 0, opt_a = 0,
    opt_m = 0, opt_t = 0, opt_q = 0, opt_f = -1;
int opt_fstype = 0;
time_t now;
void sep(void);

void sino(struct stat* s)
{
  if(!opt_q)
    printf("inode ");
  printf("%d", s->st_ino);
}

void sdev(struct stat* s)
{
  if(!opt_q)
    printf("dev ");
  printf("%d", s->st_dev);
}

void srdev(struct stat* s)
{
  if(!opt_q)
    printf("rdev ");
  printf("%d,%d", major(s->st_rdev), minor(s->st_rdev));
}

void snlink(struct stat* s)
{
  if(!opt_q)
    printf("links ");
  printf("%d", s->st_nlink);
}

void ssize(struct stat* s)
{
  if(!opt_q)
    printf("size ");
  printf("%d", s->st_size);
}

void smode(struct stat* s)
{
  int i;

  if(opt_q)
    {
      printf("%u", s->st_mode);
      return;
    }

  printf("mode is %04o/", s->st_mode & 07777);

/* sticky bit */
#ifndef S_ISTXT
#if defined(S_ISVTX)
#define S_ISTXT S_ISVTX
#else
#define S_ISTXT 0001000
#endif
#endif

  if(s->st_mode & (S_ISUID|S_ISGID|S_ISTXT))
    {
      if(s->st_mode & S_ISUID)
	putchar('u');
      else
	putchar('-');

      if(s->st_mode & S_ISGID)
	putchar('g');
      else
	putchar('-');

      if(s->st_mode & S_ISTXT)
	putchar('t');
      else
	putchar('-');
    }

  for(i = 8; i >= 0; i--)
      if(s->st_mode & (1u << i))
	putchar("rwxrwxrwx"[8-i]);
      else
	putchar('-');
}

void skind(struct stat* s)
{
  if(S_ISDIR(s->st_mode))
    printf("directory");
  else if(S_ISCHR(s->st_mode))
    printf("char_spec");
  else if(S_ISBLK(s->st_mode))
    printf("block_spec");
  else if(S_ISREG(s->st_mode))
    printf("regular");
  else if(s->st_mode & S_IFIFO)
    printf("fifo");
  else if((s->st_mode & S_IFSOCK) == S_IFSOCK)
    printf("socket");
#ifdef S_IFLNK
  else if((s->st_mode & S_IFLNK) == S_IFLNK)
    printf("link");
#endif
  else
    printf("UNKNOWN");
}

void suid(struct stat* s)
{
  struct passwd* pw;

  if(opt_q)
    {
      printf("%d", s->st_uid);
      return;
    }
  printf("uid %d", s->st_uid);
  if((pw = getpwuid(s->st_uid)) != NULL)
    printf(" (%s)", pw->pw_name);
}

void sgid(struct stat* s)
{
  struct group* gr;

  if(opt_q)
    {
      printf("%d", s->st_gid);
      return;
    }
  printf("gid %d", s->st_gid);
  if((gr = getgrgid(s->st_gid)) != NULL)
    printf(" (%s)", gr->gr_name);
}

char* get_time(time_t t)
{
  static char buff[BUFSIZ];
  char* p;
  long diff = now - t;

  sprintf(buff, "%s%u", ctime(&t), t);
  if((p = strchr(buff, '\n')) != NULL)
    *p = ' ';
  p += strlen(p);
  sprintf(p, " (%05d.", diff / (60*60*24));
  if(diff < 0)
    diff = -diff;
  p += strlen(p);
  diff %= 60*60*24;

  sprintf(p, "%02d:%02d:%02d)",
	  diff / (60*60),
	  (diff / 60) % 60,
	  diff % 60);

  return buff;
}

void sfstype(struct stat* s, char *path)
{
#if defined(HAS_ST_FSTYPE) || defined(HAS_ST_FSTYPE)
  char *fstag;
  char *fstype;

#if defined(HAS_ST_FSTYPE)
  fstag = "st_fstype";
  fstype = s->st_fstype;
#elif defined(HAS_STATVFS)
  struct statvfs vfs;
  fstag = "vfstype";
  fstype = NULL;
  if(path == NULL) {
    if(fstatvfs(opt_f, &vfs) < 0)
      goto print;
  } else {
    if(statvfs(path, &vfs) < 0)
      goto print;
  }
  fstype = vfs.f_basetype;
 print:
#endif

  sep();
  if(!opt_q)
    printf("%s: ", fstag);
  printf("%s", fstype);
#endif
}

void sctime(struct stat* s)
{
  if(opt_q)
      printf("%u", s->st_ctime);
  else
    printf("change time - %s", get_time(s->st_ctime));
}

void satime(struct stat* s)
{
  if(opt_q)
      printf("%u", s->st_atime);
  else
    printf("access time - %s", get_time(s->st_atime));
}

void smtime(struct stat* s)
{
  if(opt_q)
      printf("%u", s->st_mtime);
  else
    printf("modify time - %s", get_time(s->st_mtime));
}

void usage(void)
{
 fputs("Usage: stat [-idrlspkugcamtqL] [-f fdesc] files ...\n", stderr);
 fputs("\tinode, dev, rdev, links, size, perm, kind, user, grp\n", stderr);
 fputs("\tctime, atime, mtime, t = all times, q = quiet, L = lstat\n", stderr);
 fputs("\tdefault is everything\n", stderr);
 fputs("\t-f option takes file descriptor instead of filenames\n", stderr);
 exit(1);
}

int col = 0;
int col_max = 3;
void sep(void)
{
  if(opt_q) {
    if(col > 0)
      putchar('\n');
    col++;
  } else
    {
      if(col >= col_max)
	{
	  putchar('\n');
	  putchar('\t');
	  col = 0;
	}
      else
	{
	  col++;
	  putchar(';');
	  putchar(' ');
	}
    }
}

void print_stat(struct stat* s, char *path)
{
  col_max = 3;
  if(opt_q)
    col = 0;
  else
    col = col_max;

  if(opt_i) { sep(); sino(s); }
  if(opt_d) { sep(); sdev(s); }
  if(opt_r) { sep(); srdev(s); }
  if(opt_l) { sep(); snlink(s); }
  if(opt_s) { sep(); ssize(s); }
  if(opt_p) { sep(); smode(s); }
  if(opt_k) { sep(); skind(s); }
  col_max = 2;
  if(opt_u) { sep(); suid(s); }
  if(opt_g) { sep(); sgid(s); }

  if(!opt_q)
    col = col_max = 0;
  if(opt_fstype) { sfstype(s, path); }
  if(opt_c) { sep(); sctime(s); }
  if(opt_a) { sep(); satime(s); }
  if(opt_m) { sep(); smtime(s); }
  putchar('\n');
}  

int main(int argc, char** argv)
{
  int i, ch, opt;

  opt = 0;
  while((ch = getopt(argc, argv, "Lidrlspkugcamtqf:")) != -1)
    {
      switch(ch)
	{
	case 'L': opt_L = 1; break;
	case 'i': opt = opt_i = 1; break;
	case 'd': opt = opt_d = 1; break;
	case 'r': opt = opt_r = 1; break;
	case 'l': opt = opt_l = 1; break;
	case 's': opt = opt_s = 1; break;
	case 'p': opt = opt_p = 1; break;
	case 'k': opt = opt_k = 1; break;
	case 'u': opt = opt_u = 1; break;
	case 'g': opt = opt_g = 1; break;
	case 'c': opt = opt_c = 1; break;
	case 'a': opt = opt_a = 1; break;
	case 'm': opt = opt_m = 1; break;
	case 't': opt = opt_t = 1; break;
	case 'q': opt_q = 1; break;
	case 'f': opt_f = atoi(optarg); break;
	}
    }

  if(opt == 0) {
    opt_i = opt_d = opt_r = opt_l = opt_s = opt_p = opt_k = opt_u = 
      opt_g = opt_c = opt_a = opt_m = opt_t = opt_fstype = 1;
  }

  now = time(NULL);

  if(opt_f >= 0)
    {
      struct stat s;
      if(fstat(opt_f, &s) < 0)
	{
	  perror("fstat");
	  return 1;
	}
      if(!opt_q)
	printf("file descriptor %d:", opt_f);
      print_stat(&s, NULL);
      return 0;
    }

  if(optind == argc)
    usage();

  if(strcmp(argv[optind], "-") == 0)
    optind++;
  else if(argv[optind][0] == '-')
    usage();

  if(opt_t)
      opt_c = opt_a = opt_m = 1;

  for(i = optind; i < argc; i++)
    {
      char* file;
      struct stat s;

      file = argv[i];
      if(opt_L)
	{
	  if(lstat(file, &s) < 0)
	    {
	      perror(file);
	      continue;
	    }
	}
      else
	{
	  if(stat(file, &s) < 0)
	    {
	      perror(file);
	      continue;
	    }
	}

      if(!opt_q)
	printf("%s:", file);
      print_stat(&s, file);
    }
  return 0;
}
