/*
 * afid -- like the Apple ][ program called FID, a file handling utility
 *
 * We operate on files which contain entire Apple ][ diskettes from
 * track 0, sector 0 to track 34, sector 15.  To keep things simple,
 * the entire disk image is slurped into memory.  I assume the standard
 * disk geometry of 35 tracks and 16 sectors when initializing, but
 * otherwise the code should operate on different geometries.  Different
 * geometries are a chicken and egg problem as the geometry info is
 * defined to be on track 0x11, sector 0, but you need the sector size
 * to figure out where track 0x11 is!  I have tried to make reasonable
 * extrapolations to account for different geometry sizes (e.g., put
 * as many directory entries per sector as possible).
 *
 * Since I'll want to run this on an MS-DOS machine, I am careful to
 * allocate memory in small chunks, use an ANSI C compiler and use
 * longs when the numbers will get bigger than 16 bits (i.e., the
 * number of bytes per disk).
 *
 * This is version 1.0.
 *
 * Author:
 *	George Phillips <phillips@cs.ubc.ca>
 *	Department of Computer Science
 *	University of British Columbia
 */

#include <stdio.h>
#include <ctype.h>

struct track {
	unsigned char** sector;
};

struct disk {
	int bytes_per_sector;
	int sectors_per_track;
	int tracks_per_disk;
	struct track* track;
};

/* handy disk address macros for getting at particular bytes and words */
#define byteat(d, t, s, n) ((d)->track[(t)].sector[(s)][(n)])
#define wordat(d, t, s, n) (byteat(d, t, s, n) + byteat(d, t, s, (n) + 1) * 256)

void build_disk_mem(struct disk*);
void read_disk(struct disk*, FILE*);
void verify_geometry(struct disk*);
void catalog(struct disk*);
int apptoascii(int), asciitoapp(int);
void* alloc(int n);

void show_freemap(struct disk*);
int isfree(struct disk*, int, int);
int allocate_sector(struct disk*, int*, int*, int);
void alloc_sector(struct disk*, int, int);
void free_sector(struct disk*, int, int);
void zero_sector(struct disk*, int, int);

struct afile {
	int filetype;
	struct disk* d;
	int ts_s, ts_t, ts_ent, dt_t, dt_s, dt_pos;
	int eof, secgroup, dir_t, dir_s, dir_off;
};

int aopen(struct disk*, char*, struct afile*, char*);
int aclose(struct afile*);
int agetc(struct afile*);
int aputc(struct afile*, int);
long agetw(struct afile*);

main(int argc, char* argv[])
{
	struct disk d;
	FILE* fp;
	struct afile af;
	int textmode = 0;

	switch (argc) {
	default:
		fprintf(stderr, "usage: afid disk-image-file\n");
		exit(1);
	case 2:
		if (!(fp = fopen(argv[1], "rb"))) {
			perror("fopen");
			fprintf(stderr, "couldn't open %s\n", argv[1]);
			exit(1);
		}
		break;
/* this doesn't work with my current command line interface */
/*
	case 1:
		fp = stdin;
		break;
*/
	}

	d.bytes_per_sector = 256;
	d.sectors_per_track = 16;
	d.tracks_per_disk = 35;
	build_disk_mem(&d);
	read_disk(&d, fp);
	verify_geometry(&d);

	for (;;) {
		char *cmd, buf[1024], *p, *arg;

		printf("] ");
		fflush(stdout);
		if (!fgets(buf, 1024, stdin))
			break;
		for (p = buf; *p && isspace(*p); p++)
			;
		cmd = p;
		for (; *p && !isspace(*p); p++)
			;
		if (*p) *p++ = '\0';
		for (; *p && isspace(*p); p++)
			;
		arg = p;
		for (; *p && *p != '\n'; p++)
			;
		*p = '\0';
		if (!*cmd)
			continue;
		if (!strcmp(cmd, "catalog"))
			catalog(&d);
		else if (!strcmp(cmd, "quit") || !strcmp(cmd, "exit"))
			break;
		else if (!strcmp(cmd, "free"))
			show_freemap(&d);
		else if (!strcmp(cmd, "text"))
			textmode = 1;
		else if  (!strcmp(cmd, "binary"))
			textmode = 0;
		else if (!strcmp(cmd, "reorder")) {
			int t, s;
			void* temp;

			for (t = 0; t < d.tracks_per_disk; t++) {
				for (s = 1; s < d.sectors_per_track / 2; s++) {
					temp = d.track[t].sector[s];
					d.track[t].sector[s] =
						d.track[t].sector[d.sectors_per_track - s - 1];
					d.track[t].sector[d.sectors_per_track - s - 1] = temp;
				}
			}
		}
		else if (!strcmp(cmd, "read")) {
			FILE* fp;

			fp = fopen(arg, textmode ? "w" : "wb");
			for (p = arg; *p; p++) {
				if (islower(*p))
					*p = toupper(*p);
				if (*p == '/')
					arg = p + 1;
			}

			if (!aopen(&d, arg, &af, textmode ? "rt" : "rb"))
				fprintf(stderr, "couldn't open ][ file %s\n", arg);
			else {
				int n, ch;
				long start, len;

				if (!textmode) {
					printf("%s is a type %c file\n", arg, af.filetype);
					start = agetw(&af);
					len = agetw(&af);
					if (fp) {
						fputc(start & 255, fp);
						fputc((start >> 8) & 255, fp);
						fputc(len & 255, fp);
						fputc((len >> 8) & 255, fp);
					}
					printf("it is %ld bytes long (starts at %d)\n", len, start);
					n = 4;
					while ((ch = agetc(&af)) != EOF) {
						if (fp)
							fputc(ch, fp);
						n++;
					}
					printf("or is that %d bytes long\n", n);
				}
				else {
					while ((ch = agetc(&af)) != EOF) {
						if (ch == '\0')
							break;
						ch = apptoascii(ch);
						if (ch == '\r')
							ch = '\n';
						if (fp)
							fputc(ch, fp);
					}
				}
				if (fp)
					fclose(fp);
				if (aclose(&af) == EOF)
					fprintf(stderr, "error on closing ][ file %s\n", arg);
			}
		}
		else if (!strcmp(cmd, "write")) {
			if (!(fp = fopen(arg, textmode ? "r" : "rb")))
				fprintf(stderr, "couldn't open unix file %s\n", arg);
			else {
				for (p = arg; *p; p++) {
					if (islower(*p))
						*p = toupper(*p);
					if (*p == '/')
						arg = p + 1;
				}
				if (!aopen(&d, arg, &af, textmode ? "wt" : "wb")) {
					fprintf(stderr, "couldn't open ][ file %s\n", arg);
					fclose(fp);
				}
				else {
					int ch;

					if (!textmode) {
						while ((ch = fgetc(fp)) != EOF) {
							if (aputc(&af, ch) == EOF) {
								fprintf(stderr, "error on write to ][ file %s\n", arg);
								break;
							}
						}
					}
					else {
						while ((ch = fgetc(fp)) != EOF) {
							if (ch == '\n')
								ch = '\r';
							ch = asciitoapp(ch);
							if (aputc(&af, ch) == EOF) {
								fprintf(stderr, "error on write to ][ file %s\n", arg);
								break;
							}
						}
						/* HACK! :-( check that error code! */
						aputc(&af, '\0');
					}

					if (aclose(&af) == EOF)
						fprintf(stderr, "error on closing ][ file %s\n", arg);
				}
			}
		}
		else if (!strcmp(cmd, "save")) {
			int t, s;

			if (!(fp = fopen(arg, "wb")))
				fprintf(stderr, "can't write to unix disk image %s\n", arg);
			else {
				for (t = 0; t < d.tracks_per_disk; t++) {
					for (s = 0; s < d.sectors_per_track; s++) {
						if (fwrite(d.track[t].sector[s], d.bytes_per_sector, 1, fp) != 1)
						{
							fprintf(stderr, "error while writing unix disk image %s\n", arg);
							fclose(fp);
							fp = 0;
							t = d.tracks_per_disk;
							break;
						}
					}
				}
				if (fp && fclose(fp) == EOF)
					fprintf(stderr, "error while writing unix disk image %s\n", arg);
			}
		}
		else
			fprintf(stderr, "command %s not understood\n", cmd);
	}

	fclose(fp);
	exit(0);
}

/* allocate memory for the disk structure based on the geometry fields */
void build_disk_mem(struct disk* d)
{
	int	t, s;
	void* secmem;

	d->track = alloc(sizeof(struct track) * d->tracks_per_disk);
	for (t = 0; t < d->tracks_per_disk; t++) {
		secmem = alloc(d->bytes_per_sector * d->sectors_per_track);
		d->track[t].sector = alloc(sizeof(unsigned char*) * d->sectors_per_track);
		for (s = 0; s < d->sectors_per_track; s++)
#ifdef __TURBOC__
			d->track[t].sector[s] = (char*)secmem + s * d->bytes_per_sector;
#else
			d->track[t].sector[s] = secmem + s * d->bytes_per_sector;
#endif
	}
}

/* slurp all the sectors from the disk image into memory */
void read_disk(struct disk* d, FILE* fp)
{
	int t, s;
	char c;

	for (t = 0; t < d->tracks_per_disk; t++) {
		for (s = 0; s < d->sectors_per_track; s++) {
			if (fread(d->track[t].sector[s], d->bytes_per_sector, 1, fp) != 1) {
				fprintf(stderr, "EOF while reading disk!\n");
				exit(1);
			}
		}
	}
	if (fread(&c, 1, 1, fp) == 1)
		fprintf(stderr, "warning: extra data in disk-image\n");
}

/* do a sanity check between our pre-defined geometry and the geometry
 * information in the volume table of contents (VTOC, track 0x11, sector 0).
 * Print the DOS version number of the disk, just for fun.
 */
void verify_geometry(struct disk* d)
{
	int tracks_per_disk, sectors_per_track, bytes_per_sector;

	tracks_per_disk = byteat(d, 0x11, 0, 0x34);
	sectors_per_track = byteat(d, 0x11, 0, 0x35);
	bytes_per_sector = wordat(d, 0x11, 0, 0x36);

	printf("DOS release number: %d\n", byteat(d, 0x11, 0, 3));

	if (tracks_per_disk != d->tracks_per_disk ||
		sectors_per_track != d->sectors_per_track ||
		bytes_per_sector != d->bytes_per_sector)
	{
		fprintf(stderr, "afid: warning: initial disk geometry isn't the same as the disk geometry\n");
		fprintf(stderr, "information that is stored on the disk.\n");
		fprintf(stderr, "initial: %d tracks, %d sectors, %d bytes per sector\n",
			d->tracks_per_disk, d->sectors_per_track, d->bytes_per_sector);
		fprintf(stderr, "on disk: %d tracks, %d sectors, %d bytes per sector\n",
			tracks_per_disk, sectors_per_track, bytes_per_sector);
	}
}

/* print out what sectors have been allocated */
void show_freemap(struct disk* d)
{
	int s, t, tens, prev, total, allocated;

	printf("Sectors     Tracks 0 -> %d, * = sector allocated:\n",
		d->tracks_per_disk);
	printf("   ");
	for (t = 0, prev = 0; t < d->tracks_per_disk; t++) {
		tens = (t / 10) % 10;
		printf("%c", tens == prev ? ' ' : '0' + tens);
		prev = tens;
	}
	printf("\n   ");
	for (t = 0; t < d->tracks_per_disk; t++) {
		printf("%d", t % 10);
	}
	printf("\n");

	total = d->tracks_per_disk * d->sectors_per_track;
	allocated = 0;
	for (s = 0; s < d->sectors_per_track; s++) {
		printf("%2d ", s);
		for (t = 0; t < d->tracks_per_disk; t++) {
			if (isfree(d, t, s))
				putchar(' ');
			else {
				putchar('*');
				allocated++;
			}
		}
		printf("\n");
	}
	printf("%d total sectors, %d allocated, %d free\n",
		total, allocated, total - allocated);
	
	printf("sector mask: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x %s\n",
		byteat(d, 0x11, 0, 0x30),
		byteat(d, 0x11, 0, 0x31),
		byteat(d, 0x11, 0, 0x32),
		byteat(d, 0x11, 0, 0x33),
		"is this the reasonable 0xffff0000?");
}

/* display the directory of files on the disk */
void catalog(struct disk* d)
{
	int t, s, nt, ns, dir, filetype, i, eod;

	t = 0x11;
	s = 0;
	printf("disk volume %d\n", byteat(d, t, s, 6));
	while ((nt = byteat(d, t, s, 1)) && (ns = byteat(d, t, s, 2))) {
		t = nt;
		s = ns;
		eod = 0;
		for (dir = 0xb; dir + 35 <= d->bytes_per_sector; dir += 35) {
			if (byteat(d, t, s, dir) == 0xff)
				continue;
			if (byteat(d, t, s, dir) == 0) {
				eod = 1;
				break;
			}
			filetype = byteat(d, t, s, dir + 2);
			putchar((filetype & 0x80) ? '*' : ' ');
			switch (filetype & 0x7f) {
			case 0: putchar('T'); break;
			case 1: putchar('I'); break;
			case 2: putchar('A'); break;
			case 4: putchar('B'); break;
			default: putchar('?'); break;
			}
			printf(" %03d ", wordat(d, t, s, dir + 0x21));
			for (i = 3; i < 0x20; i++)
				putchar(apptoascii(byteat(d, t, s, dir + i)));
			putchar('\n');
		}
		if (eod)
			break;
	}
}

/* aopen -- open an Apple ][ file (for reading only), return !0 if successful */
/* would be nice to share directory reading stuff with catalog() */
/* I assume that I cannot extend the directory and that the entire directory
 * has been pre-allocated and pre-initialized by Apple DOS.  I also try
 * to initialize things in the same way that Apple DOS would (e.g., every
 * file will contain at least 2 sectors).
 */
int aopen(struct disk* d, char* filename, struct afile* af, char* mode)
{
	int t, s, nt, ns, ft, fs, dir, i, eod, found;
	int firstfree, fft, ffs, ffoff;
	char* p;

	t = 0x11;
	s = 0;
	found = 0;
	firstfree = 0;
	while ((nt = byteat(d, t, s, 1)) && (ns = byteat(d, t, s, 2))) {
		t = nt;
		s = ns;
		eod = 0;
		for (dir = 0xb; dir + 35 <= d->bytes_per_sector; dir += 35) {
			if (byteat(d, t, s, dir) == 0xff) {
				if (!firstfree) {
					firstfree = 1;
					fft = t;
					ffs = s;
					ffoff = dir;
				}
				continue;
			}
			if (byteat(d, t, s, dir) == 0) {
				if (!firstfree) {
					firstfree = 1;
					fft = t;
					ffs = s;
					ffoff = dir;
				}
				eod = 1;
				break;
			}
			found = 1;
			for (p = filename, i = 3; *p && i < 0x20; i++, p++) {
				if (*p != apptoascii(byteat(d, t, s, dir + i))) {
					found = 0;
					break;
				}
			}
			if (found) {
				for (; i < 0x20; i++) {
					if (apptoascii(byteat(d, t, s, dir + i)) != ' ')
						found = 0;
				}
			}
			if (found)
				break;
		}
		if (eod || found)
			break;
	}

	if (!found) {
		int tt, ts, fs, ft;
		char* p;

		if (*mode == 'r' || !firstfree)
			return 0;
		if (!allocate_sector(d, &tt, &ts, -1))
			return 0;
		if (!allocate_sector(d, &ft, &fs, t)) {
			free_sector(d, tt, ts);
			return 0;
		}
		zero_sector(d, tt, ts);
		zero_sector(d, ft, fs);
		/* fill in directory entry */
		byteat(d, fft, ffs, ffoff) = tt;
		byteat(d, fft, ffs, ffoff + 1) = ts;
		byteat(d, fft, ffs, ffoff + 2) = mode[1] == 'b' ? 4 : 0;
		for (i = 3; i <= 0x20; i++)
			byteat(d, fft, ffs, ffoff + i) = asciitoapp(' ');
		for (i = 3, p = filename; *p; i++, p++)
			byteat(d, fft, ffs, ffoff + i) = asciitoapp(*p);
		byteat(d, fft, ffs, ffoff + 0x21) = 2;
		byteat(d, fft, ffs, ffoff + 0x22) = 0;

		/* fill in first track + sector list sector */
		byteat(d, tt, ts, 1) = 0;
		byteat(d, tt, ts, 2) = 0;
		byteat(d, tt, ts, 5) = 0;
		byteat(d, tt, ts, 6) = 0;
		byteat(d, tt, ts, 0xc) = ft;
		byteat(d, tt, ts, 0xd) = fs;

		/* fudge some values so the fallthough will be right */
		t = fft;
		s = ffs;
		dir = ffoff;
	}

	/* enforce write protection on files */
	if (*mode == 'w' && (byteat(d, t, s, dir +2) & 0x80))
		return 0;

	switch (byteat(d, t, s, dir + 2) & 0x7f) {
	case 0: af->filetype = 'T'; break;
	case 1: af->filetype = 'I'; break;
	case 2: af->filetype = 'A'; break;
	case 4: af->filetype = 'B'; break;
	default: af->filetype = '?'; break;
	}

	/* note which disk this file is on */
	af->d = d;

	/* keep track of where we are in the track/sector list */
	af->ts_t = byteat(d, t, s, dir);
	af->ts_s = byteat(d, t, s, dir + 1);

	/* initialize so that first read initializes other stuff */
	af->ts_ent = -1;
	af->dt_pos = d->bytes_per_sector;

	/* these variables are only used by the writing routines */
	af->eof = 0;
	af->secgroup = 0;
	af->dir_t = t;
	af->dir_s = s;
	af->dir_off = dir;

	return 1;
}

/* close the apple ][ file */
/* returns 0 on success, EOF on failure */
/* For now, there is nothing to do, but later versions may require this
 * in order to fix up the disk (i.e., because of sector pre-allocation).
 * Also, other versions may wish to truncate the file!
 */
int aclose(struct afile* af)
{
	return 0;
}

/* get a character from the open apple ][ file */
/* return the character or EOF on end of file */
int agetc(struct afile* af)
{
	int	ch, t, s;

	if (af->ts_t == 0 && af->ts_s == 0)
		return EOF;

	if (af->dt_pos >= af->d->bytes_per_sector) {
		af->dt_pos = 0;
		if (++af->ts_ent >= byteat(af->d, 0x11, 0, 0x27)) {
			af->ts_ent = 0;
			t = byteat(af->d, af->ts_t, af->ts_s, 1);
			s = byteat(af->d, af->ts_t, af->ts_s, 2);
			af->ts_t = t;
			af->ts_s = s;
			if (t == 0 && s == 0)
				return EOF;
		}
		af->dt_t = byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2);
		af->dt_s = byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2 + 1);
	}

	if (af->dt_t == 0 && af->dt_s == 0)
		return EOF;
	
	ch = byteat(af->d, af->dt_t, af->dt_s, af->dt_pos);
	af->dt_pos++;
	return ch;
}

/* put a character into the open apple ][ file */
/* returns either the character written or EOF if some error occured */
/* I don't keep enough information around to give really good hints
 * about where to allocate the next sector.  No big deal, really.
 */
int aputc(struct afile* af, int ch)
{
	int t, s;

	if (af->eof)
		return EOF;

	if (af->dt_pos >= af->d->bytes_per_sector) {
		af->dt_pos = 0;
		if (++af->ts_ent >= byteat(af->d, 0x11, 0, 0x27)) {
			af->ts_ent = 0;
			t = byteat(af->d, af->ts_t, af->ts_s, 1);
			s = byteat(af->d, af->ts_t, af->ts_s, 2);
			af->secgroup += byteat(af->d, 0x11, 0, 0x27);
			if (t == 0 && s == 0) {
				if (!allocate_sector(af->d, &t, &s, af->ts_t)) {
					af->eof = 1;
					return EOF;
				}
				byteat(af->d, af->dir_t, af->dir_s, af->dir_off + 0x21)++;
				byteat(af->d, af->ts_t, af->ts_s, 1) = t;
				byteat(af->d, af->ts_t, af->ts_s, 2) = s;
				zero_sector(af->d, t, s);
				byteat(af->d, t, s, 5) = af->secgroup & 255;
				byteat(af->d, t, s, 6) = (af->secgroup >> 8) & 255;
			}
			af->ts_t = t;
			af->ts_s = s;
		}
		af->dt_t = byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2);
		af->dt_s = byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2 + 1);
	}

	if (af->dt_t == 0 && af->dt_s == 0) {
		if (!allocate_sector(af->d, &af->dt_t, &af->dt_s, af->ts_t)) {
			af->eof = 1;
			return EOF;
		}
		zero_sector(af->d, af->dt_t, af->dt_s);
		byteat(af->d, af->dir_t, af->dir_s, af->dir_off + 0x21)++;
		byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2) = af->dt_t;
		byteat(af->d, af->ts_t, af->ts_s, 12 + af->ts_ent * 2 + 1) = af->dt_s;
	}

	byteat(af->d, af->dt_t, af->dt_s, af->dt_pos) = ch;
	af->dt_pos++;
	return ch;
}

/* get a 2 byte word (little endian, of course) from an apple ][ file */
long agetw(struct afile* af)
{
	int b1, b2;

	if ((b1 = agetc(af)) == EOF || (b2 = agetc(af)) == EOF)
		return EOF;
	
	return b1 + b2 * 256;
}

/* take an Apple II character code and convert it to ASCII */
/* the current implementation is workable but NOT right */
int apptoascii(int c)
{
	return c & 0x7f;
}

/* take an ASCII character code and convert it to Apple II code */
/* like apptoascii(), this is just a workable but NOT right implementation */
int asciitoapp(int c)
{
	return c | 0x80;
}

/* sector allocation + de-allocation routines */

/* return non-zero if the given sector has not been allocated, 0 if free */
/* this routine is quite dependent on 16 sectors per track */
int isfree(struct disk* d, int t, int s)
{
	return
		byteat(d, 0x11, 0, 0x38 + t * 4 + (s < 8)) &	/* free map */
		(1 << (s & 7));									/* right bit in map */

/*
	This gives the sector mask which should be ANDed into the above,
	but the sector masks on disks I've seen seem bogus (they should be 0xff)
	but typically have only 1 or 2 bits set(!)

	byteat(d, 0x11, 0, 0x30 + (s < 8))
*/
}

/* allocate a sector for use.  If pref_track is valid, the sector will
 * be allocated from the given track, if possible.  If pref_track is
 * is invalid (e.g., < 0), then a search is made for an empty track
 * and if none is found then we just look for the first sector we can
 * find.
 *
 * As with the other sector routines, 16 sectors per track is hard-coded.
 */
int allocate_sector(struct disk* d, int* t, int* s, int pref_track)
{
	int	i, j, found_track;

	if (pref_track < 0 || pref_track >= d->tracks_per_disk) {
		found_track = 0;
		for (i = 0; i < d->tracks_per_disk; i++) {
			if (byteat(d, 0x11, 0, 0x38 + i * 4) == 0xff &&
				byteat(d, 0x11, 0, 0x38 + i * 4 + 1) == 0xff)
			{
				found_track = 1;
				break;
			}
		}
		if (found_track) {
			alloc_sector(d, *t = i, *s = 15);
			return 1;
		}
	}
	else {
		for (i = d->sectors_per_track - 1; i >= 0; i--) {
			if (isfree(d, pref_track, i)) {
				alloc_sector(d, *t = pref_track, *s = i);
				return 1;
			}
		}
	}

	for (i = 0; i < d->tracks_per_disk; i++) {
		for (j = d->sectors_per_track - 1; j >= 0; j--) {
			if (isfree(d, i, j)) {
				alloc_sector(d, *t = i, *s = j);
				return 1;
			}
		}
	}
	return 0;
}

/* make the given sector as in use */
void alloc_sector(struct disk* d, int t, int s)
{
	byteat(d, 0x11, 0, 0x38 + t * 4 + (s < 8)) &= ~ (1 << (s & 7));
}

/* mark the given sector as free */
void free_sector(struct disk* d, int t, int s)
{
	byteat(d, 0x11, 0, 0x38 + t * 4 + (s < 8)) |= 1 << (s & 7);
}

/* clear an entire sector to zeros */
void zero_sector(struct disk* d, int t, int s)
{
	int i;

	for (i = 0; i < d->bytes_per_sector; i++)
		byteat(d, t, s, i) = 0;
}

void* alloc(int n)
{
	void* p;
	extern void* malloc(int n);

	if (!(p = malloc(n))) {
		fprintf(stderr, "afid: out of memory\n");
		exit(1);
	}
	return p;
}
