/*
 * Write (copy) a Unix file to MSDOS
 *
 * Emmet P. Gray			US Army, HQ III Corps & Fort Hood
 * ...!uunet!uiucuxc!fthood!egray	Attn: AFZF-DE-ENV
 * 					Directorate of Engineering & Housing
 * 					Environmental Management Office
 * 					Fort Hood, TX 76544-5057
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "msdos.h"

int fd;				/* the file descriptor for the floppy */
int dir_start;			/* starting sector for directory */
int dir_len;			/* length of directory (in sectors) */
int dir_entries;		/* number of directory entries */
int dir_chain[25];		/* chain of sectors in directory */
int clus_size;			/* cluster size (in sectors) */
unsigned long clus_len;		/* The cluster lenght in bytes */
int fat_len;			/* length of FAT table (in sectors) */
int num_clus;			/* number of available clusters */
unsigned char *fatbuf;		/* the File Allocation Table */
char *mcwd;			/* the Current Working Directory */
static char *inbuf;		/* The input buffer */
static char *outbuf;		/* The output buffer */
int bufsiz;			/* Buffer size */
int maxcontig;			/* Max contiguous clusters per write call */
long size;			/* Size of DOS file */

int full = 0;
int textmode = 0;
int nowarn = 0;
int need_nl = 0;

extern union bootblock bb;
void exit(), zapit(), writefat(), writedir(), free(), perror(), move();

main(argc, argv)
int argc;
char *argv[];
{
	extern int optind;
	extern char *optarg;
	int i, entry, ismatch, nogo, slot, start, dot, single;
	int root, c, oops, verbose, first, mod_time;
	char *filename, *newfile, tname[9], text[4], *fixname(), *getname();
	char *unixname(), ans[10], *strncpy(), *pathname, *getpath(), *fixed;
	char tmp[MAX_PATH], *target, *strcat(), *strcpy();
	struct directory *dir, *search(), *writeit();

	if (init(2)) {
		fprintf(stderr, "mwrite: Cannot initialize diskette\n");
		exit(1);
	}
					/* get command line options */
	oops = 0;
	verbose = 0;
	mod_time = 0;
	while ((c = getopt(argc, argv, "tnvm")) != EOF) {
		switch(c) {
			case 't':
				textmode = 1;
				break;
			case 'n':
				nowarn = 1;
				break;
			case 'v':
				verbose = 1;
				break;
			case 'm':
				mod_time = 1;
				break;
			default:
				oops = 1;
				break;
		}
	}

	if (oops || (argc - optind) < 2) {
		fprintf(stderr, "Usage: mwrite [-tnv] unixfile msdosfile\n");
		fprintf(stderr, "    or mwrite [-tnv] unixfile [unixfiles...] msdosdirectory\n");
		exit(1);
	}
	root = 0;
	if (!strcmp(argv[argc-1], "/") || !strcmp(argv[argc-1], "\\"))
		root = 1;

	filename = getname(argv[argc-1]);
	pathname = getpath(argv[argc-1]);
					/* test if path is ok first */
	if (subdir(pathname))
		exit(1);
					/* test if last argv is a dir */
	if (isdir(filename) || root) {
		if (!strlen(pathname)) {
					/* don't alter the presence or */
					/* absence of a leading separator */
			strcpy(tmp, filename);
		}
		else {
			strcpy(tmp, pathname);
			strcat(tmp, "/");
			strcat(tmp, filename);
		}
					/* subdir is not recursive */
		subdir(tmp);
		single = 0;
	}
	else {
		single = 1;
					/* too many arguments */
		if ((argc - optind) != 2) {
			fprintf(stderr, "mwrite: too many arguments or destination directory omitted\n");
			exit(1);
		}
	}


	clus_len = clus_size * MSECSIZ;
	/* Round cylinder up to nearest cluster multiple */
	bufsiz = NTRACK(bb.sb) * NSECT(bb.sb);
	maxcontig = (bufsiz += clus_size - (--bufsiz) % clus_size) / clus_size;
	bufsiz *= MSECSIZ;

	if( (outbuf = (char *)malloc(bufsiz)) == NULL ||
	    ( inbuf = (char *)malloc(bufsiz)) == NULL) {
		fprintf(stderr,
			"mwrite: Cannot allocate I/O buffers\n");
		perror("malloc") ;
		exit(1);
	}

	for (i=optind; i<argc-1; i++) {
		if (single)
			fixed = fixname(argv[argc-1], verbose);
		else
			fixed = fixname(argv[i], verbose);

		strncpy(tname, fixed, 8);
		strncpy(text, fixed+8, 3);
		tname[8] = '\0';
		text[3] = '\0';

		target = unixname(tname, text);
					/* see if exists and get slot */
		ismatch = 0;
		slot = -1;
		dot = 0;
		nogo = 0;
		first = 1;
		for (entry=0; entry<dir_entries; entry++) {
			dir = search(entry);
					/* save the '.' entry info */
			if (first) {
				first = 0;
				if ((dir->attr & 0x10) && dir->name[0] == '.') {
					dot = dir->start[1]*0x100 + dir->start[0];
					continue;
				}
			}
					/* is empty */
			if (dir->name[0] == 0x0) {
				if (slot < 0)
					slot = entry;
				break;
			}
					/* is erased */
			if (dir->name[0] == 0xe5) {
				if (slot < 0)
					slot = entry;
				continue;
			}
					/* is dir or volume lable */
			if ((dir->attr & 0x10) || (dir->attr & 0x08))
				continue;

			strncpy(tname, (char *) dir->name, 8);
			strncpy(text, (char *) dir->ext, 3);
			tname[8] = '\0';
			text[3] = '\0';

			newfile = unixname(tname, text);
					/* if file exists, delete it first */
			if (!strcmp(target, newfile)) {
				ismatch = 1;
				start = dir->start[1]*0x100 + dir->start[0];
				if (nowarn) {
					zapit(start);
					dir->name[0] = 0xe5;
					writedir(entry, dir);
					if (slot < 0)
						slot = entry;
				} else {
					while (1) {
						printf("File \"%s\" exists, overwrite (y/n) ? ", target);
						gets(ans);
						if (ans[0] == 'n' || ans[0] == 'N') {
							nogo = 1;
							break;
						}
						if (ans[0] == 'y' || ans[0] == 'Y') {
							zapit(start);
							dir->name[0] = 0xe5;
							writedir(entry, dir);
							if (slot < 0)
								slot = entry;
							break;
						}
					}
				}
			}
			free(newfile);
			if (ismatch)
				break;
		}
		if (nogo) {		/* chickened out... */
			free(fixed);
			free(target);
			continue;
		}
					/* no '.' entry means root directory */
		if (dot == 0 && slot < 0) {
			fprintf(stderr, "mwrite: No directory slots\n");
			exit(1);
		}
					/* make the directory grow */
		if (dot && slot < 0) {
			if (grow(dot)) {
				fprintf(stderr, "mwrite: Disk full\n");
				exit(1);
			}
					/* first entry in 'new' directory */
			slot = entry;
		}
		if (!single)
			printf("Copying %s\n", target);
					/* write the file */
		if (dir = writeit(fixed, argv[i], verbose, mod_time))
			writedir(slot, dir);

		free(fixed);
		free(target);

		if (full) {
			fprintf(stderr, "mwrite: Disk Full\n");
			break;
		}
		if (single)
			break;
	}
					/* write FAT sectors */
	writefat();
	close(fd);
	exit(0);
}

/*
 * Open the named file for write, create the cluster chain, return the
 * directory structure or NULL on error.
 */

struct directory *
writeit(fixed, path, verbose, mod_time)
char *fixed, *path;
int verbose, mod_time;
{
	FILE *fp;
	int fat, firstfat, oldfat, curfat, chain;
	long time(), now;
	struct directory *dir, *mk_entry();
	struct stat stbuf;

	if (stat(path, &stbuf) < 0) {
		fprintf(stderr, "mwrite: Can't stat \"%s\"\n", path);
		return(NULL);
	}

	if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
		if (verbose)
			fprintf(stderr, "mwrite: \"%s\" is a directory\n", path);
		return(NULL);
	}

	if ((stbuf.st_mode & S_IFREG) != S_IFREG) {
		fprintf(stderr, "mwrite: \"%s\" is not a regular file\n", path);
		return(NULL);
	}
					/* preserve mod time? */
	if (mod_time)
		now = stbuf.st_mtime;
	else
		time(&now);

	if (!(fp = fopen(path, "r"))) {
		fprintf(stderr, "mwrite: Can't open \"%s\" for read\n", path);
		return(NULL);
	}
#ifdef HAVE_SETBUFFER
	setbuffer(fp,inbuf,bufsiz) ;
#endif

	fat = oldfat = firstfat = nextfat(size=0); 
	for(;;) {
		if (fat == -1) {
			full = 1;
			if(size) 
				zapit(firstfat) ;
			return(NULL) ;
		}

		/*
		 * grab a bunch of contiguous FAT slots
		 * curfat -> 1 + last grabbed slot.
		 * FIX ME!
		 * someone should try to read and cache a cylinder on
		 * first write access, do write to memory until a new
		 * cylinder is requested.
		 * The overhead is higher,  but should be more robust under
		 * fragmentation.
		 * In mread this may even be a win!  (Also see comments
		 * in putclusters() )
		 */
		for(curfat=fat; 
		    ++curfat < fat + maxcontig  &&
		    nextfat(curfat-1) == curfat;) ;
			
		if((oldfat=putclusters(oldfat,fat, curfat, fp)) == 0)
			break ;

		fat = nextfat(oldfat);
	}
	fclose(fp);
	dir = mk_entry(fixed, 0x20, firstfat, size, now);
	return(dir);
}

/*
 * Write to the cluster chain from the named Unix file descriptor.
 * N.B. all the clusters in the chain are contiguous.
 */


static int writepos = -1;

int
putclusters(chain,start,end,fp)
FILE *fp;
{
	static int blk;
	int c, nclust, current, eof=0;
	int buflen = ( end - start ) * MSECSIZ ;
	register char *tbuf=outbuf;

	blk = (start - 2)*clus_size + dir_start + dir_len;

	if (textmode) { /* '\n' to '\r\n' translation */
		current = 0;
		if (need_nl) {
			tbuf[current++] = '\n';
			need_nl = 0;
		}
		while (current < buflen) {
			if ((c = fgetc(fp)) == EOF) {
					/* put a file EOF marker */
				tbuf[current++] = 0x1a;
				++eof;
				break;
			}
			if (c == '\n') {
				tbuf[current++] = '\r';
				/* if at the end of the buffer */
				if (current == buflen) {
					need_nl++;
					break;
				}
			}
			tbuf[current++] = c;
		}
	}
	else {
		/*
		 * FIX ME!
		 * The kernel guarantees to satisfy REGULAR file
		 * read requests unless EOF,
		 * This code will break on pipes,  sockets,  etc.
		 * To fix one should do something akin to the
		 * "atomic" I/O of Berkeley multiprocess dump,  which loops
		 * on read/write requests until EOF or enough data has been
		 * gathered.  If you want to do this please change
		 * all instances of  read/write with Read/Write and
		 * add an offset parameter to allow arbitrary sorting/queuing
		 * etc,  behind the back of the DOS code.  This will
		 * make the overall code much cleaner,  and we 
		 * can put Read/Write in a sysdep.c where they belong.
		 * Also one may want to memory map the input file.  Avoiding
		 * redundant copying to user space,  this is for perfectionists
		 * as the floppy is much slower than UNIX disks,  so gains
		 * here are small.
		 */
		if ( (current = fread(outbuf, 1, buflen, fp)) < 0) {
			perror("putcluster: fread");
			exit(1);
		}
		if ( current != buflen ) 
			++eof;
	}

	size += current;

	if (current == 0) {
		putfat(chain,0xfff) ;
		return(0) ;
	}

	putfat(chain,start) ;

	/*
	 * chain the clusters, we are about to overwrite
	 * making sure to terminate the chain.
	 */
	for(end=start; current > clus_len ; ++end) {
		putfat(end,end+1);
		current -= clus_len ;
	}
	putfat(end, 0xfff) ;

	if ( blk != writepos )
		move(blk);

	nclust=(end-start)+1;
	writepos = eof ? -1 : blk + nclust*clus_size;
	buflen = nclust * clus_size * MSECSIZ;

	if (write(fd, outbuf, buflen) != buflen) {
		perror("putclusters: write");
		exit(1);
	}

	return(eof ? 0: end) ;
}

/*
 * Returns next free cluster or -1 if none are available.
 */

int
nextfat(last)
int last;
{
	register int i;

	for (i=last+1; i<num_clus+2; i++) {
		if (!getfat(i))
			return(i);
	}
	return(-1);

}
