/**********************************************************************\
 *
 *	CP.C
 *
 * Unix like file copy.
 * No wild cards are allowed in destination.
 *
 * syntax:
 *	cp [/if] srcfile destfile
 * or
 *	cp [/if] srcfile... destdir
 *
 *	J.Ruuth 10-09-1987
 *	Modified by P.Soini
\**********************************************************************/

#include	<stdio.h>
#include	<io.h>
#include	<dir.h>
#include	<dos.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<alloc.h>
#include	<string.h>
#include	<process.h>
#include	<conio.h>

#define FALSE		0
#define TRUE		1
#define ERROR		-1
#define GET_ATTR	0
#define SET_ATTR	1
#define CTRLC		3
#define	BUFFER_SIZE	0xfffeL
#define	CLUSTER_SIZE	0x800

extern int		yes_no(char *question, char yes_char, char no_char);
extern int cdecl	getopt(int argc, char **argv, char *optstring);
extern char * cdecl	optarg;
extern int cdecl	optind;

unsigned cdecl	_stklen=1024;
void cdecl	_setenvp()
{
}

typedef struct CP_NODE {
	char		*s_fname,
			*d_fname;
	struct CP_NODE	*next;
} CP_NODE;

static CP_NODE		*cp_list;
static struct ffblk	ffblk;
static int		dest_dir,	/* destination is directory or drive */
			exit_code = 0,
			cp_count = 0,
			interactive = FALSE,
			force = FALSE;
static char		dpath[MAXPATH],	/* destination path */
			spath[MAXPATH],	/* source path */
			ddrive[MAXDRIVE],
			sdrive[MAXDRIVE],
			ddir[MAXDIR],
			sdir[MAXDIR],
			name[MAXFILE],
			ext[MAXEXT];

/************************************************************************
**		out_of_memory()
*/
static void	out_of_memory(void)
{
	cputs("cp: out of memory\r\n");
	exit(1);
}


/************************************************************************
**		string_save()
*/
static char	*string_save(char *s)
{
	register char	*s1;
	
	if ((s1 = strdup(s)) == NULL)
		out_of_memory();
	return (s1);
}


/************************************************************************
**		allocate()
*/
static void	*allocate(unsigned nbytes)
{
	register void	*block;
	
	if ((block = malloc(nbytes)) == NULL)
		out_of_memory();
	return (block);
}

/************************************************************************
**		far_allocate()
*/
static void far	*far_allocate(unsigned long nbytes)
{
	register void far	*block;
	
	if ((block = farmalloc(nbytes)) == (void far *)NULL)
		out_of_memory();
	return (block);
}


/************************************************************************
**		mark()
**	Marks files to be copied.
*/
static void	mark (char *s_fname, char *d_fname)
{
	static CP_NODE	*cp_list_end = NULL;
	
	if (cp_list_end == NULL)
		cp_list = cp_list_end = allocate (sizeof (CP_NODE));
	else {
		cp_list_end->next = allocate (sizeof (CP_NODE));
		cp_list_end = cp_list_end->next;
	}
	cp_list_end->s_fname = string_save (s_fname);
	cp_list_end->d_fname = string_save (d_fname);
	cp_list_end->next = NULL;
}


/**********************************************************************
 *
 *	int helpexit (int error_code)
 *
 * Exit with help message
 *
 */
static void	helpexit (int error_code)
{
	cputs("\r\nUsage:\tcp [/if] source [source...] destination\r\n"
	        "\tIf more than one source, destination must be directory\r\n"
	        "Options :\t/i\t- interactive, ask permission for every file\r\n"
		       "\t\t/f\t- force overwrite without asking\r\n");
	exit(error_code);
}



/**********************************************************************
 *
 *	void cp_error (char *name)
 *
 * Display error message.
 *
 */
static void 	cp_error (char *name)
{
	register char	*msg;

	exit_code = errno;
	cputs(name);
	switch (errno) {
		case EACCES:
			msg = " : Permission denied\r\n";
			break;
		case EBADF:
			msg = " : Bad file number\r\n";
			break;
		case ENOENT:
			msg = " : Path or file name not found\r\n";
			break;
		case EMFILE:
			msg = " : Too many open files\r\n";
			break;
		case EINVACC:
			msg = " : Invalid access code\r\n";
			break;
		default :
			msg = " : Unidentified error\r\n";
			break;
	}
	cputs(msg);
}


/************************************************************************
 *	int	check_dest(char *path)
 *
 * Performs file name parsing. Return value as with fnsplit()
 */
static int	check_dest(char *path)
{
	register int	c;

	if ((c=fnsplit(path,NULL,NULL,name,ext))==DRIVE)
		;
	else if (c & (FILENAME | EXTENSION)) {
		if (findfirst(path, &ffblk, FA_DIREC))
			;
		else if (ffblk.ff_attrib & FA_DIREC) {
			(void)strcat(path, "\\");
			return ((c & ~(FILENAME | EXTENSION)) | DIRECTORY);
		}
	}
	return (c);
}

/************************************************************************
**		real_cp()
**	This function does the physical copy.
*/
static void	real_cp (char *src, char *dest)
{
	register int	src_h, dest_h = -1;
	unsigned	wcount, rcount;
	unsigned char	attrib;
	static char	*buffer = NULL;
	struct ftime	ftime;
	static long	bsiz;

	if (buffer==NULL) {
		bsiz = ((bsiz = coreleft()) > BUFFER_SIZE)? BUFFER_SIZE : bsiz;
		bsiz = (bsiz / CLUSTER_SIZE) * CLUSTER_SIZE;
		buffer = far_allocate(bsiz);
	}
retry:	if ((src_h = _open(src, O_DENYWRITE | O_RDONLY)) == ERROR) {
		cp_error(src);
		return;
	}
	(void)getftime(src_h, &ftime);
	/* create new file to copy to with src's attributes */
	attrib = _chmod(src, GET_ATTR);
	while ((rcount = _read(src_h, buffer,(unsigned)bsiz)) && rcount != ERROR) {
		if ((dest_h = (dest_h<0)? _creat(dest,attrib):dest_h)==ERROR) {
			_close(src_h);
			cp_error(dest);
			return;
		}					/* copy file */
		if ((wcount = _write(dest_h, buffer, rcount)) == ERROR) {
			cp_error(dest);
			break;
		}
		if (wcount < rcount) {		/* check if disk full */
			_close(src_h);
			_close(dest_h);
			unlink(dest);
			cputs(
			"Disk full, press any key to retry, or ^C to abort\r\n");
			if (getch() == CTRLC)
				exit(1);
			dest_h = -1;
			goto retry;
		}
	}
	if (rcount == ERROR) {
		cp_error(src);
		return;
	}
	++cp_count;
	(void)setftime(dest_h, &ftime);
	_close(dest_h);
	_close(src_h);
}


/************************************************************************
**		scan_cp_list()
**	Scans the cp_list and copies all files in the list.
*/
static void	scan_cp_list (void)
{
	register CP_NODE	*list_p;
	
	for (list_p = cp_list; list_p != NULL; list_p = list_p->next)
		real_cp (list_p->s_fname, list_p->d_fname);
}


/**********************************************************************
 *
 *	void cp (register char *src, register char *dest)
 *
 * Pseudo-copy function.
 *
 */
static void	cp(char *src, char *dest)
{
	register int	dest_h;	/* file handle */

	if (!stricmp(src, dest)) {
		cputs("File cannot be copied onto itself\r\n");
		return;
	}
	if (interactive) {
		cputs("cp ");
		cputs(src);
		putch(' ');
		cputs(dest);
		if (!yes_no (" ?", 'y', 'n'))
			return;
	}
	if (!interactive && !force)
		if ((dest_h = _open(dest,O_RDONLY)) != EOF) {
			_close(dest_h);
			dest_h = -1;
			if (!interactive && !force) {
				cputs ("File ");
				cputs (dest);
				if (!yes_no(" already exists, Overwrite ?", 'y', 'n'))
					return;
			}
		}
	mark (src, dest);
}


/**********************************************************************
 *
 *	Main
 *
 */
int cdecl	main (int argc, char *argv[])
{
	register int	done, c;

	while ((c = getopt (argc, argv, "iIfF")) != EOF)
		switch (c) {
			case 'i' :
			case 'I' :
				interactive = TRUE;
				break;
			case 'f' :
			case 'F' :
				force = TRUE;
				break;
			case '?' :
			default  :
				cputs ("Unknown option\r\n");
				helpexit (1);
				break;
		}
	if  (--argc <= optind)
		helpexit(1);
	(void)strcpy(dpath,argv[argc]);
	dest_dir = ((c=check_dest(dpath)) & (DRIVE | DIRECTORY))
		   && !(c & (FILENAME | EXTENSION));
	if (c & WILDCARDS) {
		cputs("Invalid destination\r\n");
		helpexit(1);
	}
	if (!dest_dir)
		if ((argc - optind) > 1
		|| (fnsplit(argv[optind],NULL,NULL,NULL,NULL) & WILDCARDS))
			helpexit(1);
		else {
			cp(argv[optind], dpath);
			goto copy;
		}
	(void)fnsplit(dpath, ddrive, ddir, NULL, NULL);
	while (optind < argc) {				/* copy files */
		if (done = findfirst(argv[optind], &ffblk, FA_RDONLY)) {
			cputs(argv[optind]);
			cputs(" : File not found\r\n");
		}
		(void)fnsplit(argv[optind],sdrive,sdir,NULL,NULL);
		while (!done) {
			(void)fnsplit(ffblk.ff_name, NULL, NULL, name, ext);
			fnmerge(dpath, ddrive, ddir, name, ext);
			fnmerge(spath, sdrive, sdir, name, ext);
			cp(spath, dpath);
			done = findnext(&ffblk);
		}
		++optind;
	}
copy:	scan_cp_list();
	if (cp_count == 0)
		cputs("No files copied\r\n");
	return(exit_code);
}
