--------------------------- cut here --------------------------------
/*
 * dosdir - list MS-DOS directories.
 * doswrite - write stdin to DOS-file
 * dosread - read DOS-file to stdout
 *
 * Author: Michiel Huisjes.
 * 
 * Usage: dos... [-lra] drive [file/dir]
 *	  l: Give long listing.
 *	  r: List recursively.
 *	  a: Set ASCII bit.
 */

#ifdef debug			/* usually avoid stdio, what a nuisance */
#include <stdio.h>
#undef EOF			/* one-off stdio redefines it different */
#endif
#include <sys/stat.h>

#define DRIVE		"/dev/dosX"
#define DRIVE_NR	8

#define MAX_CLUSTER_SIZE	4096
#define MAX_ROOT_ENTRIES	512
#define FAT_START		512L	/* After bootsector */
#define ROOTADDR		(FAT_START + 2L * (long) fat_size)
#define clus_add(cl_no)		((long) (((long) cl_no - 2L) \
					* (long) cluster_size \
					+ data_start \
				       ))
struct dir_entry {
	unsigned char d_name[8];
	unsigned char d_ext[3];
	unsigned char d_attribute;
	unsigned char d_reserved[10];
	unsigned short d_time;
	unsigned short d_date;
	unsigned short d_cluster;
	unsigned long d_size;
};

typedef struct dir_entry DIRECTORY;

#define NOT_USED	0x00
#define ERASED		0xE5
#define DIR		0x2E
#define DIR_SIZE	(sizeof (struct dir_entry))
#define SUB_DIR		0x10
#define NIL_DIR		((DIRECTORY *) 0)

#define LAST_CLUSTER	0xFFFF
#define MASK		0xFF8
#define MASK16		0xFFF8
#define FREE		0x000
#define BAD		0xFF0
#define BAD16		0xFFF0

typedef char BOOL;

#define TRUE	1
#define FALSE	0
#define NIL_PTR	((char *) 0)

#define DOS_TIME	315532800L     /* 1970 - 1980 */

#define READ			0
#define WRITE			1
#define disk_read(s, a, b)	disk_io(READ, s, a, b)
#define disk_write(s, a, b)	disk_io(WRITE, s, a, b)

#define get_fat(f, b)		buf_read(FAT_START + f, b, 1)

#define put_fat(f, b)		{ disk_io(WRITE, FAT_START + f, b, 1); \
				disk_io(WRITE, FAT_START + f + fat_size, b, 1);}

#define put_fat16(f, b)		{ disk_io(WRITE, FAT_START + f, b, 2); \
				disk_io(WRITE, FAT_START + f + fat_size, b, 2);}

#define get_fat16(f, b)		buf_read(FAT_START + f, b, 2)

#define FIND	3
#define LABEL	4
#define ENTRY	5
#define find_entry(d, e, p)	directory(d, e, FIND, p)
#define list_dir(d, e, f)	(void) directory(d, e, f, NIL_PTR)
#define label()			directory(root, root_entries, LABEL, NIL_PTR)
#define new_entry(d, e)		directory(d, e, ENTRY, NIL_PTR)

#define is_dir(d)		((d)->d_attribute & SUB_DIR)

#define EOF			0400
#define EOF_MARK		'\032'
#define STD_OUT			1
#define flush()			print(STD_OUT, NIL_PTR, 0)

short disk;
unsigned char fat_info;
DIRECTORY root[MAX_ROOT_ENTRIES];
DIRECTORY save_entry;
char null[MAX_CLUSTER_SIZE], device[] = DRIVE, path[128];
long data_start, mark;
unsigned short total_clusters, cluster_size, fat_size,
      root_entries, sub_entries;

BOOL Rflag, Lflag, Aflag, dos_read, dos_write, dos_dir, fat_16 = 0;

char disk_written = 1, buf_buf[1025];
long buf_addr = 0;

DIRECTORY *directory(), *read_cluster();
unsigned short free_cluster(), next_cluster();
char *make_name(), *num_out(), *slash(), *brk();
long lseek(), time();

leave(nr)
short nr;
{
	(void) umount(device);
	exit(nr);
}

usage(prog_name)
register char *prog_name;
{
	print_string(TRUE, "Usage: %s [%s\n", prog_name,
		     dos_dir ? "-lr] drive [dir]" : "-a] drive file");
	exit(1);
}

unsigned c2u2( ucarray )
unsigned char *ucarray;
{
	return ucarray[0] + (ucarray[1] << 8);	/* parens vital */
}

determine()
{
	struct dosboot {
		unsigned char	cjump[2];	/* unsigneds avoid bugs */
		unsigned char	nop;
		unsigned char	name[8];
		unsigned char	cbytepers[2];	/* don't use shorts, etc */
		unsigned char	secpclus;	/* to avoid struct member */
		unsigned char	creservsec[2];	/* alignment and byte */
		unsigned char	fats;		/* order bugs */
		unsigned char	cdirents[2];
		unsigned char	ctotsec[2];
		unsigned char	media;
		unsigned char	csecpfat[2];
		unsigned char	csecptrack[2];
		unsigned char	cheads[2];
		unsigned char	chiddensec[2];
		/* char    fill[482]; */
	}  boot;
	unsigned short boot_magic;		/* last of boot block */
	unsigned bytepers, reservsec, dirents, totsec;
	unsigned secpfat, secptrack, heads, hiddensec;
	
	int errcount = 0;

	/* read Bios-Parameterblock */
	disk_read(0L, &boot, sizeof boot);
	disk_read(0x1FEL, &boot_magic, sizeof boot_magic);

	/* convert some arrays */
	bytepers = c2u2(boot.cbytepers);
	reservsec = c2u2(boot.creservsec);
	dirents = c2u2(boot.cdirents);
	totsec = c2u2(boot.ctotsec);
	secpfat = c2u2(boot.csecpfat);
	secptrack = c2u2(boot.csecptrack);
	heads = c2u2(boot.cheads);
	hiddensec = c2u2(boot.chiddensec);

	/* calculate everything before checking for debugging print */
 	total_clusters = totsec / (boot.secpclus == 0 ? 1 : boot.secpclus);
	cluster_size = bytepers * boot.secpclus;
	fat_size = secpfat * bytepers;
	data_start = (long)bytepers + (long)boot.fats * (long)fat_size
		   + (long)dirents * 32L;
	root_entries = dirents;	
	sub_entries = boot.secpclus * bytepers / 32;
	if (total_clusters > 4096)
		fat_16 = 1;

#ifdef debug
	/* This used to help find foolish sign extensions and op precedences.
	 * It remains useful for looking at nonstandard formats.
	 */
	fprintf(stderr, "OEM = %8.8s\n", boot.name);
	fprintf(stderr, "Bytes/sector = %u\n", bytepers);
	fprintf(stderr, "Sectors/cluster = %u\n", boot.secpclus);
	fprintf(stderr, "Number of Reserved Clusters = %u\n", reservsec);
	fprintf(stderr, "Number of FAT's = %u\n", boot.fats);
	fprintf(stderr, "Number of root-directory entries = %u\n", dirents);
	fprintf(stderr, "Total sectors in logical volume = %u\n", totsec);
	fprintf(stderr, "Media descriptor = 0x%02x\n", boot.media);
	fprintf(stderr, "Number of sectors/FAT = %u\n", secpfat);
	fprintf(stderr, "Sectors/track = %u\n", secptrack);
	fprintf(stderr, "Number of heads = %u\n", heads);
	fprintf(stderr, "Number of hidden sectors = %u\n", hiddensec);
	fprintf(stderr, "Bootblock magic number = 0x%04x\n", boot_magic);
#endif

	/* safety checking */
	if (boot_magic != 0xAA55) {
		print_string(TRUE, "magic != 0xAA55\n");
		++errcount;
	}
	/* check sectors per track instead of inadequate media byte */
	if (secptrack < 15 &&		/* assume > 15 hard disk & wini OK */
#ifdef SECT10				/* BIOS modified for 10 sec/track */
	    secptrack != 10 &&
#endif
#ifdef SECT8				/* BIOS modified for 8 sec/track */
	    secptrack != 8 &&
#endif
	    secptrack != 9) {
		print_string(TRUE, "sectors per track not supported\n");
		++errcount;
	}
	if (boot.secpclus == 0) {
		print_string(TRUE, "sectors per cluster == 0\n");
		++errcount;
	}
	if (boot.fats != 2 && dos_write) {
		print_string (TRUE, "fats != 2\n");
		++errcount;
	}
	if (reservsec != 1) {
		print_string (TRUE, "reserved != 1\n");
		++errcount;
	}
	if (cluster_size > MAX_CLUSTER_SIZE) {
		print_string (TRUE, "cluster size too big\n");
		++errcount;
	}
	if (errcount != 0) {
		print_string (TRUE, "Can't handle disk\n");
		leave (2);
	}
}

main(argc, argv)
int argc;
register char *argv[];
{
	register char *arg_ptr = slash(argv[0]);
	DIRECTORY *entry;
	short index = 1;
	char dev_nr = '0';
	unsigned char fat_check;

	if (!strcmp(arg_ptr, "dosdir"))
		dos_dir = TRUE;
	else if (!strcmp(arg_ptr, "dosread"))
		dos_read = TRUE;
	else if (!strcmp(arg_ptr, "doswrite"))
		dos_write = TRUE;
	else {
		print_string(TRUE, "Program should be named dosread, doswrite or dosdir.\n");
		exit(1);
	}

	if (argc == 1)
		usage(argv[0]);

	if (argv[1][0] == '-') {
		for (arg_ptr = &argv[1][1]; *arg_ptr; arg_ptr++) {
			if (*arg_ptr == 'l' && dos_dir)
				Lflag = TRUE;
			else if (*arg_ptr == 'r' && dos_dir)
				Rflag = TRUE;
			else if (*arg_ptr == 'a' && !dos_dir)
				Aflag = TRUE;
			else
				usage(argv[0]);
		}
		index++;
	}

	if (index == argc)
		usage(argv[0]);

	if ((dev_nr = (0x5f & *argv[index++])) < 'A' || dev_nr > 'Z')
		usage(argv[0]);

	device[DRIVE_NR] = dev_nr;

	if ((disk = open(device, dos_write ? 2 : 0)) < 0) {
		print_string(TRUE, "Cannot open %s\n", device);
		exit(1);
	}

	disk_read(FAT_START, &fat_info, 1);
	determine();
	disk_read(FAT_START + (long) fat_size, &fat_check, sizeof(fat_check));

	if (fat_check != fat_info) {
		print_string(TRUE, "Disk type in FAT copy differs from disk type in FAT original.\n");
		leave(1);
	}

	disk_read(ROOTADDR, root, DIR_SIZE * root_entries);

	if (dos_dir && Lflag) {
		entry = label();
		print_string(FALSE, "Volume in drive %c ", dev_nr);
		if (entry == NIL_DIR)
			print(STD_OUT, "has no label.\n\n", 0);
		else
			print_string(FALSE, "is %S\n\n", entry->d_name);
	}

	if (argv[index] == NIL_PTR) {
		if (!dos_dir)
			usage(argv[0]);
		if (Lflag)
			print(STD_OUT, "Root directory:\n", 0);
		list_dir(root, root_entries, FALSE);
		if (Lflag)
			free_blocks();
		flush();
		leave(0);
	}

	for (arg_ptr = argv[index]; *arg_ptr; arg_ptr++)
		if (*arg_ptr == '\\')
			*arg_ptr = '/';
		else if (*arg_ptr >= 'a' && *arg_ptr <= 'z')
			*arg_ptr += ('A' - 'a');
	if (*--arg_ptr == '/')
		*arg_ptr = '\0';       /* skip trailing '/' */

	add_path(argv[index], FALSE);
	add_path("/", FALSE);

	if (dos_dir && Lflag)
		print_string(FALSE, "Directory %s:\n", path);

	entry = find_entry(root, root_entries, argv[index]);

	if (dos_dir) {
		list_dir(entry, sub_entries, FALSE);
		if (Lflag)
			free_blocks();
	}
	else if (dos_read)
		extract(entry);
	else {
		if (entry != NIL_DIR) {
			flush();
			if (is_dir(entry))
				print_string(TRUE, "%s is a directory.\n", path);
			else
				print_string(TRUE, "%s already exists.\n", argv[index]);
			leave(1);
		}

		add_path(NIL_PTR, TRUE);

		if (*path)
			make_file(find_entry(root, root_entries, path),
				  sub_entries, slash(argv[index]));
		else
			make_file(root, root_entries, argv[index]);
	}

	(void) close(disk);
	flush();
	leave(0);
}

DIRECTORY *directory(dir, entries, function, pathname)
DIRECTORY *dir;
short entries;
BOOL function;
register char *pathname;
{
	register DIRECTORY *dir_ptr = dir;
	DIRECTORY *mem = NIL_DIR;
	unsigned short cl_no = dir->d_cluster;
	unsigned short type, last;
	char file_name[14];
	char *name;
	int i = 0;

	if (function == FIND) {
		while (*pathname != '/' && *pathname && i < 12)
			file_name[i++] = *pathname++;
		while (*pathname != '/' && *pathname)
			pathname++;
		file_name[i] = '\0';
	}

	do {
		if (entries != root_entries) {
			mem = dir_ptr = read_cluster(cl_no);
			last = cl_no;
			cl_no = next_cluster(cl_no);
		}

		for (i = 0; i < entries; i++, dir_ptr++) {
			type = dir_ptr->d_name[0] & 0x0FF;
			if (function == ENTRY) {
				if (type == NOT_USED || type == ERASED) {
					if (!mem)
						mark = ROOTADDR + (long) i * (long) DIR_SIZE;
					else
						mark = clus_add(last) + (long) i * (long) DIR_SIZE;
					return dir_ptr;
				}
				continue;
			}
			if (type == NOT_USED)
				break;
			if (dir_ptr->d_attribute & 0x08) {
				if (function == LABEL)
					return dir_ptr;
				continue;
			}
			if (type == DIR || type == ERASED || function == LABEL)
				continue;
			type = is_dir(dir_ptr);
			name = make_name(dir_ptr, (function == FIND) ?
					 FALSE : type);
			if (function == FIND) {
				if (strcmp(file_name, name) != 0)
					continue;
				if (!type) {
					if (dos_dir || *pathname) {
						flush();
						print_string(TRUE, "Not a directory: %s\n", file_name);
						leave(1);
					}
				}
				else if (*pathname == '\0' && dos_read) {
					flush();
					print_string(TRUE, "%s is a directory.\n", path);
					leave(1);
				}
				if (*pathname) {
					dir_ptr = find_entry(dir_ptr,
						   sub_entries, pathname + 1);
				}
				if (mem) {
					if (dir_ptr) {
						bcopy(dir_ptr, &save_entry, DIR_SIZE);
						dir_ptr = &save_entry;
					}
					(void) brk(mem);
				}
				return dir_ptr;
			}
			else {
				if (function == FALSE)
					show(dir_ptr, name);
				else if (type) {	/* Recursive */
					print_string(FALSE, "Directory %s%s:\n", path, name);
					add_path(name, FALSE);
					list_dir(dir_ptr, sub_entries, FALSE);
					add_path(NIL_PTR, FALSE);
				}
			}
		}
		if (mem)
			(void) brk(mem);
	} while (cl_no != LAST_CLUSTER && mem);

	switch (function) {
		case FIND:
			if (dos_write && *pathname == '\0')
				return NIL_DIR;
			flush();
			print_string(TRUE, "Cannot find `%s'.\n", file_name);
			leave(1);
		case LABEL:
			return NIL_DIR;
		case ENTRY:
			if (!mem) {
				flush();
				print_string(TRUE, "No entries left in root directory.\n");
				leave(1);
			}

			cl_no = free_cluster(TRUE);
			link_fat(last, cl_no);
			link_fat(cl_no, LAST_CLUSTER);
			disk_write(clus_add(cl_no), null, cluster_size);

			return new_entry(dir, entries);
		case FALSE:
			if (Rflag) {
				print(STD_OUT, "\n", 0);
				list_dir(dir, entries, TRUE);
			}
	}
}

extract(entry)
register DIRECTORY *entry;
{
	register unsigned short cl_no = entry->d_cluster;
	char buffer[MAX_CLUSTER_SIZE];
	short rest;

	if (entry->d_size == 0)	       /* Empty file */
		return;

	do {
		disk_read(clus_add(cl_no), buffer, cluster_size);
		rest = (entry->d_size > (long) cluster_size) ? cluster_size : (short) entry->d_size;
		print(STD_OUT, buffer, rest);
		entry->d_size -= (long) rest;
		cl_no = next_cluster(cl_no);
		if (cl_no == (fat_16? BAD16: BAD)) {
			flush();
			print_string(TRUE, "Reserved cluster value encountered.\n");
			leave(1);
		}
	} while (entry->d_size && cl_no != LAST_CLUSTER);

	if (cl_no != LAST_CLUSTER)
		print_string(TRUE, "Too many clusters allocated for file.\n");
	else if (entry->d_size != 0)
		print_string(TRUE, "Premature EOF: %L bytes left.\n",
			     entry->d_size);
}

print(fd, buffer, bytes)
short fd;
register char *buffer;
register short bytes;
{
	static short index;
	static BOOL lf_pending = FALSE;
	static char output[MAX_CLUSTER_SIZE + 1];

	if (buffer == NIL_PTR) {
		if (dos_read && Aflag && lf_pending) {
			output[index++] = '\r';
			lf_pending = FALSE;
		}
		if (write(fd, output, index) != index)
			bad();
		index = 0;
		return;
	}

	if (bytes == 0)
		bytes = strlen(buffer);

	while (bytes--) {
		if (index >= MAX_CLUSTER_SIZE) {
			if (write(fd, output, index) != index)
				bad ();
			index = 0;
		}
		if (dos_read && Aflag) {
			if (*buffer == '\r') {
				if (lf_pending)
					output[index++] = *buffer++;
				else {
					lf_pending = TRUE;
					buffer++;
				}
			}
			else if (*buffer == '\n') {
				output[index++] = *buffer++;
				lf_pending = FALSE;
			}
			else if (lf_pending) {
				output[index++] = '\r';
				output[index++] = *buffer++;
			}
			else if ((output[index++] = *buffer++) == EOF_MARK) {
				if (lf_pending) {
					output[index - 1] = '\r';
					index++;
					lf_pending = FALSE;
				}
				index--;
				return;
			}
		}
		else
			output[index++] = *buffer++;
	}
}

make_file(dir_ptr, entries, name)
DIRECTORY *dir_ptr;
int entries;
char *name;
{
	register DIRECTORY *entry = new_entry(dir_ptr, entries);
	register char *ptr;
	char buffer[MAX_CLUSTER_SIZE];
	unsigned short cl_no, next;
	short i, r;
	long size = 0L;

	bcopy("           ",&entry->d_name[0],11);	/* clear entry */
	for (i = 0, ptr = name; i < 8 && *ptr != '.' && *ptr; i++)
		entry->d_name[i] = *ptr++;
	while (*ptr != '.' && *ptr)
		ptr++;
	if (*ptr == '.')
		ptr++;
	for (i=0;i < 3 && *ptr; i++)
		entry->d_ext[i] = *ptr++;

	for (i = 0; i < 10; i++)
		entry->d_reserved[i] = '\0';
	entry->d_attribute = '\0';

	entry->d_cluster = 0;

	while ((r = fill(buffer)) > 0) {
		if ((next = free_cluster(FALSE)) > total_clusters) {
			print_string(TRUE, "Diskette full. File truncated.\n");
			break;
		}

		disk_write(clus_add(next), buffer, r);

		if (entry->d_cluster == 0)
			cl_no = entry->d_cluster = next;
		else {
			link_fat(cl_no, next);
			cl_no = next;
		}

		size += r;
	}

	if (entry->d_cluster != 0)
		link_fat(cl_no, LAST_CLUSTER);

	entry->d_size = Aflag ? (size - 1) : size;	/* Strip added ^Z */
	fill_date(entry);
	disk_write(mark, entry, DIR_SIZE);
}


#define SEC_MIN	60L
#define SEC_HOUR	(60L * SEC_MIN)
#define SEC_DAY	(24L * SEC_HOUR)
#define SEC_YEAR	(365L * SEC_DAY)
#define SEC_LYEAR	(366L * SEC_DAY)

short mon_len[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

fill_date(entry)
DIRECTORY *entry;
{
	register long cur_time = time((long *) 0) - DOS_TIME;
	unsigned short year = 0, month = 1, day, hour, minutes, seconds;
	int i;
	long tmp;

	if (cur_time < 0)	       /* Date not set on booting ... */
		cur_time = 0;
	for (;;) {
		tmp = (year % 4 == 0) ? SEC_LYEAR : SEC_YEAR;
		if (cur_time < tmp)
			break;
		cur_time -= tmp;
		year++;
	}

	day = (unsigned short) (cur_time / SEC_DAY);
	cur_time -= (long) day *SEC_DAY;

	hour = (unsigned short) (cur_time / SEC_HOUR);
	cur_time -= (long) hour *SEC_HOUR;

	minutes = (unsigned short) (cur_time / SEC_MIN);
	cur_time -= (long) minutes *SEC_MIN;

	seconds = (unsigned short) cur_time;

	mon_len[1] = (year % 4 == 0) ? 29 : 28;
	i = 0;
	while (day >= mon_len[i]) {
		month++;
		day -= mon_len[i++];
	}
	day++;

	entry->d_date = (year << 9) | (month << 5) | day;
	entry->d_time = (hour << 11) | (minutes << 5) | seconds;
}

char *make_name(dir_ptr, dir_fl)
register DIRECTORY *dir_ptr;
short dir_fl;
{
	static char name_buf[14];
	register char *ptr = name_buf;
	short i;

	for (i = 0; i < 8; i++)
		*ptr++ = dir_ptr->d_name[i];

	while (*--ptr == ' ');

	ptr++;
	if (dir_ptr->d_ext[0] != ' ') {
		*ptr++ = '.';
		for (i = 0; i < 3; i++)
			*ptr++ = dir_ptr->d_ext[i];
		while (*--ptr == ' ');
		ptr++;
	}
	if (dir_fl)
		*ptr++ = '/';
	*ptr = '\0';

	return name_buf;
}

fill(buffer)
register char *buffer;
{
	static BOOL eof_mark = FALSE;
	char *last = &buffer[cluster_size];
	char *begin = buffer;
	register short c;

	if (eof_mark)
		return 0;

	while (buffer < last) {
		if ((c = get_char()) == EOF) {
			eof_mark = TRUE;
			if (Aflag)
				*buffer++ = EOF_MARK;
			break;
		}
		*buffer++ = c;
	}

	return (int) (buffer - begin);
}

get_char()
{
	static short read_chars, index;
	static char input[MAX_CLUSTER_SIZE];
	static BOOL new_line = FALSE;

	if (new_line == TRUE) {
		new_line = FALSE;
		return '\n';
	}

	if (index == read_chars) {
		if ((read_chars = read(0, input, cluster_size)) == 0)
			return EOF;
		index = 0;
	}

	if (Aflag && input[index] == '\n') {
		new_line = TRUE;
		index++;
		return '\r';
	}

	return input[index++];
}

#define HOUR	0xF800		       /* Upper 5 bits */
#define MIN	0x07E0		       /* Middle 6 bits */
#define YEAR	0xFE00		       /* Upper 7 bits */
#define MONTH	0x01E0		       /* Mid 4 bits */
#define DAY	0x01F		       /* Lowest 5 bits */

char *month[] = {
		 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
		 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

modes(mode)
register unsigned char mode;
{
	print_string(FALSE, "\t%c%c%c%c%c", (mode & SUB_DIR) ? 'd' : '-',
		     (mode & 02) ? 'h' : '-', (mode & 04) ? 's' : '-',
		     (mode & 01) ? '-' : 'w', (mode & 0x20) ? 'a' : '-');
}

show(dir_ptr, name)
DIRECTORY *dir_ptr;
char *name;
{
	register unsigned short e_date = dir_ptr->d_date;
	register unsigned short e_time = dir_ptr->d_time;
	unsigned short next;
	char bname[20];
	short i = 0;

	while (*name && *name != '/')
		bname[i++] = *name++;
	bname[i] = '\0';
	if (!Lflag) {
		print_string(FALSE, "%s\n", bname);
		return;
	}
	modes(dir_ptr->d_attribute);
	print_string(FALSE, "\t%s%s", bname, strlen(bname) < 8 ? "\t\t" : "\t");
	i = 1;
	if (is_dir(dir_ptr)) {
		next = dir_ptr->d_cluster;
		while ((next = next_cluster(next)) != LAST_CLUSTER)
			i++;
		print_string(FALSE, "%L", (long) i * (long) cluster_size);
	}
	else
		print_string(FALSE, "%L", dir_ptr->d_size);
	print_string(FALSE, "\t%N:%N %P %s %d\n", ((e_time & HOUR) >> 11),
		     ((e_time & MIN) >> 5), (e_date & DAY),
	   month[((e_date & MONTH) >> 5) - 1], ((e_date & YEAR) >> 9) + 1980);
}

free_blocks()
{
	register unsigned short cl_no;
	long free = 0;
	long bad = 0;

	for (cl_no = 2; cl_no <= total_clusters; cl_no++) {
		switch (next_cluster(cl_no)) {
			case FREE:
				free++;
				break;
			case BAD16:
				bad++;
				break;
			case BAD:
				if(!fat_16)
					bad++;
		}
	}

	print_string(FALSE, "Free space: %L bytes.\n", free * (long) cluster_size);
	if (bad)
		print_string(FALSE, "Bad sectors: %L bytes.\n", bad * (long) cluster_size);
}

char *num_out(number)
register long number;
{
	static char num_buf[13];
	char temp[13];
	register short i = 0;
	short j;

	if (number == 0)
		temp[i++] = '0';

	while (number) {
		temp[i++] = (char) (number % 10L + '0');
		number /= 10L;
	}

	for (j = 0; j < 11; j++)
		num_buf[j] = temp[i - j - 1];

	num_buf[i] = '\0';
	return num_buf;
}

/* VARARGS */
print_string(err_fl, fmt, args)
BOOL err_fl;
char *fmt;
int args;
{
	char buf[200];
	register char *buf_ptr = buf;
	char *scan_ptr;
	register int *arg_ptr = &args;
	short i;

	while (*fmt) {
		if (*fmt == '%') {
			fmt++;
			if (*fmt == 'c') {
				*buf_ptr++ = (char) *arg_ptr++;
				fmt++;
				continue;
			}
			if (*fmt == 'S') {
				scan_ptr = (char *) *arg_ptr;
				for (i = 0; i < 11; i++)
					*buf_ptr++ = *scan_ptr++;
				fmt++;
				continue;
			}
			if (*fmt == 's')
				scan_ptr = (char *) *arg_ptr;
			else if (*fmt == 'L') {
				scan_ptr = num_out(*((long *) arg_ptr));
				arg_ptr++;
			}
			else {
				scan_ptr = num_out((long) *arg_ptr);
				if (*fmt == 'P' && *arg_ptr < 10)
					*buf_ptr++ = ' ';
				else if (*fmt == 'N' && *arg_ptr < 10)
					*buf_ptr++ = '0';
			}
			while (*buf_ptr++ = *scan_ptr++);
			buf_ptr--;
			arg_ptr++;
			fmt++;
		}
		else
			*buf_ptr++ = *fmt++;
	}

	*buf_ptr = '\0';

	if (err_fl) {
		flush();
		write(2, buf, (int) (buf_ptr - buf));
	}
	else
		print(STD_OUT, buf, 0);
}

DIRECTORY *read_cluster(cluster)
register unsigned short cluster;
{
	register DIRECTORY *sub_dir;
	extern char *sbrk();

	if ((sub_dir = (DIRECTORY *) sbrk(cluster_size)) < 0) {
		print_string(TRUE, "Cannot set break!\n");
		leave(1);
	}
	disk_read(clus_add(cluster), sub_dir, cluster_size);

	return sub_dir;
}

unsigned short free_cluster(leave_fl)
BOOL leave_fl;
{
	static unsigned short cl_index = 2;

	while (cl_index <= total_clusters && next_cluster(cl_index) != FREE)
		cl_index++;

	if (leave_fl && cl_index > total_clusters) {
		flush();
		print_string(TRUE, "Diskette full. File not added.\n");
		leave(1);
	}

	return cl_index++;
}

link_fat(cl_1, cl_2)
unsigned short cl_1;
register unsigned short cl_2;
{
	register unsigned fat_index;
	unsigned char pad;
	unsigned pad16;

	if(fat_16) {
		pad16 = cl_2;
		put_fat16((long)(cl_1 << 1), &pad16);
		return;
	}
	fat_index = (cl_1 >> 1) * 3 + 1;
	if (cl_1 & 0x01) {
		pad = cl_2 >> 4;
		put_fat((long)(fat_index + 1), &pad);
		get_fat((long)fat_index, &pad);
		pad = (pad & 0x0F) | ((cl_2 & 0x0F) << 4);
		put_fat((long)fat_index, &pad);
	}
	else {
		pad = cl_2;
		put_fat((long)(fat_index - 1), &pad);
		get_fat((long)fat_index, &pad);
		pad = (pad & 0xF0) | (0xf & (cl_2 >> 8));
		put_fat((long)fat_index, &pad);
	}
}

unsigned short next_cluster(cl_no)
register unsigned short cl_no;
{
	register unsigned fat_index;
	unsigned char pad;
	unsigned pad16;
	unsigned mask = MASK16;
	unsigned bad = BAD16;

	if(!fat_16) {
		fat_index = (cl_no >> 1) * 3 + 1;
		get_fat((long)fat_index, &pad);
		if (cl_no & 0x01) {
			pad16 = 0x0f & (pad >> 4);
			get_fat((long)(fat_index + 1), &pad);
			cl_no = (((short)pad) << 4) | pad16;
		}
		else {
			pad16 = (0x0f & pad) << 8;
			get_fat((long)(fat_index - 1), &pad);
			cl_no = (short)pad | pad16;
		}
	mask = MASK;
	bad = BAD;
	}
	else {
		get_fat16((long)(cl_no << 1), &pad16);
		cl_no = pad16;
	}

	if ((cl_no & mask) == mask)
		cl_no = LAST_CLUSTER;
	else if ((cl_no & bad) == bad)
		cl_no = bad;

	return cl_no;
}

char *slash(str)
register char *str;
{
	register char *result = str;

	while (*str)
		if (*str++ == '/')
			result = str;

	return result;
}

add_path(file, slash_fl)
register char *file;
BOOL slash_fl;
{
	register char *ptr = path;

	while (*ptr)
		ptr++;

	if (file == NIL_PTR) {
		if(ptr != path)
			ptr--;
		if(ptr != path) do {
			ptr--;
		} while (*ptr != '/' && ptr != path);
		if (ptr != path && !slash_fl)
			*ptr++ = '/';
		*ptr = '\0';
	}
	else
		while (*ptr++ = *file++);
}

bcopy(src, dest, bytes)
register char *src, *dest;
short bytes;
{
	while (bytes--)
		*dest++ = *src++;
}

disk_io(op, seek, address, bytes)
register BOOL op;
unsigned long seek;
DIRECTORY *address;
register unsigned bytes;
{
	unsigned int r;

	if (lseek(disk, seek, 0) < 0L) {
		flush();
		print_string(TRUE, "Bad lseek\n");
		leave(1);
	}

	if (op == READ)
		r = read(disk, address, bytes);
	else {
		disk_written = 1;
		r = write(disk, address, bytes);
	}

	if (r != bytes)
		bad();
}

bad()
{
	flush();
	perror("I/O error");
	leave(1);
}

buf_read(seek, b, c)
long seek;
register char *b;
int c;
{
	if(disk_written || (seek & (~0x3ffL)) != buf_addr) {
		disk_written = 0;
		disk_io(READ, buf_addr = seek & (~0x3ffL), buf_buf, 1025);
	}

	seek &= 0x3ffL;
	*b++ = buf_buf[seek++];
	if(c == 2)
		*b = buf_buf[seek];
}
------------------------------------- cut here -----------------------