/* UPDATE Version 1.0 Copyright (c) 1994 Peter G. Aitken First published in PC Magazine, March 29, 1994, US Edition Utility for using a diskette to transfer files between two computers. Compares files on the diskette with those on a hard disk and copies files in either direction as needed so that the most recent version of each file is present in both locations. Usage 1: UPDATE path1 path2 [options] Usage 2: UPDATE path drive [options] or UPDATE drive path [options] Option switches: /L A log of all file copy operations, with times and dates, is written to the log file UPDATE.LOG. /Q Applies to usage 2 only. File(s) are copied as needed between path and drive without promptinmg. If /Q is omitted the user is queried before each copy operation. Exit codes: 0 Normal program termination. 1 Error opening a file. 2 Error reading from a file. 3 Error creating a file. 4 Error writing to a file. 5 Error allocating memory. */ /* Borland specific stuff. Permits program to be compiled with Borland C++ version 4.0 or later. */ #ifdef __BORLANDC__ #define _dosdate_t dosdate_t #define _find_t find_t #define _strupr strupr #define _stricmp stricmp #define _getch getch #define _dostime_t dostime_t #define _O_RDONLY O_RDONLY #define _mkdir mkdir #define _rmdir rmdir #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define TRUE -1 #define FALSE 0 /* Constants for program modes. */ #define PATH_TO_DRIVE 1 #define PATH_TO_PATH 2 /* Constants for exit codes. */ #define NO_ERROR 0 #define FILE_OPEN_ERROR 1 #define FILE_READ_ERROR 2 #define FILE_CREATE_ERROR 3 #define FILE_WRITE_ERROR 4 #define MEMORY_ALLOCATION_ERROR 5 /* Structure for file information. */ struct fileinfo { unsigned wr_time; unsigned wr_date; char name[13]; struct fileinfo *next; }; /* Variables for option switches. */ char quick, logfile; /* Global handle for log file. */ int loghandle; /* Log file name. */ #define LOGFILENAME "UPDATE.LOG" /* Function prototypes. */ char last_char(char *s); int is_drive(char *s); int is_path(char *s); void short_help(void); void long_help(void); void update_path(char *to_path, char *from_path); struct fileinfo *get_files(char *path); void filecopy(char *source, char *dest, unsigned date, unsigned time); void scan_drive(struct fileinfo *list, char *path, char *srcpath); int path_exists(char *path); void working(void); void write_log(char *s); void main (int argc, char *argv[]) { int i, mode = 0; char switches[10], drive[3], driveroot[4], temp[20]; char path1[_MAX_PATH], path2[_MAX_PATH]; struct _dosdate_t date; struct fileinfo *f; /* If first argument is /? display long help and exit. */ if (!strcmp(argv[1], "/?")) { long_help(); exit(NO_ERROR); } /* If there are no arguments, if there is only one argument that is not /?, or if there are more than 4 arguments, display short help and exit. */ if ((argc < 3) || (argc > 5)) { short_help(); exit(NO_ERROR); } /* Examine the first two arguments and set the program mode accordingly. Convert paths to absolute paths. Verify that the paths and/or drive actually exist. Copy arguments into variables and terminate paths with "\". */ if (is_path(argv[1]) && is_path(argv[2])) { mode = PATH_TO_PATH; if (_fullpath(path1, argv[1], _MAX_PATH) == NULL) { fprintf(stderr, "Path %s is not valid.\n", argv[1]); exit(NO_ERROR); } if (_fullpath(path2, argv[2], _MAX_PATH) == NULL) { fprintf(stderr, "Path %s is not valid.\n", argv[2]); exit(NO_ERROR); } if (last_char(path1) != '\\') strcat(path1, "\\"); if (last_char(path2) != '\\') strcat(path2, "\\"); if (!path_exists(path1)) { fprintf(stderr, "Path %s does not exist.\n", path1); exit(NO_ERROR); } if (!path_exists(path2)) { fprintf(stderr, "Path %s does not exist.\n", path2); exit(NO_ERROR); } /* Check for the unlikely occurrence that path1 == path2. */ if (!stricmp(path1, path2)) { fprintf(stderr, "Cannot update a path to itself\n"); exit(NO_ERROR); } } else if ((is_drive(argv[1]) && is_path(argv[2]))) { mode = PATH_TO_DRIVE; strcpy(drive, argv[1]); if (_fullpath(path1, argv[2], _MAX_PATH) == NULL) { fprintf(stderr, "Path %s is not valid.\n", argv[2]); exit(NO_ERROR); } if ((last_char(path1)) != '\\') strcat(path1, "\\"); if (!path_exists(path1)) { fprintf(stderr, "Path %s does not exist.\n", path1); exit(NO_ERROR); } /* We can see if a drive is valid by verifying that its root directory exists. */ strcpy(driveroot, drive); strcat(driveroot, "\\"); if (!path_exists(driveroot)) { fprintf(stderr, "Drive %s is not valid.\n", drive); exit(NO_ERROR); } } else { if (is_path(argv[1]) && is_drive(argv[2])) { mode = PATH_TO_DRIVE; strcpy(drive, argv[2]); if (_fullpath(path1, argv[1], _MAX_PATH) == NULL) { fprintf(stderr, "Path %s is not valid.\n", argv[1]); exit(NO_ERROR); } if ((last_char(path1)) != '\\') strcat(path1, "\\"); if (!path_exists(path1)) { fprintf(stderr, "Path %s does not exist.\n", path1); exit(NO_ERROR); } strcpy(driveroot, drive); strcat(driveroot, "\\"); if (!path_exists(driveroot)) { fprintf(stderr, "Drive %s is not valid.\n", drive); exit(NO_ERROR); } } } /* If mode == drive be sure that the path is not on the drive. */ if (mode == PATH_TO_DRIVE) { if (!strnicmp(drive, path1, 1)) { fprintf(stderr, "Cannot update a path to the drive it is on\n"); exit(NO_ERROR); } } /* Get option switches, if any. */ logfile = quick = FALSE; if (argc > 3) { strcpy(switches,""); for (i = 3 ; i < argc ; i++) strcat(switches,argv[i]); /* Convert all option letters to upper case. */ _strupr(switches); if (strstr(switches,"/Q")) quick = TRUE; if (strstr(switches,"/L")) logfile = TRUE; } /* If mode is 0 then the user has entered invalid arguments. Display explanatory message then exit then exit. */ if (mode == 0) { printf("\nInvalid arguments.\n"); printf("Two full paths with drive letter required, or\n"); printf("one full path and one drive letter. For example:\n\n"); printf("\tUPDATE A:\\DATA C:\\FILES\\SALES\n\n"); printf("\tor\n\n"); printf("\tUPDATE A:\\DATA C:\\\n\n"); exit(NO_ERROR); } /* Open the log file if the user requested logging. */ if (logfile) { #ifdef _MSC_VER loghandle = _open(LOGFILENAME, _O_RDWR | _O_CREAT | _O_APPEND, _S_IREAD | _S_IWRITE); #endif #ifdef __BORLANDC__ if ((_dos_open(LOGFILENAME, O_RDWR | O_CREAT | O_APPEND, &loghandle)) != 0) loghandle = -1; #endif if (loghandle == -1) /* Open failed. */ { fprintf(stderr, "Error number %d opening log file.\n", errno); exit(FILE_OPEN_ERROR); } /* Write the command line and system date to the logfile. */ _dos_getdate(&date); sprintf(temp, "Date: %d-%d-%d\nUPDATE %s %s\n", date.month, date.day, date.year, argv[1], argv[2]); write_log(temp); } /* Route execution based on value of mode. */ switch (mode) { case PATH_TO_PATH: update_path(path2, path1); update_path(path1, path2); break; case PATH_TO_DRIVE: if ((f = get_files(path1)) == NULL) { fprintf(stderr, "No files in %s\n", path1); exit(NO_ERROR); } else scan_drive(f, driveroot, path1); } if (logfile) _close(loghandle); } /* End of main() */ void update_path(char *to_path, char *from_path) { struct _find_t f_to, f_from; /* Structures for file info. */ static char ft_to[_MAX_PATH]; /* The "to" template. */ static char from_name[_MAX_PATH]; /* The full "from" name. */ static char ft_from[_MAX_PATH]; /* The "from" template. */ /* Copy the "from" path to the "from" template and add "*.*" at the end. */ strcpy(ft_from, from_path); strcat(ft_from, "*.*"); /* Look for any file in the "from" directory. */ if (_dos_findfirst(ft_from, _A_NORMAL, &f_from)) { /* No files in the "from" directory. */ return; } else { /* At least one file exists in the "from" directory. Set up a do loop that will execute once for each file in the "from" directory. */ do { /* Display activity indicator.*/ working(); /* Copy the "to" path to the "to" template, then Add the name of the file found in the "from" directory onto the end. */ strcpy(ft_to, to_path); strcat(ft_to, f_from.name); /* Create the full "from" file name and path. */ strcpy(from_name, from_path); strcat(from_name, f_from.name); /* See if a file of the same name exists in the "to" directory. */ if (_dos_findfirst(ft_to, _A_NORMAL, &f_to)) { /*A file of the same name does not exist in the "to" directory, so copy the file from "from" to "to". */ filecopy(from_name, ft_to, f_from.wr_date, f_from.wr_time); } else { /* A file of the same name does exist in the "to" directory. Copy only if the "from" copy is newer than the "to" copy. */ if ((f_to.wr_date < f_from.wr_date) || ((f_to.wr_date == f_from.wr_date) && (f_to.wr_time < f_from.wr_time))) { filecopy(from_name, ft_to, f_from.wr_date, f_from.wr_time); } } } while (!_dos_findnext(&f_from)); } } /* End of function update_path() */ struct fileinfo *get_files(char *path) /* Scans the subdirectory specified by path for files, and creates a linked list of structures that contain name, time, and date info for each file. Returns a pointer to the first element in the list; this pointer is NULL if there are no files in the subdirectory. Program should verify that path is a valid, existing path before calling this function. */ { struct _find_t f_from; /* Structure for DOS file info. */ struct fileinfo *h, *c, *c1; /* Pointers for linked list. */ int first = TRUE; char template[_MAX_PATH]; strcpy(template, path); strcat(template, "*.*"); /* Look for any file on the path. */ if (_dos_findfirst(template, _A_NORMAL, &f_from)) { /* No files on the path. */ return(NULL); } else { /* At least one file exists on the path. Set up a do loop that will execute once for each file on the path. */ do { /* Create another node in the linked list.*/ if ((c1 = malloc(sizeof(struct fileinfo))) == NULL) { if (logfile) write_log("Memory allocation error - program terminating\n"); fprintf(stderr, "Memory allocation error - program terminating.\n"); exit(MEMORY_ALLOCATION_ERROR); } if (first) { h = c1; first = FALSE; } else c->next = c1; strcpy(c1->name, f_from.name); c1->wr_date = f_from.wr_date; c1->wr_time = f_from.wr_time; c = c1; c->next = NULL; } while (!_dos_findnext(&f_from)); } return(h); } /* End of function get_files() */ void scan_drive(struct fileinfo *list, char *path, char *srcpath) /* Scans the entire directory structure of the drive whose root directory is specified by path (e.g., C:\) for files. Calls itself recursively to scan subdirectories. The name of each file that is found is compared against the linked list of file names pointed to by list. The argument srcpath is the subdirectory where the files in list are located. When a match is found the two files' time and date stamps are compared and the new is copied over the older (with or without user confirmation depending on the /Q quick flag). */ { struct _find_t found_file; /* Structure for file info. */ char template[_MAX_PATH], new_path[_MAX_PATH], fname[_MAX_PATH]; int result, match, first = TRUE; /* Flag for first time through. */ struct fileinfo *fptr; strcpy(template, path); strcat(template, "*.*"); while (1) /* Infinite loop. */ { if (first) { result = _dos_findfirst(template, _A_SUBDIR, &found_file); first = FALSE; } else result = _dos_findnext(&found_file); /* If no files were found, return. */ if (result != 0) return; /* Is it a subdirectory or a file? */ if ((found_file.attrib & _A_SUBDIR) == _A_SUBDIR) { /* It's a subdirectory. Skip the . and .. entries. */ if (strnicmp(found_file.name, ".", 1) == 0) continue; /* Create a new path. */ strcpy(new_path, path); strcat(new_path, found_file.name); strcat(new_path, "\\"); /* Scan the subdirectory. */ scan_drive(list, new_path, srcpath); continue; } else { /* It's a file. Go thru the linked list of files looking for a match. */ working(); /* Display activity indicator. */ match = FALSE; fptr = list; while (fptr != NULL) { if (_stricmp(found_file.name, fptr->name)) { fptr = fptr->next; continue; /* Does not match; continue looking thru list */ } else { match = TRUE; break; } } } if (!match) /* No matching file - get next one. */ continue; else /* A match was found; fptr points to its information. */ { /* Create file names with full paths. */ strcpy(new_path, path); strcat(new_path, found_file.name); /* The file on the drive. */ strcpy(fname, srcpath); strcat(fname, fptr->name); /* The file on the path. */ if ((found_file.wr_date == fptr->wr_date) && (found_file.wr_time == fptr->wr_time)) { /* Identical date/time stamps. */ continue; } if ((found_file.wr_date < fptr->wr_date) || ((found_file.wr_date == fptr->wr_date) && (found_file.wr_time < fptr->wr_time))) { /* File on drive is older. */ if (quick) { filecopy(fname, new_path, fptr->wr_date, fptr->wr_time); continue; } else { printf("\rCopy %s over %s (y or n)?\n", fname, new_path); result = _getch(); if (toupper(result) == 'Y') { filecopy(fname, new_path, fptr->wr_date, fptr->wr_time); continue; } } } else { /* File on path is older. */ if (quick) { filecopy(new_path, fname, found_file.wr_date, found_file.wr_time); } else { printf("\rCopy %s over %s (y or n)?\n", new_path, fname); result = _getch(); if (toupper(result) == 'Y') { filecopy(new_path, fname, found_file.wr_date, found_file.wr_time); continue; } } } } } /* End of while loop. */ } /* End of function scan_drive() */ char last_char(char *s) /* Returns the last character of its argument. */ { return (*(s + strlen(s) - 1)); } int is_drive(char *s) /* Returns TRUE if its argument refers to a drive (a letter followed by a colon); returns FALSE otherwise. Does not check to see if the drive actually exists. */ { if (strlen(s) != 2) /* Length must be 2 characters. */ return(FALSE); if (last_char(s) != ':') /* Last character must be a colon. */ return(FALSE); if (!isalpha((int)*s)) /* First character must be a letter. */ return(FALSE); return(TRUE); } /* End of function is_drive() */ int is_path(char *s) /* Returns TRUE if its argument is a path (a letter followed by a colon, a backslash, and zero or more additional characters); returns FALSE otherwise. Does not check to see if the path is valid. */ { /* Length must be between 3 and _MAX_DIR. */ if (strlen(s) < 3 || strlen(s) > _MAX_DIR) return(FALSE); if (!isalpha((int)*s)) /* First character muist be a letter. */ return(FALSE); if (*(s+1) != ':') /* Second character must be a colon. */ return(FALSE); if (*(s+2) != '\\') /* Third character must be a backslash. */ return(FALSE); return(TRUE); } /* End of function is_path() */ void short_help(void) /* Displays brief help information. */ { printf("\nUsage 1: UPDATE path1 path2 [options]\n"); printf("Usage 2: UPDATE drive path [options]\n"); printf("Usage 3: UPDATE /? displays detailed help information.\n"); } /* End of function short_help() */ void long_help(void) /* Displays detailed help information. */ { printf("UPDATE Version 1.0 Copyright (c) 1994 Peter G. Aitken\n"); printf("First published in PC Magazine, March 29, 1994, US Edition\n\n"); printf("Usage 1: UPDATE path1 path2 [/L]\n\n"); printf("\tCopies files between the subdirectories specified by the arguments\n"); printf("\tpath1 and path2. If a file exists in only one of the subdirectories\n"); printf("\tit is copied to the other subdirectory. If a file exists in both\n"); printf("\tsubdirectories the newer version is copied over the older.\n\n"); printf("Usage 2: UPDATE drive path [/L /Q]\n\n"); printf("\tFor each file in the subdirectory specified by path, the entire \n"); printf("\tdrive specified by drive is searched for matching files. If a \n"); printf("\tmatch is found, the newer version is copied over the older version. \n"); printf("\tThe default is for the user to be queried for each copy; if the /Q \n"); printf("\tswitch is used the user is not queried. \n\n"); printf("/L Option\n\n"); printf("\tEach copy operation, with time and date stamp, is logged to \n"); printf("\ta text file named UPDATE.LOG. \n"); } /* End of function long_help() */ void filecopy(char *source, char *dest, unsigned date, unsigned time) /* Copies the file source to dest. If dest already exists it is overwritten. The destination file's date and time stamps are set to the values passed in the arguments date and time, which must be in the MS-DOS date and time format. */ { #define BUFSIZE 32767 /* Copy buffer size. */ unsigned bytecount, numwritten; /* Number of bytes copied. */ int src, dst; /* DOS file handles. */ static char FileBuf[BUFSIZE]; /* Buffer for file copying. */ char temp[_MAX_PATH + _MAX_PATH + 20], *p, c; struct _dostime_t ttime; /* Open the source file in read-only mode. */ if (_dos_open(source, _O_RDONLY, &src)) { if (logfile) write_log("Error opening source file.\n"); fprintf(stderr, "\rError opening source file.\n"); exit(FILE_OPEN_ERROR); } /* Read a bufferful of bytes into FileBuf. */ if (_dos_read(src, FileBuf, BUFSIZE, &bytecount)) { if (logfile) write_log("Error reading from source file.\n"); fprintf(stderr, "\rError reading from source file.\n"); exit(FILE_READ_ERROR); } /* Create the destination file only if the source file contains at least 1 byte. Otherwise close the source file and return. */ if (bytecount > 0) { /* Make entry in log file if user requested logging. */ if (logfile) { _dos_gettime( &ttime ); /* Truncate the filename from dest; since the file name doesn't change all we need to write to the log file is the destination path. */ p = strrchr(dest, '\\'); c = *(p+1); *(p+1) = '\0'; sprintf(temp, " %02d:%02d: Copying %s to %s\n", ttime.hour, ttime.minute, source, dest); /* "Untruncate" the destination. */ *(p+1) = c; write_log(temp); } if (_dos_creat(dest, _A_NORMAL, &dst)) /* Couldn't create file. */ { _dos_close(src); if (errno == EACCES) { if (logfile) write_log("File creation error: directory full or file exists and cannot be overwritten.\n"); fprintf(stderr, "\rFile creation error: directory full or file exists\n"); fprintf(stderr, "and cannot be overwritten.\n"); exit(FILE_CREATE_ERROR); } else { if (logfile) write_log("Error creating destination file.\n"); fprintf(stderr, "\rError creating destination file.\n"); exit(FILE_CREATE_ERROR); } } } else /* Source file is zero bytes so don't copy it. */ { _dos_close(src); return; } /* Continue reading and writing until the entire file has been copied. Exit on file error. */ while (bytecount != 0) { working(); /* Display activity indicator. */ if (_dos_write(dst, FileBuf, bytecount, &numwritten)) { if (logfile) write_log("Error writing destination file.\n"); fprintf(stderr, "\rError writing destination file.\n"); exit(FILE_WRITE_ERROR); } if (numwritten != bytecount) { if (logfile) write_log("The destination disk is full.\n"); fprintf(stderr, "\rThe destination disk is full.\n"); remove(dest); exit(FILE_WRITE_ERROR); } if (_dos_read(src, FileBuf, BUFSIZE, &bytecount)) { if (logfile) write_log("Error reading source file.\n"); fprintf(stderr, "\rError reading source file.\n"); exit(FILE_READ_ERROR); } } /* Set the destination file's date and time. */ if (_dos_setftime(dst, date, time)) exit(FILE_WRITE_ERROR); /* Close the files. */ _dos_close(dst); _dos_close(src); } /* End of function filecopy() */ int path_exists(char *path) /* Returns TRUE if the argument path is a valid, existing path. Returns FALSE if not. The argument must end with \. */ { char temp[_MAX_PATH]; /* Try to create a new subdirectory on the path in question. */ strcpy(temp, path); strcat(temp, "zzzzxxxx"); if ((_mkdir(temp)) == 0) /* New subdir created OK. Delete it and return TRUE. */ { _rmdir(temp); return(TRUE); } /* Could not create subdir. */ if (errno == EACCES) /* Couldn't create subdir because a file or subdir named */ return(TRUE); /* "zzzzxxxx" already exists on the path. Path therefore exists.*/ return(FALSE); /* Path does not exist. */ } /* End of function path_exists() */ void working(void) /* Displays an animated "working" indicator on-screen. */ { static int count = 0; static char *msg[] = {"-", "\\","|","/",}; fprintf(stderr, "\r%s", msg[count++]); count = (count < 4) ? count : 0; } /* End of function working() */ void write_log(char *s) /* Writes the argument string to the log file. */ { if ((_write(loghandle, s, strlen(s))) == -1) { fprintf(stderr, "Error writing to log file.\n"); exit(FILE_WRITE_ERROR); } } /* End of function write_log() */