/* * samefile - a program to find identical files * * This source looks beautiful with tabstop every four characters * In vi type :set ts=4 */ /* * The @ enclosed @ comments are splint (formerly lclint) comments. * They reduce the noise generated by `strict' checking. */ /*@-compdef@*/ /*@-branchstate@*/ /*@-temptrans@*/ /*@-fullinitblock@*/ /*@-compdestroy@*/ /*@-ptrarith@*/ /* * Copyright by Jens Schweikhardt (samefile@schweikhardt.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" /* must be first because we need to test HAVE_LSTAT */ /*@ignore@*/ #include #include /* stat, lstat */ #if !HAVE_LSTAT # define lstat stat #endif /*@end@*/ #include /* NULL */ #include /* ENOENT */ #include /* memcmp */ #include /* malloc */ #include /* clock */ #if HAVE_FCNTL_H # include /* O_RDONLY */ #endif #if HAVE_UNISTD_H # include #endif #if HAVE_MMAP # include #endif #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* In case the POSIX S_ISREG is missing, use this alternate macro */ #ifndef S_ISREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #include "error.h" #include "fgetline.h" #include "options.h" /****************************************************************************** * CONSTANT DEFINITIONS ******************************************************************************/ #define plural(n) (n), (n) == 1 ? "" : "s" /****************************************************************************** * TYPE DEFINITIONS ******************************************************************************/ typedef off_t file_size; /* type of st_size in struct stat */ typedef struct file_list_elm { /* file list element */ char *name; /* pointer to file name */ struct file_list_elm *next; /* pointer to next element */ dev_t dev; /* device of inode */ ino_t ino; /* inode number */ } file_list_elm; typedef struct tnode { /* binary tree node */ file_size size; /* size field */ long count; /* number of names in list */ file_list_elm *list; /* points to list of names with same size */ struct tnode *left; /* points to node with lesser size */ struct tnode *right; /* points to node with greater size */ } tnode; /****************************************************************************** * STATIC VARIABLES ******************************************************************************/ /* Option related globals */ /*@observer@*/ static char *sep = "\t"; /* default separator for filenames in output */ static off_t minsize = 0; /* skip all files with size <= minsize */ /* Encoding of flags: */ #define VERBOSE 1 #define IGNORE_ERRORS 2 #define DONT_BE_SMART 4 #define ALL_INODES 8 #define DONT_ALPHASORT 16 static int flags = 0; /* where flags are kept */ /* Encoding of links: */ #define DO_NOT_OUTPUT 0 /* ...equal files that are hard links */ #define DO_NOT_CHECK 1 /* don't check if equal files are links */ #define REPORT 2 /* I want links mentioned in the output */ static int links = DO_NOT_OUTPUT; /* what to do if links are found */ /* Internal file scope varables. */ static file_size most_often = 0; /* size most often encountered */ static long longest_list = 0; /* # of elms in longest file list */ static long files_in_groups = 0; /* files in all groups */ static unsigned long btotal = 0; /* bytes in identical files */ static long n_tree_node_alloc = 0; /* calls to tree_node_alloc() */ static long n_list_elm_alloc = 0; /* calls to list_elm_alloc() */ static long malloc_free = 0; /* incremented for malloc, dec for free */ static long groups = 0; /* of different sizes = calls to comp_group */ static char *Errfile; /* filename for which error occurred */ static int Errno; /* a copy of errno */ static clock_t clock_at_start; /* to record start time */ static char *eq; /* pointer to equality table */ static size_t buffer_size; /* for file comparison */ static int eol = '\n'; /* End of Line character */ #if !HAVE_MMAP static char *c1, *c2; /* Pointers to compare buffers */ #endif /****************************************************************************** * FUNCTION PROTOTYPES ******************************************************************************/ static /*@only@*/ tnode * addtree (/*@null@*/ /*@returned@*/ tnode *p, struct stat *s, char *f) /*@globals most_often,longest_list,malloc_free,stderr,fileSystem,internalState,n_tree_node_alloc,n_list_elm_alloc,links,sep,flags@*/ /*@modifies most_often,longest_list,malloc_free,stderr,fileSystem,p->left,p->right,p->list,p->count,*f@*/; static void comp_group (file_size size, long files, char **filelist) /*@globals stderr,fileSystem,btotal,malloc_free,internalState,flags,Errfile,Errno,sep@*/ /*@modifies stderr,fileSystem,btotal,malloc_free,internalState@*/; static int compare (char *f1, char* f2, struct stat *s1, struct stat *s2) /*@globals fileSystem,internalState,Errno,Errfile@*/ /*@modifies fileSystem,internalState,Errno,Errfile,s1,s2@*/; static void free_list (file_list_elm *first) /*@globals malloc_free@*/ /*@modifies malloc_free,first@*/; int main (int argc, char **argv) /*@globals internalState,fileSystem,stderr,stdout,flags,minsize,links,sep,malloc_free,groups,files_in_groups,longest_list,most_often,n_tree_node_alloc,n_list_elm_alloc,Errno,Errfile,btotal@*/ /*@modifies internalState,fileSystem,stderr,stdout@*/; #if HAVE_MMAP && __LCLINT /*@ignore@*/ extern /*@only@*/ caddr_t mmap (/*@null@*/ caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset) /*@modifies addr@*/; extern int munmap (/*@only@*/ caddr_t addr, size_t len) /*@modifies addr@*/; /*@end@*/ #endif static void /*@exits@*/ myexit (int status) attribute(noreturn) /*@globals btotal,malloc_free,flags,stderr,fileSystem,internalState@*/ /*@modifies stderr,fileSystem@*/; static void parse_options (int argc, char **argv) /*@globals opt_err,stdout,stderr,fileSystem,flags,minsize,links,sep,btotal,malloc_free,internalState@*/ /*@modifies opt_err,stdout,stderr,fileSystem,flags,minsize,links,sep@*/; static void process_input (void) /*@globals minsize,flags,groups,files_in_groups,longest_list,most_often,n_tree_node_alloc,n_list_elm_alloc,malloc_free,links,sep,btotal,Errfile,Errno,fileSystem,internalState@*/ /*@modifies malloc_free,fileSystem@*/; static int scompare (const void *a, const void *b) /*@*/; static /*@out@*/ /*@only@*/ tnode * tree_node_alloc (void) /*@globals malloc_free,n_tree_node_alloc,stderr,fileSystem,internalState@*/ /*@modifies malloc_free,n_tree_node_alloc,stderr,fileSystem@*/; static /*@out@*/ /*@only@*/ file_list_elm * list_elm_alloc (void) /*@globals malloc_free,n_list_elm_alloc,stderr,fileSystem,internalState@*/ /*@modifies malloc_free,n_list_elm_alloc,stderr,fileSystem@*/; static void treeprint (/*@only@*/ tnode *p) /*@globals fileSystem,stdout,groups,files_in_groups,malloc_free,btotal,flags,Errfile,Errno,sep,flags,internalState@*/ /*@modifies fileSystem,stdout,groups,files_in_groups,malloc_free,p@*/; static void usage (void) /*@globals stderr,fileSystem,internalState@*/ /*@modifies stderr,fileSystem@*/; /****************************************************************************** * FUNCTIONS ******************************************************************************/ int main (int argc, char **argv) /* * The ball starts rolling here. * * Returns EXIT_FAILURE if one of these events happen: * - less than two files left to process. * - usage error * - memory exhausted * In any other case EXIT_SUCCESS. */ { clock_at_start = clock (); set_progname (argv[0], "samefile"); #if HAVE_MMAP #if HAVE_GETPAGESIZE buffer_size = (size_t) getpagesize (); #else buffer_size = 32768; #endif #else /* no mmap */ buffer_size = 32768; c1 = xmalloc (buffer_size); c2 = xmalloc (buffer_size); #endif parse_options (argc, argv); process_input (); myexit (EXIT_SUCCESS); /*@notreached@*/ return EXIT_SUCCESS; } /*============================================================================*/ static void myexit (int status) /* * All memory returned? * This function is used instead of exit(). */ { #if !HAVE_MMAP xfree (c1); xfree (c2); #endif if (malloc_free != 0) { /* did I forget to free some memory? */ err_msg ("malloc - free = %ld", malloc_free); } if (flags & VERBOSE) { /* * the btotal value is only meaningful if neither * of the -i and -x switches was supplied. */ if (!(flags & (ALL_INODES|DONT_BE_SMART))) { cfprintf (stderr, "You have a total of %lu byte%s in identical files.\n", plural (btotal)); } cfprintf (stderr, "Execution time: %.2fs\n", (clock () - clock_at_start)/(double)CLOCKS_PER_SEC); } exit (status); } /*============================================================================*/ static void process_input (void) /* * Add each input filename with filesize to the binary tree, * then call treeprint. Actual file comparison is done when * treeprint calls comp_group(). */ { tnode *root; char *filename; /* pointer to filename being processed */ struct stat s; /* where inode info goes, reference passed to addtree() */ unsigned long input_filenames = 0; /* number of filenames read from stdin */ unsigned long tree_filenames = 0; /* number of filenames inserted in tree */ root = NULL; while ((filename = fgetline (stdin, eol)) != NULL) { ++input_filenames; /* Weed out everything that * - can't be stat()ed or * - is not a regular file or * - is <= minsize */ if (lstat (filename, &s) == 0 && S_ISREG (s.st_mode) && s.st_size > minsize) { ++tree_filenames; root = addtree (root, &s, filename); } else { xfree (filename); } } if (flags & VERBOSE) { cfprintf (stderr, "%ld input filename%s.\n", plural ((long)input_filenames)); cfprintf (stderr, "%ld regular file%s left with size > %ld byte%s.\n", plural ((long)tree_filenames), plural ((long)minsize)); } if (tree_filenames == 0) { err_quit ("no files left"); } check (root != NULL); if (tree_filenames == 1) { /* * Normally, tnode *root and root->list are freed in treeprint. * Because treeprint is not called at all if there is only one * filename, we need to free them here. */ xfree (root->list->name); xfree (root->list); xfree (root); err_quit ("only one file left"); } eq = xmalloc ((longest_list - 1) * (longest_list - 1)); treeprint (root); xfree (eq); if (flags & VERBOSE) { cfprintf (stderr, "%ld file%s left after removing sizes that appear only once.\n", plural ((long)files_in_groups)); cfprintf (stderr, "%ld group%s of files with same size.\n", plural ((long)groups)); if (longest_list > 1) { cfprintf (stderr, "Largest group of one size is %ld files with size %ld.\n", (long)longest_list, (long)most_often); } cfprintf (stderr, "Memory consumption:\n"); cfprintf (stderr, "Largest equality table used %ld byte%s.\n", plural ((long)(longest_list * (longest_list - 1) / 2))); cfprintf (stderr, "Binary tree built with %ld node%s of size %ld = %ld bytes.\n", plural ((long)n_tree_node_alloc), (long) sizeof *root, (long)(n_tree_node_alloc * sizeof *root)); cfprintf (stderr, "Allocated %ld node%s of size %ld = %ld bytes for file lists.\n", plural ((long)n_list_elm_alloc), (long) sizeof *root->list, (long)(n_list_elm_alloc * sizeof *root->list)); } if (files_in_groups == 0) { myexit (EXIT_SUCCESS); /* nothing to do */ } } /*============================================================================*/ static tnode * addtree (tnode *p, struct stat *s, char *fname) /* * Insert a size/filename pair into the binary tree. * If size is new, make new node, else add the filename to * the list of filenames in the corresponding node. * Also check if file is on the same device and has same inode. * If so, don't add to the tree but print a line if -v is set. * * p is a pointer to a treenode under which the filename fname * should be inserted. p == NULL means make a new node. * Return value is the pointer to the new node or p. * If p != NULL, addtree calls itself recursively until * it finds a node with same size or reaches a leaf. */ { if (p == NULL) { /* make and initialize new node */ p = tree_node_alloc (); p->size = s->st_size; p->count = 1; p->left = p->right = NULL; p->list = list_elm_alloc (); p->list->name = fname; p->list->next = NULL; p->list->dev = s->st_dev; p->list->ino = s->st_ino; if (longest_list < 1) { longest_list = 1; most_often = p->size; } } else if (s->st_size == p->size) { /* same size; check if hard linked to a file we already know about */ file_list_elm *e; /* pointer to a file list element */ for (e = p->list; e != NULL; e = e->next) { /* scan through the list */ /* dev/inode already in list? */ if (s->st_dev == e->dev && s->st_ino == e->ino) { /* yes: want a report? */ if (links == REPORT) { cfprintf (stdout, "%ld%s%s%s%s%s[%ld]\n", (long)s->st_size, sep, e->name, sep, fname, sep, (long)s->st_nlink); } /* * if we want all filenames (even those for identical inodes) * then we do not return right away */ if (!(flags & ALL_INODES)) { xfree (fname); return p; } } } /* insert this file at the beginning of the list */ e = list_elm_alloc (); e->next = p->list; e->name = fname; e->dev = s->st_dev; e->ino = s->st_ino; p->list = e; ++(p->count); if (p->count > longest_list) { longest_list = p->count; most_often = p->size; } } else if (s->st_size < p->size) { p->left = addtree (p->left, s, fname); } else { p->right = addtree (p->right, s, fname); } /*@i7@*/ return p; } /*============================================================================*/ static void treeprint (tnode *p) /* * Recursively process the binary tree in reverse order, * that is, process node with largest size first. */ { if (p != NULL) { treeprint (p->right); if (p->count > 1) { long i; file_list_elm *l = p->list; /* * Build the filelist[] array from the file name list. * Freed at the end of this block. */ char **filelist = xmalloc (p->count * sizeof filelist[0]); ++groups; for (i = 0; i < p->count; ++i) { ++files_in_groups; filelist[i] = l->name; l = l->next; } check (l == NULL); if (!(flags & DONT_ALPHASORT)) { /*@i@*/ qsort (filelist, (size_t)p->count, sizeof filelist[0], scompare); } /*@i@*/ comp_group (p->size, p->count, filelist); xfree (filelist); } free_list (p->list); /* list was printed or skipped and can be freed */ treeprint (p->left); xfree (p); /* node itself has been processed, can be freed now */ } } /*============================================================================*/ static int scompare (const void *a, const void *b) /* * Compare function for qsort. Receives pointers to pointers to strings. */ { return strcmp (*(const char * const *)a, *(const char * const *)b); } /*============================================================================*/ static void free_list (file_list_elm *first) /* * Free a file list. */ { file_list_elm *current, *tmp; for (current = first; current != NULL; current = tmp) { tmp = current->next; xfree (current->name); xfree (current); } } /*============================================================================*/ static tnode * tree_node_alloc (void) /* * Tree node allocation. * Memory allocated in this function is freed when * the tree is output using the treeprint() function. */ { tnode *t; t = xmalloc (sizeof *t); ++n_tree_node_alloc; return t; } /*============================================================================*/ static file_list_elm * list_elm_alloc (void) /* * File list element allocation. * Memory allocated in this function is freed when * the tree is output using the treeprint() function. */ { file_list_elm *f; f = xmalloc (sizeof *f); ++n_list_elm_alloc; return f; } /*============================================================================*/ static void parse_options (int argc, char **argv) /* * Parse command line options. */ { int c; opt_err = 0; while ((c = get_opt (argc, argv, "0ag:ilqrs:Vvx")) != EOF) { switch (c) { case '0': eol = '\0'; break; case 'a': flags |= DONT_ALPHASORT; break; case 'g': if (sscanf (opt_arg, "%ld", (long *)&minsize) != 1) { err_msg ("warning: can't convert -g %s, using -g 0 instead", opt_arg); minsize = 0; } /*@switchbreak@*/ break; case 'i': flags |= ALL_INODES; /*@switchbreak@*/ break; case 'l': links = DO_NOT_CHECK; /*@switchbreak@*/ break; case 'q': flags |= IGNORE_ERRORS; /*@switchbreak@*/ break; case 'r': links = REPORT; /*@switchbreak@*/ break; case 's': sep = opt_arg; /*@switchbreak@*/ break; case 'V': (void) puts ("$Id: samefile.c,v 2.12 2005/08/07 17:20:16 schweikh Exp $"); myexit (EXIT_SUCCESS); case 'v': flags |= VERBOSE; /*@switchbreak@*/ break; case 'x': flags |= DONT_BE_SMART; /*@switchbreak@*/ break; default: usage (); myexit (EXIT_FAILURE); } } } /*============================================================================*/ static void usage (void) /* * Display the usage message. */ { cfprintf (stderr, "\n"); (void) err_progname (stderr); cfprintf (stderr, " reads a list of filenames from stdin and\n" "writes a list of identical files on stdout.\n\n" "usage: "); (void) err_progname (stderr); cfprintf (stderr, " [-g size] [-l | -r] [-s sep] [-0aiqVvx]\n\n" " options: -0 : input lines are 0 terminated (newline)\n" " -a : don't sort files with same size alphabetically\n" " -g : only output files greater than size (0)\n" " -i : compare even files that are hard linked\n" " -l : skip checking for hard links\n" " -q : do not warn when open(2) fails\n" " -r : report if identical files are hard links\n" " -s : use sep as separator string for files (tab)\n" " -V : output version information and exit\n"); cfprintf (stderr, " -v : verbose output with statistics on stderr\n" " -x : do not try to save work by using logic\n" " default: names which are hard links to the same file\n" " are neither compared nor output (ex and vi for example).\n\n" ); } /*============================================================================*/ static void comp_group (file_size size, long files, char **filelist) { long i, j, k, idx, row = files - 1; struct stat si, sj; (void) memset (eq, 0, (size_t) row * row); /* * The eq array is organized as follows: * example for files = 7, eq[6*6 = 36]. * * i=0 1 2 3 4 5 * +---------------------- * j=1 | 0 *6 *12 *18 *24 *30 * 2 | 1 7 *13 *19 *25 *31 The *-marked elements are unused but * 3 | 2 8 14 *20 *26 *32 simplify processing a lot. * 4 | 3 9 15 21 *27 *33 * 5 | 4 10 16 22 28 *34 * 6 | 5 11 17 23 29 35 * * The diagonal elements 0, 7, 14... compute like i*files. */ /* * The nested for loop processes the above scheme in the * order of the indices and ignores the *-marked elements. */ for (i = 0; i < row; ++i) { for (j = i + 1, idx = i * files; j < files; ++j, ++idx) { if (eq[idx] != '\0' && !(flags & DONT_BE_SMART)) continue; switch (compare (filelist[i], filelist[j], &si, &sj)) { case 0: /* files are equal */ cfprintf (stdout, "%ld%s%s%s%s", (long)size, sep, filelist[i], sep, filelist[j]); cfprintf (stdout, "%s%c%s%ld%s%ld\n", sep, si.st_dev == sj.st_dev ? '=' : 'X', sep, (long)si.st_nlink, sep, (long)sj.st_nlink); btotal += size; if (j == i + 1) /* already at diagonal element */ break; /* mark indices to the right up to the diagonal element */ for (k = idx + row; k <= (j - 1) * files; k += row) { ++eq[k]; } /*@switchbreak@*/ break; case 1: /* files are differing */ /*@switchbreak@*/ break; case 2: /* no such file error */ /* temporary files may have disappeared, I don't treat 'file not found' as an error */ /*@switchbreak@*/ break; default: /* other error */ if (!(flags & IGNORE_ERRORS)) { err_ret ("warning: open(2) failed for %s", Errfile); } } /* switch (compare) */ } } } /*============================================================================*/ static int compare (char *f1, char* f2, struct stat *s1, struct stat *s2) /* * Compare the files with names f1 and f2. * Return 0 for equal, 1 for differing, * 2 for 'no file error', 3 for other errors. * Write the fstats to s1 and s2. */ { #if HAVE_MMAP caddr_t c1, c2; #endif int fd1, fd2; file_size whole, i; /*@-moduncon@*/ fd1 = open (f1, O_RDONLY); /*@=moduncon@*/ if (fd1 < 0) { Errno = errno; Errfile = f1; return Errno == ENOENT ? 2 : 3; } /*@-moduncon@*/ fd2 = open (f2, O_RDONLY); /*@=moduncon@*/ if (fd2 < 0) { Errno = errno; Errfile = f2; check_sys (close (fd1) == 0); /* or we run out of file descriptors */ return Errno == ENOENT ? 2 : 3; } if (fstat (fd1, s1) != 0) { Errno = errno; Errfile = f1; check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return Errno == ENOENT ? 2 : 3; } if (fstat (fd2, s2) != 0) { Errno = errno; Errfile = f2; check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return Errno == ENOENT ? 2 : 3; } /* Just to be sure. The file's size may have changed in the meantime */ if (s1->st_size != s2->st_size) { check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return 1; } /* * whole is the file size rounded down * to a multiple of buffer_size */ whole = (s1->st_size / buffer_size) * buffer_size; /* compare alle completely fillable buffers */ for (i = 0; i != whole; i += buffer_size) { #if HAVE_MMAP c1 = mmap (0, buffer_size, PROT_READ, MAP_SHARED, fd1, i); if (c1 == NULL || c1 == (caddr_t) - 1 || c1 == MAP_FAILED) error (E_PERROR, "mmap failed"); c2 = mmap (0, buffer_size, PROT_READ, MAP_SHARED, fd2, i); if (c2 == NULL || c2 == (caddr_t) - 1 || c2 == MAP_FAILED) error (E_PERROR, "mmap failed"); #else check_sys (read (fd1, c1, buffer_size) != -1); check_sys (read (fd2, c2, buffer_size) != -1); #endif if (memcmp (c1, c2, buffer_size) != 0) { check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); #if HAVE_MMAP check_sys (munmap (c1, buffer_size) == 0); check_sys (munmap (c2, buffer_size) == 0); #endif return 1; } #if HAVE_MMAP check_sys (munmap (c1, buffer_size) == 0); check_sys (munmap (c2, buffer_size) == 0); #endif } /* Done if nothing remains -- files have identical contents */ if (whole == s1->st_size) { check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return 0; } /* compare the remaining incompletely filled buffer */ #if HAVE_MMAP c1 = mmap (0, buffer_size, PROT_READ, MAP_SHARED, fd1, i); if (c1 == NULL || c1 == (caddr_t) - 1 || c1 == MAP_FAILED) error (E_PERROR, "mmap failed"); c2 = mmap (0, buffer_size, PROT_READ, MAP_SHARED, fd2, i); if (c2 == NULL || c2 == (caddr_t) - 1 || c1 == MAP_FAILED) error (E_PERROR, "mmap failed"); { int differs = memcmp (c1, c2, (size_t) s1->st_size % buffer_size); check_sys (munmap (c1, buffer_size) == 0); check_sys (munmap (c2, buffer_size) == 0); check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return differs != 0 ? 1 : 0; } #else /* kein mmap */ check_sys (read (fd1, c1, s1->st_size % buffer_size) != -1); check_sys (read (fd2, c2, s1->st_size % buffer_size) != -1); check_sys (close (fd1) == 0); check_sys (close (fd2) == 0); return memcmp (c1, c2, s1->st_size % buffer_size) ? 1 : 0; #endif } /*==================== THAT'S IT! ============================================*/