
/* Minitar:  extract .tar.gz files on Win32 platforms.
   Uses zlib for decompression.

   This is very simple-minded, it ignores checksums, and any type of file
   that is not a plain file or a directory.  Nonetheless it is useful.

   Author: Charles G. Waldman (cgw@pgt.com),  Aug 4 1998

   This file is placed in the public domain; you can
   do whatever you like with it.  There is NO WARRANTY.
   If it breaks, you get to keep both pieces */


#include <stdio.h>
#include <Errno.h>

#include <zlib.h>

Usage(char *name)
{
  fprintf(stderr,"Usage: %s file.tar.gz [base-dir]\n",name);
  fprintf(stderr,"\tExtracts the contents compressed tar file to base-dir\n");
  exit(-1);
}


#define BLOCKSIZE 512
#define MAXNAMELEN 1024

int octal(char *str)
{
  int ret = -1;
  sscanf(str,"%o",&ret);
  return ret;
}

/* this is like mkdir -p, except if there is no trailing slash,
   the final component is assumed to be a file, rather than a
   path component, so it is not created as a directory */

int makepath(char *path)
{
  char tmp[MAXNAMELEN];
  char *cp;
  extern int errno;

  for (cp=path; cp; cp = (char*)strchr(cp+1,'/')){
    if (!*cp)
      break;
    if (*cp != '/')
      continue;
    strncpy(tmp, path, cp-path);
    tmp[cp-path] = '\0';
    if (strlen(tmp) == 0)
      continue;
    if (mkdir(tmp,0777)){
      if (errno == EEXIST)
	continue;
      else
	return -1;
    }
  }
  return 0;
}




main(int argc, char **argv)
{
  char fullname[MAXNAMELEN];
  char *basedir = ".";
  char *tarfile;
  char *cp;
  int size;
  char osize[13];
  char name[101];
  char magic[7];
  char type;

  gzFile *infile = (gzFile*)0;
  FILE *outfile = (FILE*)0;

  char block[BLOCKSIZE];
  int nbytes, nread, nwritten;

  int in_block = 0;
  int directory = 0;

  if (argc < 2 || argc > 3)
    Usage(argv[0]);

  tarfile = argv[1];
  if (argc==3)
    basedir = argv[2];

  if (! (infile = gzopen(tarfile,"rb"))){
    fprintf(stderr,"Cannot open %s\n", tarfile);
    exit(-2);
  }

  while (1){


    nread = gzread(infile,block,512);

    if (!in_block && nread == 0)
      break;

    if (nread != BLOCKSIZE){
      fprintf(stderr,"Error: incomplete block read. Exiting.\n");
      exit(-2);
    }

    if (!in_block){
      if (block[0]=='\0')  /* We're done */
	break;

      strncpy(magic,block+257,6);
      magic[6] = '\0';
      if (strcmp(magic,"ustar ")){
	fprintf(stderr,
		"Error: incorrect magic number in tar header. Exiting\n");
      }

      strncpy(name,block,100);
      name[100] = '\0';
      sprintf(fullname,"%s/%s",basedir,name);
      printf("%s\n",fullname);
      type = block[156];

      switch(type){
      case '0':
      case '\0':
	directory = 0;
	break;
      case '5':
	directory = 1;
	break;
      default:
	fprintf(stderr,"Error: unknown type flag %c. Exiting.\n",type);
	break;
      }

      if (directory){
	in_block = 0;

	/* makepath will ignore the final path component, so make sure
	   dirnames have a trailing slash */

	if (fullname[strlen(fullname)-1] != '/')
	  strcat(fullname,"/");
	if (makepath(fullname)){
	  fprintf(stderr, "Error: cannot create directory %s. Exiting.\n",
		  fullname);
	  exit(-2);
	}
	continue;
      } else { /*file */
	in_block = 1;
	if (outfile){
	  if (fclose(outfile)){
	    fprintf(stderr,"Error: cannot close file %s. Exiting.\n",
		    fullname);
	    exit(-2);
	  }
	  outfile = (FILE*)0;
	}

	if ( !(outfile = fopen(fullname,"wb"))){
	  /*try creating the directory, maybe it's not there */
	  if (makepath(fullname)){
	    fprintf(stderr,"Error: cannot create file %s. Exiting.\n",
		    fullname);
	    exit(-2);
	  }
	  /* now try again to open the file */
	  if (!(outfile = fopen(fullname,"wb"))){
	    fprintf(stderr,"Error: cannot create file %s. Exiting.\n",
		    fullname);
	    exit(-2);
	  }
	}

	strncpy(osize,block+124,12);
	osize[12] = '\0';
	size = octal(osize);
	if (size<0){
	  fprintf(stderr,"Error: invalid size in tar header. Exiting.\n");
	  exit(-2);
	}
      }
    } else { /* write or continue writing file contents */
      nbytes = size>512? 512:size;

      nwritten = fwrite(block, 1, nbytes, outfile);
      if (nwritten != nbytes){
	fprintf(stderr, "Error: only wrote %d bytes to file %s. Exiting.\n",
		nwritten, fullname);
      }
      size -= nbytes;
      if (size==0)
	in_block = 0;
    }
  }

  exit(0);
}




