> Another suggestion for efficiency is to open the file in
> parse_samefile (and fstat the file descriptor to figure out if it is a
> regular file) and keep the reference file open from then on.
>

I have tried to follow most of your suggestions.  Please excuse me for not 
adding error checks in the calls to lseek and other few places, I would like to 
know if you think the major issues are fixed and then as a matter of fact I 
will work on all the minor tweaks...

> Thanks,
> James.

Thank you,
Roberto
--- findutils/import-gnulib.config	2010-04-05 13:18:59.000000000 +0200
+++ findutils-rreale/import-gnulib.config	2010-04-08 10:49:22.000000000 +0200
@@ -73,6 +73,7 @@
 realloc
 regex
 rpmatch
+safe-read
 savedir
 selinux-at
 stat-macros
--- findutils/find/defs.h	2010-04-05 13:18:59.000000000 +0200
+++ findutils-rreale/find/defs.h	2010-04-08 13:31:49.000000000 +0200
@@ -168,6 +168,15 @@
   int   fd;
 };
 
+struct samecontent_args
+{
+  struct stat st;
+  char *first_block;
+  size_t read;
+  char *ref_pathname;
+  int fd;
+};
+
 struct size_val
 {
   enum comparison_type kind;
@@ -319,6 +328,7 @@
     struct time_val reftime;	/* newer newerXY anewer cnewer mtime atime ctime mmin amin cmin */
     struct perm_val perm;	/* perm */
     struct samefile_file_id samefileid; /* samefile */
+    struct samecontent_args samecontentargs; /* samecontent */
     mode_t type;		/* type */
     struct format_val printf_vec; /* printf fprintf fprint ls fls print0 fprint0 print */
     security_context_t scontext; /* security context */
@@ -467,6 +477,7 @@
 PREDICATEFUNCTION pred_writable;
 PREDICATEFUNCTION pred_xtype;
 PREDICATEFUNCTION pred_context;
+PREDICATEFUNCTION pred_samecontent;
 
 
 
@@ -528,6 +539,9 @@
 boolean looks_like_expression PARAMS((const char *arg, boolean leading));
 
 
+/* cmp.c */
+boolean cmp (int fd0, int fd1, char *ref_pathname, char *pathname, struct predicate *pred_ptr);
+
 enum DebugOption
   {
     DebugNone             = 0,
@@ -547,7 +561,7 @@
   /* If true, -depth was EXPLICITLY set (as opposed to having been turned
    * on by -delete, for example).
    */
-   boolean explicit_depth;
+  boolean explicit_depth;
 
   /* If >=0, don't descend more than this many levels of subdirectories. */
   int maxdepth;
--- findutils/find/Makefile.am	2010-04-05 13:18:59.000000000 +0200
+++ findutils-rreale/find/Makefile.am	2010-04-05 14:48:52.000000000 +0200
@@ -5,7 +5,7 @@
 # regexprops_SOURCES = regexprops.c
 
 noinst_LIBRARIES = libfindtools.a
-libfindtools_a_SOURCES = finddata.c fstype.c parser.c pred.c tree.c util.c sharefile.c
+libfindtools_a_SOURCES = finddata.c fstype.c parser.c pred.c tree.c util.c sharefile.c cmp.c
 
 
 # We always build two versions of find, one with fts, one without.
--- findutils/find/parser.c	2010-04-05 13:18:59.000000000 +0200
+++ findutils-rreale/find/parser.c	2010-04-08 17:21:49.000000000 +0200
@@ -159,6 +159,7 @@
 static boolean parse_xtype         PARAMS((const struct parser_table*, char *argv[], int *arg_ptr));
 static boolean parse_quit          PARAMS((const struct parser_table*, char *argv[], int *arg_ptr));
 static boolean parse_context       PARAMS((const struct parser_table*, char *argv[], int *arg_ptr));
+static boolean parse_samecontent   PARAMS((const struct parser_table*, char *argv[], int *arg_ptr));
 
 boolean parse_print             PARAMS((const struct parser_table*, char *argv[], int *arg_ptr));
 
@@ -326,6 +327,7 @@
   {ARG_TEST,       "writable",               parse_accesscheck, pred_writable}, /* GNU, 4.3.0+ */
   PARSE_OPTION     ("xdev",                  xdev), /* POSIX */
   PARSE_TEST       ("xtype",                 xtype),	     /* GNU */
+  PARSE_TEST       ("samecontent",           samecontent),
 #ifdef UNIMPLEMENTED_UNIX
   /* It's pretty ugly for find to know about archive formats.
      Plus what it could do with cpio archives is very limited.
@@ -2348,20 +2350,17 @@
   return true;
 }
 
-
-static boolean
-parse_samefile (const struct parser_table* entry, char **argv, int *arg_ptr)
+boolean
+same_file_or_content_helper (char **argv, int *arg_ptr, struct stat *st, const char **filename, int *fd)
 {
   /* General idea: stat the file, remember device and inode numbers.
    * If a candidate file matches those, it's the same file.
    */
-  struct predicate *our_pred;
-  struct stat st, fst;
-  int fd, openflags;
-  const char *filename;
+  struct stat fst;
+  int openflags;
 
-  set_stat_placeholders (&st);
-  if (!collect_arg_stat_info (argv, arg_ptr, &st, &filename))
+  set_stat_placeholders (st);
+  if (!collect_arg_stat_info (argv, arg_ptr, st, filename))
     return false;
 
   set_stat_placeholders (&fst);
@@ -2370,7 +2369,7 @@
    * the file open if we can.  This would prevent the system reusing
    * the file.
    */
-  fd = -3;			/* means, uninitialised */
+  *fd = -3;			/* means, uninitialised */
   openflags = O_RDONLY;
 
   if (options.symlink_handling == SYMLINK_NEVER_DEREF)
@@ -2379,11 +2378,11 @@
 	{
 	  assert (O_NOFOLLOW != 0);
 	  openflags |= O_NOFOLLOW;
-	  fd = -1;		/* safe to open it. */
+	  *fd = -1;		/* safe to open it. */
 	}
       else
 	{
-	  if (S_ISLNK(st.st_mode))
+	  if (S_ISLNK(st->st_mode))
 	    {
 	      /* no way to ensure that a symlink will not be followed
 	       * by open(2), so fall back on using lstat().  Accept
@@ -2392,11 +2391,11 @@
 	       *
 	       * Avoid opening the file.
 	       */
-	      fd = -2;		/* Do not open it */
+	      *fd = -2;		/* Do not open it */
 	    }
 	  else
 	    {
-	      fd = -1;
+	      *fd = -1;
 	      /* Race condition here: the file might become a symlink here. */
 	    }
 	}
@@ -2404,26 +2403,26 @@
   else
     {
       /* We want to dereference the symlink anyway */
-      fd = -1;			/* safe to open it without O_NOFOLLOW */
+      *fd = -1;			/* safe to open it without O_NOFOLLOW */
     }
 
-  assert (fd != -3);		/* check we made a decision */
-  if (fd == -1)
+  assert (*fd != -3);		/* check we made a decision */
+  if (*fd == -1)
     {
       /* Race condition here.  The file might become a
        * symbolic link in between out call to stat and
        * the call to open.
        */
-      fd = open (argv[*arg_ptr], openflags);
+      *fd = open (*filename, openflags);
 
-      if (fd >= 0)
+      if (*fd >= 0)
 	{
 	  /* We stat the file again here to prevent a race condition
 	   * between the first stat and the call to open(2).
 	   */
-	  if (0 != fstat (fd, &fst))
+	  if (0 != fstat (*fd, &fst))
 	    {
-	      fatal_file_error (argv[*arg_ptr]);
+	      fatal_file_error (*filename);
 	    }
 	  else
 	    {
@@ -2432,19 +2431,19 @@
 	       * open, fst may contain the stat information for the
 	       * destination of the link, not the link itself.
 	       */
-	      if ((*options.xstat) (argv[*arg_ptr], &st))
-		fatal_file_error (argv[*arg_ptr]);
+	      if ((*options.xstat) (*filename, st))
+		fatal_file_error (*filename);
 
 	      if ((options.symlink_handling == SYMLINK_NEVER_DEREF)
 		  && (!options.open_nofollow_available))
 		{
-		  if (S_ISLNK(st.st_mode))
+		  if (S_ISLNK(st->st_mode))
 		    {
 		      /* We lost the race.  Leave the data in st.  The
 		       * file descriptor points to the wrong thing.
 		       */
-		      close (fd);
-		      fd = -1;
+		      close (*fd);
+		      *fd = -1;
 		    }
 		  else
 		    {
@@ -2460,8 +2459,8 @@
 		       * so the open() call may have followed a symlink
 		       * even if the -P option is in effect.
 		       */
-		      if ((st.st_dev == fst.st_dev)
-			  && (st.st_ino == fst.st_ino))
+		      if ((st->st_dev == fst.st_dev)
+			  && (st->st_ino == fst.st_ino))
 			{
 			  /* No race.  No need to copy fst to st,
 			   * since they should be identical (modulo
@@ -2473,19 +2472,33 @@
 			  /* We lost the race.  Leave the data in st.  The
 			   * file descriptor points to the wrong thing.
 			   */
-			  close (fd);
-			  fd = -1;
+			  close (*fd);
+			  *fd = -1;
 			}
 		    }
 		}
 	      else
 		{
-		  st = fst;
+		  memcpy (st, &fst, sizeof (struct stat));
 		}
 	    }
 	}
     }
 
+  return true;
+}
+
+static boolean
+parse_samefile (const struct parser_table* entry, char **argv, int *arg_ptr)
+{
+  struct predicate *our_pred;
+  struct stat st;
+  int fd;
+  const char *filename;
+
+  if (!same_file_or_content_helper (argv, arg_ptr, &st, &filename, &fd))
+    return false;
+
   our_pred = insert_primary (entry, filename);
   our_pred->args.samefileid.ino = st.st_ino;
   our_pred->args.samefileid.dev = st.st_dev;
@@ -2820,6 +2833,35 @@
 {
   return insert_type (argv, arg_ptr, entry, pred_xtype);
 }
+
+static boolean
+parse_samecontent (const struct parser_table* entry, char **argv, int *arg_ptr)
+{
+  struct predicate *our_pred;
+  struct stat st;
+  int fd;
+  const char *filename;
+
+  if (!same_file_or_content_helper (argv, arg_ptr, &st, &filename, &fd))
+    return false;
+
+  if (!S_ISREG(st.st_mode))
+    return false;
+
+  our_pred = insert_primary (entry, filename);
+
+  memcpy (&our_pred->args.samecontentargs.st, &st, sizeof (struct stat));
+  our_pred->args.samecontentargs.first_block = NULL;
+  our_pred->args.samecontentargs.ref_pathname = xmalloc (strlen (filename) + 1);
+  strcpy (our_pred->args.samecontentargs.ref_pathname, filename);
+  our_pred->args.samecontentargs.fd = fd;
+
+  our_pred->need_type = false;
+  our_pred->need_stat = true;
+  our_pred->est_success_rate = 0.0001f;
+
+  return true;
+}
 
 static boolean
 insert_type (char **argv, int *arg_ptr,
--- findutils/find/pred.c	2010-04-05 13:18:59.000000000 +0200
+++ findutils-rreale/find/pred.c	2010-04-08 17:22:51.000000000 +0200
@@ -234,6 +234,7 @@
   {pred_writable, "writable "},
   {pred_xtype, "xtype   "},
   {pred_context, "context"},
+  {pred_samecontent, "samecontent"},
   {0, "none    "}
 };
 #endif
@@ -1905,6 +1906,28 @@
   return (pred_type (pathname, &sbuf, pred_ptr));
 }
 
+boolean
+pred_samecontent (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
+{
+  struct stat *st = &pred_ptr->args.samecontentargs.st;
+  char *ref_pathname = pred_ptr->args.samecontentargs.ref_pathname;
+  int fd0, fd1;
+  boolean exit_status = false;
+
+  assert (S_ISREG(st->st_mode));
+
+  if (!S_ISREG (stat_buf->st_mode) || (stat_buf->st_size != st->st_size))
+    return false;
+
+  fd0 = pred_ptr->args.samecontentargs.fd;
+  fd1 = open (pathname, O_RDONLY | O_BINARY, 0);
+
+  exit_status = cmp (fd0, fd1, ref_pathname, pathname, pred_ptr);
+
+  close (fd1);
+
+  return exit_status;
+}
 
 boolean
 pred_context (const char *pathname, struct stat *stat_buf,
--- findutils/find/cmp.c	1970-01-01 01:00:00.000000000 +0100
+++ findutils-rreale/find/cmp.c	2010-04-08 18:23:56.000000000 +0200
@@ -0,0 +1,174 @@
+/* Buffer primitives for comparison operations.  Adapted from the
+   following files:
+  
+   - src/cmp.c in GNU diffutils 2.9
+
+   Copyright (C) 1990-1996, 1998, 2001-2002, 2004, 2006-2007, 2009-2010 Free
+   Software Foundation, Inc.
+
+   - lib/cmpbuf.c in GNU diffutils 2.9
+
+   Copyright (C) 1993, 1995, 1998, 2001-2002, 2006, 2009-2010 Free Software
+   Foundation, Inc.  */
+
+#define LARGE_BLOCK_SIZE 4096
+
+#include <config.h>
+
+#include "defs.h"
+#include <stdint.h>
+#include <assert.h>
+#include "xalloc.h"
+#include "error.h"
+#include "safe-read.h"
+
+#ifndef word
+# define word uintmax_t
+#endif
+
+/* Read up to NBYTES bytes at BUF from descriptor FD, retrying if interrupted.
+   Return the actual number of bytes read, zero for EOF, or SAFE_READ_ERROR
+   upon error.  */
+
+size_t
+safe_block_read (int fd, char *pathname, char *buf, size_t nbytes)
+{
+  char *bp = buf;
+  char const *buflim = buf + nbytes;
+
+  do
+    {
+      size_t bytes_remaining = buflim - bp;
+      size_t nread = safe_read (fd, (void *) bp, bytes_remaining);
+
+      if (nread == 0)
+	break;
+
+      if (nread == SAFE_READ_ERROR)
+	{
+          error (EXIT_FAILURE, errno, "%s", safely_quote_err_filename (0, pathname));
+          /* NOTREACHED */
+          return -1;
+	}
+
+      bp += nread;
+    }
+  while (bp < buflim);
+
+  return bp - buf;
+}
+
+/* Compare two blocks of memory P0 and P1 until they differ.
+   If the blocks are not guaranteed to be different, put sentinels at the ends
+   of the blocks before calling this function.
+
+   Return the offset of the first byte that differs.  */
+
+size_t
+block_compare (word const *p0, word const *p1)
+{
+  word const *l0, *l1;
+  char const *c0, *c1;
+
+  /* Find the rough position of the first difference by reading words,
+     not bytes.  */
+
+  for (l0 = p0, l1 = p1;  *l0 == *l1;  l0++, l1++)
+    continue;
+
+  /* Find the exact differing position (endianness independent).  */
+
+  for (c0 = (char const *) l0, c1 = (char const *) l1;
+       *c0 == *c1;
+       c0++, c1++)
+    continue;
+
+  return c0 - (char const *) p0;
+}
+
+/* Compare the target file (opened on file descriptor fd0) with the
+   reference file (opened on file descriptor fd1).
+   Return true if files don't differ, otherwise false.  */
+
+boolean
+cmp (int fd0, int fd1, char *ref_pathname, char *pathname, struct predicate *pred_ptr)
+{
+  uintmax_t remaining = UINTMAX_MAX;
+  size_t read0, read1;		/* Number of bytes read from each file. */
+  size_t first_diff;		/* Offset (0...) in buffers of 1st diff. */
+  char *buffer0 = NULL;
+  char *buffer1 = NULL;
+  boolean is_first_block = true;
+  boolean differing = false;
+  size_t buf_size = LARGE_BLOCK_SIZE;
+
+  lseek (fd0, 0, SEEK_SET);
+  lseek (fd1, 0, SEEK_SET);
+
+  do
+    {
+      size_t bytes_to_read = buf_size;
+
+      if (remaining != UINTMAX_MAX)
+	{
+	  if (remaining < bytes_to_read)
+	    bytes_to_read = remaining;
+	  remaining -= bytes_to_read;
+	}
+
+      if (is_first_block)
+	{
+          struct samecontent_args *refer = &pred_ptr->args.samecontentargs;
+
+	  if (refer->first_block == NULL)
+	    {
+              refer->first_block = (char *) xmalloc (buf_size);
+              refer->read = safe_block_read (fd0, ref_pathname, refer->first_block, bytes_to_read);
+	    }
+
+	  buffer0 = refer->first_block;
+	  read0 = refer->read;
+	  lseek (fd0, read0, SEEK_SET);
+
+          buffer1 = (char *) xmalloc (buf_size);
+          read1 = safe_block_read (fd1, pathname, buffer1, bytes_to_read);
+	}
+      else
+        {
+          if (buffer0 == NULL)
+            buffer0 = (char *) xmalloc (buf_size);
+
+          read0 = safe_block_read (fd0, ref_pathname, buffer0, bytes_to_read);
+          read1 = safe_block_read (fd1, pathname, buffer1, bytes_to_read);
+	}
+
+      assert (read0 == read1);
+
+      /* Insert sentinels for the block compare.  */
+
+      buffer0[read0] = ~buffer1[read0];
+      buffer1[read1] = ~buffer0[read1];
+
+      first_diff = block_compare ((word *) buffer0, (word *) buffer1);
+
+      if (is_first_block)
+        {
+	  buffer0 = NULL;
+	  is_first_block = false;
+	}
+
+      if (first_diff < read0)
+        {
+	  differing = true;
+          break;
+	}
+    }
+  while (!differing && read0 == buf_size);
+
+  if (buffer0 != NULL)
+    free (buffer0);
+  if (buffer1 != NULL)
+    free (buffer1);
+
+  return !differing;
+}

Reply via email to