#include	"mk.h"
#define	ARMAG	"!<arch>\n"
#define	SARMAG	8

#define	ARFMAG	"`\n"
#define SARNAME	16

struct	ar_hdr
{
	char	name[SARNAME];
	char	date[12];
	char	uid[6];
	char	gid[6];
	char	mode[8];
	char	size[10];
	char	fmag[2];
};
#define	SAR_HDR	(SARNAME+44)

static int dolong = 1;

static void atimes(char *);
static char *split(char*, char**);

long
readn(int f, void *av, long n)
{
	char *a;
	long m, t;

	a = av;
	t = 0;
	while(t < n){
		m = read(f, a+t, n-t);
		if(m <= 0){
			if(t == 0)
				return m;
			break;
		}
		t += m;
	}
	return t;
}
long
atimeof(int force, char *name)
{
	Symtab *sym;
	long t;
	char *archive, *member, buf[512];

	archive = split(name, &member);
	if(archive == 0)
		Exit();

	t = mtime(archive);
	sym = symlook(archive, S_AGG, 0);
	if(sym){
		if(force || (t > sym->u.value)){
			atimes(archive);
			sym->u.value = t;
		}
	}
	else{
		atimes(archive);
		/* mark the aggegate as having been done */
		symlook(strdup(archive), S_AGG, "")->u.value = t;
	}
		/* truncate long member name to sizeof of name field in archive header */
	if(dolong)
		snprint(buf, sizeof(buf), "%s(%s)", archive, member);
	else
		snprint(buf, sizeof(buf), "%s(%.*s)", archive, SARNAME, member);
	sym = symlook(buf, S_TIME, 0);
	if (sym)
		return sym->u.value;
	return 0;
}

void
atouch(char *name)
{
	char *archive, *member;
	int fd, i;
	struct ar_hdr h;
	long t;

	archive = split(name, &member);
	if(archive == 0)
		Exit();

	fd = open(archive, ORDWR);
	if(fd < 0){
		fd = create(archive, OWRITE, 0666);
		if(fd < 0){
			fprint(2, "create %s: %r\n", archive);
			Exit();
		}
		write(fd, ARMAG, SARMAG);
	}
	if(symlook(name, S_TIME, 0)){
		/* hoon off and change it in situ */
		LSEEK(fd, SARMAG, 0);
		while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
			for(i = SARNAME-1; i > 0 && h.name[i] == ' '; i--)
					;
			h.name[i+1]=0;
			if(strcmp(member, h.name) == 0){
				t = SARNAME-sizeof(h);	/* ughgghh */
				LSEEK(fd, t, 1);
				fprint(fd, "%-12ld", time(0));
				break;
			}
			t = atol(h.size);
			if(t&01) t++;
			LSEEK(fd, t, 1);
		}
	}
	close(fd);
}

static void
atimes(char *ar)
{
	struct ar_hdr h;
	long t;
	int fd, i, namelen;
	char buf[2048], *p, *strings;
	char name[1024];
	Symtab *sym;

	strings = nil;
	fd = open(ar, OREAD);
	if(fd < 0)
		return;

	if(read(fd, buf, SARMAG) != SARMAG){
		close(fd);
		return;
	}
	while(readn(fd, (char *)&h, sizeof(h)) == sizeof(h)){
		t = atol(h.date);
		if(t == 0)	/* as it sometimes happens; thanks ken */
			t = 1;
		namelen = 0;
		if(memcmp(h.name, "#1/", 3) == 0){	/* BSD */
			namelen = atoi(h.name+3);
			if(namelen >= sizeof name){
				namelen = 0;
				goto skip;
			}
			if(readn(fd, name, namelen) != namelen)
				break;
			name[namelen] = 0;
		}else if(memcmp(h.name, "// ", 2) == 0){ /* GNU */
			/* date, uid, gid, mode all ' ' */
			for(i=2; i<16+12+6+6+8; i++)
				if(h.name[i] != ' ')
					goto skip;
			t = atol(h.size);
			if(t&01)
				t++;
			free(strings);
			strings = malloc(t+1);
			if(strings){
				if(readn(fd, strings, t) != t){
					free(strings);
					strings = nil;
					break;
				}
				strings[t] = 0;
				continue;
			}
			goto skip;
		}else if(strings && h.name[0]=='/' && isdigit((uchar)h.name[1])){
			i = strtol(h.name+1, &p, 10);
			if(*p != ' ' || i >= strlen(strings))
				goto skip;
			p = strings+i;
			for(; *p && *p != '/'; p++)
				;
			namelen = p-(strings+i);
			if(namelen >= sizeof name){
				namelen = 0;
				goto skip;
			}
			memmove(name, strings+i, namelen);
			name[namelen] = 0;
			namelen = 0;
		}else{
			strncpy(name, h.name, sizeof(h.name));
			for(i = sizeof(h.name)-1; i > 0 && name[i] == ' '; i--)
					;
			if(name[i] == '/')		/* system V bug */
				i--;
			name[i+1]=0;
		}
		snprint(buf, sizeof buf, "%s(%s)", ar, name);
		sym = symlook(strdup(buf), S_TIME, (void *)t);
		sym->u.value = t;
	skip:
		t = atol(h.size);
		if(t&01) t++;
		t -= namelen;
		LSEEK(fd, t, 1);
	}
	close(fd);
	free(strings);
}

static int
type(char *file)
{
	int fd;
	char buf[SARMAG];

	fd = open(file, OREAD);
	if(fd < 0){
		if(symlook(file, S_BITCH, 0) == 0){
			if(strlen(file) < 2 || strcmp(file+strlen(file)-2, ".a") != 0)
				Bprint(&bout, "%s doesn't exist: assuming it will be an archive\n", file);
			symlook(file, S_BITCH, (void *)file);
		}
		return 1;
	}
	if(read(fd, buf, SARMAG) != SARMAG){
		close(fd);
		return 0;
	}
	close(fd);
	return !strncmp(ARMAG, buf, SARMAG);
}

static char*
split(char *name, char **member)
{
	char *p, *q;

	p = strdup(name);
	q = utfrune(p, '(');
	if(q){
		*q++ = 0;
		if(member)
			*member = q;
		q = utfrune(q, ')');
		if (q)
			*q = 0;
		if(type(p))
			return p;
		free(p);
		fprint(2, "mk: '%s' is not an archive\n", name);
	}
	return 0;
}