/*
	docp.c - Directory-Oriented CoPy

	Fancy file copy program for Atari ST

	Copywrite 1992, Roy Bixler
	Originally by: David Oertel
	Atari ST port, overall cheez-whiz: Roy Bixler

    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 1, 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 <ctype.h>
#include <fcntl.h>
#include <osbind.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <types.h>
#include <stat.h>
#include <unistd.h>

#define MINT_LIB
#ifdef MINT_LIB

/* long _read(int, void *, long); */
/* long _write(int, const void *, long); */

#define read(x, y, z) _read(x, y, z)
#define write(x, y, z) _write(x, y, z)

#else

#define read(x, y, z) lread(x, y, z)
#define write(x, y, z) lwrite(x, y, z)

#endif

#define MAXDIR	128
#define MAXPATH	128

#define OPT_LIST "abcdfghijlmnorstvwz?"
#define GET_OPT_LIST "AaBbCcD:d:F:f:GgHhIiJjLlMmNnOoRrSsTtVvW:w:Zz?"
#define DOT_NOTATION(dir) !strcmp(dir, ".")
#define MAX_BUF 0xfffeU


#include "docp.h"
#include "doc.h"
#include "elib.h"
#include "protypes.h"

/* options */

extern int Optind;
extern char *optarg;
long Options = 0L;
char Cur_source_dir[FILENAME_MAX], Org_dest_dir[FILENAME_MAX];
ENTRY *Ptr;
void *Buf_ptr;
void *File_buf;
int Reading_flag;	/* set if 'reading:' has been printed last
					 * reset if 'writing:' has been printed last */

/* modes of date compare via 'd' option */
#define D_BEFORE 1
#define D_ON 2
#define D_AFTER 4

DATE_NODE *Fdate;		/* used to store date entered via 'd' option */
TIME_NODE *Ftime;		/* used to store time entered via 'w' option */

FILE *From_file_ptr;	/* file containing file list ('-f' option) */
int Retry = 0;		/* set if target disk is full and user chooses to
					 * continue */
int Copied_a_file = 0;  /* set if at least one file was copied */

ENTRY *Src_tab[HASH_TAB_SIZE]; /* contains source-file names */
ENTRY *Dst_tab[HASH_TAB_SIZE]; /* contains destination-file names */


typedef struct file_list {
	char *string;
	struct file_list *next;
} STDIN_TOKEN;

/* linked list globals containing file list from stdin */
STDIN_TOKEN *Stdin_list_head = NULL;
STDIN_TOKEN *Stdin_list_tail = NULL;
STDIN_TOKEN *Stdin_list_current = NULL;

int main(int argc, char *argv[])
{
	char src_dir[FILENAME_MAX];
	char dst_dir[FILENAME_MAX];
	unsigned long buf_size;
	int num_args;
	char **argv_ptr;
	char *all[2] = { "*.*", NULL };

  	get_flags(argv, argc);
	check_flags();
	check_and_format_dirs(argc, argv, src_dir, dst_dir);
	check_target_removeable(dst_dir);
	File_buf = get_file_buf(&buf_size);
	Buf_ptr = File_buf;
	argv += Optind + 2;
	num_args = argc - Optind - 2;
	argv_ptr = argv;
	if (Options & O_FROM_STDIN)
		build_stdin_file_list(&num_args);
	else if (Options & O_FROM_FILE){
		char buf[80];

		num_args = 0;
		while (fgets(buf, 79, From_file_ptr) != NULL)
			num_args++;
		rewind(From_file_ptr);
	} else if (num_args == 0) {
		num_args = 1;
		argv_ptr = all;
	}

	strcpy(Org_dest_dir, dst_dir);
	if (Options & O_CHECK)
		fprintf(stdout, "The following files would be copied or moved:\n");
	else if (Options & O_ZAPTARGET)
		zap_target(dst_dir, 1);
	copy_files(src_dir, dst_dir, argv_ptr, 
			   num_args, File_buf, buf_size);
	write_buffer(File_buf);
	clear_archive_bits(src_dir);

	if (!Copied_a_file)
		fprintf(stdout, "no files copied\n");

	free(File_buf);
	exit(0);
}

/*
 *	check_and_format_dirs()
 *
 *	Input:
 *		argc - command line argument count
 *		argv - command line arguments
 *		Optind - argument index from getopt()
 *	Output:
 *		src_dir - source directory name
 *		dst_dir - destination directory name
 *	Comments:
 *		The source and destination directories are formatted
 *		The source directory is checked for existence
 *		The destination directory is created if it doesn't exist
 *		The two directories are checked to insure that they are
 *			different
 *		The destination is checked to insure that it is not a
 *			subdirectory of the source
 */

void check_and_format_dirs(int argc, char *argv[],
	char *src_dir, char *dst_dir)
{
	if ((argc-Optind) < 2)
		usage();

	strcpy(src_dir, argv[Optind]);
	check_if_dir_exists(src_dir, 0);
	strcpy(dst_dir, argv[Optind + 1]);
	check_if_dir_exists(dst_dir, 1);

	format_dir(argv[Optind], '\1', src_dir);
	format_dir(argv[Optind + 1], '\1', dst_dir);

	check_if_dirs_compatible(src_dir, dst_dir);
}

/*
 *	check_target_removeable()
 *
 *	Input:
 *		dst_dir - destination directory name
 *	Output:
 *		Options - will be modified if target directory is removeable
 *				  (i.e. a floppy disk)
 */

void check_target_removeable(char *dst_dir)

{
	char dst_drive;

	if (islower(dst_drive = dst_dir[0]))
		dst_drive = toupper(dst_drive);
	if ((dst_drive == 'A') || (dst_drive == 'B'))
		Options |= O_TARGET_REMOVEABLE;
}

/*
 *	check_if_dirs_compatible()
 *
 *	Input:
 *		src_dir - The formatted source directory name
 *		dst_dir - The formatted destination directory name
 *	Comments:
 *		terminates if:
 *			1 - The two directories are the same
 *			2 - The destination is a subdirectory of 
 *				the source
 */

void check_if_dirs_compatible(char *src_dir, char *dst_dir)

{
	if (Options & O_CHECK)
		return;
	if (!strcmp(src_dir, dst_dir)){
		fprintf(stderr,
			"source and destination directories are the same\n");
		exit(-1);
	}
	if ((Options & O_RECURSIVE) &&
		!strncmp(src_dir, dst_dir, strlen(src_dir))){
		fprintf(stderr,
			"destination directory is a subdirectory of the source directory \nwhile in recursive mode\n");
		exit(-1);
	}
}

/*
 *	check_if_dir_exists()
 *
 *	Input:
 *		dir - the directory name
 *		is_dest - flag set if directory is a destination directory
 *	Comments:
 *		creates destination directory if it doesn't exist
 *		terminates if the source directory doesn't exist
 */

void check_if_dir_exists(char *dir, int is_dest)

{
	int retval;
	struct stat statbuf;

	if (!((DOT_NOTATION(dir)) ||
		  ((!stat(dir, &statbuf)) && (statbuf.st_mode & S_IFDIR))))
		if ((is_dest) && ((Options & O_BATCH) ||
						  (printf("directory %s does not exist - ", dir),
						   ask_user("create it (Y/N/Q) ? "))))
			create_dir(dir);
		else {
			printf("docp: directory '%s' does not exist\n", dir);
			exit(-1);
		}
}

/*
 *	create_dir()
 *
 *	Input:
 *		dir - the directory to be created
 *	Comments:
 *		creates destination directory
 */

void create_dir(char *dir)

{
	int retval;
	char next_dir[MAXDIR], *p;

	strcpy(next_dir, dir);
	for (p=next_dir; *p; p++)
		if (*p == '/')
			*p = '\\';
	
	p = next_dir;
	while ((p = strchr(p, '\\')) != NULL) {
		*p = '\0';
		mkdir(next_dir, 0);
		*p++ = '\\';
	}
	;
	if (mkdir(dir, 0) == -1){
		fprintf(stderr, "cannot create directory '%s'\n", dir);
  		exit(-1);
  	}
	else if (Options & O_VERBOSE)
		printf("created directory '%s'\n", dir);
}
  
/*
 * 	return_to_cur_dir()
 *
 *	Input:
 *		cur_dir - the current working directory
 *		other_dir - the current working directory of the
 *			destination drive
 *	Comments:
 *		returns to current working directory
 */

void return_to_cur_dir(char *cur_dir)
{
	chdir(cur_dir);
}

/*
 *	root_dir()
 *
 *	Input:
 *		dir - the directory to be checked
 *	Output:
 *		returns : 
 *			1 - if 'dir' is a root directory
 *			0 - otherwise
 */

int root_dir(char *dir)
{
	if (((*(dir + 1) == ':') && (strlen(dir) == 2)) ||
		((strlen(dir) == 3) && (*(dir + 2) == '\\')))
			return(1);
	else
		return(0);
}

/*
 *	copy_files()
 *
 *	Input:
 *		src_dir - the source directory
 *		dst_dir - the destination directory
 *		file_spec - the command-line file list
 *		num_args - the number of arguments in the file list
 *		file_buf - the buffer for reading and writing files
 *		buf_size - the size of 'file_buf'
 *	Comments:
 *		copy or move the files from the source directory to the
 *		destination directory
 */

void copy_files(char *src_dir, char *dst_dir, char *file_spec[], int num_args, 
				void *file_buf, unsigned long buf_size)

{
	struct _dta fblk;
	char ref_list[FILENAME_MAX];
	char src_file[FILENAME_MAX], dst_file[FILENAME_MAX];
	int done, index;
	long file_size;
	char *file;

	strcpy(Cur_source_dir, src_dir);
	build_hash_tab(src_dir, Src_tab, num_args, file_spec);
	build_hash_tab(dst_dir, Dst_tab, num_args, file_spec);

	file = get_first(&index);
	while (file != NULL) {
		strcpy(src_file, src_dir);
		strcat(src_file, file);

		strcpy(dst_file, dst_dir);
		strcat(dst_file, file);
		if (should_file_copy(file, src_file))
			if (Options & O_CHECK)
				fprintf(stdout, "\t%s -> %s\n", src_file, dst_file);
			else
  				copy_file(Ptr, dst_file, src_file, buf_size);
		else
			report_not_copied(src_file);

		file = get_next(&index);
	}

	clear_archive_bits(src_dir);
	copy_sub_dirs(src_dir, dst_dir, file_spec, num_args, file_buf, buf_size);
	if (Options & O_MOVE)
		rmdir(src_dir);	/* don't try too hard, but do it if we can ... */
	if (Options & O_ZAPTARGET)
		rmdir(dst_dir);
}

/*
 *	report_not_copied()
 *
 *	Input:
 *		src_dir - source directory
 *	Comments:
 *		reports that a file was not copied
 */

void report_not_copied(char *src_file)
{
	if ((Options & O_X_VERBOSE) && !(Options & O_CHECK)){
		if (!Reading_flag){
			fprintf(stdout, "reading:\n");
			Reading_flag = 1;
		}
		printf("\t%s ** NOT COPIED **\n", src_file);
	}
}

/*
 *	zap_target()
 *
 *	Input:
 *		dst_dir - the destination directory
 *		print_heading - true on entry, false for recursive calls
 *	Comments:
 *		zaps the files in the target directory
 */

void zap_target(char *dst_dir, int print_banner)

{
	int done, attrib = 0, i;
	struct _dta *odta, fblk;
	char dst_file[FILENAME_MAX], buf[MAXPATH+20];

	odta = (struct _dta *) Fgetdta();
	Fsetdta(&fblk);
	if (Options & O_COPY_HIDDEN)
		attrib |= (FA_HIDDEN|FA_SYSTEM);
	if ((Options & O_RECURSIVE) && (!(Options & O_GATHER)))
		attrib |= FA_DIR;
	strcpy(dst_file, dst_dir);
	if (dst_file[(i = strlen(dst_file))-1] != '\\') {
		strcat(dst_file, "\\");
		i++;
	}
	strcat(dst_file, "*.*");
	done = Fsfirst(dst_file, attrib);
	if ((!done) && (Options & O_VERBOSE) && (print_banner))
		printf("deleting:\n");
	while (!done) {
		strcpy(dst_file+i, fblk.dta_name);
		if (fblk.dta_attribute & FA_DIR) {
			if (!is_special(fblk.dta_name)) {
				zap_target(dst_file, 0);
				rmdir(dst_file);
			}
		}
		else if ((!(Options & O_INTERACTIVE)) ||
			(sprintf(buf, "Delete file %s (Y/N/Q) ?", dst_file),
			 ask_user(buf))) {
			if (Options & O_VERBOSE)
				printf("\t%s\n", dst_file);
			delete_file(dst_file, '\1');
		}

		done = Fsnext();
	}

	Fsetdta(odta);
}

/*
 *	build_hash_tab()
 *
 *	Input:
 *		dir - the directory for which to table will be built
 *		num_args - the number of arguments in the file list
 *		file_spec - the command-line file list
 *	Output:
 *		hash_tab - the hash table containing all the file names
 *			of the directory 'dir'
 *	Comments:
 *		builds a hash table containing all the file names of a 
 *		directory specified by the command-line file list.
 *		First it adds all the names specified by the file 
 *		list, then it subtracts those specified
 *		by the '-' notation in the file list.
 */

void build_hash_tab(char *dir, ENTRY *hash_tab[],
					int num_args, char *file_spec[])

{
	add_to_hash_tab(dir, hash_tab, 1, num_args, file_spec);
	take_from_hash_tab(dir, hash_tab, num_args, file_spec);
}

/*
 *	add_to_hash_tab()
 *
 *	Input:
 *		dir - the directory for which to table will be built
 *		from_file_poss - is it possible to get file list from a file?
 *		num_args - the number of arguments in the file list
 *		file_spec - the command-line file list
 *	Output:
 *		hash_tab - the hash table containing all the file names
 *			of the directory 'dir'
 *	Comments:
 *		adds all the files specified by the file list and within
 *		the directory 'dir' to the hash table.
 */

void add_to_hash_tab(char *dir, ENTRY *hash_tab[],
					 int from_file_poss, int num_args, char *file_spec[])

{
	int done;
	struct _dta *odta, fblk;
	char ref_list[FILENAME_MAX];
	int i;
	int files_added = 0;
	char buf[80];

	if ((from_file_poss) && (Options & O_FROM_STDIN))
		Stdin_list_current = Stdin_list_head;
	if ((from_file_poss) && (Options & O_FROM_FILE))
		rewind(From_file_ptr);
	odta = (struct _dta *) Fgetdta();
	Fsetdta(&fblk);
	for (i = 0; i < num_args; i++){
		strcpy(ref_list, dir);
		get_file_spec(buf, file_spec, i);
		if (*buf != '-'){
			int attrib = 0;

			files_added = 1;
  			strcat(ref_list, (*buf == '\\') ? buf+1 : buf);
			if (Options & O_COPY_HIDDEN)
				attrib |= (FA_HIDDEN|FA_SYSTEM);
			done = Fsfirst(ref_list, attrib);
			while (!done) {
				if (find_entry(fblk.dta_name, hash_tab) == NULL)
					add_entry(&fblk, hash_tab);
				done = Fsnext();
			}
		}
	}

	if (!files_added){
		char *all[2] = { "*.*", NULL };
		
		add_to_hash_tab(dir, hash_tab, 0, 1, all);
	}
	Fsetdta(odta);
}

/*
 *	take_from_hash_tab()
 *
 *	Input:
 *		dir - the directory for which to table will be built
 *		num_args - the number of arguments in the file list
 *		file_spec - the command-line file list
 *	Output:
 *		hash_tab - the hash table containing all the file names
 *			of the directory 'dir' and specified by the file
 *			list, 'file_spec'.
 *	Comments:
 *		takes all the files specified by using the '-' notation 
 *		and within the directory 'dir' from the hash table.
 */

void take_from_hash_tab(char *dir, ENTRY *hash_tab[],
						int num_args, char *file_spec[])

{
	int done;
	struct _dta *odta, fblk;
	char ref_list[FILENAME_MAX];
	int i;
	char buf[80];

	odta = (struct _dta *) Fgetdta();
	Fsetdta(&fblk);
	if (Options & O_FROM_STDIN)
		Stdin_list_current = Stdin_list_head;
	if (Options & O_FROM_FILE)
		rewind(From_file_ptr);
	for (i = 0; i < num_args; i++){
		strcpy(ref_list, dir);
		get_file_spec(buf, file_spec, i);
		if (*buf == '-'){
			int attrib = 0;
  			strcat(ref_list, buf + 1);
			if (Options & O_COPY_HIDDEN)
				attrib |= (FA_HIDDEN|FA_SYSTEM);
			done = Fsfirst(ref_list, attrib);
  			while (!done){
				remove_entry(fblk.dta_name, hash_tab);
				done = Fsnext();
			}
		}
	}
	Fsetdta(odta);
}


/*
 *	get_file_spec()
 *
 *	Input:
 *		file_spec - the command-line file list
 *		i - index into the command-line file list
 *	Output:
 *		buf - the next token from the command line file list
 *	Comments:
 *		gets the next token from the command-line file list
 */

void get_file_spec(char *buf, char *file_spec[], int i)
{
	if (Options & O_FROM_STDIN){
		strcpy(buf, Stdin_list_current->string);
		Stdin_list_current = Stdin_list_current->next;
	} else if (Options & O_FROM_FILE){
		fgets(buf, 79, From_file_ptr);
		zap_trailing_nl(buf, 79, From_file_ptr); /* clobber newline */
	} else {
		strcpy(buf, file_spec[i]);
	}
}


/*
 *	add_entry()
 *
 *	Input:
 *		fblk - structure containing the file name, date, and time
 *	Output:
 *		hash_tab - add entry to this array of pointers
 *	Comments:
 *		ands a file name along with its date and time to a hash table
 */

void add_entry(struct _dta *fblk, ENTRY *hash_tab[])
{
	int bucket;
	ENTRY *ptr;

	bucket = hashpjw(fblk->dta_name);
	if (hash_tab[bucket] == NULL){
		if ((hash_tab[bucket] = (ENTRY *)malloc(sizeof(ENTRY))) ==
			NULL){
			fprintf(stderr, "out of memory");
			exit(-1);
		}
		hash_tab[bucket]->next = NULL;
	} else {
		if ((ptr = (ENTRY *)malloc(sizeof(ENTRY))) == NULL){
			fprintf(stderr, "out of memory");
			exit(-1);
		}
		ptr->next = hash_tab[bucket];
		hash_tab[bucket] = ptr;
	}
	strcpy(hash_tab[bucket]->name, fblk->dta_name);
	hash_tab[bucket]->attr = fblk->dta_attribute;
	hash_tab[bucket]->copied = 0;
	hash_tab[bucket]->fdate = fblk->dta_date;
	hash_tab[bucket]->ftime = fblk->dta_time;
}

/*
 *	get_first()
 *
 *	Input:
 *		none
 *	Output:
 *		index - index to first hash table bucket. Each bucket is a
 *			linked-list of structures, one for each file.
 *		Ptr - pointer to first hash table file entry
 *		returns - the name of the first file in the hash table
 *	Comments:
 *		selects the proper hash table and finds its first entry
 */


char *get_first(int *index)
{
	*index = 0;

	Ptr = (Options & O_TARGET_DIR) ? Dst_tab[0] : Src_tab[0];
	move_Ptr(index);

	return (Ptr == NULL) ? NULL : Ptr->name;
}

/*
 *	get_next()
 *
 *	Input:
 *		index - index to current hash-table bucket. Each bucket is a
 *			linked-list of structures, one for each file.
 *		Ptr - pointer to current hash-table file entry
 *	Output:
 *		index - index to next hash table bucket (may not be different
 *			from current bucket).
 *		Ptr - pointer to next hash table file entry
 *		returns - the name of the next file in the hash table
 *		
 *	Comments:
 *		finds the next entry in the currently selected hash table
 *		of file names
 */

char *get_next(int *index)
{
	if (Ptr != NULL)
		Ptr = Ptr->next;
	move_Ptr(index);
	return (Ptr == NULL) ? NULL : Ptr->name;
}

/*
 *	move_Ptr()
 *
 *	Input:
 *		index - index to current hash-table bucket. Each bucket is a
 *			linked-list of structures, one for each file.
 *		Ptr - pointer to hash table file entry
 *	Output:
 *		index - index to hash-table bucket.
 *		Ptr - pointer to hash table file entry
 *	Comments:
 *		finds the next non-NULL entry, only if the hash-table 
 *			pointer is NULL
 */

void move_Ptr(int *index)
{
	if (Ptr == NULL)
		for ((*index)++; (*index < HASH_TAB_SIZE); (*index)++)
			if ((Ptr = ((Options & O_TARGET_DIR) ? Dst_tab[*index]
												 : Src_tab[*index])) != NULL)
				break;
}

/*
 * clear_archive_bits
 *
 * given a source directory, go through the hash table (up to the value
 * of Ptr on entry to this function) and clear the archive bits of all
 * file entries.  This has the desired effect of clearing archive bits
 * of all source files which have been copied.
 */
void clear_archive_bits(char *src_dir)

{
	ENTRY *Org_Ptr = Ptr;
	char source_file[FILENAME_MAX], *cur_name;
	int index, n;

	if ((Options & O_ARCHIVE) && (!(Options & O_CHECK))) {
		n = strlen(src_dir);
		strcpy(source_file, src_dir);
		cur_name = get_first(&index);
		while (Ptr != NULL) {
			if (Ptr->copied) {
				strcat(source_file, cur_name);
				Fattrib(source_file, 1, ((Ptr->copied)&(~FA_CHANGED)));
				source_file[n] = '\0';
			}
			if (Org_Ptr == Ptr)
				break;
			else
				cur_name = get_next(&index);
		}
	}
}

/*
 *	clear_hash_tab()
 *
 *	Input:
 *		hash_tab - the hash table to be cleared
 *	Output:
 *		hash_tab - with all its entries cleared and all its
 *			buckets set to NULL
 *	Comments:
 *		removes all entries from a hash table
 */

void clear_hash_tab(ENTRY *hash_tab[])
{
	int i;
	ENTRY *ptr, *temp;

	for (i = 0; i < HASH_TAB_SIZE; i++){

		ptr = hash_tab[i];
		while (ptr != NULL){
			temp = ptr;
			ptr = ptr->next;
			free(temp);
		}
		hash_tab[i] = NULL;
	}
}

/*
 *	remove_entry()
 *
 *	Input:
 *		file - the name of the file to be removed
 *		hash_tab - the hash table from which the file is to be
 *			removed
 *	Output:
 *		hash_tab - the hash table with the file removed
 *	Comments:
 *		removes one entry from a hash table
 */

void remove_entry(char *file, ENTRY *hash_tab[])
{
	int bucket;
	ENTRY *ptr, *temp;
	ENTRY **lastptr;

	bucket = hashpjw(file);
	if (hash_tab[bucket] != NULL) {
		ptr = hash_tab[bucket];
		lastptr = &(hash_tab[bucket]);
		while(ptr != NULL){
			if (!strcmp(file, ptr->name)){
				*lastptr = ptr->next;
				free(ptr);
				break;
			}
			lastptr = &(ptr->next);
			ptr = ptr->next;
		}
	}
}


/*
 *	find_entry()
 *
 *	Input:
 *		file - the name of the file to be found
 *		hash_tab - the hash table which is to be searched
 *	Output:
 *		returns - a pointer to the entry in the hash table,
 *			or NULL if not found
 *	Comments:
 *		finds an entry in the hash table
 */
  
ENTRY *find_entry(char *file, ENTRY *hash_tab[])
{
	int bucket;
	ENTRY *ptr, *temp;

	bucket = hashpjw(file);
	if (hash_tab[bucket] == NULL){
		ptr = NULL;
	} else {
		ptr = hash_tab[bucket];
		while(ptr != NULL){
			if (!strcmp(file, ptr->name)){
				break;
			}
			ptr = ptr->next;
		}
	}
	return(ptr);
}

/*
 *	copy_sub_dirs()
 *
 *	Input:
 *		src_dir - the source directory
 *		dst_dir - the destination directory
 *		file_spec - the command-line file list
 *		num_args - the number of arguments in the file list
 *		file_buf - the buffer for reading and writing files
 *		buf_size - the size of 'file_buf'
 *	Comments:
 *		copies the files in the sub-directories if the recursive
 *		mode is specified
 */

void copy_sub_dirs(char *src_dir, char *dst_dir, char *file_spec[],
				   int num_args, void *file_buf, unsigned long buf_size)

{
	int done;
	char ref_list[FILENAME_MAX];
	struct _dta *odta, fblk;

	if (Options & O_RECURSIVE){
		strcpy(ref_list, src_dir);
		strcat(ref_list, "*.*");
		odta = (struct _dta *) Fgetdta();
		Fsetdta(&fblk);

		done = Fsfirst(ref_list, FA_DIR);
		while (!done){
			if ((fblk.dta_attribute & FA_DIR) && (!is_special(fblk.dta_name))) {
				char new_src_dir[FILENAME_MAX], new_dst_dir[FILENAME_MAX];
				if (should_dir_copy(src_dir, dst_dir,  fblk.dta_name,
									new_src_dir, new_dst_dir)){
					clear_hash_tab(Src_tab);
					clear_hash_tab(Dst_tab);
					copy_files(new_src_dir, new_dst_dir,
							   file_spec, num_args,
							   file_buf, buf_size);
				}
			}
			done = Fsnext();
		}
		Fsetdta(odta);
	}
}

/*
 *	should_dir_copy()
 *
 *	Input:
 *		src_dir - the full path of the current source directory
 *		dst_dir - the full path of the current destination directory
 *		name - the name of the sub-directory
 *	Output:
 *		new_src_dir - the full path of the source sub-directory
 *		new_dst_dir - the full path of the destination sub-directory
 *		returns - 1 if sub-directory should be copied
 *			  0 if sub-directory should NOT be copied
 *	Comments:
 *		determines whether a sub-directory should be copied
 */

int should_dir_copy(char *src_dir, char *dst_dir, char *name,
					char *new_src_dir, char *new_dst_dir)

{
	struct stat src_buf, dst_buf;
	int ret_src, ret_dst;
	int status;
	int ret_val;

	strcpy(new_src_dir, src_dir);
	strcat(new_src_dir, name);

	strcpy(new_dst_dir, dst_dir);
	if (Options & O_GATHER)
		new_dst_dir[strlen(new_dst_dir) - 1] = '\0'; /* chop slash */
	else
		strcat(new_dst_dir, name);

	ret_src = stat(new_src_dir, &src_buf);
	ret_dst = stat(new_dst_dir, &dst_buf);

	if (ret_src == -1) /* sub dir does not exist in source dir */
		ret_val = 0;
	else if (!(src_buf.st_mode & S_IFDIR)) /* src dir is a file */
		ret_val = 0;
	else if (Options & (O_GATHER|O_CHECK))
		ret_val = 1;
	else if (ret_dst == -1) { /* destination dir does not exist */
		status =  mkdir(new_dst_dir, 0);
		if (status) {
			fprintf(stderr, "unable to create directory\n");
			exit(-1);
		}
		ret_val = 1;
	} else
		ret_val = 1;

	strcat(new_src_dir, "\\");
	strcat(new_dst_dir, "\\");

	return(ret_val);
}

/*
 *	should_file_copy()
 *
 *	Input:
 *		file - name of file to be copied
 *		src_file - full path of file to be copied
 *	Output:
 *		returns: 1 if file should be copied/moved
 *			 0 if file should NOT be copied/moved
 *	Comments:
 *		looks up the file name in the source and destination
 *		hash tables and determines whether a file should be
 *		copied/moved
 */

int should_file_copy(char *file, char *src_file)

{
	ENTRY *src, *dst;

	/* source does not exit */
	if ((src = find_entry(file, Src_tab)) != NULL) {
		if (Options & O_DATE_CHECK)
			if (!(within_date_range(src)))
				return 0;

		if (Options & O_ARCHIVE)
			if (!(src->attr & FA_CHANGED))
				return 0;
  
		if ((dst = find_entry(file, Dst_tab)) != NULL) {

			if ((Options & (O_CP_IF_SRC_NEWER|O_COPY_IF_SRC_OLDER)) ==
				(O_CP_IF_SRC_NEWER|O_COPY_IF_SRC_OLDER))
				return 0;

			if (Options & O_CP_IF_SRC_NEWER)
				if (cmptime_entry(src, dst) <= 0L)
					return 0;

			if (Options & O_COPY_IF_SRC_OLDER)
				if (cmptime_entry(src, dst) >= 0L)
					return 0;

		}
	}
	else /* source file missing?  of course don't copy (something's fishy!) */
		return 0;

	if (Options & O_INTERACTIVE) {
		char buf[MAXPATH + 20];

		sprintf(buf, "copy %s (Y/N/Q) ? ", src_file);
		return ask_user(buf);	/* user has the final say-so */
	}

	return 1;
}

/*
 * cmptime_entry
 *
 * given two files, return positive if the first has a more recent modification
 * date/time, zero if the files have the same modification date/time or
 * negative if the second is more recent.
 */
long cmptime_entry(ENTRY *a, ENTRY *b)
{
	return (((unsigned long)a->fdate) << 16 | (unsigned long)a->ftime) -
		   (((unsigned long)b->fdate) << 16 | (unsigned long)b->ftime);
}

int within_date_range(ENTRY *src)
{
	int retval = 1;
	DATE_NODE *d = Fdate;
	TIME_NODE *t = Ftime;
	int saw_after_or_before = 0;

	/* AND the 'before' and 'after' modes */
	while (d != NULL){
		if ((d->mode & D_BEFORE) || (d->mode & D_AFTER))
			saw_after_or_before = 1;
		if (((d->mode & D_BEFORE) && (src->fdate >= d->fdate)) ||
			((d->mode & D_AFTER) && (src->fdate <= d->fdate))){
				retval = 0;
		}
		d = d->next;
	}

	if (!saw_after_or_before)
		retval = 0;
	/* OR the 'on' modes */
	d = Fdate;
	while (d != NULL){
		if ((d->mode & D_ON) && (src->fdate == d->fdate))
			retval = 1;
		d = d->next;
	}

	/* AND the 'before' and 'after' modes */
	if (retval)
		while (t != NULL){
			if (((t->mode & D_BEFORE) &&
				(src->ftime >= t->ftime)) ||
				((t->mode & D_AFTER) &&
				(src->ftime <= t->ftime)))
					retval = 0;
			t = t->next;
		}

	return retval;
}

/*
 *	file_exists()
 *
 *	Input:
 *		name - full path of file
 *	Output:
 *		returns: 1 if file exists
 *			 0 if file does NOT exist
 */

int file_exists(char *name)
{
	return (access(name, 0) == 0);
}

/*
 *	get_file_buf()
 *
 *	Input:
 *	Output:
 *		buf_size - size of buffer
 *		returns - pointer to buffer
 */

void *get_file_buf(unsigned long *buf_size)
{
	void *buf_mem;

	*buf_size = MAX_BUF;
	do {
		if ((buf_mem = malloc(*buf_size)) != NULL)
			break;
		*buf_size /= 2;
	} while (*buf_size >= 512);

	if (buf_mem == NULL){
		fprintf(stderr, "could not allocate file buffer\n");
		exit(-1);
	}

	return(buf_mem);
}

/*
 *	copy_file()
 *
 *	Input:
 *		ptr - pointer to hash table entry for file
 *		dst_file - full path of destination file
 *		src_file - full path of source file
 *		buf_size - size of buffer for file i/o
 *		File_buf - buffer for file i/o
 *		Buf_ptr - pointer to next available memory in i/o buffer
 *		Reading_flag - indicates whether 'reading:' has been printed
 *	Comments:
 *		copies source file to destination file
 */

void copy_file(ENTRY *ptr, char *dst_file, char *src_file,
			   unsigned long buf_size)

{
	int src_handle, dst_handle;
	long bytes;
	int retval;
	long bytes_needed, bytes_left;
	struct stat stat_buf;
	_DOSTIME ftime_buf;
	char fattr;

	if ((Options & O_MOVE) && (*src_file == *dst_file)){
		if (!Reading_flag && (Options & O_VERBOSE)){
			fprintf(stdout, "renaming file:\n");
			fprintf(stdout, "\t%s -> %s\n", src_file, dst_file);
			Reading_flag = 0;
		}
		if (rename_file(ptr, src_file, dst_file))
			clean_up_and_exit();
	} else {
		if ((src_handle = open(src_file, O_RDONLY, 0)) < 0){
			fprintf(stderr, "unable to open file '%s'\n", src_file);
			clean_up_and_exit();
		}
		Fdatime(&ftime_buf, src_handle, 0);
		fstat(src_handle, &stat_buf);
		fattr = Fattrib(src_file, 0, 0);
		bytes_needed = sizeof(ENTRY *) + (strlen(src_file) + 1) +
			(strlen(dst_file) + 1) + sizeof(stat_buf.st_size) +
				stat_buf.st_size + sizeof(_DOSTIME) +
					sizeof(fattr);
		bytes_left = (char *)File_buf - (char *)Buf_ptr + buf_size;

		if ((bytes_needed > MAX_BUF) || (Options & O_INTERACTIVE)) {
			if (Buf_ptr != File_buf)
				write_buffer(File_buf);
			copy_file_unbuffered(src_handle, ptr, src_file, dst_file,
								 stat_buf.st_size, &ftime_buf, fattr,
								 buf_size);
		} else {
			if (bytes_left < bytes_needed)
				write_buffer(File_buf);
			if (!Reading_flag && (Options & O_VERBOSE)){
				fprintf(stdout, "reading:\n");
				Reading_flag = 1;
			}
			if (Options & O_VERBOSE)
				fprintf(stdout, "\t%s\n", src_file);
			copy_file_to_buffer(src_handle, ptr, src_file, dst_file,
								&ftime_buf, stat_buf.st_size, fattr);
		}
		close(src_handle);
	}
}

/*
 *	rename_file()
 *
 *	Input:
 *		ptr - pointer to hash table entry (so we can mark this as 'copied')
 *		src_file - source file name (full path)
 *		dst_file - destination file name (full path)
 *	Comments:
 *		renames a file. if the destination file already exits,
 *		then it is deleted.
 */

int rename_file(ENTRY *ptr, char *src_file, char *dst_file)

{
	int old_attrib;

	if ((old_attrib = Fattrib(src_file, 0, 0)) < 0) {
		fprintf(stderr, "cannot move file %s\n", src_file);
		return -1;
	}
	if (rename(src_file, dst_file) == -1){
		if (file_exists(dst_file))
			if (delete_file(dst_file, '\0')){
				if ((!(Options & O_BATCH)) &&
					(printf("Target %s protected - ", dst_file),
					 !(ask_user("force move onto it (Y/N/Q)? ")))){
					fprintf(stderr, "%s NOT moved to %s\n", src_file,
							dst_file);
					return -1;
				}
				if (delete_file(dst_file, '\1')){
					fprintf(stderr, "could not remove %s\n", dst_file);
					return -1;
				}
			}

		if (old_attrib & FA_RDONLY)
			Fattrib(src_file, 1, old_attrib&(~FA_RDONLY));
		if (rename(src_file, dst_file) == -1){
			if (old_attrib & FA_RDONLY)
				Fattrib(src_file, 1, old_attrib);
			fprintf(stderr, "cannot move file %s\n", src_file);
			return -1;
		}
	}

	Fattrib(dst_file, 1, old_attrib);
	Copied_a_file = 1;
	ptr->copied = (old_attrib) ? old_attrib : FA_CHANGED;

	return 0;
}

/*
 *	copy_file_to_buffer()
 *
 *	Input:
 *		src_handle - source file handle
 *		src_file - source file name (full path)
 *		dst_file - destination file name (full path)
 *		st_buf - stat buffer of source file
 *		attrib - file access mode (contains hidden)
 *	Output:
 *		Buf_ptr - pointer to unused position in buffer
 *	Comments:
 *		copies the source file along with a header to memory.
 *		the header contains source-file name, dest-file name,
 *		source-file date, source-file size, and the source-file
 *		modes.
 */
void copy_file_to_buffer(int src_handle, ENTRY *ptr,
						 char *src_file, char *dst_file,
						 _DOSTIME *ftime_buf, long fsize, char fattr)

{
	memcpy((char *)Buf_ptr, &ptr, sizeof(ENTRY *));
	Buf_ptr = (char *)Buf_ptr + sizeof(ENTRY *);
	strcpy((char *)Buf_ptr, src_file);
	Buf_ptr = (char *)Buf_ptr + strlen(src_file) + 1;
	strcpy((char *)Buf_ptr, dst_file);
	Buf_ptr = (char *)Buf_ptr + strlen(dst_file) + 1;
	memcpy(Buf_ptr, &fsize, sizeof(fsize));
	Buf_ptr = (char *) Buf_ptr + sizeof(long);
	memcpy(Buf_ptr, ftime_buf, sizeof(_DOSTIME));
	Buf_ptr = (char *) Buf_ptr + sizeof(_DOSTIME);
	memcpy(Buf_ptr, &fattr, sizeof(char));
	Buf_ptr = (char *) Buf_ptr + sizeof(char);
	read(src_handle, Buf_ptr, fsize);
	Buf_ptr = (char *) Buf_ptr + fsize;
}

/*
 *	copy_file_unbuffered()
 *
 *	Input:
 * 		src_handle - handle of source file
 *		src_file - name of source file (full path name)
 *		dst_file - name of destination file (full path name)
 *		buf_size - size of file buffer
 *		Reading_flag - indicates whether 'reading:' has been printed
 *		File_buf - buffer for file i/o
 *	Output:
 *		Reading_flag - reset to indicate that the message
 *			'reading and writing file:' has been printed.
 *	Comments:
 *		copies the source file to the destination file without
 *		buffering (as is done with smaller files).  the file modes
 *		and file date are also copied.
 */

void copy_file_unbuffered(int src_handle, ENTRY *ptr,
						  char *src_file, char *dst_file,
						  long fsize, _DOSTIME *ftime_buf, char fattr,
						  unsigned long buf_size)
{
	unsigned bytes = 0;
	int dst_handle;
	int retval;

	do {
		Retry = 0;
		if (open_dest_file(&dst_handle, dst_file,
						   (fattr & FA_RDONLY) ? S_IREAD : S_IREAD | S_IWRITE))
			return;
		if (!Reading_flag && (Options & O_VERBOSE)){
			fprintf(stdout, "reading and writing file:\n");
			fprintf(stdout, "\t%s -> %s\n", src_file, dst_file);
			Reading_flag = 0;
		}

		if (!(Options & O_LARGEFILES))
			lseek(src_handle, 0L, SEEK_SET);
		copy_file_contents(src_handle, dst_handle, src_file, dst_file, 
						   buf_size);
	} while (Retry);

	if (Options & O_MOVE)
		delete_file(src_file, '\0');
	Copied_a_file = 1;
	Fdatime(ftime_buf, dst_handle, 1);
	close(dst_handle);
	Fattrib(dst_file, 1, fattr);
	ptr->copied = (fattr) ? fattr : FA_CHANGED;
}

/*
 * ensure_dest_dir_exist
 *
 * given a destination file name, first ensure the existence of the directory
 * which will contain it.  This function exists to make a target-disk swap work
 * together with the recursive option.  Return non-zero on success, zero on
 * failure.
 */
int ensure_dest_dir_exist(char *dst_file)

{
	int stat_err, ret_val = 1;
	struct stat statbuf;
	char *p;

	if ((p = strrchr(dst_file, '\\')) == NULL)
		return 0;
	*p = '\0';
	if ((!(stat_err = stat(dst_file, &statbuf))) &&
		(!(statbuf.st_mode & S_IFDIR)))
		ret_val = 0;
	else if (stat_err)
		create_dir(dst_file);
	*p = '\\';

	return ret_val;	/* if 'create_dir()' returns, it was successful */
}

/*
 * target_disk_full
 *
 * called when the target disk is full.  Depending on the options, it may
 * be possible to change target disks and go on.
 */
void target_disk_full(char *dst_file, int dst_handle)

{
	close(dst_handle);
	if (!(Options & O_LARGEFILES))
		delete_file(dst_file, '\1');
	if (ok_to_retry())
		if (ask_user("out of disk space, continue (Y/N/Q) ? ")) {
			if (Options & O_ZAPTARGET)
				zap_target(Org_dest_dir, 1);
			Retry = 1;
		}
		else
			clean_up_and_exit();
	else {
		fprintf(stderr, "out of disk space\n");
		clean_up_and_exit();
	}
}

/*
 * clean_up_and_exit
 *
 * error occurred (like write error/target full).  Clean up (clear archive
 * bits of source files already copied) and exit.
 */
void clean_up_and_exit()

{
	clear_archive_bits(Cur_source_dir);
	exit(-1);
}

/*
 *	copy_file_contents()
 *
 *	Input:
 * 		src_handle - handle of source file
 * 		src_handle - handle of destination file
 *		src_file - name of source file (full path name)
 *		dst_file - name of destination file (full path name)
 *		buf_size - size of file buffer
 *		File_buf - buffer for file i/o
 *	Comments:
 *		copies the contents of the source file to the destination 
 *		file.
 */

void copy_file_contents(int src_handle, int dst_handle, char *src_file, 
						char *dst_file, unsigned long buf_size)

{
	long last_read_pos, bytes_read, bytes_written;

	while(1){
		last_read_pos = tell(src_handle);
		bytes_read = read(src_handle, File_buf, buf_size);
		if (bytes_read == -1L){
			fprintf(stderr, "Can't read file '%s'\n", src_file);
			exit(-1);
		}
		if (bytes_read){
			bytes_written = write(dst_handle, File_buf,
								  (unsigned long) bytes_read);
			if (bytes_written == -1L){
				fprintf(stderr, "Can't write to file '%s'\n",
					dst_file);
				exit(-1);
			}
		} else
			break;
		if (bytes_read != bytes_written) {
			if (Options & O_LARGEFILES)
				lseek(src_handle, last_read_pos+bytes_written, SEEK_SET);
			target_disk_full(dst_file, dst_handle);
			break;
		}
	}
}

/*
 *	ok_to_retry()
 *
 *	Input:
 *		none
 *	Comments:
 *		checks if the target directory was used to determine the file
 *		list.  If it was, then the copy is aborted when disk is full
 */

int ok_to_retry()

{
	return ((!(Options & O_BATCH)) && (Options & O_TARGET_REMOVEABLE) &&
			(!(Options &
			   (O_TARGET_DIR | O_COPY_IF_SRC_OLDER | O_CP_IF_SRC_NEWER))));
}

void setftime(char *fname, struct stat *statbuf)

{
	struct utimbuf ftime;

	ftime.actime = statbuf->st_atime;
	ftime.modtime = statbuf->st_mtime;
	utime(fname, &ftime);
}

/*
 *	write_buffer()
 *
 *	Input:
 *		file_buf - memory buffer containing file contents along
 *			with their headers
 *		File_buf - starting location of memory buffer
 *	Output:
 *		Reading_flag - reset to indicate recent output of message,
 *			'writing:'
 *		Buf_ptr - location of available memory in memory buffer
 *	Comments:
 *		copies all the files contained in the memory buffer to their
 *		destination files
 */

void write_buffer(void *file_buf)

{
	ENTRY *ptr;
	int dst_handle;
	unsigned retval;
	char src_file[FILENAME_MAX];
	char dst_file[FILENAME_MAX];
	_DOSTIME ftime_buf;
	long fsize;
	char fattr;

	if ((Options & O_VERBOSE) && (file_buf < Buf_ptr))
		printf("writing:\n");
	Reading_flag = 0;
	while (file_buf < Buf_ptr){
		file_buf = get_header_info(file_buf, &ptr, src_file, dst_file,
								   &ftime_buf, &fsize, &fattr);
		if (!(Options & O_CHECK))
			if (!write_dest_file(&dst_handle, dst_file, file_buf, fsize,
								 fattr)) {
				if (Options & O_MOVE)
					delete_file(src_file, '\0');
				Copied_a_file = 1;
				Fdatime(&ftime_buf, dst_handle, 1);
				close(dst_handle);
				Fattrib(dst_file, 1, fattr);
				ptr->copied = (fattr) ? fattr : FA_CHANGED;
			}
		file_buf = (char *)file_buf + fsize;
	}
	Buf_ptr = File_buf;
}

/*
 *	get_header_info()
 *
 *	Input:
 *		file_buf - pointer to memory buffer containing file 
 *			contents and header
 *	Output:
 *		src_file - source file name (full path)
 *		dst_file - destination file name (full path)
 *		stat_buf - file status buffer
 *		mode - mode settings of source file
 *		returns - location of next file in memory buffer
 *	Comments:
 *		gets a file's header information from the memory buffer
 */

void *get_header_info(void *file_buf, ENTRY **ptr,
					  char *src_file, char *dst_file, _DOSTIME * ftimebuf,
					  long *fsize, char *fattr)
{
	memcpy(ptr, (char *)file_buf, sizeof(ENTRY *));
	file_buf = (char *)file_buf + sizeof(ENTRY *);
	strcpy(src_file, (char *)file_buf);
	file_buf = (char *)file_buf + strlen((char *)file_buf) +  1;
	strcpy(dst_file, (char *)file_buf);
	file_buf = (char *)file_buf + strlen((char *)file_buf) +  1;
	memcpy(fsize, file_buf, sizeof(long));
	file_buf = (char *) file_buf + sizeof(long);
	memcpy(ftimebuf, file_buf, sizeof(_DOSTIME));
	file_buf = (char *) file_buf + sizeof(_DOSTIME);
	memcpy(fattr, file_buf, sizeof(char));
	file_buf = (char *) file_buf + sizeof(char);
	return(file_buf);
}

/*
 *	write_dest_file()
 *
 *	Input:
 *		dst_handle - handle of file to be written
 *		dst_file - full-path name of file to be written
 *		file_buf - memory buffer containing file contents
 *		file_size - size of file to be written
 *		fattr - file attributes
 *	Comments:
 *		copies a file's contents from a memory buffer to a disk file.
 *		Returns 0 normally, 1 if the destination couldn't be opened for write.
 */

int write_dest_file(int *dst_handle, char *dst_file, void *file_buf,
					long file_size, int fattr)

{
	long bytes_written, written_so_far = 0L;

	do {
		Retry = 0;
		if (Options & O_VERBOSE)
			printf("\t%s\n", dst_file);
		if (open_dest_file(dst_handle, dst_file, ((fattr & FA_RDONLY)
												   ? S_IREAD
												   : S_IREAD | S_IWRITE)))
			break;
		bytes_written = write(*dst_handle, file_buf+written_so_far,
							  (unsigned long) file_size-written_so_far);
		if (bytes_written == -1L){
			fprintf(stderr, "Can't write to file '%s'\n",  dst_file);
			exit(-1);
		}
		if (bytes_written != (file_size-written_so_far)) {
			if (Options & O_LARGEFILES)
				written_so_far += bytes_written;
			target_disk_full(dst_file, *dst_handle);
		}
		else
			return 0;
	} while (Retry);

	return 1;
}

/*
 *	open_dest_file()
 *
 *	Input:
 *		dst_handle - handle of file to be opened
 *		dst_file - full-path name of file to be opened
 *		mode - mode settings of file to be opened
 *	Comments:
 *		opens a file for write.  If not successful because file is read-only,
 *		tries to recover.
 */

int open_dest_file(int *handle, char *name, unsigned modes)
{
	if ((*handle = open((char *)name,
						(Options & O_JOIN) ? O_WRONLY | O_APPEND | O_CREAT
						                   : O_WRONLY | O_TRUNC | O_CREAT,
						(unsigned)modes)) < 0){
		struct stat statbuf;

		if (stat(name, &statbuf)) {
			ensure_dest_dir_exist(name);
			if ((*handle = open((char *)name,
								(Options & O_JOIN)
								  ? O_WRONLY | O_APPEND | O_CREAT
								  : O_WRONLY | O_TRUNC | O_CREAT,
								(unsigned)modes)) < 0){
				fprintf(stderr, "unable to open file '%s' for write\n", name);
				return -1;
			}
		}
		else if ((Options & O_BATCH) ||
				 (printf("file '%s' is not writeable.\n", name),
				  ask_user("Attempt to delete and overwrite it (Y/N/Q) ? "))) {
				delete_file(name, '\1');
				if ((*handle = open((char *)name,
									(Options & O_JOIN)
									  ? O_WRONLY | O_APPEND | O_CREAT
									  : O_WRONLY | O_TRUNC | O_CREAT,
									(unsigned)modes)) < 0) {
					fprintf(stderr, "unable to open file '%s' for write\n",
							name);
					return -1;
				}
			}
		else {
			printf("Skipped copy to '%s' (open for write failed).\n",
				   name);
			return -1;
		}
	}

	return 0;
}

/*
 *	usage()
 *
 *	Comments:
 *		outputs brief instructions on the proper use of docp
 */

void usage()
{
	fprintf(stderr, USAGE_MESS);
	exit(-1);
}

/*
 *	get_flags()
 *
 *	Input:
 *		argv - command line arguments
 *		argc - count of the command line arguments
 *	Output:
 *		From_file_ptr - pointer to file containing file list
 *	Comments:
 *		parses the flags specified on the command line, and sets
 *		the appropriate bit fields in a variable called 'options'
 */

void get_flags(char *argv[], int argc)

{
	int c;
	char from_file[FILENAME_MAX];

	while ((c = getopt(argc, argv, GET_OPT_LIST)) != EOF) {
		if (isupper(c))
			c = tolower(c);
		switch(c){
			case 'a': /* copy using archive bit */
				Options |=  O_ARCHIVE;
				break;
			case 'b': /* batch mode - don't ask questions */
				Options |=  O_BATCH;
				break;
			case 'c': /* check mode, don't copy files */
				Options |=  O_VERBOSE | O_X_VERBOSE | O_CHECK;
				break;
			case 'd': /* date check */
				Options |=  O_DATE_CHECK;
				set_Fdate(optarg);
				break;
			case 'f': /* file list from file */
				strcpy(from_file, optarg);
				if (!strcmp(from_file, "-"))
					Options |= O_FROM_STDIN;
				else {
					Options |= O_FROM_FILE;
					if ((From_file_ptr = fopen(from_file, "r"))
						== NULL){
						fprintf(stderr, "unable to open '-f' file '%s'\n", from_file);
						exit(-1);
					}
				}
				break;
			case 'g': /* gather files into one directory */
				Options |= O_GATHER | O_RECURSIVE;
				break;
			case 'h': /* copy hidden files as well */
				Options |= O_COPY_HIDDEN;
				break;
			case 'i': /* interactive confirm */
				Options |= O_INTERACTIVE;
				break;
			case 'j': /* join files */
				Options |= O_JOIN;
				break;
			case 'l': /* large files (split) */
			    Options |= O_LARGEFILES;
				break;
			case 'm': /* move mode */
				Options |= O_MOVE;
				break;
			case 'n': /* no action */
				Options |= O_CP_IF_SRC_NEWER;
				break;
			case 'o': /* copy if source is older */
				Options |= O_COPY_IF_SRC_OLDER;
				break;
			case 'r': /* update subdirectories */
				Options |= O_RECURSIVE;
				break;
			case 's': /* reference list from source */
				Options |= O_SOURCE_DIR;
				break;
			case 't':  /* get file list from target */
				Options |= O_TARGET_DIR;
				break;
			case 'v': /* verbose */
				if (Options & O_VERBOSE)
					Options |= O_X_VERBOSE;
				else
					Options |= O_VERBOSE;

				break;
			case 'w': /* time check */
				Options |=  O_TIME_CHECK;
				set_Ftime(optarg);
				break;
			case 'z': /* zap the target before copying */
			    Options |= O_ZAPTARGET;
				break;
			case '?': /* documentation */
				show_doc();
				break;
			case '\0':
				usage();
				break;
			default:
				break;
		}
	}
	set_defaults();
}

/*
 *	set_defaults()
 *
 *	Input:
 *		none
 *	Output:
 *		none
 *	Comments:
 *		sets the default command-line flags
 */

void set_defaults()

{
	if (!(Options & 
		(O_ARCHIVE | O_COPY_IF_SRC_OLDER | O_CP_IF_SRC_NEWER |
		 O_DATE_CHECK | O_TIME_CHECK)))
		Options |= O_COPY_ALL;

	if (!(Options & (O_SOURCE_DIR | O_TARGET_DIR)))
		Options |= O_SOURCE_DIR;
	if ((Options & O_TIME_CHECK) && !(Options & O_DATE_CHECK)){
		Options |= O_DATE_CHECK;
		set_todays_date();
	}
}

/*
 *	check_flags()
 *
 *	Input:
 *		none
 *	Comments:
 *		terminates if the command-line flags are inconsistent
 */

void check_flags()
{
	if ((Options & O_SOURCE_DIR) && (Options & O_TARGET_DIR)){
		fprintf(stderr, "specify only one of '-s' and '-t' options\n");
		exit(-1);
	}

	if ((Options & O_BATCH) && (Options & O_INTERACTIVE)) {
		fprintf(stderr, "specify only one of '-b' and '-i' options\n");
		exit(-1);
	}
}

/*
 *	show_doc()
 *
 *	Comments:
 *		outputs expanded description of docp
 */

void show_doc()
{
	fprintf(stdout, FULL_DOC1);
	fprintf(stdout, FULL_DOC2);
	fprintf(stdout, FULL_DOC3);
	fprintf(stdout, FULL_DOC4);
	exit(0);
}

/*
 *	set_Ftime()
 *
 *	Input:
 *		time_str - time string following '-w' option. includes relation
 *			followed by time (e.g. "a3:15pm")
 *	Output:
 *		Ftime - global with reference time
 *		Time_check_mode - global with relationship to reference time
 *	Comments:
 *		takes time string specified in '-w' option and stores the
 *		time in Ftime and the relationship in Time_check_mode.
 */

void set_Ftime(char *time_str)
{
	TIME time;
	int i;
	char *time_arg = time_str;

	time.mode = 0;
	while ((*time_str == 'o') || (*time_str == 'b') || (*time_str == 'a'))
		switch(*time_str){
			case 'o':
				time_str++;
				time.mode |= D_ON;
				break;
			case 'b':
				time_str++;
				time.mode |= D_BEFORE;
				break;
			case 'a':
				time_str++;
				time.mode |= D_AFTER;
				break;
			default:
				break;
		}
	time.hour =  atoi(time_str);
	if ((time_str = get_field(time_str, (int *)&(time.min))) == NULL)
		bad_time(time_arg);
	while (*time_str != '\0')
		if (((*time_str == 'p') || (*time_str == 'P')) &&
			(time.hour < 13)){
			time.hour += 12;
			break;
		} else time_str++;

	check_time(&time, time_arg);
	store_time(&time);
}

/*
 *	store_time()
 *
 *	Input:
 *		time - structure containing time
 *	Output:
 *		Ftime - time in form returned by findnext()
 *	Comments:
 *		converts time to form returned by findnext()
 */

void store_time(TIME *time)
{
	TIME_NODE *d;

	if ((d = (TIME_NODE *)malloc(sizeof(TIME_NODE))) == NULL){
		fprintf(stderr, "out of memory");
		exit(-1);
	}
	d->ftime = (time->min << 5) | (time->hour << 11);
	d->mode = time->mode;
	d->next = Ftime;
	Ftime = d;
}

/*
 *	check_time()
 *
 *	Input:
 *		time - structure containing time
 *	Comments:
 *		does a crude check on the time entered
 */

void check_time(TIME *time, char *time_arg)
{
	if ((time->hour < 1) || (time->hour > 24) ||
		 (time->min < 0) || (time->min > 60))
		bad_time(time_arg);
}

/*
 *	bad_time()
 *
 *	Comments:
 *		termination routine called when a bad time is found
 */

void bad_time(char *time_arg)
{
	fprintf(stderr, "bad time specified, '%s'\n", time_arg);
	exit(-1);
}

/*
 *	set_Fdate()
 *
 *	Input:
 *		date_str - date string following '-d' option. includes relation
 *			followed by date (e.g. ">=3/23/91")
 *	Output:
 *		Fdate - global with reference date
 *		Date_check_mode - global with relationship to reference date
 *	Comments:
 *		takes date string specified in '-d' option and stores the
 *		date in Fdate and the relationship in Date_check_mode.
 */

void set_Fdate(char *date_str)
{
	DATE date;
	int *p;
	int i;

	date.mode = 0;
	while ((*date_str == 'o') || (*date_str == 'b') || (*date_str == 'a')){
		switch(*date_str){
			case 'o':
				date_str++;
				date.mode |= D_ON;
				break;
			case 'b':
				date_str++;
				date.mode |= D_BEFORE;
				break;
			case 'a':
				date_str++;
				date.mode |= D_AFTER;
				break;
			default:
				break;
		}
	}

	convert_date_str(&date, date_str);
	check_date(&date, date_str);
	store_date(&date);
}

/*
 *	convert_date_str()
 *
 *	Input:
 *		date_str - date string from command line
 *	Output:
 *		date - numeric date structure
 *	Comments:
 *		converts command-line date string into numeric structure
 */

void convert_date_str(DATE *date, char *date_str)
{
	char *new_date_str;

	date->mo = atoi(date_str);
	if ((date_str = get_field(date_str, (int *)&(date->day))) == NULL)
		return;
	if ((date_str = get_field(date_str, (int *)&(date->year))) == NULL)
		return;
}


/*
 *	get_field()
 *
 *	Input:
 *		date_str - current position in date string from command line
 *	Output:
 *		date_str - new position in date string from command line
 *		date_field - numeric value of date field (day or year)
 *	Comments:
 *		gets numeric date from field of command-line date string
 */

char *get_field(char *string, int *field)
{
	char *new_string;

	new_string = strchr(string, '/');
	if (new_string == NULL)
		new_string = strchr(string, '-');
	if (new_string == NULL)
		new_string = strchr(string, ':');
	if (new_string == NULL){
		*field = 0;
	} else {
		new_string++;
		*field = atoi(new_string);
	}

	return(new_string);
}

/*
 *	set_todays_date()
 *
 *	Output:
 *		Fdate - adds today's date in date table with ON option set.
 *	Comments:
 *		stores today's date in date table.  Done when user only 
 *		specifies time option without date option.
 */

void set_todays_date()
{
	DATE date;

	get_todays_date(&date);
	date.mode = D_ON;
	store_date(&date);
}

/*
 *	get_todays_date()
 *
 *	Output:
 *		date - today's date from the operating system
 *	Comments:
 *		gets the current date from the operating system
 */

void get_todays_date(DATE *date)
{
	short cur_date = Tgetdate();

	date->year = (cur_date >> 9) + 80;
	date->mo = (cur_date >> 5) & 0xf;
	date->day = (cur_date) & 0x1f;
}

/*
 *	store_date()
 *
 *	Input:
 *		date - structure containing date
 *	Output:
 *		Fdate - date in form returned by findnext()
 *	Comments:
 *		converts date to form returned by findnext()
 */

void store_date(DATE *date)
{
	DATE_NODE *d;

	if ((d = (DATE_NODE *)malloc(sizeof(DATE_NODE))) == NULL){
		fprintf(stderr, "out of memory");
		exit(-1);
	}
	d->fdate = date->day | (date->mo << 5) | ((date->year - 80) << 9);
	d->mode = date->mode;
	d->next = Fdate;
	Fdate = d;
}

/*
 *	check_date()
 *
 *	Input:
 *		date - structure containing date
 *		date_str - date string from command line
 *	Comments:
 *		does a crude check on the date entered
 */

void check_date(DATE *date, char *date_str)
{
	if ((date->mo < 1) || (date->mo > 12) ||
		 (date->day < 1) || (date->day > 31))
		bad_date(date_str);
}

/*
 *	bad_date()
 *
 *	Input:
 *		date_str - date string from command line
 *	Comments:
 *		termination routine called when a bad date is found
 */

void bad_date(char *date_str)
{
	fprintf(stderr, "bad date specified '%s'\n", date_str);
	exit(-1);
}

/*
 *	build_stdin_file_list()
 *
 *	Output:
 *		num_args - the number of args in the stdin file list
 *	Comments:
 *		builds linked list containing file list tokens from stdin.
 */

void build_stdin_file_list(int *num_args)
{
	char s[80];

	while (fgets(s, 79, stdin) != NULL){
		zap_trailing_nl(s, 79, stdin); /* clobber newline */
		add_to_stdin_list(s);
		(*num_args)++;
	}
	if (*num_args == 0){
		add_to_stdin_list("*.*");
		*num_args = 1;
	}
}


/*
 *	add_to_stdin_list()
 *
 *	input:
 *		s - the file list token
 *      Output:
 *              Stdin_list_head - first element of file list
 *              Stdin_list_tail - last element of file list
 *	Comments:
 *		adds a file list token to a linked list
 */

void add_to_stdin_list(char *s)
{
	STDIN_TOKEN *new_token;

	if ((new_token = (STDIN_TOKEN *)malloc(sizeof(STDIN_TOKEN))) == NULL){
			fprintf(stderr, "out of memory");
			exit(-1);
	}
	if ((new_token->string = (char *)malloc(strlen(s) + 1)) == NULL){
			fprintf(stderr, "out of memory");
			exit(-1);
	}
	strcpy(new_token->string, s);
	new_token->next = NULL;
	if (Stdin_list_head == NULL){
		Stdin_list_head = Stdin_list_tail = new_token;
		
	} else {
		Stdin_list_tail->next = new_token;
		Stdin_list_tail = new_token;
	}
}
