#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libv.h>
#include "ftwlk.h"
#include "tar.h"
#include "misc.h"

#define MIN(A, B) ((A) < (B) ? (A) : (B))

int vflag;

int nerr;

struct hardlink {
	char *name;
	int ino;
	int dev;
	struct hardlink *next;
};

struct hardlink *links;

struct hardlink *
findlink(char *name, int ino, int dev)
{
	struct hardlink *h;

	for (h = links; h; h = h->next)
		if (ino == h->ino && dev == h->dev)
			return h;
	h = xmalloc(sizeof (struct hardlink));
	h->name = xstrdup(name);
	h->ino = ino;
	h->dev = dev;
	h->next = links;
	links = h;
	return 0;	/* make a new entry, but say we didn't find it */
}

#ifndef S_ISREG
#define S_ISREG(M) (((M) & S_IFMT) == S_IFREG)
#endif

void
zeropad(long from, long to)
{
	static char zero[512];
	static int n;

	while (from < to) {
		n = MIN(to - from, sizeof zero);
		if (fwrite(zero, 1, n, stdout) != n) {
			fprintf(stderr, "tarc: zero padding failed\n");
			exit(1);
		}
		from += n;
	}
}

int
dofile(char *name, struct stat *st, int code, struct FTWLK *f)
{
	struct tarhdr hdr;
	struct tarbuf buf;
	struct hardlink *hl;
	FILE *fp;
	long cc;

	if (code == FTWLK_NSL || code == FTWLK_SL)
		lstat(name, st);	/* fix ftwlk braindamage */

	memset(&hdr, 0, sizeof hdr);
	if (strlen(name) > TNAMEMAX) {
		fprintf(stderr, "tarc: file name too long '%s'\n", name);
		exit(1);
	}

	strcpy(hdr.name, name);
	hdr.mode = st->st_mode & TMASK;
	hdr.uid = st->st_uid;
	hdr.gid = st->st_gid;
	hdr.size = st->st_size;
	hdr.mtime = st->st_mtime;

	switch (code) {
	case FTWLK_D:
		if (strlen(name) <= TNAMEMAX - 1)
			strcat(hdr.name, "/");
		hdr.typeflag = DIRTYPE;
		break;

	case FTWLK_F:
		if (st->st_nlink > 1
		&& (hl = findlink(name, st->st_ino, st->st_dev))
		&& strlen(hl->name) <= TLINKMAX) {
			hdr.typeflag = LNKTYPE;
			strcpy(hdr.linkname, hl->name);
		} else if (S_ISREG(st->st_mode))
			hdr.typeflag = REGTYPE;
		else {
			fprintf(stderr, "tarc: not a regular file '%s'\n",
				name);
			++nerr;
			return 0;
		}
		break;

	case FTWLK_SL:
		if (f->level == 0) {
			f->quit = FTWLK_FOLLOW;
			return 0;
		}
		hdr.typeflag = SYMTYPE;
		cc = readlink(name, hdr.linkname, sizeof hdr.linkname);
		if (cc < 0) {
			fprintf(stderr, "tarc: can't read symlink '%s'\n",
				name);
			++nerr;
			return 0;
		}
		break;

	default:
		return 0;
	}

	if (hdr.typeflag != REGTYPE)
		hdr.size = 0;
	if (vflag)
		fprintf(stderr, "%s\n", hdr.name);
	if (thdrput(&buf, &hdr) != 0) {
		fprintf(stderr, "tarc: failed making header for '%s'\n", name);
		++nerr;
		return 0;
	}
	if (hdr.typeflag == REGTYPE && !(fp = fopen(name, "r")))
		return 0;
	if (fwrite(&buf, sizeof buf, 1, stdout) != 1) {
		fprintf(stderr, "tarc: write failed\n");
		exit(1);
	}
	if (hdr.typeflag == REGTYPE) {
		cc = fpcopy(stdout, fp, st->st_size);
		if (cc < 0) {
			fprintf(stderr, "tarc: file copy failed '%s'\n", name);
			exit(1);
		}
		if (cc != st->st_size)
			fprintf(stderr, "tarc: warning: size change '%s'\n",
				name);
		zeropad(cc, (st->st_size + TSIZE - 1) / TSIZE * TSIZE);
		fclose(fp);
	}
	return 0;
}

void
usage(void)
{
	fprintf(stderr, "usage: tarc [-v] files...\n");
	exit(1);
}

int
main(int argc, char *argv[])
{
	int i, c;

	prog = argv[0];
	while ((c = getopt(argc, argv, "v")) != EOF)
		switch (c) {
		case 'v':
			++vflag;
			break;
		default:
			usage();
			break;
		}
	for (i = optind; i < argc; ++i)
		ftwlk(argv[i], dofile, 10);
	zeropad(0, 2 * TSIZE);
	return nerr ? 1 : 0;
}
