/* update - copy new/altered files onto another disk with confirmation usage... update [options] update [options] update [options] File names on the source and destination disks are compared. If a file on the source disk is newer (more recent creation date) than the corresponding file on the destination disk, or if no file by that name exists on the destination disk, the user is asked whether that file should be copied. A .BAT file of the necessary commands is written, and the requested copies are performed by a copy of the command line interpreter specified by the environment variable COMSPEC. The default source is current drive. The default destination is a compile time option (currently c:). If no default destination is #defined, executing the program with an empty command line will cause a usage message to be printed. The source and/or destination can be a pathname, and the source can include a generic file name. options... -b make batch file $$.bat but don't execute it -t today's files - copy only files less than 24 hours old -q quiet - don't ask for confirmation -r recursive - visit subdirectories recursively -u update - don't copy new files Options can appear anywhere on the command line, and can be combined, as in: update -tu i: c: notes... Requires getargs() (see file GETARGS.C in the IBM-PC library), and exec() from the library supplied with the DeSmet compiler. exec() takes two arguments, each a pointer to a string. The first string is the pathname of a program to be executed. The second string is the rest of the command line that program gets. For example, exec("a:command.com","/cdir *.*") uses a subsidiary copy of the command line interpreter to print out a directory. The source file was created with tabs set every four columns. author... James R. Van Zandt (jrv@mitre-bedford) revisions... James R. Van Zandt VERSION 1.10, 15 Nov 86 Generic file names are allowed in source, and pathnames, with or without trailing '\', are allowed in either source or destination. Volume labels are never copied. Updates can be to a specified subdirectory on the same disk. James A. Mullens (jcm @ ornl-msr.arpa) VERSION 1.11, Jan 86 Bug and Syntax Cleaning The new attribute byte specifies that system and hidden files are always eligible for updating. (To restrict to "normal" files only, pass 0.) Empty command line results in usage message. James R. Van Zandt VERSION 1.13, 6 Feb 87 Destination directory can be created if needed. Warning printed if a file in the destination directory is newer. James R. Van Zandt VERSION 2.00, 7 Feb 87 Recursive option added. */ #define VERSION "2.00" /* If the following macro is defined, executing update with an empty command line will copy new/updated files to the current directory on the designated drive. If it is not defined, then executing update with an empty command line will result in a usage message (with mention of a default drive omitted). */ #define DEFAULT_DRIVE "c:" #include /*---------------------------------------------------------------------------*/ /* typedefs and defines needed for getargs */ #define INTEGER 0 #define BOOLEAN 1 #define CHARACTER 2 #define STRING 3 #define PROC 4 typedef struct { unsigned arg ; /* command line switch */ unsigned type ; /* variable type (of those #defined above) */ int *variable ; /* pointer to variable */ char *errmsg ; /* pointer to error message */ } ARG; typedef struct { char fi_resv[21]; /* bytes 0-20 reserved by DOS */ char fi_attrib; /* byte 21 File attribute */ long fi_time; /* bytes 22-23 Create/update time */ /* bytes 24-25 create/update date */ long fi_fsize; /* bytes 26-29 file size in bytes */ char fi_name[13]; /* bytes 30-42 file name & extension */ } FILE_INFO; #define SUBDIR 0x10 #define READONLY 0x01 #define HIDDEN 0x02 #define SYSTEM 0x04 #define VOLUMELABEL 0x08 #define ARCHIVED 0x20 #define IS_SUBDIR(p) ((p).fi_attrib & SUBDIR ) #define IS_READONLY(p) ((p).fi_attrib & READONLY ) #define IS_HIDDEN(p) ((p).fi_attrib & HIDDEN ) #define IS_SYSTEM(p) ((p).fi_attrib & SYSTEM ) #define IS_VOLUMELABEL(p) ((p).fi_attrib & VOLUMELABEL ) #define IS_ARCHIVED(p) ((p).fi_attrib & ARCHIVED ) extern unsigned _rax,_rbx,_rcx,_rdx,_rsi,_rdi,_res,_rds,_doint(); extern char _carryf,_zerof; /*--------------------------------------------------------------------*/ /* directory related BDOS function numbers */ #define FINDFIRST 0x4e #define FINDNEXT 0x4f #define SETDTA 0x1a #define GETDTA 0x2f #define MAXFILES 200 /* maximum number of files to check */ #define OPEN 0x3d /* dos function call to open a file */ #define CLOSE 0x3e /* dos function call to close a file */ #define DATETIME 0x57 /* " to get/set file's date & time */ #define BATFILE "$$.BAT" /* output file with copy commands */ #define DEFTIME 0 /* default time if file doesn't exist */ char source_file[40], source_pattern[40],source_drive[40]=""; char dest_file[40]; #ifdef DEFAULT_DRIVE char dest_drive[40]=DEFAULT_DRIVE; #else char dest_drive[40]; #endif char filepattern[40]="*.*"; char *(filev[MAXFILES]); /* pointers to names of source files */ long time[MAXFILES]; /* creation time/date for source files */ int filec=0; /* number of files to be checked on destination disk */ int today=0; /* nonzero if only today's files are to be checked */ int quiet=0; /* nonzero if user isn't to be asked to confirm */ int recursive=0; /* nonzero if subdirectories are to be updated too */ int updated_only=0; /* nonzero if user wants only updated files copied */ int batchfile_only=0; /* zero if user wants batchfile executed */ long cutoff; /* only files newer than this are to be checked */ int copying=0; /* number of files being copied */ FILE *out; /*--------------------------------------------------------------------------- The time field is a 32 bit long consisting of the date and time fields returned from a DOS 0x57 call. The date and time are concataneted with the date in the most significant 16 bits and the time in the least significant. This way they can be compared as a single number. */ /*---------------------------------------------------------------------------*/ long gtime( file ) char *file; { /* Return the time and date for a file. The DOS time and date are concatenated to form one large number. Note that the high bit of this number will be set to 1 for all dates after 2043, which will cause the date comparisons done in make() to fail. THIS ROUTINE IS NOT PORTABLE (because it assumes a 32 bit long). */ short handle = 0 ; /* place to remember file handle */ long time ; _rds=-1; _rax=(OPEN<<8)|0; /* open the file */ _rdx=(short) file; _doint(0x21); if(_carryf) return DEFTIME; /* file doesn't exist */ handle=_rbx=_rax; _rax=(DATETIME<<8)|0; /* get the time */ _doint(0x21); if(_carryf) err("DOS returned error from date/time request"); time=(((long)(_rdx))<<16) | ((long)(_rcx)&0xffffL); _rax=CLOSE<<8; /* close the file */ _doint(0x21); if(_carryf) err("DOS returned error from file close request"); return time; } err(s) char *s; { fprintf(stderr,s); exit(1); } find_first(filespec, attributes) char *filespec; int attributes; { /* Get directory information for the indicated file. Ambiguous file references are okay but you have to use find_next to get the rest of the file references. In this case, the regs structure used by find_first must be passed to find_next. 0 is returned on success, otherwise the DOS error code is returned. */ _rds=-1; _rdx=(short)filespec; _rcx=attributes; doscall(FINDFIRST); return(_rax); } /*----------------------------------------------------------------------*/ find_next(t) char *t; { /* Get the next file in a ambiguous file reference. A call to this function must be preceded by a find_first call. The regp argument must be the same register image used by the find_first call. 0 is returned on success, otherwise the error code generated by DOS is returned. */ _rds=-1; _rdx=t; doscall(FINDNEXT); return(_rax); } /*----------------------------------------------------------------------*/ doscall(id) { /* Do the DOS system call specified by "id" */ _rax=id<<8; _doint(33); } copy(s,d) char *s,*d; { printf(" COPYING."); fprintf(out,"copy %s %s\n",s,d); copying++; } create(s) char *s; { printf(" CREATING."); fprintf(out,"md %s\n", s); copying++; } yes() { while(1) {switch (getchar()) {case 'n': case 'N': return 0; case 'y': case 'Y': return 1; } } } envsearch(target,value) char *target,*value; { char buf[256],*s,t[25],*env; int nt; s=t; while(*target) *s++=toupper(*target++); *s++= '='; *s=0; nt = strlen(t); _lmove(2,44,_showcs()-0x10,&env,_showds()); _lmove(256,0,env,buf,_showds()); s=buf; while(*s) {/* printf("examining entry: %s \n",s); */ if (strncmp(t,s,nt)) /* strings differ */ while(*s++) ; else return (strcpy(value,s+nt)); } *value=0; /* no value found */ } help() { printf("update ver %s - move new/altered files to another disk \n", VERSION); puts("usage: update [options] \n"); puts(" update [options] \n"); #ifdef DEFAULT_DRIVE puts(" update [options] \n"); puts("Default source is current drive \n"); printf("Default destination is %s \n", DEFAULT_DRIVE); #else puts("Default source is current drive \n"); #endif puts("Source and/or destination can be a pathname.\n"); puts("Source can include a generic file name.\n\n"); puts("Options are:\n"); puts(" -b make batch file $$.bat but don\'t execute it\n"); puts(" -t today's files - copy only files less than 24 hours old\n"); puts(" -q quiet - don't ask for confirmation\n"); puts(" -r recursive - repeat for subdirectories\n"); puts(" -u update - copy only altered files, not new ones\n"); puts("example: update -tu i: c:archives\\\n"); exit(0); } version() /* return MS-DOS version number. Version 2.01 returned as 201 */ { extern unsigned _rax; _rax=0x3000; _doint(0x21); return ( (_rax&0xff)<<8 | (_rax&0xff00)>>8 ); } ARG Argtab[]={ {'b', BOOLEAN, &batchfile_only,"make $$.BAT but don\'t execute"} {'t', BOOLEAN, &today, "today's files only"}, {'q', BOOLEAN, &quiet, "quiet"} {'r', BOOLEAN, &recursive, "recursive"} {'u', BOOLEAN, &updated_only, "copy updated files only"} }; #define TABSIZE (sizeof(Argtab)/sizeof(ARG)) /*--------------------------------------------------------------------------*/ static FILE_INFO info ; /* DOS puts dirs here */ main( argc, argv ) int argc; char **argv; { char *filename, command[40], ch, *r, *s, *t, *u; int error, i; if( #ifndef DEFAULT_DRIVE argc<=1 || #endif (argc>1 && strcmp(argv[1],"?")==0)) help(); argc=getargs(argc,argv,Argtab,TABSIZE); if(argc>2) /* update */ {strcpy(dest_drive,argv[2]); s=t=argv[1]; while(*t) t++; u=t-1; /* u points to last char in source path */ while(t>=s && *t!='\\' && *t!=':') t--; if(t>=s) /* '\' or ':' was found... pathname was specified */ {r=source_drive; while(s<=t) *r++=*s++; *r=0; } if(t1) /* "update " */ {strcpy(dest_drive,argv[1]); } /* printf(" source pathname = \"%s\",",source_drive); printf(" source filepattern = \"%s\"\n",filepattern); */ /* printf(" destination pathname = \"%s\"\n",dest_drive); */ if(!(out=fopen(BATFILE,"w"))) err("can't open output file"); if(today) cutoff=gtime(BATFILE)-0x10000; /* 24 hours ago */ else cutoff=0; _rds=-1; _rdx=(char *)&info; /* change the Disk Transfer Addr */ doscall(SETDTA); /* to point at info structure */ update(source_drive, dest_drive); fclose(out); if(copying && !batchfile_only) {if(version()<0x200) {puts( "DOS version 2.00 or higher required for automatic copying \n"); fallback(); } envsearch("comspec",command); if(find_first(command,0)) /* COMMAND.COM is missing */ {printf("%s is missing. \n",command); fallback(); } exec(command,strcat("/c\000 ",BATFILE)); } if(!batchfile_only) unlink(BATFILE); } fallback() { puts("Please enter these commands... \n $$ \n erase $$.bat \n "); exit(0); } update(source_drive, dest_drive) char *source_drive, *dest_drive; { int error, i, dest_existed, subc; char ch, *s, **subv, *s_file, *s_drive, *d_file, *d_drive; long td; filec=0; #ifdef DEBUG printf("update(%s,%s)\n",source_drive,dest_drive); #endif strcpy(source_pattern, source_drive); /* start with source pathname */ strncat(source_pattern, filepattern, 40); /* add "*.*" or user's pattern */ error=find_first(source_pattern, 6); while(!error) { if(strcmp(info.fi_name,BATFILE) /* we never copy our own .BAT file */ && info.fi_time>cutoff ) {if(!(filev[filec]=malloc(strlen(info.fi_name)+1))) err("can't allocate buffer space"); strcpy(filev[filec],info.fi_name); time[filec]=info.fi_time; filec++; if(filec>=MAXFILES) {fprintf(stderr,"too many files...checking only the first %d \n", MAXFILES); break; } } error=find_next(source_pattern); } s=dest_drive+strlen(dest_drive)-1; if(*s=='\\') *s=0; dest_existed=is_directory(dest_drive); if(dest_existed==2) {printf("subdirectory name %s conflicts with existing file \ in destination directory\n" ,source_drive); return; } if( !dest_existed ) {printf("destination directory %s doesn't exist", dest_drive); if(quiet||(puts(" create it? "), yes())) {create(dest_drive); putchar('\n'); } else {putchar('\n'); return; } } ch=dest_drive[strlen(dest_drive)-1]; if(ch!=':' && ch!='\\') strncat(dest_drive,"\\", 40); for(i=0; itime[i]) {printf("%-15s WARNING: file in destination directory is newer!\n", filev[i]); } free(filev[i]); } if(!recursive) return; /* record the names of all the subdirectories */ filec=0; strcpy(source_pattern, source_drive); /* start with source pathname */ ch=source_pattern[strlen(source_pattern)-1]; if(ch!=':' && ch!='\\') strncat(source_pattern,"\\", 40); /* add "\" if needed */ strncat(source_pattern, "*.*", 40); /* add "*.*" */ error=find_first(source_pattern, 0x10); /* look for subdirectories */ while(!error) {if(IS_SUBDIR(info) && strcmp(info.fi_name,".") && strcmp(info.fi_name,"..")) {if(!(filev[filec]=malloc(strlen(info.fi_name)+1))) err("can't allocate buffer space"); strcpy(filev[filec],info.fi_name); filec++; if(filec>=MAXFILES) {fprintf(stderr, "too many subdirectories...checking only the first %d \n", MAXFILES); break; } } error=find_next(source_pattern); } if(!filec) return; /* make local copies of everything, lest it be overwritten during the recursive call to update */ s_file=malloc(40); s_drive=malloc(40); d_file=malloc(40); d_drive=malloc(40); subv=malloc(filec*sizeof(filev[0])); if(!subv || !s_file || !s_drive || !d_file || !d_drive) err("can't allocate buffer space"); subc=filec; strcpy(s_drive, source_drive); strcpy(d_drive, dest_drive); for (i=0; i\n", d); */ error=find_first(d, 0x10); /* look for subdirectories */ /* printf(" ...find_first() returns %d\n", error); */ if(error) return 0; if (IS_SUBDIR(info)) return 1; /* it's a subdirectory */ return 2; /* it's a normal file */ } return 1; /* current directory on destination drive always exists */ }