home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume16 / finddup / part01 next >
Encoding:
Internet Message Format  |  1991-02-07  |  14.0 KB

  1. From: davidsen@crdos1.crd.ge.com (Bill Davidsen)
  2. Newsgroups: comp.sources.misc
  3. Subject: v16i095:  finddup - Find duplicate files, Part01/01
  4. Message-ID: <1991Feb7.185240.16151@sparky.IMD.Sterling.COM>
  5. Date: 7 Feb 91 18:52:40 GMT
  6. Approved: kent@sparky.imd.sterling.com
  7. X-Checksum-Snefru: d288c996 f18e7c14 36b46d00 0ca5a73c
  8.  
  9. Submitted-by: davidsen@crdos1.crd.ge.com (Bill Davidsen)
  10. Posting-number: Volume 16, Issue 95
  11. Archive-name: finddup/part01
  12.  
  13.   This is a program I wrote to help a friend sort through 300MB of GIF
  14. files, many of which were duplicates with different names, like
  15. "redwood.gif" and "bigtree.gif."
  16.  
  17.   It does a fairly fast and very complete scan for duplicates, and
  18. identifies hard links as well. Files with duplicate length are checked
  19. by CRC, and then if they still match a byte by byte compare is done.
  20. This seems to be the minimum which will find all duplicates while
  21. avoiding any possibility of a false match.
  22.  
  23. Bill
  24. ---------------- Cut here --------------------
  25. #!/bin/sh
  26. # shar:    Shell Archiver  (v1.29)
  27. #
  28. #    Run the following text with /bin/sh to create:
  29. #      finddup.c
  30. #      finddup.1C
  31. #      makefile
  32. #
  33. echo "x - extracting finddup.c (Text)"
  34. sed 's/^X//' << 'SHAR_EOF' > finddup.c &&
  35. X/*****************************************************************
  36. X |  finddup.c - the one true find duplicate files program
  37. X |----------------------------------------------------------------
  38. X |  Bill Davidsen, last hacked 2/5/91
  39. X |  Copyright (c) 1991 by Bill Davidsen, all rights reserved. This
  40. X |  program may be freely used and distributed in its original
  41. X |  form. Modified versions must be distributed with the original
  42. X |  unmodified source code, and redistribution of the original code
  43. X |  or any derivative program may not be restricted.
  44. X |----------------------------------------------------------------
  45. X |  Calling sequence:
  46. X |   finddup checklist
  47. X |
  48. X |  where checklist is the name of a file containing filenames to 
  49. X |  be checked, such as produced by "find . -type f -print >file"
  50. X |  returns a list of linked and duplicated files.
  51. X ****************************************************************/
  52. X
  53. X#include <stdio.h>
  54. X#include <sys/types.h>
  55. X#include <sys/stat.h>
  56. X#include <malloc.h>
  57. X
  58. X/* parameters */
  59. X#define MAXFN    120             /* max filename length */
  60. X
  61. X/* constants */
  62. X#define EOS        ((char) '\0')    /* end of string */
  63. X#define FL_CRC    0x0001            /* flag if CRC valid */
  64. X#define FL_DUP    0x0002            /* files are duplicates */
  65. X#define FL_LNK    0x0004            /* file is a link */
  66. X
  67. X/* macros */
  68. X#ifdef DEBUG
  69. X#define debug(X) printf X
  70. X#else
  71. X#define debug(X)
  72. X#endif
  73. X#define SORT qsort((char *)filelist, n_files, sizeof(filedesc), comp1);
  74. X#define GetFlag(x,f) ((filelist[x].flags & (f)) != 0)
  75. X#define SetFlag(x,f) (filelist[x].flags |= (f))
  76. X
  77. Xtypedef struct {
  78. X    off_t length;                /* file length */
  79. X    unsigned long crc32;        /* CRC for same length */
  80. X    dev_t device;                /* physical device # */
  81. X    ino_t inode;                /* inode for link detect */
  82. X    off_t nameloc;                /* name loc in names file */
  83. X    char flags;                    /* flags for compare */
  84. X} filedesc;
  85. X
  86. Xfiledesc *filelist;                /* master sorted list of files */
  87. Xlong n_files = 0;                /* # files in the array */
  88. Xlong max_files = 0;                /* entries allocated in the array */
  89. XFILE *namefd;                    /* file for names */
  90. X#ifndef    lint
  91. Xstatic char *SCCSid[] = {
  92. X    "@(#)finddup.c v1.10 - 2/5/91",
  93. X    "Copyright (c) 1991 by Bill Davidsen, all rights reserved"
  94. X};
  95. X#endif
  96. X
  97. Xint comp1();                    /* compare two filedesc's */
  98. Xvoid scan1();                    /* make the CRC scan */
  99. Xvoid scan2();                    /* do full compare if needed */
  100. Xvoid scan3();                    /* print the results */
  101. Xunsigned long get_crc();        /* get crc32 on a file */
  102. Xchar *getfn();                    /* get a filename by index */
  103. X
  104. Xmain(argc, argv)
  105. Xint argc;
  106. Xchar *argv[];
  107. X{
  108. X    char curfile[MAXFN];
  109. X    struct stat statbuf;
  110. X    off_t loc;                    /* location of name in the file */
  111. X    int zl_hdr = 1;                /* need header for zero-length files list */
  112. X    filedesc *curptr;            /* pointer to current storage loc */
  113. X
  114. X    /* check for filename given, and open it */
  115. X    if (argc != 2) {
  116. X        fprintf(stderr, "Needs name of file with filenames\n");
  117. X        exit(1);
  118. X    }
  119. X    namefd = fopen(argv[1], "r");
  120. X    if (namefd == NULL) {
  121. X        perror("Can't open names file");
  122. X        exit(1);
  123. X    }
  124. X
  125. X    /* start the list of name info's */
  126. X    filelist = (filedesc *) malloc(50 * sizeof(filedesc));
  127. X    if (filelist == NULL) {
  128. X        perror("Can't start files vector");
  129. X        exit(1);
  130. X    }
  131. X    /* finish the pointers */
  132. X    max_files = 50;
  133. X    debug(("First vector allocated @ %08lx, size %d bytes\n",
  134. X        (long) filelist, 50*sizeof(filedesc)
  135. X    ));
  136. X    fprintf(stderr, "build list...");
  137. X
  138. X    /* this is the build loop */
  139. X    while (loc = ftell(namefd), fgets(curfile, MAXFN, namefd) != NULL) {
  140. X        /* check for room in the buffer */
  141. X        if (n_files == max_files) {
  142. X            /* allocate more space */
  143. X            max_files += 50;
  144. X            filelist =
  145. X                (filedesc *) realloc(filelist, (max_files)*sizeof(filedesc));
  146. X            if (filelist == NULL) {
  147. X                perror("Out of memory!");
  148. X                exit(1);
  149. X            }
  150. X            debug(("Got more memory!\n"));
  151. X        }
  152. X        curfile[strlen(curfile)-1] = EOS;
  153. X
  154. X        /* add the data for this one */
  155. X        if (stat(curfile, &statbuf)) {
  156. X            fprintf(stderr, "%s - ", curfile);
  157. X            perror("ignored");
  158. X            continue;
  159. X        }
  160. X
  161. X        /* check for zero length files */
  162. X        if ( statbuf.st_size == 0) {
  163. X            if (zl_hdr) {
  164. X                zl_hdr = 0;
  165. X                printf("Zero length files:\n\n");
  166. X            }
  167. X            printf("%s\n", curfile);
  168. X            continue;
  169. X        }
  170. X
  171. X        curptr = filelist + n_files++;
  172. X        curptr->nameloc = loc;
  173. X        curptr->length = statbuf.st_size;
  174. X        curptr->device = statbuf.st_dev;
  175. X        curptr->inode = statbuf.st_ino;
  176. X        curptr->flags = 0;
  177. X        debug(("Name[%d] %s, size %ld, inode %d\n", n_files, curfile,
  178. X            (long) statbuf.st_size, statbuf.st_ino
  179. X        ));
  180. X    }
  181. X
  182. X    /* sort the list by size, device, and inode */
  183. X    fprintf(stderr, "sort...");
  184. X    SORT;
  185. X
  186. X    /* make the first scan for equal lengths */
  187. X    fprintf(stderr, "scan1...");
  188. X    scan1();
  189. X
  190. X    /* make the second scan for dup CRC also */
  191. X    fprintf(stderr, "scan2...");
  192. X    scan2();
  193. X
  194. X    fprintf(stderr, "done\n");
  195. X
  196. X#ifdef DEBUG
  197. X    for (loc = 0; loc < n_files; ++loc) {
  198. X        curptr = filelist + loc;
  199. X        printf("%8ld %08lx %6d %6d %02x\n",
  200. X            curptr->length, curptr->crc32,
  201. X            curptr->device, curptr->inode,
  202. X            curptr->flags
  203. X        );
  204. X    }
  205. X#endif
  206. X
  207. X    /* now scan and output dups */
  208. X    scan3();
  209. X
  210. X    exit(0);
  211. X}
  212. X
  213. X/* comp1 - compare two values */
  214. Xint
  215. Xcomp1(p1, p2)
  216. Xchar *p1, *p2;
  217. X{
  218. X    register filedesc *p1a = (filedesc *)p1, *p2a = (filedesc *)p2;
  219. X    register int retval;
  220. X
  221. X    (retval = p1a->length - p2a->length) ||
  222. X    (retval = p1a->crc32 - p2a->crc32) ||
  223. X    (retval = p1a->device - p2a->device) ||
  224. X    (retval = p1a->inode - p2a->inode);
  225. X    
  226. X    return retval;
  227. X}
  228. X
  229. X/* scan1 - get a CRC32 for files of equal length */
  230. X
  231. Xvoid
  232. Xscan1() {
  233. X    FILE *fp;
  234. X    int ix, needsort = 0;
  235. X
  236. X    for (ix = 1; ix < n_files; ++ix) {
  237. X        if (filelist[ix-1].length == filelist[ix].length) {
  238. X            /* get a CRC for each */
  239. X            if (! GetFlag(ix-1, FL_CRC)) {
  240. X                filelist[ix-1].crc32 = get_crc(ix-1);
  241. X                SetFlag(ix-1, FL_CRC);
  242. X            }
  243. X            if (! GetFlag(ix, FL_CRC)) {
  244. X                filelist[ix].crc32 = get_crc(ix);
  245. X                SetFlag(ix, FL_CRC);
  246. X            }
  247. X            needsort = 1;
  248. X        }
  249. X    }
  250. X
  251. X    if (needsort) SORT;
  252. X}
  253. X
  254. X/* scan2 - full compare if CRC is equal */
  255. X
  256. Xvoid
  257. Xscan2() {
  258. X    int ix, ix2, lastix;
  259. X    int inmatch;                /* 1st filename has been printed */
  260. X    int need_hdr = 1;            /* Need a hdr for the hard link list */
  261. X    int lnkmatch;                /* flag for matching links */
  262. X    register filedesc *p1, *p2;
  263. X    filedesc wkdesc;
  264. X
  265. X    /* mark links and output before dup check */
  266. X    for (ix = 0; ix < n_files; ix = ix2) {
  267. X        p1 = filelist + ix;
  268. X        for (ix2 = ix+1, p2 = p1+1, inmatch = 0;
  269. X            ix2 < n_files
  270. X                && p1->device == p2->device
  271. X                && p1->inode == p2->inode;
  272. X            ++ix2, ++p2
  273. X        ) {
  274. X            SetFlag(ix2, FL_LNK);
  275. X            if (need_hdr) {
  276. X                need_hdr = 0;
  277. X                printf("\n\nHard link summary:\n\n");
  278. X            }
  279. X
  280. X            if (!inmatch) {
  281. X                inmatch = 1;
  282. X                printf("\nFILE: %s\n", getfn(ix));
  283. X            }
  284. X            printf("LINK: %s\n", getfn(ix2));
  285. X        }
  286. X    }
  287. X    debug(("\nStart dupscan"));
  288. X
  289. X    /* now really scan for duplicates */
  290. X    for (ix = 0; ix < n_files; ix = lastix) {
  291. X        p1 = filelist + ix;
  292. X        for (lastix = ix2 = ix+1, p2 = p1+1, lnkmatch = 1;
  293. X            ix2 < n_files
  294. X                && p1->length == p2->length
  295. X                && p1->crc32 == p2->crc32;
  296. X            ++ix2, ++p2
  297. X        ) {
  298. X            if ((GetFlag(ix2, FL_LNK) && lnkmatch)
  299. X                || fullcmp(ix, ix2) == 0
  300. X            ) {
  301. X                SetFlag(ix2, FL_DUP);
  302. X                /* move if needed */
  303. X                if (lastix != ix2) {
  304. X                    int n1, n2;
  305. X
  306. X                    debug(("\n  swap %d and %d", lastix, ix2));
  307. X                    wkdesc = filelist[ix2];
  308. X                    for (n1 = ix2; n1 > lastix; --n1) {
  309. X                        filelist[n1] = filelist[n1-1];
  310. X                    }
  311. X                    filelist[lastix++] = wkdesc;
  312. X                }
  313. X                lnkmatch = 1;
  314. X            }
  315. X            else {
  316. X                /* other links don't match */
  317. X                lnkmatch = 0;
  318. X            }
  319. X        }
  320. X    }
  321. X}
  322. X
  323. X/* scan3 - output dups */
  324. X
  325. Xvoid
  326. Xscan3()
  327. X{
  328. X    register filedesc *p1, *p2;
  329. X    int ix, ix2, inmatch, need_hdr = 1;
  330. X
  331. X    /* now repeat for duplicates, links or not */
  332. X    for (ix = 0; ix < n_files; ++ix) {
  333. X        if (GetFlag(ix, FL_DUP)) {
  334. X            /* header on the very first */
  335. X            if (need_hdr) {
  336. X                need_hdr = 0;
  337. X                printf("\n\nList of files with duplicate contents %s\n",
  338. X                    "(includes hard links)"
  339. X                );
  340. X            }
  341. X
  342. X            /* put out a header if you haven't */
  343. X            if (!inmatch) printf("\nFILE: %s\n", getfn(ix-1));
  344. X            inmatch = 1;
  345. X            printf("DUP:  %s\n", getfn(ix));
  346. X        }
  347. X        else inmatch = 0;
  348. X    }
  349. X}
  350. X
  351. X/* get_crc - get a CRC32 for a file */
  352. X
  353. Xunsigned long
  354. Xget_crc(ix)
  355. Xint ix;
  356. X{
  357. X    FILE *fp;
  358. X    register unsigned long val1 = 0x90909090, val2 = 0xeaeaeaea;
  359. X    register int carry;
  360. X    char ch;
  361. X    char fname[MAXFN];
  362. X
  363. X    /* open the file */
  364. X    fseek(namefd, filelist[ix].nameloc, 0);
  365. X    fgets(fname, MAXFN, namefd);
  366. X    fname[strlen(fname)-1] = EOS;
  367. X    debug(("\nCRC start - %s ", fname));
  368. X    if ((fp = fopen(fname, "r")) == NULL) {
  369. X        fprintf(stderr, "Can't read file %s\n", fname);
  370. X        exit(1);
  371. X    }
  372. X
  373. X    /* build the CRC values */
  374. X    while ((ch = fgetc(fp)) != EOF) {
  375. X        carry = (val1 & 0x8000000) != 0;
  376. X        val1 = (val1 << 1) ^ ch + carry;
  377. X        val2 += ch << (ch & 003);
  378. X    }
  379. X    fclose(fp);
  380. X    debug(("v1: %08lx v2: %08lx ", val1, val2));
  381. X
  382. X    return ((val1 & 0xffff) << 12) ^ (val2 && 0xffffff);
  383. X}
  384. X
  385. X/* getfn - get filename from index */
  386. X
  387. Xchar *
  388. Xgetfn(ix)
  389. Xoff_t ix;
  390. X{
  391. X    static char fnbuf[MAXFN];
  392. X
  393. X    fseek(namefd, filelist[ix].nameloc, 0);
  394. X    fgets(fnbuf, MAXFN, namefd);
  395. X    fnbuf[strlen(fnbuf)-1] = EOS;
  396. X
  397. X    return fnbuf;
  398. X}
  399. X
  400. X/* fullcmp - compare two files, bit for bit */
  401. X
  402. Xint
  403. Xfullcmp(v1, v2)
  404. Xint v1, v2;
  405. X{
  406. X    FILE *fp1, *fp2;
  407. X    char filename[MAXFN];
  408. X    register int ch;
  409. X
  410. X    /* open the files */
  411. X    strcpy(filename, getfn(v1));
  412. X    fp1 = fopen(filename, "r");
  413. X    if (fp1 == NULL) {
  414. X        fprintf(stderr, "%s: ", filename);
  415. X        perror("can't access for read");
  416. X        exit(1);
  417. X    }
  418. X    debug(("\nFull compare %s\n         and", filename));
  419. X
  420. X    strcpy(filename, getfn(v2));
  421. X    fp2 = fopen(filename, "r");
  422. X    if (fp2 == NULL) {
  423. X        fprintf(stderr, "%s: ", filename);
  424. X        perror("can't access for read");
  425. X        exit(1);
  426. X    }
  427. X    debug(("%s", filename));
  428. X
  429. X    /* now do the compare */
  430. X    while ((ch = getc(fp1)) != EOF) {
  431. X        if (ch - getc(fp2)) break;
  432. X    }
  433. X
  434. X    /* close files and return value */
  435. X    fclose(fp1);
  436. X    fclose(fp2);
  437. X    debug(("\n      return %d", !(ch == EOF)));
  438. X    return (!(ch == EOF));
  439. X}
  440. SHAR_EOF
  441. chmod 0644 finddup.c || echo "restore of finddup.c fails"
  442. echo "x - extracting finddup.1C (Text)"
  443. sed 's/^X//' << 'SHAR_EOF' > finddup.1C &&
  444. X.TH FINDDUP 1 LOCAL
  445. X.SH NAME
  446. Xfinddup - find duplicate files in a list
  447. X.SH SYNOPSIS
  448. Xfinddup filename
  449. X.SH DESCRIPTION
  450. X.ds fd \fBfinddup\fP
  451. X\*(fd reads a list of filenames from the named file and scans them,
  452. Xbuilding a list of duplicate files and hard links. These are then
  453. Xwritten to stdout for the user's information. This can be used to reduce
  454. Xdisk usage, etc.
  455. X.SS How it works
  456. X\*(fd stats each name and saves the file length, device, and inode. It
  457. Xthen sorts the list and builds a CRC for each file which has the same
  458. Xlength as another file. For files which have the same length and CRC, a
  459. Xbyte by byte comparison is done to be sure that they are duplicates.
  460. X.sp
  461. XThe CRC step for N files of size S bytes requires reading n*S total
  462. Xbytes, while the byte by byte check must be done for every file against
  463. Xevery other, and read S*N*(N-1) bytes. Thus the CRC is a large timesaver
  464. Xin most cases.
  465. X.SH EXAMPLES
  466. X $ find /u -type f -print > file.list.tmp
  467. X $ finddup file.list.tmp
  468. X.SH FILES
  469. XOnly the file with the filenames.
  470. X.SH SEE ALSO
  471. Xfind(1).
  472. X.SH DIAGNOSTICS
  473. XIf files are named in the specification file but not present they will
  474. Xbe ignored. If an existing file can not be read the program will
  475. Xterminate rather than generate an incomplete list of duplicates.
  476. X.SH LIMITATIONS
  477. XAn option to generate a partial list could be added when a file can't be
  478. Xaccessed. An option to list only duplites which are not hard links could
  479. Xbe added.
  480. X.SH AUTHOR
  481. XBill Davidsen, davidsen@crdos1.crd.ge.com
  482. X.SH Copyright
  483. XCopyright (c) 1991 by Bill Davidsen, all rights reserved. This
  484. Xprogram may be freely used and distributed in its original
  485. Xform. Modified versions must be distributed with the original
  486. Xunmodified source code, and redistribution of the original code
  487. Xor any derivative program may not be restricted.
  488. SHAR_EOF
  489. chmod 0666 finddup.1C || echo "restore of finddup.1C fails"
  490. echo "x - extracting makefile (Text)"
  491. sed 's/^X//' << 'SHAR_EOF' > makefile &&
  492. X# makefile for finddup
  493. X
  494. X# ================> custom here
  495. XCC        = /bin/cc
  496. XCFLAGS        = -O
  497. XLFLAGS        =
  498. XLIBS        =
  499. X# your favorite shar prog
  500. XSHAR        = shar
  501. XSHARLST        = finddup.c finddup.1C makefile
  502. X# ================> custom ends
  503. X
  504. Xfinddup:    finddup.o
  505. X    $(CC) -o finddup $(LFLAGS) finddup.o -s
  506. X
  507. Xdebug:        rfinddup
  508. X
  509. X# note that this does NOT leave an object file
  510. Xrfinddup:    finddup.c
  511. X    $(CC) -o rfinddup -g -DDEBUG finddup.c
  512. X
  513. Xshar:        finddup.shar
  514. X
  515. Xfinddup.shar:    $(SHARLST)
  516. X    $(SHAR) $(SHARLST) > finddup.shar
  517. X
  518. Xclean:
  519. X    [ -f p.finddup.c -o ! -f s.finddup.c ] && exit 1
  520. X    rm -f finddup.[co] finddup rfinddup finddup.shar
  521. SHAR_EOF
  522. chmod 0644 makefile || echo "restore of makefile fails"
  523. exit 0
  524.  
  525. exit 0 # Just in case...
  526. -- 
  527. Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
  528. Sterling Software, IMD           UUCP:     uunet!sparky!kent
  529. Phone:    (402) 291-8300         FAX:      (402) 291-4362
  530. Please send comp.sources.misc-related mail to kent@uunet.uu.net.
  531.