/*
  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 Thomas Schmitt, <scdbackup@gmx.net>

  Provided under GPL version 2.


  cc -g -o compare_file compare_file.c
*/

#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>


/* @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];
 off_t r1count= 0, r2count= 0, diffcount= 0, first_diff= -1;

 ret= lstat(adr1, &s1);
 if(ret==-1) {
   printf("? %s : cannot lstat() : %s\n", adr1, strerror(errno));
   return(0);
 }
 if(S_ISDIR(s1.st_mode))
   strcpy(a, "d");
 else if(S_ISREG(s1.st_mode))
   strcpy(a, "-");
 else if(S_ISLNK(s1.st_mode))
   strcpy(a, "l");
 else if(S_ISBLK(s1.st_mode))
   strcpy(a, "b");
 else if(S_ISCHR(s1.st_mode))
   strcpy(a, "c");
 else if(S_ISFIFO(s1.st_mode))
   strcpy(a, "p");
 else if(S_ISSOCK(s1.st_mode))
   strcpy(a, "s");
 else
   strcpy(a, "?");
 strcat(a, " ");
 if(adrc[0])
   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) {
   printf("%s : st_mode :  %7.7o  <>  %7.7o\n", a, s1.st_mode, s2.st_mode);
   differs= 1;
 }
 if(s1.st_uid != s2.st_uid) {
   printf("%s : st_uid :   %d  <>  %d\n", a, s1.st_uid, s2.st_uid);
   differs= 1;
 }
 if(s1.st_gid != s2.st_gid) {
   printf("%s : st_gid :   %d  <>  %d\n", a, s1.st_gid, 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\n",
          a, (double) s1.st_size, (double) s2.st_size);
   differs= 1;
 }
 if(s1.st_mtime != s2.st_mtime) {
   printf("%s : st_mtime :  %u  <>  %u\n",
          a, (unsigned int) s1.st_mtime, (unsigned int) s2.st_mtime);
   differs= 1;
 }
 if(flag&1) {
   if(s1.st_atime != s2.st_atime) {
     printf("%s : st_atime :  %u  <>  %u\n",
            a, (unsigned int) s1.st_atime, (unsigned int) s2.st_atime);
     differs= 1;
   }
 }
 if(flag&2) {
   if(s1.st_ctime != s2.st_ctime) {
     printf("%s : st_ctime :  %u  <>  %u\n",
            a, (unsigned int) s1.st_ctime, (unsigned int) s2.st_ctime);
     differs= 1;
   }
 }
 if(S_ISREG(s1.st_mode)) {
   fd1= open(adr1, O_RDONLY);
   if(fd1==-1) {
     printf("- %s : cannot open() : %s\n", adr1, strerror(errno));
     return(0);
   }
   fd2= open(adr2, O_RDONLY);
   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);
     printf("%s : %s : differs by at least %.f bytes. First at %.f\n", a,
            (s1.st_mtime==s2.st_mtime ?  "CONTENT":"content"),
            (double) (diffcount + abs(r1count-r2count)), (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(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(1);
   }
 }

 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);
 }
 strcpy(adr1, argv[1]);
 strcpy(adrc, argv[1]+strlen(argv[2]));
 sprintf(adr2, "%s%s%s", argv[3], (adrc[0]=='/' ? "" : "/"), adrc);

 ret=  Compare_2_files(adr1, adr2, adrc, (with_ctime<<1));
 exit(ret<=0);
}