#include "embfeatures.h"
#ifdef SUPPORT_64BIT_FILESIZE
#define _FILE_OFFSET_BITS 64
#endif
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <time.h>
#include <utime.h>
#include <errno.h>
#include <limits.h>
#include <sys/mman.h>
#include <dirent.h>
#include <ftw.h>
#include "write12.h"

#define UIDCACHE

#ifdef TAR_SUPPORT_SYMBOLIC_UID
#include <pwd.h>
#include <grp.h>

#include "openreadclose.c"
#include "split.c"

#ifndef UIDCACHE
#define DONT_NEED_PASSWD
#define DONT_NEED_GECOS
#define DONT_NEED_DIR
#define DONT_NEED_SHELL
#include "mygetpwnam.c"
#define DONT_NEED_MEMBERS
#include "mygetgrnam.c"
#endif

#endif

#ifdef UIDCACHE
struct uid {
  char* name;
  id_t id;
  struct uid* next;
}* uids,* gids;

static char* mmap_read(const char* filename,unsigned long* filesize) {
  int fd=open(filename,O_RDONLY);
  char *map;
  if (fd>=0) {
    *filesize=lseek(fd,0,SEEK_END);
    if ((map=mmap(0,*filesize,PROT_READ,MAP_SHARED,fd,0))==(char*)-1) map=0;
    close(fd);
    return map;
  }
  return 0;
}

static void populate(const char* filename,struct uid** root,unsigned int fields) {
  unsigned long len;
  char* map;
  int count;
  if (map=mmap_read(filename,&len)) {
    size_t tmp=0;
    while (tmp<len) {
      size_t nl,colon;
      struct uid u;
      size_t o[8],on; /* offsets and offset number */
      for (nl=tmp; nl<len && map[nl]!='\n'; ++nl) ; /* find end of line */
      on=0;
      for (colon=tmp; colon<nl; ++colon) /* split line by colons */
	if (map[colon]==':' && on<8) {
	  o[on]=colon;
	  ++on;
	}
      if (on==fields) { /* good number of fields; syntax looks good */
	if (u.name=malloc(o[0]-tmp+1)) {
	  memcpy(u.name,map+tmp,o[0]-tmp);
	  u.name[o[0]-tmp]=0;

	  u.id=0;
	  for (colon=o[1]+1; colon<o[2]; ++colon) {
	    unsigned char digit=map[colon]-'0';
	    if (digit>'9') { free(u.name); u.name=0; break; }
	    u.id=u.id*10+digit;
	  }
	}
	if (u.name) {
	  struct uid* x;
	  if (x=malloc(sizeof(struct uid))) {
	    memcpy(x,&u,sizeof(struct uid));
	    x->next=*root;
	    *root=x;
	  }
	}
      }
      tmp=nl+1;
    }
    munmap(map,len);
  }
}

static void initcache(void) {
  populate("/etc/passwd",&uids,6);
  populate("/etc/group",&gids,3);
}

static struct uid* name2uid(struct uid* list,const char* name) {
  while (list) {
    if (!strcmp(list->name,name))
      return list;
    list=list->next;
  }
  return 0;
}

static struct uid* uid2name(struct uid* list,id_t uid) {
  while (list) {
    if (list->id==uid)
      return list;
    list=list->next;
  }
  return 0;
}
#endif

#include "fmt_str.c"
#include "fmt_ulong.c"
#include "fmt_8long.c"
// #define DONT_NEED_BUFFER_1
#include "buffer.c"

int tarfd;
unsigned long long written;

char buffer_2_space[BUFFER_INSIZE];
static buffer it2 = BUFFER_INIT(write,2,buffer_2_space,sizeof buffer_2_space);
buffer *buffer_2 = &it2;

int fmt_pad(char *dest,char *src,char pad,int padto,int len) {
  int i;
  register char *d;
  if (!(d=dest))
    return len<padto?padto:len;
  while (len<padto) { *d=pad; ++d; --padto; }
  for (i=0; i<len; ++i) { *d=src[i]; ++d; }
  return d-dest;
}

void die(const char *message) {
  __write2(message);
  close(0);
  exit(1);
}

void couldnot(const char *what,const char *name) {
  __write2("Could not ");
  __write2(what);
  __write2(" \"");
  __write2(name);
  __write2("\": ");
  __write2(strerror(errno));
  __write2("\n");
}

struct posix_header {		/* byte offset */
  char name[100];		/*   0 */
  char mode[8];			/* 100 */
  char uid[8];			/* 108 */
  char gid[8];			/* 116 */
  char size[12];		/* 124 */
  char mtime[12];		/* 136 */
  char chksum[8];		/* 148 */
  char typeflag;		/* 156 */
  char linkname[100];		/* 157 */
  char magic[6];		/* 257 */
  char version[2];		/* 263 */
  char uname[32];		/* 265 */
  char gname[32];		/* 297 */
  char devmajor[8];		/* 329 */
  char devminor[8];		/* 337 */
  char prefix[155];		/* 345 */
				/* 500 */
  char filler[12];
};

void __m2s(int i, char *s) {
  if (i&4) s[0]='r'; else s[0]='-';
  if (i&2) s[1]='w'; else s[1]='-';
  if (i&1) s[2]='x'; else s[2]='-';
}

void mode2str(mode_t m,char *s) {
  if (S_ISREG(m)) s[0]='-';
  else if (S_ISCHR(m)) s[0]='c';
  else if (S_ISDIR(m)) s[0]='d';
  else if (S_ISBLK(m)) s[0]='b';
  else if (S_ISFIFO(m)) s[0]='p';
  else if (S_ISSOCK(m)) s[0]='s';
  else if (S_ISLNK(m)) s[0]='l';
  else s[0]='?';
  __m2s(m>>6,s+1);
  __m2s(m>>3,s+4);
  __m2s(m,s+7);
  if (m&S_ISUID) s[1]='s';
  if (m&S_ISGID) s[4]='s';
}

void time2str(time_t t,char *s) {
  struct tm *T=localtime(&t);
  char buf[50];
  /* 1999-09-11 01:10:32 */
  s+=fmt_pad(s,buf,'0',4,fmt_ulong(buf,T->tm_year+1900));
  *s='-'; ++s;
  s+=fmt_pad(s,buf,'0',2,fmt_ulong(buf,T->tm_mon+1));
  *s='-'; ++s;
  s+=fmt_pad(s,buf,'0',2,fmt_ulong(buf,T->tm_mday));
  *s=' '; ++s;
  s+=fmt_pad(s,buf,'0',2,fmt_ulong(buf,T->tm_hour));
  *s=':'; ++s;
  s+=fmt_pad(s,buf,'0',2,fmt_ulong(buf,T->tm_min));
  *s=':'; ++s;
  s+=fmt_pad(s,buf,'0',2,fmt_ulong(buf,T->tm_sec));
  *s=' '; ++s;
  *s=0;
/* int fmt_pad(char *dest,char *src,char pad,int padto,int len) */
/*  snprintf(s,22,"%04d-%02d-%02d %02d-%02d-%02d ",
	   T->tm_year+1900,T->tm_mon+1,T->tm_mday,
	   T->tm_hour,T->tm_min,T->tm_sec); */
}

int piperead(int fd,void *buf,int len) {
  int x=0;
  do {
    int tmp=read(fd,buf,len);
    if (tmp<0) return -1;
    if (tmp==0) return x;
    x+=tmp;
    len-=tmp;
    buf+=tmp;
  } while (len);
  return x;
}

void domkdir(char *path) {
  char *tmp=path;
  while (*tmp) {
    char old;
    for (; *tmp && *tmp!='/'; ++tmp) ;
    if ((old=*tmp)) {
      if (tmp[1]==0) break;
      *tmp=0;
/*      printf("mkdir %s\n",path); */
      mkdir(path,0755);
      *tmp=old;
      ++tmp;
    } else
      break;
  }
}

buffer* db=0; /* display buffer, either buffer_1 or buffer_2 */

static void say(const char* c) { buffer_puts(db,c); }
static void blank() { buffer_putc(db,' '); }

int verbose;

static void display(const struct stat* ss,const struct posix_header* ph) {
  if (!db) db=tarfd==1?buffer_2:buffer_1;
  if (verbose>=1) {
    if (verbose>=2) {
      char tmp[30];
      static int muglen=6;
      static int mslen=5;
      int uglen,slen;
      mode2str(ss->st_mode,tmp);
      buffer_put(db,tmp,10);
      blank();

      {
	char tmp[200];
	if (*ph->uname)
	  uglen=fmt_str(tmp,ph->uname);
	else
	  uglen=fmt_ulong(tmp,ss->st_uid);
	tmp[uglen]='/'; ++uglen;
	if (*ph->uname)
	  uglen+=fmt_str(tmp+uglen,ph->gname);
	else
	  uglen+=fmt_ulong(tmp+uglen,ss->st_gid);
	if (uglen>muglen) muglen=uglen;
	buffer_put(db,tmp,uglen);
      }
/*	    tmp+=snprintf(tmp,BUFSIZE-(tmp-Buf),"%s/%s ",ph->uname,ph->gname); */
      while (uglen<muglen+1) {
	++uglen;
	blank();
      }

      {
	char numbuf[100];
	if (S_ISCHR(ss->st_mode) || S_ISBLK(ss->st_mode)) {
	  slen=fmt_ulong(numbuf,major(ss->st_dev));
	  numbuf[slen]=','; ++slen;
	  slen+=fmt_ulong(numbuf,minor(ss->st_dev));
	  /*slen=snprintf(numbuf,100,"%u,%u",major(ss->st_dev),minor(ss->st_dev)); */
	} else
	  slen=fmt_ulong(numbuf,ss->st_size);
	  /*slen=snprintf(numbuf,100,"%lu",ss->st_size); */
	if (slen>mslen) mslen=slen;
	{
	  char numbuf2[100];
	  buffer_put(db,numbuf2,fmt_pad(numbuf2,numbuf,' ',mslen,slen));
	}
      }
      blank();

      time2str(ss->st_mtime,tmp);
      say(tmp);
      /*tmp+=strlen(tmp); */
      say(ph->name);
      /*strncat(tmp,ph->name,BUFSIZE-(tmp-Buf)-2); */
      if (S_ISLNK(ss->st_mode)) {
	say(" -> ");
	say(ph->linkname);
	/*strlcat(tmp," -> ",BUFSIZE);
	strlcat(tmp,ph->linkname,BUFSIZE); */
      }
    } else
      say(ph->name);
    buffer_putnlflush(db);
  }
}


char* prefix;
int prefixlen;

int tarcreate(const char *file, const struct stat *sb, int flag) {
  char buf[100];
  struct posix_header ph;
  const char* tarfilename;
  memset(&ph,0,sizeof(ph));
  tarfilename=file;
  if (prefixlen)
    if (!memcmp(prefix,file,prefixlen)) {
      tarfilename+=prefixlen;
      if (*tarfilename=='/') ++tarfilename;
    }
  if (strlen(tarfilename)>100) {
    couldnot("write tape archive (name >100 chars) for ",file);
    return 1;
  }
  strcpy(ph.name,tarfilename);
  fmt_pad(ph.mode,buf,'0',7,fmt_8long(buf,sb->st_mode));
  fmt_pad(ph.uid,buf,'0',7,fmt_8long(buf,sb->st_uid));
  fmt_pad(ph.gid,buf,'0',7,fmt_8long(buf,sb->st_gid));
  fmt_pad(ph.mtime,buf,'0',11,fmt_8long(buf,sb->st_mtime));
  /* ph.chksum? */
  if (S_ISREG(sb->st_mode)) ph.typeflag='0'; else
  if (S_ISCHR(sb->st_mode)) ph.typeflag='3'; else
  if (S_ISBLK(sb->st_mode)) ph.typeflag='4'; else
  if (S_ISDIR(sb->st_mode)) ph.typeflag='5'; else
  if (S_ISFIFO(sb->st_mode)) ph.typeflag='6'; else
  if (S_ISSOCK(sb->st_mode)) {
    couldnot("write socket to tape archive: ",file);
    return 1;
  } else if (S_ISLNK(sb->st_mode)) {
    int temp;
    ph.typeflag='2';
    if ((temp=readlink(file,ph.linkname,sizeof(ph.linkname)))<0)
      couldnot("readlink ",file);
  }
  fmt_pad(ph.size,buf,'0',11,fmt_8long(buf,ph.typeflag=='0'?sb->st_size:0));
  if (sb->st_nlink>1) {
    /* TODO: keep data structure of hard links, check if we already had
     * this hard link, then set ph.typeflag to '1' and write file name
     * to ph.linkname. */
  }
  strcpy(ph.magic,"ustar  ");
#ifdef TAR_SUPPORT_SYMBOLIC_UID
#ifdef UIDCACHE
  {
    struct uid* t;
    if (t=uid2name(uids,sb->st_uid))
      strncpy(ph.uname,t->name,sizeof(ph.uname));
    else
      ph.uname[fmt_ulong(ph.uname,sb->st_uid)]=0;
    if (t=uid2name(gids,sb->st_gid))
      strncpy(ph.gname,t->name,sizeof(ph.gname));
    else
      ph.gname[fmt_ulong(ph.gname,sb->st_gid)]=0;
  }
#else
  {
    struct passwd *pwd;
    struct group *grp;
    if ((pwd=getpwuid(sb->st_uid)))
      strcpy(ph.uname,pwd->pw_name);
    else
      ph.uname[fmt_ulong(ph.uname,sb->st_uid)]=0;
    if ((grp=getgrgid(sb->st_gid)))
      strcpy(ph.gname,grp->gr_name);
    else
      ph.gname[fmt_ulong(ph.gname,sb->st_gid)]=0;
  }
#endif
#else
  ph.uname[fmt_ulong(ph.uname,sb->st_uid)]=0;
  ph.gname[fmt_ulong(ph.gname,sb->st_gid)]=0;
#endif
  if (ph.typeflag=='3' || ph.typeflag=='4') {
    ph.devmajor[fmt_8long(ph.devmajor,major(sb->st_dev))]=0;
    ph.devminor[fmt_8long(ph.devminor,minor(sb->st_dev))]=0;
  }

  {
    unsigned int sum=0;
    unsigned int i;
    memset(&ph.chksum,' ',sizeof(ph.chksum));
    for (i=0; i<sizeof(ph); ++i)
      sum+=((const char*)&ph)[i];
    ph.chksum[fmt_pad(ph.chksum,buf,'0',6,fmt_8long(buf,sum))]=0;
  }

  display(sb,&ph);

  if (ph.typeflag=='0') {
    int fd=open(file,O_RDONLY);
    if (fd!=-1) {
      off_t off=0;
      if (write(tarfd,&ph,sizeof(ph))<sizeof(ph))
writeerror:
	die("write error!\n");
      if (sendfile(tarfd,fd,&off,sb->st_size)!=sb->st_size) goto writeerror;
      written+=sb->st_size+sizeof(ph);
      close(fd);
    }
  } else
    if (write(tarfd,&ph,sizeof(ph))<sizeof(ph))
      goto writeerror;
  memset(&ph,0,sizeof(ph));
  {
    int todo=512-(written&511);
    if (todo!=512) {
      write(tarfd,&ph,todo);
      written+=todo;
    }
  }

  return 0;
}

/* el-cheapo tar that can only create and extract archives */
int main(int argc,char *argv[]) {
  int myuid=geteuid();
  int compress=0;
  int unlink_first=0;
  const char *file=0;
  char **filez;
  int filefd;
  char buf[10240];
  int len;
  enum {
    NOOP,
    CREATE,
    EXTRACT,
    TEST
  } todo=NOOP;
  if (argc<2)
    die("usage: tar czvf file.tar.gz filename...\n"
	"       tar xzvUf file.tar.gz\n");
  switch (*argv[1]) {
  case 'c': todo=CREATE; break;
  case 't': todo=TEST; break;
  case 'x': todo=EXTRACT; break;
  default:
	    die("unknown command, expected [ctx]\n");
  }
  {
    char *tmp=argv[1];
    while ((tmp=strchr(tmp,'v'))) {
      ++verbose;
      ++tmp;
    }
  }
  if (strchr(argv[1],'z')) compress=1;
  if (strchr(argv[1],'I')) compress=2;
  if (strchr(argv[1],'j')) compress=2;
  if (strchr(argv[1],'U')) unlink_first=1;
  filez=argv+2;
  if (strchr(argv[1],'f')) file=*filez++;

#ifdef UIDCACHE
  initcache();
#endif

  switch (todo) {
  case CREATE:
    if (strcmp(file,"-")) {
      tarfd=open(file,O_WRONLY|O_CREAT,0600);
    } else tarfd=1;

    if (compress) {
      char *argv[]={"gzip","-c",0};
      int fds[2];
      if (compress==2) argv[0]="bzip2";
      if (pipe(fds)) die("pipe failed\n");
      switch (fork()) {
      case -1:
	die("could not fork to exec gzip/bzip2");
      case 0:	/* child */
	if (dup2(fds[0],0)<0 || dup2(tarfd,1)<0) die("dup2 failed\n");
	close(fds[0]); close(tarfd); close(fds[1]);
	execvp(argv[0],argv);
	die("could not execvp gzip/bzip2");
      default:
	if (dup2(fds[1],tarfd)<0) die("dup2 failed\n");
	close(fds[1]);
	close(fds[0]);
      }
    }

    while (*filez) {
      struct stat ss;
      if (!lstat(*filez,&ss)) {
	prefixlen=0;
	if (S_ISDIR(ss.st_mode)) {
	  if (*filez[0]!='/') {
	    prefix=(char*)alloca(PATH_MAX);
	    if (getcwd(prefix,PATH_MAX-1))
	      prefixlen=strlen(prefix);
	    else
	      prefixlen=0;
	  }
	  ftw(*filez,tarcreate,100);
	} else
	  tarcreate(*filez,&ss,0);
      }
      ++filez;
    }
    {
      int todo=10*1024-(written%(10*1024));
      char buf[10*1024];
      memset(buf,0,todo);
      write(tarfd,buf,todo);
    }
    close(tarfd);
    buffer_flush(buffer_1);
    break;
  case TEST:
    ++verbose;
  case EXTRACT:
    if (file && file[0]=='-' && !file[1]) file=0;
    if (!file) tarfd=0; else { close(0); tarfd=open(file,O_RDONLY); }
    if (tarfd<0) die("could not open tar file\n");
    if (tarfd!=0)
      if (dup2(tarfd,0)<0) die("dup2 failed\n");
    tarfd=0;

    if (compress) {
      char *argv[]={"gzip","-dc",0};
      int fds[2];
      if (compress==2) argv[0]="bzip2";
      if (pipe(fds)) die("pipe failed\n");
      switch (fork()) {
      case -1:
	die("could not fork to exec gzip/bzip2");
      case 0:	/* child */
	if (dup2(fds[1],1)<0) die("dup2 failed\n");
	close(fds[1]); close(fds[0]);
	execvp(argv[0],argv);
	die("could not execvp gzip/bzip2");
      default:
	if (dup2(fds[0],0)<0) die("dup2 failed\n");
	close(fds[0]); close(fds[1]);
      }
    }

    umask(0);
    do {
#if 0
      {
	char tmp[100];
	strcpy(tmp,"ofs ");
	tmp[4+__ltostr(tmp+4,90,lseek(0,0,SEEK_CUR),10,0)]=0;
	write(2,tmp,strlen(tmp)); write(2,"\n",1);
      }
#endif
      len=piperead(0,buf,512);
      if (len<512) {
	if (len==0) break;
	die("unexpected end of tar file\n");
      }
      if (!buf[0]) break;
      {
	struct posix_header *ph=(struct posix_header*)buf;
	struct stat ss;
	if (memcmp(ph->magic,"ustar",5)) {
	  static int warned=0;
	  if (!warned) {
	    __write2("tar: warning: pre-POSIX tar file!\n");
	    warned=1;
	  }
	}
	if (ph->name[99])
	  die("tar: this does not look like a tar file to me\n");
	ph->uname[31]=0; ph->gname[31]=0;
	memset(&ss,0,sizeof(ss));
	ss.st_size=strtoull(ph->size,0,8);
	ss.st_mode=strtoul(ph->mode,0,8);
	ss.st_dev=0;
	ss.st_uid=strtoul(ph->uid,0,8);
	ss.st_gid=strtoul(ph->gid,0,8);
#ifdef TAR_SUPPORT_SYMBOLIC_UID
#ifdef UIDCACHE
	{
	  struct uid* t;
	  if ((t=name2uid(uids,ph->uname)))
	    ss.st_uid=t->id;
	  if ((t=name2uid(gids,ph->gname)))
	    ss.st_gid=t->id;
	}
#else
	{
	  struct passwd *pwd;
	  struct group *grp;
	  if ((pwd=getpwnam(ph->uname)))
	    ss.st_uid=pwd->pw_uid;
	  if ((grp=getgrnam(ph->gname)))
	    ss.st_gid=grp->gr_gid;
	}
#endif
#endif
	{
	  int maj,min;
	  maj=strtoul(ph->devmajor,0,8);
	  min=strtoul(ph->devminor,0,8);
	  ss.st_dev=makedev(maj,min);
	}
	ss.st_mtime=strtoul(ph->mtime,0,8);

	display(&ss,ph);

	if (todo==EXTRACT) {
	  filefd=-1;
	  if (unlink_first)
	    if (unlink(ph->name))
	      couldnot("delete file",ph->name);
	  domkdir(ph->name);
	  switch (ph->typeflag) {
	  case '5': ss.st_mode|=S_IFDIR; break;
	  case '2': ss.st_mode|=S_IFLNK; break;
	  case '6': ss.st_mode|=S_IFSOCK; break;
	  }
	  if (S_ISLNK(ss.st_mode)) {
	    if (symlink(ph->linkname,ph->name))
	      couldnot("create symlink",ph->name);
	  } else if (S_ISCHR(ss.st_mode) || S_ISBLK(ss.st_mode) || S_ISFIFO(ss.st_mode)) {
	    if (mknod(ph->name,ss.st_mode,ss.st_dev))
	      couldnot("mknod",ph->name);
	    if (myuid==0)
	      chown(ph->name,ss.st_uid,ss.st_gid);
	  } else if (S_ISDIR(ss.st_mode)) {
	    if (mkdir(ph->name,ss.st_mode))
	      couldnot("create directory",ph->name);
	  } else {
	    filefd=open(ph->name,O_WRONLY|O_CREAT|O_TRUNC,ss.st_mode);
	    if (filefd<0)
	      couldnot("create file",ph->name);
	  }
	} else filefd=-1;
	if (ss.st_size>0) {
	  long long realsize=(ss.st_size+511)&(~511);
	  long long towrite=ss.st_size;
	  while (realsize>0) {
	    long long foo=(realsize>1024?1024:realsize);
	    char tmp[1024];
	    int len=piperead(0,tmp,foo);
	    int need=len>towrite?towrite:len;
	    if (len<foo) die("short read\n");
	    if (filefd>=0) {
	      if (write(filefd,tmp,need)<need)
		die("short write\n");
	      else
		towrite-=need;
	    }
	    realsize-=foo;
	  }
	  if (filefd>=0)
	    close(filefd);
	}
	if (filefd>=0 && !S_ISLNK(ss.st_mode)) {
	  struct utimbuf ut;
	  ut.actime=ut.modtime=ss.st_mtime;
	  if (utime(ph->name,&ut))
	    couldnot("set utime of ",ph->name);
	}
      }
    } while (1);
  }
  return 0;
}
