316 lines
8.4 KiB
C
316 lines
8.4 KiB
C
/*
|
|
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 - 2024 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 ||
|
|
(strlen(adr2) > 0 && adr2[strlen(adr2) - 1] == '/')))
|
|
strcat(adr2, "/");
|
|
strcat(adr2, adrc);
|
|
|
|
ret= Compare_2_files(adr1, adr2, adrc, (with_ctime<<1));
|
|
exit(ret<=0);
|
|
}
|
|
|