static char *rcsid = "$Id$"; /* * $Log$ */ /* generate a list of all files on the server * accept a list of all files in the TSM library * compare the two lists of files * output the differences... files on the server not in the library */ #include #include #include #include #include #include #include typedef struct _file { FILETIME mtime; unsigned int size; unsigned int flags; char *name; } File; /* global variables */ int verbose = 0; /* logfile */ static FILE *logfp; static char *logfn; /* simple error routing */ void err(char *msg) { (void) fprintf(stderr, "dsmpmj: %s\n", msg); if(logfp) { (void) fprintf(logfp, "dsmpmj: %s\n", msg); (void) fclose(logfp); } (void) exit(1); } /* warning messages */ void warn(char *msg) { time_t t; char buf[256]; (void) time(&t); (void) strcpy(buf, ctime(&t)); if(strchr(buf, '\n')) { *(strchr(buf, '\n')) = '\0'; } (void) fprintf(stderr, "%s: dsmpmj: %s\n", buf, msg); if(logfp) { (void) fprintf(logfp, "%s: dsmpmj: %s\n", buf, msg); } } /* comparison function for qsort */ int filesCompare(const void *a, const void *b) { File *af = (File *) a; File *bf = (File *) b; return strcmp(af->name, bf->name); } /* comparison function for bsearch */ int keyCompare(const void *a, const void *b) { File *bf = (File *) b; return strcmp((char *) a, bf->name); } /* change text to lower case */ void toLower(char *s) { while(s && *s) { *s = tolower(*s); s++; } } /* load the file previos day's run list of files */ File *loadFiles(char *fn, int *n, int missingok) { int i, nfiles; FILE *fp; File *files; char buf[1024], *p; if((fp = fopen(fn, "r")) == (FILE *) NULL) { if(missingok) { *n = 0; return (File*) NULL; } (void) sprintf(buf, "unable to read file '%s'", fn); err(buf); } /* how many files found? */ if(verbose) { warn("counting output from filesystem scans"); } (void) fseek(fp, 0, SEEK_SET); /* rewind to beginning of file */ nfiles = 0; while(fgets(buf, sizeof(buf) - 1, fp)) { /* count number of lines/files in fn */ nfiles++; } if(verbose) { (void) sprintf(buf, "counted a total of %d filenames", nfiles); warn(buf); } /* allocate memory for the file data */ if((files = (File *) malloc(sizeof(File) * nfiles)) == (File *) NULL) { err("unable to allocate memory for local files"); } if(verbose) { (void) sprintf(buf, "allocate %d bytes", sizeof(File) * nfiles); warn(buf); } /* load the data */ i = 0; (void) fseek(fp, 0, SEEK_SET); /* rewind to beginning of file */ while(fgets(buf, sizeof(buf) - 1, fp)) { files[i].flags = (unsigned int) atoi(buf); p = strchr(buf, '\t') + 1; files[i].mtime.dwLowDateTime = (unsigned int) atoi(p); p = strchr(p, '\t') + 1; files[i].mtime.dwHighDateTime = (unsigned int) atoi(p); p = strchr(p, '\t') + 1; files[i].size = (unsigned int) atoi(p); p = strchr(p, '\t') + 1; if(strchr(p, '\n')) { *(strchr(p, '\n')) = '\0'; } files[i++].name = strdup(p); } (void) fclose(fp); /* sort the lines */ (void) qsort(files, nfiles, sizeof(File), filesCompare); /* remove the temporary file */ (void) unlink(fn); if(verbose) { warn("done"); } *n = nfiles; return files; } /* get a list of all local files */ void findFiles(FILE *fp, char drive, char *fn, int *nfiles) { HANDLE h; WIN32_FIND_DATA finddata; char dir[1024], buf[2048]; LPVOID msgbuf; int errnum; (void) strcpy(dir, fn); (void) strcat(dir, "\\*.*"); h = FindFirstFile(dir, &finddata); if(h == INVALID_HANDLE_VALUE) { errnum = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &msgbuf, 0, NULL); if(strchr(msgbuf, '\n')) { *(strchr(msgbuf, '\n')) = '\0'; } (void) sprintf(buf, "FindFirstFile() failed with error %d for path '%s': %s", errnum, dir, msgbuf); warn(buf); return; } do { if(!strcmp(finddata.cFileName, ".") || !strcmp(finddata.cFileName, "..")) { continue; } if(finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { char path[1024]; (void) strcpy(path, dir); if(strchr(path, '*')) { *(strchr(path, '*')) = '\0'; } (void) strcat(path, finddata.cFileName); findFiles(fp, drive, path, nfiles); } else { char path[1024]; (void) strcpy(path, dir); if(strchr(path, '*')) { *(strchr(path, '*')) = '\0'; } (void) strcat(path, finddata.cFileName); (void) fprintf(fp, "%u\t%u\t%u\t%u\t%c:%s\n", finddata.dwFileAttributes, /* flags */ finddata.ftLastWriteTime.dwLowDateTime, /* mtime (first part) */ finddata.ftLastWriteTime.dwHighDateTime, /* mtime (second part) */ finddata.nFileSizeLow, /* size */ drive, /* drive letter */ path); /* path on that drive */ } (*nfiles)++; if(verbose && (*nfiles % 1000) == 0) { (void) fprintf(stderr, "scanned %d files\n", *nfiles); } } while(FindNextFile(h, &finddata)); FindClose(h); } /* return a list of all local files */ File *getLocalFiles(char *drives, int *n) { FILE *fp; int i, nfiles; char buf[1024], fn[1024], d; File *files; (void) sprintf(fn, "%s%s", getenv("TEMP"), tmpnam((char *) NULL)); (void) unlink(fn); if(verbose) { (void) sprintf(buf, "scanning drives '%s' temporary file at %s", drives, fn); warn(buf); } if((fp = fopen(fn, "w+")) == (FILE *) NULL) { (void) sprintf(buf, "unable to create temporary file for local files '%s'", fn); err(buf); } nfiles = 0; for(i = 3; i < 27; i++) { /* switch drives for finding files */ if(drives) { /* if only doing specific drives */ d = (char) ('a' + i - 1); if(strchr(drives, d)) { if(verbose) { (void) sprintf(buf, "changing to drive %c:", 'a' + i - 1); warn(buf); } if(_chdrive(i)) { continue; } *buf = '\0'; findFiles(fp, (char) ('a' + i - 1), buf, &nfiles); } } else { if(verbose) { (void) sprintf(buf, "changing to drive %c:", 'a' + i - 1); warn(buf); } if(_chdrive(i)) { continue; } *buf = '\0'; findFiles(fp, (char) ('a' + i - 1), buf, &nfiles); } } (void) fclose(fp); if(verbose) { (void) sprintf(buf, "scanned %d files", nfiles); warn(buf); } /* reload the files */ files = loadFiles(fn, n, 0); /* remove the temporary file */ (void) unlink(fn); return files; } /* load exclude patterns */ char **loadExcludePatterns(char *fn, int *n) { FILE *fp; int i, nlines; char buf[1024], **lines; lines = (char **) NULL; nlines = *n = 0; /* open the excludes file */ if((fp = fopen(fn, "r")) == (FILE *) NULL) { (void) sprintf(buf, "unable to load exclude patterns from file '%s': skipping excludes", fn); warn(buf); return lines; } /* how many files found? */ if(verbose) { warn("counting exclude patterns"); } nlines = 0; while(fgets(buf, sizeof(buf) - 1, fp)) { /* count number of lines/files in fn */ nlines++; } if(verbose) { (void) sprintf(buf, "counted a total of %d filenames", nlines); warn(buf); } /* read the lines into memory */ if(verbose) { warn("loading exclude patterns"); } (void) fseek(fp, 0, SEEK_SET); /* rewind to beginning of file */ if((lines = (char **) malloc(sizeof(char *) * nlines)) == (char **) NULL) { err("unable to allocate memory for exclude patterns"); } i = 0; while(fgets(buf, sizeof(buf) - 1, fp)) { if(strchr(buf, '\n')) { *(strchr(buf, '\n')) = '\0'; } toLower(buf); lines[i++] = strdup(buf); } (void) fclose(fp); if(verbose) { (void) sprintf(buf, "%d exclude patterns loaded", nlines); warn(buf); } *n = nlines; return (char **) lines; } /* write today's list to disk for tomorrow */ void writeFiles(char *fn, File *files, int nfiles) { FILE *fp; int i; char buf[1024]; if(verbose) { (void) sprintf(buf, "writing to file '%s'", fn); warn(buf); } /* open the file */ if((fp = fopen(fn, "w")) == (FILE *) NULL) { (void) sprintf(buf, "unable to create file '%s'", fn); err(buf); } /* write the files to the file */ for(i = 0; i < nfiles; i++) { if(!files[i].name) { continue; } (void) fprintf(fp, "%u\t%u\t%u\t%u\t%s\n", files[i].flags, /* flags */ files[i].mtime.dwLowDateTime, /* mtime (first part) */ files[i].mtime.dwHighDateTime, /* mtime (second part) */ files[i].size, /* size */ files[i].name); /* filename */ } /* close the file */ (void) fclose(fp); if(verbose) { warn("done"); } } /* compare the previous files with the current files * generate the expire list */ void genExpireList(char *fn, File *tfiles, int ntfiles, File *yfiles, int nyfiles) { int i, nexpired; FILE *fp; char buf[1024]; /* make sure the previous file is gone */ (void) unlink(fn); /* if there are no previous files, no need to expire any files */ if(nyfiles == 0) { return; } if(verbose) { (void) sprintf(buf, "creating expire list to file '%s'", fn); warn(buf); } /* open file for list of expired files */ if((fp = fopen(fn, "w")) == (FILE *) NULL) { (void) sprintf(buf, "unable to create expired list file '%s'", fn); err(buf); } /* create the expired list */ nexpired = 0; for(i = 0; i < nyfiles; i++) { if(!bsearch(yfiles[i].name, tfiles, ntfiles, sizeof(File), keyCompare)) { (void) fprintf(fp, "\"%s\"\n", yfiles[i].name); nexpired++; } } /* close the file */ (void) fclose(fp); if(verbose) { (void) sprintf(buf, "done: expiring %d files", nexpired); warn(buf); } /* if there are no files to expire, remove the expire list */ if(!nexpired) { (void) unlink(fn); if(verbose) { warn("no files to expire: removing expire list"); } } } /* using today's list exclude all files not modified since the timestamp * also exclude all files with the Offline (O) flag */ void genBackupList(char *fn, File *files, int nfiles, FILETIME *timestamp, char **pats, int npats) { FILE *fp; int i, j, nbackup; char buf[1024]; /* open the file */ if((fp = fopen(fn, "w")) == (FILE *) NULL) { (void) sprintf(buf, "unable to create backup list file '%s'", fn); err(buf); } if(verbose) { (void) sprintf(buf, "creating backup list to file '%s'", fn); warn(buf); } /* exclude files */ nbackup = 0; for(i = 0; i < nfiles; i++) { /* if for some reason this file has already been handled */ if(!files[i].name) { continue; } /* first remove all files from today's list where the timestamp has not changed */ if(CompareFileTime(&files[i].mtime, timestamp) < 0) { /* timestamp */ (void) free(files[i].name); files[i].name = (char *) NULL; continue; } /* remove any files with the OFFLINE flag set */ if((files[i].flags & FILE_ATTRIBUTE_OFFLINE) == FILE_ATTRIBUTE_OFFLINE) { (void) free(files[i].name); files[i].name = (char *) NULL; continue; } /* remove any files that match something in the exclude list */ (void) strcpy(buf, files[i].name); toLower(buf); for(j = 0; j < npats; j++) { if(strstr(buf, pats[j])) { (void) free(files[i].name); files[i].name = (char *) NULL; break; } } /* if the file made it through the exclude checks */ if(files[i].name) { (void) fprintf(fp, "\"%s\"\n", files[i].name); nbackup++; } } /* close the file */ (void) fclose(fp); if(verbose) { (void) sprintf(buf, "done: backing up %d files", nbackup); warn(buf); } } void usage(char *msg) { (void) fprintf(stderr, "%s\n", msg); (void) fprintf(stderr, "usage: dsmpmj [-v] -y SAVED-FILELIST -b BACKUP-LIST -e EXPIRE-LIST -x EXCLUDE-PATTERNS -d DRIVE-LETTER [-r FILE][-l LOGFILE]\n\n"); (void) fprintf(stderr, "\t-v - verbose\n"); (void) fprintf(stderr, "\t-y SAVED-FILELIST - filename of the file having yesterday's files\n"); (void) fprintf(stderr, "\t-b BACKUP-LIST - filename to use for the files to backup today\n"); (void) fprintf(stderr, "\t\t(ex. dsmc selective -filelist=BACKUP-LIST\n"); (void) fprintf(stderr, "\t-e EXPIRE-LIST - filename to use for storing the files to expire\n"); (void) fprintf(stderr, "\t\t(ex. dsmc expire -filelist=EXPIRE-LIST\n"); (void) fprintf(stderr, "\t-x EXCLUDE-PATTERNS - filename of the file having the patterns of files to exclude from backup\n"); (void) fprintf(stderr, "\t-r REFERENCE-FILE - use the timestamp of this file as a reference for backups\n"); (void) fprintf(stderr, "\t-d DRIVE-LETTER - drive letter to scan (ex. -d cdegst)\n"); (void) fprintf(stderr, "\t-l LOGFILE - write errors here in addition to stderr\n"); (void) exit(0); } int main(int argc, char **argv) { File *tfiles, *yfiles; char **xfiles; int ntfiles, nyfiles, nxfiles; char *drives; int i; char *yfn, *bfn, *efn, *xfn, *rfn; HANDLE h; WIN32_FIND_DATA finddata; /* parse the command-line */ yfn = bfn = efn = xfn = rfn = drives = (char *) NULL; i = 1; while(i < argc && argv[i] != (char *) NULL) { if(!strcmp(argv[i], "-v")) { verbose = 1; } if(!strcmp(argv[i], "-y")) { yfn = argv[++i]; } if(!strcmp(argv[i], "-b")) { bfn = argv[++i]; } if(!strcmp(argv[i], "-e")) { efn = argv[++i]; } if(!strcmp(argv[i], "-x")) { xfn = argv[++i]; } if(!strcmp(argv[i], "-r")) { rfn = argv[++i]; } if(!strcmp(argv[i], "-d")) { drives = argv[++i]; } if(!strcmp(argv[i], "-l")) { logfn = argv[++i]; } i++; } /* if no rfn is given, use yfn */ if(!rfn) { rfn = yfn; } /* check command line arguments */ if(!yfn) { usage("no -y filename given"); } else if(!bfn) { usage("no backup filename given with -b FILENAME"); } else if(!efn) { usage("no expire filename given with -e FILENAME"); } else if(!xfn) { usage("no excludes given with -x FILENAME"); } else if(!drives) { usage("no drives given with -d"); } /* if logging, open the log file */ if(logfn) { if((logfp = fopen(logfn, "w")) == (FILE *) NULL) { err("unable to create logfile"); } } if(_chdrive(3)) { err("trying to find dsmc: unable to change to C: drive\n"); } if(chdir("/")) { err("unable to cd to \\\n"); } /* get a timestamp of the previous day's files */ h = FindFirstFile(rfn, &finddata); if(h == (HANDLE) -1) { finddata.ftLastWriteTime.dwLowDateTime = 0; finddata.ftLastWriteTime.dwHighDateTime = 0; } tfiles = getLocalFiles(drives, &ntfiles); /* what files exist today? */ yfiles = loadFiles(yfn, &nyfiles, 1); /* load the files that existed yesterday */ writeFiles(yfn, tfiles, ntfiles); /* write today's files for tomorrow */ genExpireList(efn, tfiles, ntfiles, yfiles, nyfiles); /* what files are gone */ xfiles = loadExcludePatterns(xfn, &nxfiles); /* what files to ignore? */ genBackupList(bfn, tfiles, ntfiles, &finddata.ftLastWriteTime, xfiles, nxfiles); if(logfp) { (void) fclose(logfp); } return 0; }