/*///---------------------------------------------------------------------\\\*/
/*// Copyright (c)               (_)/=\~/_(_)         All rights reserved. \\*/
/*/  2005-2006                (_)  /=\  ~/_  (_)        Released under the  \*/
/*\  Jim Peters            (_)    /=\    ~/_    (_)             GNU GPL v2  /*/
/*\\ http://uazu.net    (_) ____ /=\ ____ ~/_ ____ (_)      http://fsf.org //*/
/*\\\---------------------------------------------------------------------///*/

//
//	Code to append files/directories/symlinks (but no other types
//	of filesystem objects) to a .tgz archive, using POSIX.1-1988
//	"ustar" format if possible, but using the POSIX.1-2001 "pax
//	interchange" extensions for entries that don't fit within
//	ustar's limits.
//
//	These functions do not use scratch[].
//
//	void tgz_open(path);	// Note: opens file for appending
//	void tgz_close();
//	void tgz_append(path);	// Removes basename from 'path'
//	void tgz_append_as(path, nam);  // Allows file to be written as any name
//
 
#ifdef HEADER

#else

#include "all.h"

struct tar_header {
   char name[100];
   char mode[8];
   char uid[8];
   char gid[8];
   char size[12];
   char mtime[12];
   char chksum[8];
   char typeflag;
   char linkname[100];
   char magic[6];
   char version[2];
   char uname[32];
   char gname[32];
   char devmajor[8];
   char devminor[8];
   char prefix[155];
   char pad[12];
};

gzFile *tgz_out;
static int req_pax;	// Do we require a pax header for this record?

static void write_pax_header(struct tar_header *, struct stat *, char *, char *);

static void 
writeblock(void *data) {
   if (512 != gzwrite(tgz_out, data, 512)) 
      error("Can't write block to TGZ file (%s)", strerror(errno));
}

static void 
synctgz() {
   if (Z_OK != gzflush(tgz_out, Z_SYNC_FLUSH))
      error("Error sync-flushing to TGZ file", gzerror(tgz_out, 0));
}

static void 
wrnum(char *ptr, int len, int num) {
   char buf[16];
   if (len == sprintf(buf, "%0*o", len, num)) {
      memcpy(ptr, buf, len);
      return;
   }
   //warn("Octal value overflowed writing TGZ file; writing '@'s: %s", buf);
   while (len-- > 0) *ptr++= '@';
   req_pax= 1;
}

#define WRNUM(field, num) wrnum(field, sizeof(field), num)

void 
tgz_open(char *path) {
   if (tgz_out)
      error("Attempt to open TGZ stream twice (tgz_open())");
   if (!(tgz_out= gzopen(path, "ab")))
      error("Can't open TGZ file for appending (%s):\n  %s", strerror(errno), path);
}

void 
tgz_close() {
   if (tgz_out && Z_OK != gzclose(tgz_out))
      error("Error closing TGZ file (%s)", gzerror(tgz_out, 0));
   tgz_out= 0;
}


void 
tgz_append(char *path) {
   char *nam= path;
   if (starts_with(nam, base)) {
      nam += strlen(base);
      if (*nam++ != '/') DIE;
   }
   tgz_append_as(path, nam);
}

void 
tgz_append_as(char *path, char *nam) {
   struct passwd *pw;
   struct group *gr;
   struct stat sb;
   struct tar_header th;
   int a, sum;
   int isdir, islnk, isfil;
   char tmp[300];
   char *nam0, *nam1, *nam2;

   req_pax= 0;

   if (0 != lstat(path, &sb))
      error("Can't stat file to write to TGZ (%s):\n  %s", strerror(errno), path);
   
   isdir= S_ISDIR(sb.st_mode);
   islnk= S_ISLNK(sb.st_mode);
   isfil= S_ISREG(sb.st_mode);

   if (!isdir && !islnk && !isfil) {
      warn("Can't write file to TGZ; not a symlink or regular file:\n  %s", path);
      return;
   }

   // Write header block
   strncpy(tmp, nam, sizeof(tmp)); tmp[sizeof(tmp)-2]= 0;
   nam0= tmp;
   nam1= 0;
   nam2= strchr(tmp, 0);
   if (isdir && nam2 > nam0 && nam2[-1] != '/') { 
      *nam2++= '/'; *nam2= 0; 
   }

   memset(&th, 0, sizeof(th));
   if (nam2-nam0 > sizeof(th.name)) {
      nam1= strchr(nam2 - sizeof(th.name), '/');
      if (!nam1) {
	 //warn("Filename too long to save to TGZ; truncating:\n  %s", nam0);
	 req_pax= 1;
	 nam2= nam0 + sizeof(th.name);
      }
      *nam1++= 0;
      if (nam1-nam0-1 > sizeof(th.prefix)) {
	 //warn("Filename too long to save to TGZ; truncating:\n  %s/%s", nam0, nam1);
	 req_pax= 1;
	 nam0= nam1;
      }
   }

   if (nam1) {
      memcpy(th.name, nam1, nam2-nam1);
      memcpy(th.prefix, nam0, nam1-nam0-1);
   } else {
      memcpy(th.name, nam0, nam2-nam0);
   }

   WRNUM(th.mode, sb.st_mode);
   WRNUM(th.uid, sb.st_uid);
   WRNUM(th.gid, sb.st_gid);
   WRNUM(th.size, isfil ? sb.st_size : 0);
   WRNUM(th.mtime, sb.st_mtime);
   memset(th.chksum, ' ', sizeof(th.chksum));
   th.typeflag= isfil ? '0' : islnk ? '2' : '5';
   if (islnk) {
      int len= readlink(path, th.linkname, 1+sizeof(th.linkname));
      if (len < 0)
	 error("Can't read symbolic link:\n  %s", path);
      if (len > sizeof(th.linkname))
	 req_pax= 1;
	 //warn("Symbolic link truncated as written to TGZ file:\n  %s", path);
   }
   strcpy(th.magic, "ustar");
   memcpy(th.version, "00", 2);
   
   if ((pw= getpwuid(sb.st_uid))) {
      if (strlen(pw->pw_name) >= sizeof(th.uname)) req_pax= 1;
      strncpy(th.uname, pw->pw_name, -1+sizeof(th.uname));
   }
   if ((gr= getgrgid(sb.st_gid))) {
      if (strlen(gr->gr_name) >= sizeof(th.gname)) req_pax= 1;
      strncpy(th.gname, gr->gr_name, -1+sizeof(th.gname));
   }

   for (a= sum= 0; a<512; a++) sum += 255 & ((char*)&th)[a];
   WRNUM(th.chksum, sum);

   if (req_pax) write_pax_header(&th, &sb, path, nam);

   writeblock(&th);

   // Write file contents 
   if (isfil) {
      char block[512];
      size_t size;
      FILE *in;
      
      if (!(in= fopen(path, "rb")))
	 error("Can't open file for reading (%s):\n  %s", strerror(errno), path);
      
      size= sb.st_size;
      while (size > 0) {
	 int cnt= size < 512 ? size : 512;
	 if (1 != fread(&block, cnt, 1, in))
	    error("Error reading file (%s):\n  %s", strerror(errno), path);
	 if (cnt < 512)
	    memset(block+cnt, 0, 512-cnt);
	 writeblock(&block);
	 size -= cnt;
      }
      
      fclose(in);
   }

   // Sync zlib output after each file so that if we die suddenly, the
   // archive is still valid
   synctgz();
}      

// Extended pax stuff
typedef struct AttrLine AttrLine;
struct AttrLine {
   AttrLine *nxt;
   int len;
   char str[8];
};

static void 
fmt_pax(AttrLine **alp, char *fmt, ...) {
   va_list ap;
   AttrLine *rv;
   int len, prelen, tot;
   char dmy[1];
   char pre[10];
   
   va_start(ap, fmt);
   len= vsnprintf(dmy, 0, fmt, ap);
   if (len < 0) DIE;
   va_end(ap);
   
   pre[0]= 0; prelen= 0;
   do {
      tot= prelen + len + 1;
      prelen= sprintf(pre, "%d ", tot);
   } while (prelen + len + 1 != tot);

   rv= Alloc(sizeof(*rv) - sizeof(rv->str) + tot + 1);
   memcpy(rv->str, pre, prelen);
   va_start(ap, fmt);
   if (len != vsnprintf(rv->str+prelen, len+1, fmt, ap)) DIE;
   va_end(ap);
   strcpy(rv->str + prelen + len, "\n");

   rv->len= len+1;
   rv->nxt= *alp;
   *alp= rv;
}
   
static void 
write_pax_header(struct tar_header *th, struct stat *sb, char *path, char *nam) {
   AttrLine *list= 0, *al;
   char *p;
   struct tar_header xh;
   struct passwd *pw;
   struct group *gr;
   int len, blklen, a, sum;
   char *dat;
   
   fmt_pax(&list, "mtime=%d", sb->st_mtime);
   fmt_pax(&list, "uid=%d", sb->st_uid);
   fmt_pax(&list, "gid=%d", sb->st_gid);
   fmt_pax(&list, "size=%d", sb->st_size);

   p= strchr(nam, 0);
   if (th->typeflag == '5' && p[-1] != '/') 
      fmt_pax(&list, "path=%s/", nam);
   else 
      fmt_pax(&list, "path=%s", nam);

   if ((pw= getpwuid(sb->st_uid))) 
      fmt_pax(&list, "uname=%s", pw->pw_name);
   if ((gr= getgrgid(sb->st_gid))) 
      fmt_pax(&list, "gname=%s", gr->gr_name);

   if (th->typeflag == '2') {
      char dmy[1], *tmp;
      int len= readlink(path, dmy, 1);
      if (len < 0) error("Can't read symbolic link:\n  %s", path);
      tmp= Alloc(len+1);
      if (len != readlink(path, tmp, len+1)) DIE;
      fmt_pax(&list, "linkname=%s", tmp);
      free(tmp);
   }

   // Repack into an extent of whole blocks
   len= 0;
   for (al= list; al; al= al->nxt) 
      len += al->len;
   blklen= ((len-1)|511)+1;
   dat= p= Alloc(blklen);
   while (list) {
      al= list; list= list->nxt;
      memcpy(p, al->str, al->len);
      p += al->len;
      free(al);
   }

   // Form a header for this record and write it all out; note that
   // name is used only if archiver doesn't understand pax; make no
   // more effort than to try to make it unique
   memset(&xh, 0, sizeof(xh));
   sprintf(xh.name, "PaxHeader-%08X", do_hash(dat, len, 0));
   WRNUM(xh.mode, 0666);
   WRNUM(xh.uid, 0);
   WRNUM(xh.gid, 0);
   WRNUM(xh.size, len);
   WRNUM(xh.mtime, 0);
   memset(xh.chksum, ' ', sizeof(xh.chksum));
   xh.typeflag= 'x';
   strcpy(xh.magic, "ustar");
   memcpy(xh.version, "00", 2);
   for (a= sum= 0; a<512; a++) sum += 255 & ((char*)&xh)[a];
   WRNUM(xh.chksum, sum);

   writeblock(&xh);
   for (p= dat; blklen > 0; p += 512, blklen -= 512) 
      writeblock(p);

   free(dat);
}

#endif

// END //

/*///---------------------------------------------------------------------\\\*/
/*// )`( )()/)(`/\`//\`          (_)/=\~/_(_)           '/\\'/\')(\()( )'( \\*/
/*/ ()(')()\)(/`/\\ `         (_)  /=\  ~/_  (_)          ' //\'\)(/()(`)() \*/
/*\  )`()( )(/'            (_)    /=\    ~/_    (_)             `\)( )()'(  /*/
/*\\  ()()(`)(/         (_) ____ /=\ ____ ~/_ ____ (_)          \)(')()()  //*/
/*\\\---------------------------------------------------------------------///*/
