#include "stdio.h"
#include "stat.h"
#define EQ(x,y)	(strcmp(x,y)==0)
#define MAGIC	070707
#define IN	1
#define OUT	2
#define PASS	3
#define HDRSIZE	((sizeof Hdr)-256)
#define INPUT	0
#define OUTPUT	1

struct	statb	Statb, Xstatb;

struct header {
	int	h_magic,
		h_dev,
		h_ino,
		h_mode,
		h_uid,
		h_gid,
		h_nlink,
		h_majmin;
	long	h_mtime;
	int	h_namesize;
	long	h_filesize;
	char	h_name[256];
} Hdr;

int	Buf[256], Wbuf[256];
int	*Wp	Wbuf,
	Wct	256;

union { int	integ; };

int	Option,
	Mod_time,
	Dir,
	Uncond,
	Link,
	Rename,
	Toc,
	Verbose,
	Select;

int	Ifile,
	Ofile;
unsigned	Blocks;

char	Fullname[256],
	Name[128];

FILE	*Rtty,
	*Wtty;

char	*Pattern;
int	Dev;
int	Uid, Gid;
int	A_directory,
	A_special;

main(argc, argv)
char **argv;
{
	register ct;
	long	filesz;
	register char *fullp, *lastarg;
	long itol();

	if(argc < 2 || argc > 4) {
usage:
		fprintf(stderr,
		"Usage: cpio -o[v] <name-list >archive\n%s\n%s\n",
		"       cpio -i[dmrtuv] [pattern] <archive",
		"       cpio -p[dlmruv] [pattern] directory <name-list");
		exit(1);
	}
	lastarg = argv[argc-1];
	if(*argv[1] != '-')
		goto usage;
	Uid = getuid() & 0377;
	Gid = getgid() & 0377;

	while(*++argv[1]) {
		switch(*argv[1]) {
		case 'i':
			Option = IN;
			Pattern = argc == 2? "*": argv[2];
			break;
		case 'o':
			Option = OUT;
			break;
		case 'p':
			if(access(lastarg, 2) == -1) {
accerr:
				fprintf(stderr, "cannot write in <%s>\n",
					lastarg);
				exit(1);
			}
			strcpy(Fullname, lastarg);
			strcat(Fullname, "/");
			stat(Fullname, &Xstatb);
			if((Xstatb.i_mode&IFMT) != IFDIR)
				goto accerr;
			Option = PASS;
			Dev = Xstatb.i_dev;
			Pattern = argc == 3? "*": argv[2];
			break;
		case 'm':
			++Mod_time;
			break;
		case 'd':
			Dir++;
			break;
		case 'r':
			Rename++;
			Rtty = fopen("/dev/tty", "r");
			Wtty = fopen("/dev/tty", "w");
			break;
		case 't':
			Toc++;
			break;
		case 'u':
			Uncond++;
			break;
		case 'l':
			Link++;
			break;
		case 'v':
			Verbose++;
			break;
		default:
			goto usage;
		}
	}
	if(!Option) {
		fprintf(stderr, "Options must include o|i|p\n");
		exit(1);
	}

	switch(Option) {

	case OUT:
		while(getname()) {
			if(Hdr.h_filesize == 0L) {
				bwrite(OUTPUT, &Hdr, HDRSIZE+Hdr.h_namesize);
				continue;
			}
			if((Ifile = open(Hdr.h_name, 0)) < 0) {
				fprintf(stderr, "<%s> ?\n", Hdr.h_name);
				continue;
			}
			bwrite(OUTPUT, &Hdr, HDRSIZE+Hdr.h_namesize);
			for(filesz=Hdr.h_filesize; filesz>0; filesz-= 512){
				ct = filesz>512? 512: filesz;
				read(Ifile, Buf, ct);
				bwrite(OUTPUT, Buf, ct);
			}
			close(Ifile);
			if(Verbose)
				fprintf(stderr, "%s\n", Hdr.h_name);
		}
		strcpy(Hdr.h_name, "TRAILER!!!");
		Hdr.h_filesize = 0L;
		Hdr.h_namesize = strlen("TRAILER!!!") + 1;
		bwrite(OUTPUT, &Hdr, 600);
		break;

	case IN:
		while(gethdr()) {
			Ofile = ckname(Hdr.h_name)? openout(Hdr.h_name): 0;
			for(filesz=Hdr.h_filesize; filesz>0; filesz-= 512){
				ct = filesz>512? 512: filesz;
				bread(ct, Wbuf);
				if(Ofile)
					write(Ofile, &Wbuf, ct);
			}
			if(Ofile) {
				close(Ofile);
				set_time(Hdr.h_name, Hdr.h_mtime, Hdr.h_mtime);
			}
			if(!Select)
				continue;
			if(Verbose)
				if(Toc)
					pentry(Hdr.h_name);
				else
					puts(Hdr.h_name);
			else if(Toc)
				puts(Hdr.h_name);
		}
		break;

	case PASS:
		fullp = Fullname + strlen(Fullname);

		while(getname()) {
			if(!ckname(Hdr.h_name))
				continue;
			strcpy(fullp, Hdr.h_name);

			if(Link
			&& !A_directory
			&& Dev == Statb.i_dev
			&& (Uid == Statb.i_uid || !Uid)) {
				unlink(Fullname);
				if(link(Hdr.h_name, Fullname) < 0) {
					fprintf(stderr,
					 "Cannot link <%s> & <%s>\n",
					 Hdr.h_name, Fullname);
					continue;
				}
				set_time(Fullname, Hdr.h_mtime, Hdr.h_mtime);
				goto ckverbose;
			}
			if(!(Ofile = openout(Fullname)))
				continue;
			if((Ifile = open(Hdr.h_name, 0)) < 0) {
				fprintf(stderr, "<%s> ?\n", Hdr.h_name);
				close(Ofile);
				continue;
			}
			filesz = itol(Statb.i_size0 & 0377, Statb.i_size1);
			for(; filesz > 0; filesz -= 512) {
				ct = filesz>512? 512: filesz;
				read(Ifile, Buf, ct);
				if(Ofile)
					write(Ofile, Buf, ct);
				++Blocks;
			}
			close(Ifile);
			if(Ofile) {
				close(Ofile);
				set_time(Fullname, Statb.i_atime,
					Statb.i_mtime);
ckverbose:
				if(Verbose)
					puts(Fullname);
			}
		}
	}
	fprintf(stderr, "%l blocks\n", Blocks);
	exit(0);
}

getname()
{
	register char *namep  Name;

	for(;;) {
		if(gets(namep) == NULL)
			return 0;
		if(*namep == '.' && namep[1] == '/')
			strcpy(Hdr.h_name, namep+2);
		else
			strcpy(Hdr.h_name, namep);
		if(stat(Hdr.h_name, &Statb) < 0) {
			fprintf(stderr, "<%s> ?\n", Hdr.h_name);
			continue;
		}
		A_directory = (Statb.i_mode & IFMT) == IFDIR;
		A_special = Statb.i_mode & IFMT;
		Hdr.h_magic = MAGIC;
		Hdr.h_namesize = strlen(Hdr.h_name) + 1;
		Hdr.h_uid = Statb.i_uid;
		Hdr.h_gid = Statb.i_gid;
		Hdr.h_dev = Statb.i_dev;
		Hdr.h_ino = Statb.i_ino;
		Hdr.h_mode = Statb.i_mode;
		Hdr.h_mtime = Statb.i_mtime;
		Hdr.h_nlink = Statb.i_nlink;
		Hdr.h_filesize = Hdr.h_mode&IFMT? 0L:
			itol(Statb.i_size0 & 0377, Statb.i_size1);
		Hdr.h_majmin = Statb.i_addr[0];
		return 1;
	}
}

gethdr()
{

	bread(HDRSIZE, &Hdr);

	if(Hdr.h_magic != MAGIC) {
		fprintf(stderr,
		"Out of phase--get help (or try `_cpio' for old format)\n");
		exit(1);
	}
	bread(Hdr.h_namesize, &Hdr.h_name);
	if(EQ(Hdr.h_name, "TRAILER!!!"))
		return 0;
	A_directory = (Hdr.h_mode & IFMT) == IFDIR;
	A_special = Hdr.h_mode & IFMT;
	return 1;
}

ckname(namep)
register char *namep;
{
	++Select;
	if(!gmatch(namep, Pattern)) {
		Select = 0;
		return 0;
	}
	if(Rename) {
		fprintf(Wtty, "Rename <%s>\n", namep);
		fflush(Wtty);
		fgets(namep, 128, Rtty);
		if(feof(Rtty))
			exit(1);
		namep[strlen(namep) - 1] = '\0';
		if(EQ(namep, "")) {
			printf("Skipped\n");
			return 0;
		}
	}
	return !Toc;
}

openout(namep)
register char *namep;
{
	register f;

	if(A_directory) {
		if(!Dir
		|| Rename
		|| EQ(namep, ".")
		|| EQ(namep, "..")
		|| stat(namep, &Xstatb) == 0)
			return 0;

		makdir(namep);
ret:
		chmod(namep, Hdr.h_mode);
		if(Uid == 0)
			chown(namep, Hdr.h_uid.integ);
		return 0;
	}
	if(Hdr.h_nlink > 1)
		if(!postml(namep))
			return 0;
	if(A_special) {
		unlink(namep);
		if(mknod(namep, Hdr.h_mode, Hdr.h_majmin) < 0) {
			fprintf(stderr, "Cannot mknod <%s>\n", namep);
			return 0;
		}
		goto ret;
	}
	if(stat(namep, &Xstatb) == 0)
		if(!Uncond && (Hdr.h_mtime < Xstatb.i_mtime)) {
			fprintf(stderr, "current <%s> newer\n",
				namep);
			return 0;
		}
	if(Option == PASS
	&& Hdr.h_ino == Xstatb.i_ino
	&& Hdr.h_dev == Xstatb.i_dev) {
		fprintf(stderr, "Attempt to pass file to self!\n");
		exit(1);
	}
	if((f = creat(namep, 0600)) < 0) {
		if(Dir) {
			register char *np;
			register v;

			for(np = namep; *np; ++np) {
				if(*np == '/') {
					*np = '\0';
					if((v=stat(namep, &Xstatb))== -1) {
						makdir(namep);
						*np = '/';
					} else if((Xstatb.i_mode&IFMT)
						!= IFDIR) {
						*np = '/';
						break;
					}
					*np = '/';
				}
			}
		}
		if((f = creat(namep, 0600)) < 0) {
			fprintf(stderr, "Cannot create <%s>\n", namep);
			return 0;
		}
	}
	if(Uid == 0)
		chown(namep, Hdr.h_uid.integ);
	chmod(namep, Hdr.h_mode); /* move til after copy */
	return f;
}

bread(fct, b)
register fct, *b;
{
	static nleft;
	static *ip;

	fct = (fct+1)>>1;
	while(fct--) {
		if(!nleft) {
			if(read(INPUT, Buf, 512)!=512) {
				fprintf(stderr, "Read error\n");
				exit(1);
			}
			nleft = 256;
			ip = Buf;
			++Blocks;
		}
		*b++ = *ip++;
		--nleft;
	}
}

bwrite(f, rp, c)
register *rp;
register c;
{
	c = (c+1) >> 1;
	while(c--) {
		if(!Wct) {
			if(write(f, Wbuf, 512)<0) {
				fprintf(stderr, "Cannot write.\n");
				exit(1);
			}
			Wct = 256;
			Wp = Wbuf;
			++Blocks;
		}
		*Wp++ = *rp++;
		--Wct;
	}
}

postml(namep)
register char *namep;
{
	register i;

	static struct ml {
		int	m_dev,
			m_ino;
		char	m_name[2];
	} *ml[250];
	
	static	mlinks 0;

	for(i = 0; i < mlinks; ++i) {
		if(mlinks == 250) break;
		if(ml[i]->m_ino==Hdr.h_ino &&
			ml[i]->m_dev==Hdr.h_dev) {
			if(Verbose)
			  printf("%s linked to %s\n", ml[i]->m_name,
				Hdr.h_name);
			unlink(namep);
			if(link(ml[i]->m_name, namep) < 0) {
				fprintf(stderr, "Cannot link <%s>&<%s>.\n",
					ml[i]->m_name, namep);
			}
			set_time(namep, Hdr.h_mtime, Hdr.h_mtime);
			return 0;
		}
	}
	if(mlinks == 250
	|| (ml[mlinks] = alloc(strlen(namep) + sizeof(struct ml))) < 0) {
		fprintf(stderr, "Out of core for links\n");
		mlinks = 250;
		return 1;
	}
	ml[mlinks]->m_dev = Hdr.h_dev;
	ml[mlinks]->m_ino = Hdr.h_ino;
	strcpy(ml[mlinks]->m_name, namep);
	++mlinks;
	return 1;
}

pentry(namep)
register char *namep;
{

	register i;
	static lastid -1;
	static char nbuf[100], tbuf[32];

	printf("%-7o", Hdr.h_mode);
	if(lastid == Hdr.h_uid)
		printf("%-6s", nbuf);
	else {
		if(getpw(Hdr.h_uid&0377, nbuf)==0) {
			for(i=0; nbuf[i] != ':'; ++i);
			nbuf[i] = '\0';
			printf("%-6s", nbuf);
			lastid = Hdr.h_uid;
		} else
			printf("%-6d", Hdr.h_uid);
	}
	printf("%7D ", Hdr.h_filesize);
	strcpy(tbuf, ctime(&Hdr.h_mtime));
	tbuf[24] = '\0';
	printf(" %s  %s\n", &tbuf[4], namep);
}

gmatch(s, p)
register char *s, *p;
{
	register int c;
	int cc, ok, lc, scc;

	if(EQ(p, "*"))
		return 1;
	scc = *s;
	lc = 077777;
	switch (c = *p) {

	case '[':
		ok = 0;
		while (cc = *++p) {
			switch (cc) {

			case ']':
				if (ok)
					return(gmatch(++s, ++p));
				else
					return(0);

			case '-':
				ok =| lc <= scc & scc <= (cc=p[1]);
			}
			if (scc==(lc=cc)) ok++;
		}
		return(0);

	case '?':
	caseq:
		if(scc) return(gmatch(++s, ++p));
		return(0);
	case '*':
		return(umatch(s, ++p));
	case 0:
		return(!scc);
	}
	if (c==scc) goto caseq;
	return(0);
}

umatch(s, p)
register char *s, *p;
{
	if(*p==0) return(1);
	while(*s)
		if (gmatch(s++,p)) return(1);
	return(0);
}

makdir(namep)
register char *namep;
{
	if(fork())
		wait(0);
	else {
		execl("/bin/mkdir", "mkdir", namep, 0);
		fprintf(stderr, "mkdir <%s> failed\n", namep);
		exit(1);
	}
}

set_time(namep, atime, mtime)
register *namep;
long atime, mtime;
{
	static long timevec[2];

	if(!Mod_time)
		return;
	timevec[0] = atime;
	timevec[1] = mtime;
	utime(namep, timevec);
}
