// ddiff/dpatch - manipulate differences between debian packages.
// Copyright 2000 Tom Rothamel <tom-ddiff@onegeek.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "genfile.h"

#include <unistd.h>

#define debug(x, y...) fprintf(stderr, "gfarchive %d: " x "\n", getpid() , ## y) /**/
/* #define debug(x, y...) /**/

struct ArGA : public GARCHIVE {
	GFILE *parent;
	FINFO *fis; // Still here for historical reasons.
	long start;
	long memstart;
	int valid;
	long left;
	int pad;
	int offset;
	
	void reset();
	int member();

	char *gets(char *, int);
	size_t read(void *, size_t, size_t);
	void seek(long);
	void skip(long);
	long tell();
	GFILE *dup();
	
	ArGA(GFILE *);
	~ArGA();
};

/* This structure courtesy of GNU Binutils. */
struct ar_hdr {
	char ar_name[16];             /* name of this member */
	char ar_date[12];             /* file mtime */
	char ar_uid[6];               /* owner uid; printed as decimal */
	char ar_gid[6];               /* owner gid; printed as decimal */
	char ar_mode[8];              /* file mode, printed as octal   */
	char ar_size[10];             /* file size, printed as decimal */
	char ar_fmag[2];              /* should contain ARFMAG */
	char nul[1];
};

void ArGA::reset() {
	parent->seek(start);
	left = 0;
	pad = 0;
	valid = 0;
}

int ArGA::member() {
	ar_hdr arh;
	char name[21];
	char *c;
	
	if (fis->name) free(fis->name);	
	fis->name = NULL;
	finfo = fis;

	parent->skip(left);
	left = 0;
	parent->skip(pad);
	pad = 0;
	
	if (!parent->gets((char *) &arh, 61)) return 0;
	if (strlen((char *) &arh) != 60) {
		valid = 0;
		return 0;
	}

	extrstr(name, arh.ar_name, 16);
	c = &name[15];
	while (*c == ' ') {
		if (c == name) return 0; // We'll call an empty name invalid.
		*c = 0;
		c--;
	}
	if (*c == '/') {
		*c = 0;
	}

	// If we made it here, there is another member in the archive. 
	// Extract its info and set everything up.
	
	fis->name = strdup(name);
	fis->size = extrlong(arh.ar_size, 10);
	fis->mtime = extrlong(arh.ar_date, 12);
	fis->uid = extrint(arh.ar_uid, 6);
	fis->gid = extrint(arh.ar_gid, 6);
	fis->mode = extroct(arh.ar_mode, 8);

	finfo = fis;
	memstart = parent->tell();
	left = fis->size;
	pad = left & 1;
	offset = 0;
	valid = 1;

	return 1;
}

char *ArGA::gets(char *buf, int len) {
	char *rv;
	int l;
	
	if (!valid) return NULL;
	if (!left) return NULL;

	if (len > left + 1) len = left + 1;
	rv = parent->gets(buf, len);
	if (!rv) return rv;

	l = strlen(buf);
	left -= l;
	offset += l;
	
	return rv;
}

size_t ArGA::read(void *buf, size_t size, size_t nmemb) {
	int l;

	if (!valid) return 0;
	if (!left) return 0;

	while (size * nmemb > left) nmemb--;
	l = parent->read(buf, size, nmemb);

	offset += l * size;
	left -= l * size;

	return l;
}

void ArGA::seek(long off) {
	if (!valid) return;

	if (off < offset) {
		parent->seek(memstart);
		offset = 0;
		left = finfo->size;
		skip(off);
		return;
	}

	skip(off - offset);
}

void ArGA::skip(long off) {
	if (!valid) return;

	off = (left < off) ? left : off;
	parent->skip(off);
	left -= off;
	offset += off;
}

long ArGA::tell() {
	return offset;
}

GFILE *ArGA::dup() {
	ArGA *ar;

	if (!valid) return NULL;
	
	ar = new ArGA(parent->dup());
	ar->parent->seek(memstart - 60); // evil... but it works.
	ar->member();
	ar->skip(offset);

	return ar;
}

ArGA::ArGA(GFILE *gf) {
	parent = gf;
	start = parent->tell();
	fis = new FINFO;
	finfo = fis;
	valid = 0;
	left = 0;
	pad = 0;
	offset = 0;
}

ArGA::~ArGA() {
}

GARCHIVE *open_gfarchive(GFILE *gf) {
	char buf[20];

	if (!gf->gets(buf, 20)) return NULL;
	if (strcmp(buf, "!<arch>\n")) return NULL;

	return new ArGA(gf);
}
