/* 9e.c */

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

enum {
  NAMELEN = 28,
  NBUFLEN = 2048,
  DBUFLEN = 8192
};

/* A really simple argument processor from Plan 9 */

extern char *argv0;
#define	ARGBEGIN	for((argv0? 0: (argv0 = *argv)),argv++,argc--;\
			    argv[0] && argv[0][0]=='-' && argv[0][1];\
			    argc--, argv++) {\
				char *_args, *_argt;\
				char _argc;\
                                if((_argt = NULL));\
				_args = &argv[0][1];\
				if(_args[0]=='-' && _args[1]==0){\
					argc--; argv++; break;\
				}\
				_argc = 0;\
				while(*_args && (_argc = (*_args++)))\
				switch(_argc)
#define	ARGEND		;};
#define	ARGF()		(_argt=_args, _args="",\
				(*_argt? _argt: argv[1]? (argc--, *++argv): 0))
#define	ARGC()		_argc

#ifndef S_ISDIR
#define S_ISDIR(m) (((m)&(S_IFDIR))!=0)
#endif

#define CHDIR 0x80000000   /* OSF/1 enums fail */

void usage(void);
void warn(char *msg, char *file);
void die(char *msg, char *file);
void do_file(char *file, FILE*);

int Hdrs;
int Verbose;
char *argv0;
char *Base;
unsigned char buf[DBUFLEN+1];
char tmp[NBUFLEN];
char namebuf[NBUFLEN];
char owner[NAMELEN];
char group[NAMELEN];

static const char vc_id[] = "$Id: 9e.c,v 1.2 2000/06/15 13:40:21 wkj Exp $";

int main(int argc, char *argv[]) {
  FILE *fin;
  struct stat stbuf;
  int i;


  Verbose = 0;
  Base = ((char *) 0);

  ARGBEGIN{
  case 'h':
    Hdrs = 1;
    break;
  case 'r':
    Base = ARGF();
    break;
  case 'v':
    Verbose = 1;
    break;
  case '?':
  default:
    usage();
  }ARGEND;

  if (Base && !Hdrs) {
    if (stat(Base, &stbuf) < 0) {
      if(mkdir(Base, 0755) < 0) { 
	die("mkdir(2) failed for ", Base); 
      }
    } else if(! S_ISDIR(stbuf.st_mode)) {
      fprintf(stderr, "%s is not a directory.\n", Base);
      exit(1);
    }
  }

  if(argc) {
    for(i=0; i<argc; i++) {
      fin = fopen(argv[i], "rb");
      if(fin == NULL) {
        fprintf(stderr, "Cannot open ");
        perror(argv[i]);
        continue;
      }
      do_file(argv[i], fin);
      fclose(fin);
    }
  } else
    do_file("<stdin>", stdin);

  return 0;
}

void do_file(char *file, FILE *fin) {
  long n, tot, mode, mtime, size;
  FILE *fout;
  char *name;

  assert(file != 0);
  assert(fin != 0);

  fout = NULL;

  /* filename octalmode owner group mtime size */
  while(fscanf(fin, "%s %lo %s %s %ld %ld", namebuf, &mode, 
                owner, group, &mtime, &size) == 6) {

    if(fgetc(fin) != '\n' || namebuf[0] != '/' || size < 0) {
      warn("Bad format", file);
      goto end;
    }

    if (Base) {
      /* I don't want to have to deal with -ldb on OSF/1 */
      sprintf(tmp, "%s%s", Base, namebuf);
      name = tmp;
    } else {
      name = namebuf+1;
    }

    if(Hdrs) {
      fprintf(stderr, "%s %lo %s %s %ld %ld\n", namebuf, mode,
	      owner, group, mtime, size);
      fout = NULL;
    } else {
      if (Verbose)
	fprintf(stderr, "%s %d\n", namebuf, size);
      if(mode & CHDIR) {
        assert(size == 0);
	/* Give ourselves read, write, and execute permission */
        if(mkdir(name, (mode & ~CHDIR) | 0700) < 0)
	  warn("mkdir(2) failed for", name);
        continue;
      }
      if((fout=fopen(name, "w+b")) == NULL)
	warn("Could not extract", name);
    }

    for(tot=0; tot<size; tot+=n) {
      n = sizeof(buf);
      if(n > size-tot)
        n = size-tot;
      if((n=fread(buf, 1, n, fin)) <= 0) {
	warn("Short read on", name);
	goto end;
      }

      if(fout && (fwrite(buf, 1, n, fout) != n)) {
	warn("Short write creating", name);
        fclose(fout);
        fout = NULL;
      }
    }
    if (fout) {
      fclose(fout);
      fout = NULL;
    }
  }

 end:
  if (fout)
    fclose(fout);
  return;
}

void usage(void) {
  fprintf(stderr, "Usage: %s <file> ...\n", argv0);
  exit(1);
}

void warn(char *msg, char *file) {
  assert(msg != 0);
  fprintf(stderr, "%s ", msg);
  if (file)
    perror(file);
  else
    perror("<null>");
  return;
}

void die(char *msg, char *file) {
  assert(msg != 0);
  fprintf(stderr, "%s ", msg);
  if (file)
    perror(file);
  else
    perror("<null>");
  exit(1);
}

/* EOF */
