| /* |
| Compare two copies of a file object in as many aspects as i can imagine |
| to make sense. (E.g.: comparing atime makes no sense.) |
| |
| To compare tree /media/dvd and /original/dir : |
| find /media/dvd -exec compare_file '{}' /media/dvd /original/dir ';' |
| |
| Copyright 2008 - 2015 Thomas Schmitt, <scdbackup@gmx.net> |
| |
| Provided under GPL version 2 or later. |
| |
| |
| cc -g -o compare_file compare_file.c |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "../config.h" |
| #endif |
| |
| #include <ctype.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <time.h> |
| |
| /* O_BINARY is needed for Cygwin but undefined elsewhere */ |
| #ifndef O_BINARY |
| #define O_BINARY 0 |
| #endif |
| |
| /* @param flag bit0= single letters */ |
| char *Ftypetxt(mode_t st_mode, int flag) |
| { |
| if(flag&1) |
| goto single_letters; |
| if(S_ISDIR(st_mode)) |
| return("directory"); |
| else if(S_ISREG(st_mode)) |
| return("regular_file"); |
| else if(S_ISLNK(st_mode)) |
| return("symbolic_link"); |
| else if(S_ISBLK(st_mode)) |
| return("block_device"); |
| else if(S_ISCHR(st_mode)) |
| return("char_device"); |
| else if(S_ISFIFO(st_mode)) |
| return("name_pipe"); |
| else if(S_ISSOCK(st_mode)) |
| return("unix_socket"); |
| return("unknown"); |
| single_letters:; |
| if(S_ISDIR(st_mode)) |
| return("d"); |
| else if(S_ISREG(st_mode)) |
| return("-"); |
| else if(S_ISLNK(st_mode)) |
| return("l"); |
| else if(S_ISBLK(st_mode)) |
| return("b"); |
| else if(S_ISCHR(st_mode)) |
| return("c"); |
| else if(S_ISFIFO(st_mode)) |
| return("p"); |
| else if(S_ISSOCK(st_mode)) |
| return("s"); |
| return("?"); |
| } |
| |
| |
| char *Ftimetxt(time_t t, char timetext[40], int flag) |
| { |
| char *rpt; |
| struct tm tms, *tmpt; |
| static char months[12][4]= { "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; |
| |
| tmpt= localtime_r(&t, &tms); |
| rpt= timetext; |
| rpt[0]= 0; |
| if(tmpt==0) |
| sprintf(rpt+strlen(rpt), "%12.f", (double) t); |
| else if(time(NULL)-t < 180*86400 && time(NULL)-t >= 0) |
| sprintf(rpt+strlen(rpt), "%3s %2d %2.2d:%2.2d", |
| months[tms.tm_mon], tms.tm_mday, tms.tm_hour, tms.tm_min); |
| else |
| sprintf(rpt+strlen(rpt), "%3s %2d %4.4d", |
| months[tms.tm_mon], tms.tm_mday, 1900+tms.tm_year); |
| return(timetext); |
| } |
| |
| |
| /* @param flag bit0= compare atime |
| bit1= compare ctime |
| */ |
| int Compare_2_files(char *adr1, char *adr2, char *adrc, int flag) |
| { |
| struct stat s1, s2; |
| int ret, differs= 0, r1, r2, fd1= -1, fd2= -1, i, done; |
| char buf1[4096], buf2[4096], a[4096], ttx1[40], ttx2[40]; |
| off_t r1count= 0, r2count= 0, diffcount= 0, first_diff= -1; |
| double dcount; |
| |
| ret= lstat(adr1, &s1); |
| if(ret==-1) { |
| printf("? %s : cannot lstat() : %s\n", adr1, strerror(errno)); |
| return(0); |
| } |
| strcpy(a, Ftypetxt(s1.st_mode, 1)); |
| strcat(a, " "); |
| if(adrc[0]) { |
| if(strlen(a) + strlen(adrc) < 4096) |
| strcat(a, adrc); |
| } else { |
| strcat(a, "."); |
| } |
| |
| ret= lstat(adr2, &s2); |
| if(ret==-1) { |
| printf("? %s : cannot lstat() : %s\n", adr2, strerror(errno)); |
| return(0); |
| } |
| |
| /* Attributes */ |
| if(s1.st_mode != s2.st_mode) { |
| if((s1.st_mode&~S_IFMT)!=(s2.st_mode&~S_IFMT)) |
| printf("%s : st_mode : %7.7o <> %7.7o\n", a, |
| (unsigned int) (s1.st_mode & ~S_IFMT), |
| (unsigned int) (s2.st_mode & ~S_IFMT)); |
| if((s1.st_mode&S_IFMT)!=(s2.st_mode&S_IFMT)) |
| printf("%s : type : %s <> %s\n", |
| a, Ftypetxt(s1.st_mode, 0), Ftypetxt(s2.st_mode, 0)); |
| differs= 1; |
| } |
| if(s1.st_uid != s2.st_uid) { |
| printf("%s : st_uid : %lu <> %lu\n", |
| a, (unsigned long) s1.st_uid, (unsigned long) s2.st_uid); |
| differs= 1; |
| } |
| if(s1.st_gid != s2.st_gid) { |
| printf("%s : st_gid : %lu <> %lu\n", |
| a, (unsigned long) s1.st_gid, (unsigned long) s2.st_gid); |
| differs= 1; |
| } |
| if((S_ISCHR(s1.st_mode) && S_ISCHR(s2.st_mode)) || |
| (S_ISBLK(s1.st_mode) && S_ISBLK(s2.st_mode))) { |
| if(s1.st_rdev != s2.st_rdev) { |
| printf("%s : %s st_rdev : %lu <> %lu\n", a, |
| (S_ISCHR(s1.st_mode) ? "S_IFCHR" : "S_IFBLK"), |
| (unsigned long) s1.st_rdev, (unsigned long) s1.st_rdev); |
| differs= 1; |
| } |
| } |
| if(S_ISREG(s2.st_mode) && s1.st_size != s2.st_size) { |
| printf("%s : st_size : %.f <> %.f diff= %.f\n", |
| a, (double) s1.st_size, (double) s2.st_size, |
| ((double) s1.st_size) - (double) s2.st_size); |
| differs= 1; |
| } |
| if(s1.st_mtime != s2.st_mtime) { |
| printf("%s : st_mtime : %s <> %s diff= %.f s\n", |
| a, Ftimetxt(s1.st_mtime, ttx1, 0), |
| Ftimetxt(s2.st_mtime, ttx2, 0), |
| ((double) s1.st_mtime) - (double) s2.st_mtime); |
| differs= 1; |
| } |
| if(flag&1) { |
| if(s1.st_atime != s2.st_atime) { |
| printf("%s : st_atime : %s <> %s diff= %.f s\n", |
| a, Ftimetxt(s1.st_atime, ttx1, 0), |
| Ftimetxt(s2.st_atime, ttx2, 0), |
| ((double) s1.st_atime) - (double) s2.st_atime); |
| differs= 1; |
| } |
| } |
| if(flag&2) { |
| if(s1.st_ctime != s2.st_ctime) { |
| printf("%s : st_ctime : %s <> %s diff= %.f s\n", |
| a, Ftimetxt(s1.st_ctime, ttx1, 0), |
| Ftimetxt(s2.st_ctime, ttx2, 0), |
| ((double) s1.st_ctime) - (double) s2.st_ctime); |
| differs= 1; |
| } |
| } |
| if(S_ISREG(s1.st_mode) && S_ISREG(s2.st_mode)) { |
| fd1= open(adr1, O_RDONLY | O_BINARY); |
| if(fd1==-1) { |
| printf("- %s : cannot open() : %s\n", adr1, strerror(errno)); |
| return(0); |
| } |
| fd2= open(adr2, O_RDONLY | O_BINARY); |
| if(fd2==-1) { |
| printf("- %s : cannot open() : %s\n", adr2, strerror(errno)); |
| close(fd1); |
| return(0); |
| } |
| |
| /* Content */ |
| done= 0; |
| while(!done) { |
| r1= read(fd1, buf1, sizeof(buf1)); |
| r2= read(fd2, buf2, sizeof(buf2)); |
| if((r1==EOF && r2==EOF) || (r1==0 && r2==0)) |
| break; |
| if(r1==EOF || r1==0) { |
| if(r1==EOF) |
| r1= 0; |
| if(s1.st_size > r1count + r1) |
| printf("- %s : early EOF after %.f bytes\n", adr1, (double) r1count); |
| differs= 1; |
| } |
| r1count+= r1; |
| if(r2==EOF || r2<r1) { |
| if(r2==EOF) |
| r2= 0; |
| if(s2.st_size > r2count + r2) |
| printf("- %s : early EOF after %.f bytes\n", adr2, (double) r2count); |
| differs= 1; |
| done= 1; |
| } |
| if(r2>r1) { |
| if(s1.st_size > r1count + r1) |
| printf("- %s : early EOF after %.f bytes\n", adr1, (double) r1count); |
| differs= 1; |
| done= 1; |
| } |
| r2count+= r2; |
| if(r1>r2) |
| r1= r2; |
| for(i= 0; i<r1; i++) { |
| if(buf1[i]!=buf2[i]) { |
| if(first_diff<0) |
| first_diff= i; |
| diffcount++; |
| } |
| } |
| } |
| if(diffcount>0 || r1count!=r2count) { |
| if(first_diff<0) |
| first_diff= (r1count>r2count ? r2count : r1count); |
| if(r1count > r2count) |
| dcount= diffcount + (r1count - r2count); |
| else |
| dcount= diffcount + (r2count - r1count); |
| printf("%s : %s : differs by at least %.f bytes. First at %.f\n", a, |
| (s1.st_mtime==s2.st_mtime ? "CONTENT":"content"), |
| dcount, (double) first_diff); |
| differs= 1; |
| } |
| } |
| if(fd1!=-1) |
| close(fd1); |
| if(fd2!=-1) |
| close(fd2); |
| return(!differs); |
| } |
| |
| |
| int main(int argc, char **argv) |
| { |
| int ret, i, with_ctime= 1; |
| char adr1[4096], adr2[4096], adrc[4096]; |
| |
| if(sizeof(off_t) < 8) { |
| fprintf(stderr, |
| "%s : FATAL : Compile time misconfiguration. sizeof(off_t) too small.\n\n", |
| argv[0]); |
| exit(4); |
| } |
| if(argc<4) { |
| fprintf(stderr, "usage: %s path prefix1 prefix2\n", argv[0]); |
| exit(2); |
| } |
| for(i= 4; i<argc; i++) { |
| if(strcmp(argv[i], "-no_ctime")==0) |
| with_ctime= 0; |
| else { |
| fprintf(stderr, "%s : Option not recognized: '%s'\n", argv[0], argv[i]); |
| exit(2); |
| } |
| } |
| |
| if(strncmp(argv[1], argv[2], strlen(argv[2]))!=0) { |
| fprintf(stderr, "%s: path '%s' does not match prefix1 '%s'\n", |
| argv[0], argv[1], argv[2]); |
| exit(2); |
| } |
| if(strlen(argv[1]) >= 4096) { |
| fprintf(stderr, "path exceeds size limit of 4095\n"); |
| exit(3); |
| } |
| if(strlen(argv[1]) - strlen(argv[2]) > 4000) { |
| fprintf(stderr, "common address part exceeds size limit of 4000\n"); |
| exit(3); |
| } |
| if(strlen(argv[3]) + 1 + strlen(argv[1]) - strlen(argv[2]) >= 4096) { |
| fprintf(stderr, "prefix2 exceeds size limit of 4095\n"); |
| exit(3); |
| } |
| strcpy(adr1, argv[1]); |
| strcpy(adrc, argv[1]+strlen(argv[2])); |
| strcpy(adr2, argv[3]); |
| if(adrc[0] == '/' || adrc[0] == 0) |
| strcat(adr2, "/"); |
| strcat(adr2, adrc); |
| |
| ret= Compare_2_files(adr1, adr2, adrc, (with_ctime<<1)); |
| exit(ret<=0); |
| } |
| |