
#include "dir.h"
#include "backup.h"

extern char *strsave ();
extern char *pop ();
extern int push ();
extern char *build_name ();
extern int in_dir ();
extern int out_of_date ();
extern struct FileLock *CreateDir ();


void *stack = NULL;
char sprbuf[ 1024 ];
struct DateStamp bdate;
int first_time;


main ( argc , argv )
int argc;
char **argv;
{
	FILE *fp;
#define LINE_SIZE	100
	char linebuf[ LINE_SIZE ];
	char *p , *parm1 , *parm2;


	first_time = FALSE;
	argc--;
	argv++;
	while ( argc > 0  &&  argv[0][0] == '-' ) {
		switch ( argv[0][1] ) {
		case 'f' :
			first_time = TRUE;
			break;
		default :
			puts ( "Unknown option %s\n" , argv[0] );
			exit ( 1 );
		}
		argc--;
		argv++;
	}
	if ( argc == 2 ) {
		backup ( argv[0] , argv[1] );
	}
	else if ( argc == 1 ) {

		/* read from DO_BACKUP file on destination drive. */
		/* it should contain lines containing two parameters for the */
		/* backup command (eg: h2:thai thai-backup:) */

		fp = fopen ( build_name ( argv[0] , DO_BACKUP ) , "r" );
		if ( fp == NULL ) {
			printf ( "usage: unable to find %s file on destination disk\n" ,
				DO_BACKUP );
		}
		else {
			while ( fgets ( linebuf , LINE_SIZE , fp ) != NULL ) {
				p = linebuf;
				while ( isspace ( *p ) )
					p++;
				if ( *p == '\0' )
					continue;
				parm1 = p;
				while ( !isspace ( *p ) )
					p++;
				*p++ = '\0';
				while ( isspace ( *p ) )
					p++;
				if ( *p == '\0' )
					continue;
				parm2 = p;
				while ( !isspace ( *p ) )
					p++;
				*p++ = '\0';
				while ( isspace ( *p ) )
					p++;
				if ( *p != '\0' ) {
					printf ( "illegal syntax in %s file\n" , DO_BACKUP );
					break;
				}
				printf ( "\nbackup %s %s\n\n" , parm1 , parm2 );
				backup ( parm1 , parm2 );
			}
			fclose ( fp );
		}
	}
	else {
		printf ( "usage: backup from to\n" );
		printf ( "or     backup todisk\n" );
	}
}


backup ( from , to )
char *from , *to;
{
	struct dir_st *dir , *todir;
	char *path;
	struct dir_st *p;
	int isdir;
	struct DPTR *dptr;
	struct FileLock *lock;
	FILE *fp;
	struct FileInfoBlock *fib;
	int checkdir;


	lock = Lock ( build_name ( to , BACKUP_FILE_NAME ) , (LONG)ACCESS_READ );
	if ( lock == NULL ) {
		if ( ! first_time ) {
			puts ( ".backup not found: use -f for first time backup" );
			exit ( 1 );
		}
		bdate.ds_Days = 0;
		bdate.ds_Minute = 0;
		bdate.ds_Tick = 0;
	}
	else {
		fib = (struct FileInfoBlock *)
			AllocMem ( (LONG) sizeof ( struct FileInfoBlock ) ,
			(LONG) MEMF_PUBLIC );
		if ( fib != NULL  &&  Examine ( lock , fib ) )
			bdate = fib->fib_Date;
		else {
			bdate.ds_Days = 0;
			bdate.ds_Minute = 0;
			bdate.ds_Tick = 0;
		}
		if ( fib != NULL )
			FreeMem ( fib , (LONG) sizeof ( struct FileInfoBlock ) );
		UnLock ( lock );
	}

	fp = fopen ( build_name ( to , BACKUP_FILE_NAME ) , "w" );
	if ( fp == NULL ) {
		puts ( "Failed to create .backup file for time stamp" );
		return ( 0 );
	}
	fclose ( fp );

	if ( ! push ( &stack , "" ) ) {
		puts ( "Aborting" );
		return ( 0 );
	}
	while ( path = pop ( &stack ) ) {

		printf ( "Searching %s\n" , build_name ( from , path ) );
		dir = getdir ( build_name ( from , path ) );
		if ( dir == NULL ) {
			puts ( "Aborting" );
			return ( 0 );
		}

		/* if original directory changed since last backup, a file */
		/* may have been deleted. If this is so, must do dir on dest too */

		checkdir = FALSE;
		dptr = dopen ( build_name ( from , path ) , &isdir );
		if ( dptr == NULL  ||  ! isdir ) {
			puts ( "Internal error!!!" );
			freedir ( dir );
			return ( 0 );
		}
		if ( out_of_date ( &dptr->fib->fib_Date ) )
			checkdir = TRUE;
		dclose ( dptr );

		/* ok, must also check destination disk in case files there */
		/* have been added */

		dptr = dopen ( build_name ( to , path ) , &isdir );
		if ( dptr == NULL  ||  ! isdir ) {

			if ( dptr != NULL  &&  ! isdir ) {
				/* Found a file where we want a directory */
				rm_file ( to , "" , path , isdir );
			}

			printf ( "Creating  %s\n" ,
				build_name ( to , path ) );
			lock = CreateDir ( build_name ( to , path ) );
			if ( lock == NULL ) {
				printf ( "Failed to create %s\n" ,
					build_name ( to , path ) );
				freedir ( dir );
				dclose ( dptr );
				return ( 0 );
			}
			UnLock ( lock );
		}
		else {

			/* now, check date on destination directory. if changed, then */
			/* files may have been created (which will have to be deleted) */

			if ( out_of_date ( &dptr->fib->fib_Date ) )
				checkdir = TRUE;
		}
		dclose ( dptr );

		if ( checkdir ) {

			/* Have to check if files have been deleted. */
			/* To do this, must do a dir on destination drive. */

			printf ( "Searching %s\n" , build_name ( to , path ) );
			todir = getdir ( build_name ( to , path ) );
			if ( todir == NULL ) {
				puts ( "Aborting" );
				freedir ( dir );
				return ( 0 );
			}

			for ( p = todir->next; p != NULL; p = p->next ) {
				if ( ! in_dir ( p->filename , dir ) )
					rm_file ( to , path , p->filename , p->dir );
			}
			freedir ( todir );
		}

		for ( p = dir->next; p != NULL; p = p->next ) {
			if ( p->dir ) {
				if ( ! push ( &stack , build_name ( path , p->filename ) ) ) {
					freedir ( dir );
					puts ( "Aborting" );
					return ( 0 );
				}
			}
			else {
				if ( out_of_date ( &p->date ) )
					copy_file ( from , to , path , p->filename );
			}
		}
	}
	return ( 1 );
}



#define BSIZE	10000

copy_file ( from , to , path , filename )
char *from , *to , *path , *filename;
{
	FILE *fromfp , *tofp;
	static char buf[ BSIZE ];
	int bytes;

	if ( path == NULL  ||  path[0] == '\0' )
		strcpy ( sprbuf , filename );
	else
		sprintf ( sprbuf , "%s/%s" , path , filename );

	fromfp = fopen ( build_name ( from , sprbuf ) , "r" );
	if ( fromfp == NULL ) {
		printf ( "Failed to open '%s' for copying\n" ,
			build_name ( from , sprbuf ) );
		return;
	}
	tofp = fopen ( build_name ( to , sprbuf ) , "w" );
	if ( tofp == NULL ) {
		fclose ( fromfp );
		printf ( "Failed to create '%s' to copy to\n" ,
			build_name ( to , sprbuf ) );
		return;
	}
	printf ( "Copying   %s ... " , sprbuf );
	fflush ( stdout );
	while ( ( bytes = fread ( buf , 1 , BSIZE , fromfp ) ) > 0 ) {
		if ( fwrite ( buf , 1 , bytes , tofp ) != bytes ) {
			printf ( "write error, " );
			break;
		}
	}
	fclose ( tofp );
	fclose ( fromfp );
	puts ( "done" );
}


rm_file ( root , path , filename , isdir )
char *root;
char *path;
char *filename;
int isdir;
{
	struct dir_st *p , *dir;
	char *buf;


	if ( path == NULL  ||  path[0] == '\0' ) {
		strcpy ( sprbuf , filename );
		buf = strsave ( sprbuf );
	}
	else {
		sprintf ( sprbuf , "%s/%s" , path , filename );
		buf = strsave ( sprbuf );
	}
	if ( strcmp ( buf , BACKUP_FILE_NAME ) == 0 )
		return;
	if ( buf == NULL ) {
		puts ( "Out of memory!\n" );
		return;
	}
	printf ( "Deleting %s %s\n" ,
		isdir ? "directory" : "file" ,
		build_name ( root , buf ) );
	if ( isdir ) {
		printf ( "Searching %s\n" , build_name ( root , buf ) );
		dir = getdir ( build_name ( root , buf ) );
		if ( dir == NULL ) {
			printf ( "Failed to open directory %s\n" ,
				build_name ( root , buf ) );
		}
		else {
			for ( p = dir->next; p != NULL; p = p->next )
				rm_file ( root , build_name ( path , filename ) ,
					p->filename , p->dir );
			freedir ( dir );
			/* Delete the directory */
			DeleteFile ( build_name ( root , buf ) );
		}
	}
	else
		DeleteFile ( build_name ( root , buf ) );
	free ( buf );
}


int
in_dir ( filename , dir )
char *filename;
struct dir_st *dir;
{
	struct dir_st *p;

	for ( p = dir->next; p != NULL; p = p->next )
		if ( strcmp ( p->filename , filename ) == 0 )
			return ( 1 );
	return ( 0 );
}


char *
build_name ( root , filename )
char *root , *filename;
{
	static char buf[ 512 ];

	strcpy ( buf , root );
	if ( buf[0] != '\0'  &&  buf[ strlen ( buf ) - 1 ] != ':' )
		strcat ( buf , "/" );
	strcat ( buf , filename );
	return ( buf );
}


char *
strsave ( s )
char *s;
{
	char *p;

	p = malloc ( strlen ( s ) + 1 );
	if ( p != NULL )
		strcpy ( p , s );
	return ( p );
}


int
out_of_date ( date )
struct DateStamp *date;
{
	if ( date->ds_Days < bdate.ds_Days )
		return ( 0 );
	if ( date->ds_Days > bdate.ds_Days )
		return ( 1 );
	if ( date->ds_Minute < bdate.ds_Minute )
		return ( 0 );
	if ( date->ds_Minute > bdate.ds_Minute )
		return ( 1 );
	if ( date->ds_Tick < bdate.ds_Tick )
		return ( 0 );
	if ( date->ds_Tick > bdate.ds_Tick )
		return ( 1 );
	return ( 1 );
}


/* Aztec C playing up! Used by perror() */
int sys_nerr = -1;
char *sys_errlist[1];
