/* Still missing: * * Damage counter * Change counter * Time stamp * prefix * cmd-line args * %100 deltas * delta and Add are different. delta -> Equ. * * mkctm * -B regex Bogus * -I regex Ignore * -D int Damage * -q decrease verbosity * -v increase verbosity * (-l str control logging.) * name cvs-cur * prefix src/secure * dir1 "Soll" * dir2 "Ist" * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_IGNORE "/CVS$|/\\.#|00_TRANS\\.TBL$" #define DEFAULT_BOGUS "\\.core$|\\.orig$|\\.rej$" regex_t reg_ignore, reg_bogus; int flag_ignore, flag_bogus; int verbose; int damage, damage_limit; int change; u_long s1_ignored, s2_ignored; u_long s1_bogus, s2_bogus; u_long s1_wrong, s2_wrong; u_long s_new_dirs, s_new_files, s_new_bytes; u_long s_del_dirs, s_del_files, s_del_bytes; u_long s_files_chg, s_bytes_add, s_bytes_del; u_long s_same_dirs, s_same_files, s_same_bytes; u_long s_edit_files, s_edit_bytes, s_edit_saves; u_long s_sub_files, s_sub_bytes; void Usage(void) { fprintf(stderr, "Usage:\n"); fprintf(stderr, "\tmkctm [-options] name number timestamp prefix"); fprintf(stderr, " dir1 dir2"); fprintf(stderr, "Options:\n"); fprintf(stderr, "\t\t-B bogus_regexp\n"); fprintf(stderr, "\t\t-D damage_limit\n"); fprintf(stderr, "\t\t-I ignore_regexp\n"); fprintf(stderr, "\t\t-q\n"); fprintf(stderr, "\t\t-v\n"); } void print_stat(FILE *fd, char *pre) { fprintf(fd,"%sAvoided:\n",pre); fprintf(fd,"%s ignore: %5lu old %5lu new\n", pre, s1_ignored, s2_ignored); fprintf(fd,"%s bogus: %5lu old %5lu new\n", pre, s1_bogus, s2_bogus); fprintf(fd,"%s wrong: %5lu old %5lu new\n", pre, s1_wrong, s2_wrong); fprintf(fd,"%sDelta:\n",pre); fprintf(fd,"%s new: %5lu dirs %5lu files %9lu plus\n", pre, s_new_dirs, s_new_files, s_new_bytes); fprintf(fd,"%s del: %5lu dirs %5lu files %9lu minus\n", pre, s_del_dirs, s_del_files, s_del_bytes); fprintf(fd,"%s chg: %5lu files %9lu plus %9lu minus\n", pre, s_files_chg, s_bytes_add, s_bytes_del); fprintf(fd,"%s same: %5lu dirs %5lu files %9lu bytes\n", pre, s_same_dirs, s_same_files, s_same_bytes); fprintf(fd,"%sMethod:\n",pre); fprintf(fd,"%s edit: %5lu files %9lu bytes %9lu saved\n", pre, s_edit_files, s_edit_bytes, s_edit_saves); fprintf(fd,"%s sub: %5lu files %9lu bytes\n", pre, s_sub_files, s_sub_bytes); } void stat_info(int foo) { signal(SIGINFO,stat_info); print_stat(stderr,"INFO: "); } void DoDir(const char *dir1, const char *dir2, const char *name); static struct stat st; static __inline struct stat * StatFile(char *name) { if (lstat(name,&st) < 0) err(1,"Couldn't stat %s\n",name); return &st; } int dirselect(struct dirent *de) { if (!strcmp(de->d_name,".")) return 0; if (!strcmp(de->d_name,"..")) return 0; return 1; } void name_stat(const char *pfx, const char *dir, const char *name, struct dirent *de) { char *buf = alloca(strlen(dir) + strlen(name) + strlen(de->d_name) + 3); struct stat *st; strcpy(buf,dir); strcat(buf,"/"); strcat(buf,name); strcat(buf,"/"); strcat(buf,de->d_name); st = StatFile(buf); printf("%s %s%s %lu %lu %o", pfx, name, de->d_name, st->st_uid, st->st_gid, st->st_mode & ~S_IFMT); if (verbose > 1) { fprintf(stderr,"%s %s%s\n", pfx, name, de->d_name); } } void Equ(const char *dir1, const char *dir2, const char *name, struct dirent *de) { if (de->d_type == DT_DIR) { char *p = alloca(strlen(name)+strlen(de->d_name)+2); strcpy(p,name); strcat(p,de->d_name); strcat(p, "/"); DoDir(dir1,dir2,p); s_same_dirs++; } else { char *buf1 = alloca(strlen(dir1) + strlen(name) + strlen(de->d_name) + 3); char *buf2 = alloca(strlen(dir2) + strlen(name) + strlen(de->d_name) + 3); char *m1,md5_1[33],*m2, md5_2[33]; u_char *p1,*p2; int fd1,fd2; struct stat s1,s2; strcpy(buf1,dir1); strcat(buf1,"/"); strcat(buf1,name); strcat(buf1,"/"); strcat(buf1,de->d_name); fd1 = open(buf1,O_RDONLY); if(fd1 < 0) { perror(buf1); exit(3); } fstat(fd1,&s1); strcpy(buf2,dir2); strcat(buf2,"/"); strcat(buf2,name); strcat(buf2,"/"); strcat(buf2,de->d_name); fd2 = open(buf2,O_RDONLY); if(fd2 < 0) { perror(buf2); exit(3); } fstat(fd2,&s2); #if 0 /* XXX if we could just trust the size to change... */ if (s1.st_size == s2.st_size) { s_same_files++; s_same_bytes += s1.st_size; close(fd1); close(fd2); goto finish; } #endif p1=mmap(0,s1.st_size,PROT_READ,MAP_PRIVATE,fd1,0); if ((int)p1 == -1) { perror(buf1); exit(3); } close(fd1); p2=mmap(0,s2.st_size,PROT_READ,MAP_PRIVATE,fd2,0); if ((int)p2 == -1) { perror(buf2); exit(3); } close(fd2); /* If identical, we're done. */ if((s1.st_size == s2.st_size) && !memcmp(p1,p2,s1.st_size)) { s_same_files++; s_same_bytes += s1.st_size; goto finish; } s_files_chg++; damage++; change++; if (s1.st_size > s2.st_size) s_bytes_del += (s1.st_size - s2.st_size); else s_bytes_add += (s2.st_size - s1.st_size); m1 = MD5Data(p1, s1.st_size, md5_1); m2 = MD5Data(p2, s2.st_size, md5_2); /* Just a curiosity... */ if(!strcmp(m1,m2)) { if (s1.st_size != s2.st_size) fprintf(stderr, "Notice: MD5 same for files of diffent size:\n\t%s\n\t%s\n", buf1,buf2); goto finish; } { u_long l = s2.st_size + 2; u_char *cmd = alloca(strlen(buf1)+strlen(buf2)+100); u_char *ob = alloca(l), *p; int j; FILE *F; if (p1[s1.st_size-1] != '\n') { if (verbose > 0) fprintf(stderr, "last char != \\n in %s\n", buf1); goto subst; } if (p2[s2.st_size-1] != '\n') { if (verbose > 0) fprintf(stderr, "last char != \\n in %s\n", buf2); goto subst; } for (p=p1; p 0) fprintf(stderr, "NULL char in %s\n", buf1); goto subst; } for (p=p2; p 0) fprintf(stderr, "NULL char in %s\n", buf2); goto subst; } strcpy(cmd, "diff -n "); strcat(cmd, buf1); strcat(cmd, " "); strcat(cmd, buf2); F = popen(cmd,"r"); for (j = 1, l = 0; l < s2.st_size; ) { j = fread(ob+l, 1, s2.st_size - l, F); if (j < 1) break; l += j; continue; } if (j) { l = 0; while (EOF != fgetc(F)) continue; } pclose(F); if (l && l < s2.st_size) { name_stat("CTMFN",dir2,name,de); printf(" %s %s %d\n",m1,m2,(unsigned)l); fwrite(ob,1,l,stdout); putchar('\n'); s_edit_files++; s_edit_bytes += l; s_edit_saves += (s2.st_size - l); } else { subst: name_stat("CTMFS",dir2,name,de); printf(" %s %s %u\n",m1,m2,(unsigned)s2.st_size); fwrite(p2,1,s2.st_size,stdout); putchar('\n'); s_sub_files++; s_sub_bytes += s2.st_size; } } finish: munmap(p1,s1.st_size); munmap(p2,s2.st_size); } } void Add(const char *dir1, const char *dir2, const char *name, struct dirent *de) { change++; if (de->d_type == DT_DIR) { char *p = alloca(strlen(name)+strlen(de->d_name)+2); strcpy(p,name); strcat(p,de->d_name); strcat(p, "/"); name_stat("CTMDM",dir2,name,de); putchar('\n'); s_new_dirs++; DoDir(dir1,dir2,p); } else if (de->d_type == DT_REG) { char *buf2 = alloca(strlen(dir2) + strlen(name) + strlen(de->d_name) + 3); char *m2, md5_2[33]; u_char *p1; struct stat st; int fd1; strcpy(buf2,dir2); strcat(buf2,"/"); strcat(buf2,name); strcat(buf2,"/"); strcat(buf2,de->d_name); fd1 = open(buf2,O_RDONLY); if (fd1 < 0) {perror(buf2); exit (3); } fstat(fd1,&st); p1=mmap(0,st.st_size,PROT_READ,MAP_PRIVATE,fd1,0); if ((int)p1 == -1) { perror(buf2); exit(3); } close(fd1); m2 = MD5Data(p1, st.st_size, md5_2); name_stat("CTMFM",dir2,name,de); printf(" %s %u\n",m2,(unsigned)st.st_size); fwrite(p1,1,st.st_size,stdout); putchar('\n'); munmap(p1,st.st_size); s_new_files++; s_new_bytes += st.st_size; } } void Del (const char *dir1, const char *dir2, const char *name, struct dirent *de) { damage++; change++; if (de->d_type == DT_DIR) { char *p = alloca(strlen(name)+strlen(de->d_name)+2); strcpy(p,name); strcat(p,de->d_name); strcat(p, "/"); DoDir(dir1,dir2,p); printf("CTMDR %s%s\n",name,de->d_name); s_del_dirs++; } else if (de->d_type == DT_REG) { char *buf1 = alloca(strlen(dir1) + strlen(name) + strlen(de->d_name) + 3); char *m1, md5_1[33]; strcpy(buf1,dir1); strcat(buf1,"/"); strcat(buf1,name); strcat(buf1,"/"); strcat(buf1,de->d_name); m1 = MD5File(buf1, md5_1); printf("CTMFR %s%s %s\n",name,de->d_name,m1); s_del_files++; s_del_bytes += StatFile(buf1)->st_size; } } void GetNext(int *i, int *n, struct dirent **nl, const char *dir, const char *name, u_long *ignored, u_long *bogus, u_long *wrong) { char buf[BUFSIZ]; for (;;) { for (;;) { (*i)++; if (*i >= *n) return; *buf = 0; if (*dir != '/') strcat(buf,"/"); strcat(buf,dir); if (buf[strlen(buf)-1] != '/') strcat(buf,"/"); strcat(buf,name); if (buf[strlen(buf)-1] != '/') strcat(buf,"/"); strcat(buf,nl[*i]->d_name); if (flag_ignore && !regexec(®_ignore,buf,0,0,0)) { (*ignored)++; if (verbose > 2) { fprintf(stderr,"Ignore %s\n",buf); } } else if (flag_bogus && !regexec(®_bogus,buf,0,0,0)) { (*bogus)++; if (verbose > 0) { fprintf(stderr,"Bogus %s\n",buf); } } else { break; } free(nl[*i]); nl[*i] = 0; } /* If the filesystem didn't tell us, find type */ if (nl[*i]->d_type == DT_UNKNOWN) nl[*i]->d_type = IFTODT(StatFile(buf)->st_mode); if (nl[*i]->d_type == DT_REG || nl[*i]->d_type == DT_DIR) break; (*wrong)++; if (verbose > 0) fprintf(stderr,"Wrong %s\n",buf); free(nl[*i]); nl[*i] = 0; } } void DoDir(const char *dir1, const char *dir2, const char *name) { int i1,i2,n1,n2,i; struct dirent **nl1,**nl2; char *buf1 = alloca(strlen(dir1) + strlen(name) + 4); char *buf2 = alloca(strlen(dir2) + strlen(name) + 4); strcpy(buf1,dir1); strcat(buf1,"/"); strcat(buf1,name); strcpy(buf2,dir2); strcat(buf2,"/"); strcat(buf2,name); n1 = scandir(buf1, &nl1, dirselect, alphasort); n2 = scandir(buf2, &nl2, dirselect, alphasort); i1 = i2 = -1; GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong); GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong); for (;i1 < n1 || i2 < n2;) { if (damage_limit && damage > damage_limit) break; /* Get next item from list 1 */ if (i1 < n1 && !nl1[i1]) GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong); /* Get next item from list 2 */ if (i2 < n2 && !nl2[i2]) GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong); if (i1 >= n1 && i2 >= n2) { /* Done */ break; } else if (i1 >= n1 && i2 < n2) { /* end of list 1, add anything left on list 2 */ Add(dir1,dir2,name,nl2[i2]); free(nl2[i2]); nl2[i2] = 0; } else if (i1 < n1 && i2 >= n2) { /* end of list 2, delete anything left on list 1 */ Del(dir1,dir2,name,nl1[i1]); free(nl1[i1]); nl1[i1] = 0; } else if (!(i = strcmp(nl1[i1]->d_name, nl2[i2]->d_name))) { /* Identical names */ if (nl1[i1]->d_type == nl2[i2]->d_type) { /* same type */ Equ(dir1,dir2,name,nl1[i1]); } else { /* different types */ Del(dir1,dir2,name,nl1[i1]); Add(dir1,dir2,name,nl2[i2]); } free(nl1[i1]); nl1[i1] = 0; free(nl2[i2]); nl2[i2] = 0; } else if (i < 0) { /* Something extra in list 1, delete it */ Del(dir1,dir2,name,nl1[i1]); free(nl1[i1]); nl1[i1] = 0; } else { /* Something extra in list 2, add it */ Add(dir1,dir2,name,nl2[i2]); free(nl2[i2]); nl2[i2] = 0; } } if (n1 >= 0) free(nl1); if (n2 >= 0) free(nl2); } int main(int argc, char **argv) { int i; extern char *optarg; extern int optind; setbuf(stderr, NULL); if (regcomp(®_bogus,DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE)) /* XXX use regerror to explain it */ err(1,"Default regular expression argument to -B is botched"); flag_bogus = 1; if (regcomp(®_ignore,DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE)) /* XXX use regerror to explain it */ err(1,"Default regular expression argument to -I is botched"); flag_ignore = 1; while ((i = getopt(argc,argv,"D:I:B:qv")) != -1) switch (i) { case 'D': damage_limit = strtol(optarg,0,0); if (damage_limit < 0) err(1,"Damage limit must be positive"); break; case 'I': if (flag_ignore) regfree(®_ignore); flag_ignore = 0; if (!*optarg) break; if (regcomp(®_ignore,optarg, REG_EXTENDED | REG_NEWLINE)) /* XXX use regerror to explain it */ err(1,"Regular expression argument to -I is botched"); flag_ignore = 1; break; case 'B': if (flag_bogus) regfree(®_bogus); flag_bogus = 0; if (!*optarg) break; if (regcomp(®_bogus,optarg, REG_EXTENDED | REG_NEWLINE)) /* XXX use regerror to explain it */ err(1,"Regular expression argument to -B is botched"); flag_bogus = 1; break; case 'q': verbose--; break; case 'v': verbose++; break; case '?': default: Usage(); return (1); } argc -= optind; argv += optind; setbuf(stdout,0); if (argc != 6) { Usage(); return (1); } signal(SIGINFO,stat_info); printf("CTM_BEGIN 2.0 %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]); DoDir(argv[4],argv[5],""); if (damage_limit && damage > damage_limit) { print_stat(stderr,""); err(1,"Damage would exceede %d files", damage_limit); } else if (!change) { err(1,"No changes"); } else { printf("CTM_END "); print_stat(stderr,""); } exit(0); }