On 03/08/2016 11:42 AM, Young Mo Kang wrote:
> Please find the attached patch files to enable -type x,y,z search.

Sorry for the late reply.
I thought a bit about the implementation and thought it'd be better
to avoid artificially creating the OR-ed predicates, and rather store
the wanted file types in an array and check for that in pred.c.
I think this should be faster, too.

The attached implements it like that.
I've also added extensive tests for the new feature.

@James: did you get green lights regarding FSF Copyright assignment?
Otherwise, or if you, Young, feel that my re-implementation is too far
away from your original, I can also set myself as author (and change
the "Co-authored-by: ..." to "Inspired by a proposal of ..." in the
commit msg).

Happy testing & have a nice day,
Berny
>From 3f7852c7ad1aa7dad7ed8d1095212739834420c0 Mon Sep 17 00:00:00 2001
From: Young Mo Kang <[email protected]>
Date: Wed, 6 Apr 2016 10:09:40 +0200
Subject: [PATCH] find: support list of file types for -type and -xtype

* find/defs.h (enum file_type): Add enumeration for all (supported)
file types.
(struct predicate): Replace 'type' by 'types' as an array of bool
for the above enum file_type.
* find/parser.c (insert_type): Treat the argument to -type and -xtype
as a comma-separated list of type letters: instead of storing the
single type of the predicate, save the searched type letters in the
above 'types' array.
* find/pred.c (pred_type): Change the comparison against the saved file
type predicate: now check if the type of the actual file is in the array
of searched 'types'.
* find/testsuite/test_type-list.sh: Add test.
* find/testsuite/Makefile.am: Reference the test.
* find/find.1: Document the new feature.
* doc/find.texi: Likewise.
* NEWS: Likewise.

Co-authored-by: Bernhard Voelker.
---
 NEWS                             |   5 +
 doc/find.texi                    |   4 +
 find/defs.h                      |  23 ++-
 find/find.1                      |  19 +++
 find/parser.c                    | 252 +++++++++++++++++------------
 find/pred.c                      |  77 +++++++--
 find/testsuite/Makefile.am       |   1 +
 find/testsuite/test_type-list.sh | 340 +++++++++++++++++++++++++++++++++++++++
 8 files changed, 598 insertions(+), 123 deletions(-)
 create mode 100755 find/testsuite/test_type-list.sh

diff --git a/NEWS b/NEWS
index 556eab8..59f6cb9 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,11 @@ the --help option.  Previously, when the user passed invalid options
 or arguments, the user's attention to the corresponding error
 diagnostic was distracted by that lengthy text.
 
+find now accepts multiple file type arguments to the -type and -xtype
+options separated by comma ','.  For example, to search for symbolic
+links and directories simply provide the shorter '-type l,d' instead
+of the - yet more portable - '( -type l -o -type d )'.
+
 ** Bug Fixes
 
 #46784: frcode drops last char if no final newline
diff --git a/doc/find.texi b/doc/find.texi
index 634e229..93c67c3 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -1081,6 +1081,10 @@ socket
 @item D
 door (Solaris)
 @end table
+
+As a GNU extension, multiple file types can be provided as a combined list
+separated by comma @samp{,}. For example, @samp{-type f,d,l} is logically
+interpreted as @samp{( -type f -o -type d -o -type l )}.
 @end deffn
 
 @deffn Test -xtype c
diff --git a/find/defs.h b/find/defs.h
index 9311565..52e522f 100644
--- a/find/defs.h
+++ b/find/defs.h
@@ -163,6 +163,27 @@ struct size_val
   uintmax_t size;
 };
 
+/* Supported file types for the -type/-xtype options.  */
+enum file_type
+  {
+    FTYPE_BLK,
+    FTYPE_CHR,
+    FTYPE_DIR,
+    FTYPE_REG,
+#ifdef S_IFLNK
+    FTYPE_LNK,
+#endif
+#ifdef S_IFIFO
+    FTYPE_FIFO,
+#endif
+#ifdef S_IFSOCK
+    FTYPE_SOCK,
+#endif
+#ifdef S_IFDOOR
+    FTYPE_DOOR,
+#endif
+    FTYPE_COUNT
+  };
 
 enum xval
   {
@@ -305,7 +326,7 @@ struct predicate
     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 */
-    mode_t type;		/* type */
+    bool types[FTYPE_COUNT];	/* file type(s) */
     struct format_val printf_vec; /* printf fprintf fprint ls fls print0 fprint0 print */
     security_context_t scontext; /* security context */
   } args;
diff --git a/find/find.1 b/find/find.1
index c948b4b..7b626b5 100644
--- a/find/find.1
+++ b/find/find.1
@@ -954,6 +954,9 @@ socket
 .IP D
 door (Solaris)
 .RE
+.IP
+To search for more than one type at once, you can supply the combined list of
+type letters separated by a comma `,' (GNU extension).
 .IP "\-uid \fIn\fR"
 File's numeric user ID is \fIn\fR.
 
@@ -1653,6 +1656,8 @@ previous versions of findutils.
 .IP \fB\-type\fR
 Supported.   POSIX specifies `b', `c', `d', `l', `p', `f' and `s'.
 GNU find also supports `D', representing a Door, where the OS provides these.
+Furthermore, GNU find allows multiple types to be specified at once in a
+comma-separated list.
 
 .IP \fB\-ok\fR
 Supported.
@@ -2087,6 +2092,20 @@ discovered (for example we do not search project3/src because we
 already found project3/.svn), but ensures sibling directories
 (project2 and project3) are found.
 
+.P
+.nf
+.B find /tmp -type f,d,l
+.fi
+
+Search for files, directories, and symbolic links in the directory
+.B /tmp
+passing these types as a comma-separated list (GNU extension),
+which is otherwise equivalent to the longer, yet more portable:
+
+.nf
+.B find /tmp \e( -type f -o -type d -o -type l \e)
+.fi
+
 .SH EXIT STATUS
 .PP
 .B find
diff --git a/find/parser.c b/find/parser.c
index 57fb296..6ea6e05 100644
--- a/find/parser.c
+++ b/find/parser.c
@@ -2679,131 +2679,171 @@ insert_type (char **argv, int *arg_ptr,
 	     PRED_FUNC which_pred)
 {
   struct predicate *our_pred;
-  float rate = 0.01;
+  float type_rate = 0.0;
   const char *typeletter;
+  const char *pred_string = which_pred == pred_xtype ? "-xtype" : "-type";
 
-  if (collect_arg (argv, arg_ptr, &typeletter))
+  if (! collect_arg (argv, arg_ptr, &typeletter))
+    return false;
+
+  if (!*typeletter)
     {
-      if (strlen (typeletter) != 1u)
-	{
-	  error (EXIT_FAILURE, 0,
-		 _("Arguments to -type should contain only one letter"));
-	  /*NOTREACHED*/
-	  return false;
-	}
+      error (EXIT_FAILURE, 0,
+	     _("Arguments to %s should contain at least one letter"),
+	     pred_string);
+      /*NOTREACHED*/
+      return false;
+    }
 
-      /* From a real system here are the counts of files by type:
-         Type   Count  Fraction
-         f    4410884  0.875
-         d     464722  0.0922
-         l     156662  0.0311
-         b       4476  0.000888
-         c       2233  0.000443
-         s         80  1.59e-05
-         p         38  7.54e-06
-       */
-      {
-	mode_t type_cell;
+  our_pred = insert_primary_withpred (entry, which_pred, typeletter);
 
-	switch (typeletter[0])
-	  {
-	  case 'b':			/* block special */
-	    type_cell = S_IFBLK;
-	    rate = 0.000888f;
-	    break;
-	  case 'c':			/* character special */
-	    type_cell = S_IFCHR;
-	    rate = 0.000443f;
-	    break;
-	  case 'd':			/* directory */
-	    type_cell = S_IFDIR;
-	    rate = 0.0922f;
-	    break;
-	  case 'f':			/* regular file */
-	    type_cell = S_IFREG;
-	    rate = 0.875f;
-	    break;
-	  case 'l':			/* symbolic link */
+  /* Figure out if we will need to stat the file, because if we don't
+   * need to follow symlinks, we can avoid a stat call by using
+   * struct dirent.d_type.
+   */
+  if (which_pred == pred_xtype)
+    {
+      our_pred->need_stat = true;
+      our_pred->need_type = false;
+    }
+  else
+    {
+      our_pred->need_stat = false; /* struct dirent is enough */
+      our_pred->need_type = true;
+    }
+
+  /* From a real system here are the counts of files by type:
+     Type   Count  Fraction
+     f    4410884  0.875
+     d     464722  0.0922
+     l     156662  0.0311
+     b       4476  0.000888
+     c       2233  0.000443
+     s         80  1.59e-05
+     p         38  7.54e-06
+  */
+
+  for (; *typeletter; )
+    {
+      unsigned int type_cell;
+      float rate = 0.01;
+
+      switch (*typeletter)
+      {
+      case 'b':			/* block special */
+	type_cell = FTYPE_BLK;
+	rate = 0.000888f;
+	break;
+      case 'c':			/* character special */
+	type_cell = FTYPE_CHR;
+	rate = 0.000443f;
+	break;
+      case 'd':			/* directory */
+	type_cell = FTYPE_DIR;
+	rate = 0.0922f;
+	break;
+      case 'f':			/* regular file */
+	type_cell = FTYPE_REG;
+	rate = 0.875f;
+	break;
+      case 'l':			/* symbolic link */
 #ifdef S_IFLNK
-	    type_cell = S_IFLNK;
-	    rate = 0.0311f;
+	type_cell = FTYPE_LNK;
+	rate = 0.0311f;
 #else
-	    type_cell = 0;
-	    error (EXIT_FAILURE, 0,
-		   _("-type %c is not supported because symbolic links "
-		     "are not supported on the platform find was compiled on."),
-		   (*typeletter));
+	type_cell = 0;
+	error (EXIT_FAILURE, 0,
+	       _("%s %c is not supported because symbolic links "
+		 "are not supported on the platform find was compiled on."),
+	       pred_string, (*typeletter));
 #endif
-	    break;
-	  case 'p':			/* pipe */
+	break;
+      case 'p':			/* pipe */
 #ifdef S_IFIFO
-	    type_cell = S_IFIFO;
-	    rate = 7.554e-6f;
+	type_cell = FTYPE_FIFO;
+	rate = 7.554e-6f;
 #else
-	    type_cell = 0;
-	    error (EXIT_FAILURE, 0,
-		   _("-type %c is not supported because FIFOs "
-		     "are not supported on the platform find was compiled on."),
-		   (*typeletter));
+	type_cell = 0;
+	error (EXIT_FAILURE, 0,
+	       _("%s %c is not supported because FIFOs "
+		 "are not supported on the platform find was compiled on."),
+	       pred_string, (*typeletter));
 #endif
-	    break;
-	  case 's':			/* socket */
+	break;
+      case 's':			/* socket */
 #ifdef S_IFSOCK
-	    type_cell = S_IFSOCK;
-	    rate = 1.59e-5f;
+	type_cell = FTYPE_SOCK;
+	rate = 1.59e-5f;
 #else
-	    type_cell = 0;
-	    error (EXIT_FAILURE, 0,
-		   _("-type %c is not supported because named sockets "
-		     "are not supported on the platform find was compiled on."),
-		   (*typeletter));
+	type_cell = 0;
+	error (EXIT_FAILURE, 0,
+	       _("%s %c is not supported because named sockets "
+		 "are not supported on the platform find was compiled on."),
+	       pred_string, (*typeletter));
 #endif
-	    break;
-	  case 'D':			/* Solaris door */
+	break;
+      case 'D':			/* Solaris door */
 #ifdef S_IFDOOR
-	    type_cell = S_IFDOOR;
-	    /* There are no Solaris doors on the example system surveyed
-	     * above, but if someone uses -type D, they are presumably
-	     * expecting to find a non-zero number.  We guess at a
-	     * rate. */
-	    rate = 1.0e-5f;
+	type_cell = FTYPE_DOOR;
+	/* There are no Solaris doors on the example system surveyed
+	 * above, but if someone uses -type D, they are presumably
+	 * expecting to find a non-zero number.  We guess at a
+	 * rate. */
+	rate = 1.0e-5f;
 #else
-	    type_cell = 0;
-	    error (EXIT_FAILURE, 0,
-		   _("-type %c is not supported because Solaris doors "
-		     "are not supported on the platform find was compiled on."),
-		   (*typeletter));
+	type_cell = 0;
+	error (EXIT_FAILURE, 0,
+	       _("%s %c is not supported because Solaris doors "
+		 "are not supported on the platform find was compiled on."),
+	       pred_string, (*typeletter));
 #endif
-	    break;
-	  default:			/* None of the above ... nuke 'em. */
-	    type_cell = 0;
-	    error (EXIT_FAILURE, 0,
-		   _("Unknown argument to -type: %c"), (*typeletter));
-	    /*NOTREACHED*/
-	    return false;
-	  }
-	our_pred = insert_primary_withpred (entry, which_pred, typeletter);
-	our_pred->est_success_rate = rate;
-
-	/* Figure out if we will need to stat the file, because if we don't
-	 * need to follow symlinks, we can avoid a stat call by using
-	 * struct dirent.d_type.
-	 */
-	if (which_pred == pred_xtype)
-	  {
-	    our_pred->need_stat = true;
-	    our_pred->need_type = false;
-	  }
-	else
-	  {
-	    our_pred->need_stat = false; /* struct dirent is enough */
-	    our_pred->need_type = true;
-	  }
-	our_pred->args.type = type_cell;
+	break;
+      default:			/* None of the above ... nuke 'em. */
+	type_cell = 0;
+	error (EXIT_FAILURE, 0,
+	       _("Unknown argument to %s: %c"), pred_string, (*typeletter));
+	/*NOTREACHED*/
+	return false;
       }
-      return true;
+
+      if (our_pred->args.types[type_cell])
+	{
+	  error (EXIT_FAILURE, 0,
+		 _("Duplicate file type '%c' in the argument list to %s."),
+		 (*typeletter), pred_string);
+	}
+
+      our_pred->est_success_rate += rate;
+      our_pred->args.types[type_cell] = true;
+
+      /* Advance.
+       * Currently, only 1-character file types separated by ',' are supported.
+       */
+      typeletter++;
+      if (*typeletter)
+	{
+	  if (*typeletter != ',')
+	    {
+	      error (EXIT_FAILURE, 0,
+		     _("Must separate multiple arguments to %s using: ','"),
+		     pred_string);
+	      /*NOTREACHED*/
+	      return false;
+	    }
+	  typeletter++;
+	  if (!*typeletter)
+	    {
+	      error (EXIT_FAILURE, 0,
+		     _("Last file type in list argument to %s "
+		       "is missing, i.e., list is ending on: ','"),
+		     pred_string);
+	      /*NOTREACHED*/
+	      return false;
+	    }
+	}
     }
-  return false;
+
+  return true;
 }
 
 
diff --git a/find/pred.c b/find/pred.c
index d633ab9..f7e9b59 100644
--- a/find/pred.c
+++ b/find/pred.c
@@ -1032,7 +1032,7 @@ bool
 pred_type (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
   mode_t mode;
-  mode_t type = pred_ptr->args.type;
+  enum file_type type = FTYPE_COUNT;
 
   assert (state.have_type);
 
@@ -1052,31 +1052,76 @@ pred_type (const char *pathname, struct stat *stat_buf, struct predicate *pred_p
      mode = state.type;
 
 #ifndef S_IFMT
-  /* POSIX system; check `mode' the slow way. */
-  if ((S_ISBLK (mode) && type == S_IFBLK)
-      || (S_ISCHR (mode) && type == S_IFCHR)
-      || (S_ISDIR (mode) && type == S_IFDIR)
-      || (S_ISREG (mode) && type == S_IFREG)
+  /* POSIX system; check `mode' the slow way.
+   * Search in the order of probability (f,d,l,b,c,s,p,D).
+   */
+  if (S_ISREG (mode))
+     type = FTYPE_REG;
+  else if (S_ISDIR (mode))
+     type = FTYPE_DIR;
 #ifdef S_IFLNK
-      || (S_ISLNK (mode) && type == S_IFLNK)
-#endif
-#ifdef S_IFIFO
-      || (S_ISFIFO (mode) && type == S_IFIFO)
+  else if (S_ISLNK (mode))
+     type = FTYPE_LNK;
 #endif
+  else if (S_ISBLK (mode))
+     type = FTYPE_BLK;
+  else if (S_ISCHR (mode))
+     type = FTYPE_CHR;
 #ifdef S_IFSOCK
-      || (S_ISSOCK (mode) && type == S_IFSOCK)
+  else if (S_ISSOCK (mode))
+     type = FTYPE_SOCK;
+#endif
+#ifdef S_IFIFO
+  else if (S_ISFIFO (mode))
+     type = FTYPE_FIFO;
 #endif
 #ifdef S_IFDOOR
-      || (S_ISDOOR (mode) && type == S_IFDOOR)
+  else if (S_ISDOOR (mode))
+    type = FTYPE_DOOR;
 #endif
-      )
 #else /* S_IFMT */
   /* Unix system; check `mode' the fast way. */
-  if ((mode & S_IFMT) == type)
+  switch (mode & S_IFMT)
+    {
+    case S_IFREG:
+      type = FTYPE_REG;
+      break;
+    case S_IFDIR:
+      type = FTYPE_DIR;
+      break;
+#ifdef S_IFLNK
+    case S_IFLNK:
+      type = FTYPE_LNK;
+      break;
+#endif
+    case S_IFBLK:
+      type = FTYPE_BLK;
+      break;
+    case S_IFCHR:
+      type = FTYPE_CHR;
+      break;
+#ifdef S_IFSOCK
+    case S_IFSOCK:
+      type = FTYPE_SOCK;
+      break;
+#endif
+#ifdef S_IFIFO
+    case S_IFIFO:
+      type = FTYPE_FIFO;
+      break;
+#endif
+#ifdef S_IFDOOR
+    case S_IFDOOR:
+      type = FTYPE_DOOR;
+      break;
+#endif
+    }
 #endif /* S_IFMT */
-    return (true);
+
+  if ((type != FTYPE_COUNT) && pred_ptr->args.types[type])
+    return true;
   else
-    return (false);
+    return false;
 }
 
 bool
diff --git a/find/testsuite/Makefile.am b/find/testsuite/Makefile.am
index 228957f..65d5d32 100644
--- a/find/testsuite/Makefile.am
+++ b/find/testsuite/Makefile.am
@@ -257,6 +257,7 @@ sv-bug-32043.sh \
 test_escapechars.sh \
 test_escape_c.sh \
 test_inode.sh \
+test_type-list.sh \
 sv-34079.sh \
 sv-34976-execdir-fd-leak.sh
 
diff --git a/find/testsuite/test_type-list.sh b/find/testsuite/test_type-list.sh
new file mode 100755
index 0000000..c500b02
--- /dev/null
+++ b/find/testsuite/test_type-list.sh
@@ -0,0 +1,340 @@
+#! /bin/sh
+# Copyright (C) 2016 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This test verifies find's behavior regarding comma-separated file
+# type arguments to the -type/-xtype options.
+
+testname="$(basename $0)"
+
+. "${srcdir}"/binary_locations.sh
+
+die() {
+  echo "$@" >&2
+  exit 1
+}
+
+# This is used to simplify checking of the return value
+# which is useful when ensuring a command fails as desired.
+# I.e., just doing `command ... &&fail=1` will not catch
+# a segfault in command for example.  With this helper you
+# instead check an explicit exit code like
+#   returns_ 1 command ... || fail
+returns_ () {
+  # Disable tracing so it doesn't interfere with stderr of the wrapped command
+  { set +x; } 2>/dev/null
+
+  local exp_exit="$1"
+  shift
+  "$@"
+  test $? -eq $exp_exit && ret_=0 || ret_=1
+
+  set -x
+  { return $ret_; } 2>/dev/null
+}
+
+# Define the nicest compare available (borrowed from gnulib).
+if diff_out_=`exec 2>/dev/null; diff -u "$0" "$0" < /dev/null` \
+   && diff -u Makefile "$0" 2>/dev/null | grep '^[+]#!' >/dev/null; then
+  # diff accepts the -u option and does not (like AIX 7 'diff') produce an
+  # extra space on column 1 of every content line.
+  if test -z "$diff_out_"; then
+    compare () { diff -u "$@"; }
+  else
+    compare ()
+    {
+      if diff -u "$@" > diff.out; then
+        # No differences were found, but Solaris 'diff' produces output
+        # "No differences encountered". Hide this output.
+        rm -f diff.out
+        true
+      else
+        cat diff.out
+        rm -f diff.out
+        false
+      fi
+    }
+  fi
+elif diff_out_=`exec 2>/dev/null; diff -c "$0" "$0" < /dev/null`; then
+  if test -z "$diff_out_"; then
+    compare () { diff -c "$@"; }
+  else
+    compare ()
+    {
+      if diff -c "$@" > diff.out; then
+        # No differences were found, but AIX and HP-UX 'diff' produce output
+        # "No differences encountered" or "There are no differences between the
+        # files.". Hide this output.
+        rm -f diff.out
+        true
+      else
+        cat diff.out
+        rm -f diff.out
+        false
+      fi
+    }
+  fi
+elif cmp -s /dev/null /dev/null 2>/dev/null; then
+  compare () { cmp -s "$@"; }
+else
+  compare () { cmp "$@"; }
+fi
+
+# Check if the given file type is supported by find.
+# Used for the file type compiled in conditionally: l,p,s,D)
+find_supports_type() {
+  find '.' -maxdepth 0 -type "$1"
+}
+
+# Create test files of all possible types (if possible):
+# f,d,p,l,b,c,s,D, and a dangling symlink.
+make_test_data() {
+  d="$1"
+  (
+    cd "$1" || exit 1
+    # regular file
+    : > reg || exit 1
+    # directory
+    mkdir dir || exit 1
+    # BLK device
+    mknod blk b 0 0 || :  # ignore failure
+    # CHR device
+    mknod chr c 0 0 || :  # ignore failure
+
+    # FIFO
+    if [ $HAVE_FIFO = 1 ]; then
+      mkfifo fifo || :  # ignore failure
+    fi
+
+    # Socket: try various ways to create one.
+    if [ $HAVE_SOCK = 1 ]; then
+      perl -e '
+        use IO::Socket::UNIX;
+        my $s = IO::Socket::UNIX->new (Type => SOCK_STREAM(), Local => "sock");'
+
+      test -S sock \
+        || python -c \
+             "import socket as s;
+              sock = s.socket(s.AF_UNIX);
+              sock.bind('sock');
+              "
+      # Also the netcat family leaves the socket behind ...
+      test -S sock \
+        || { nc -lU sock & pid=$!; \
+             sleep 1; kill $pid; wait $pid; }
+      test -S sock \
+        || { netcat -lU sock & pid=$!; \
+             sleep 1; kill $pid; wait $pid; }
+      # ...  while socat has to be forcefully killed.
+      test -S sock \
+        || { socat unix-listen:sock fd:2 & pid=$!; \
+             sleep 1; kill -9 $!; wait $pid; }
+    fi
+
+    # Door: not that easy.
+    if [ $HAVE_DOOR = 1 ]; then
+      : # TODO
+    fi
+
+    # Symbolic links to all types, and a dangling one.
+    if [ $HAVE_LINK = 1 ]; then
+      test -f reg && ln -s reg reg-link
+      test -d dir && ln -s dir dir-link
+      test -b blk && ln -s blk blk-link
+      test -c chr && ln -s chr chr-link
+      test -S sock && ln -s sock sock-link
+      test -p fifo && ln -s fifo fifo-link
+      ln -s enoent dangling-link
+    fi
+  ) \
+  || die "failed to set up the test in ${outdir}"
+}
+
+set -x
+outdir="$(mktemp -d)" || die "FAIL: could not create a test directory."
+all="${outdir}.all"
+exp="${outdir}.exp"
+out="${outdir}.out"
+err="${outdir}.err"
+
+# Check what file types are compiled into find(1).
+find_supports_type l && HAVE_LINK=1 || HAVE_LINK=0
+find_supports_type p && HAVE_FIFO=1 || HAVE_FIFO=0
+find_supports_type s && HAVE_SOCK=1 || HAVE_SOCK=0
+find_supports_type D && HAVE_DOOR=1 || HAVE_DOOR=0
+
+# Create some test files.
+make_test_data "${outdir}" \
+  && "${ftsfind}" "${outdir}" -mindepth 1 > $all \
+  && sort -o $all $all \
+  || die "FAIL: failed to set up the test in ${outdir}"
+# Just to see what's there.
+"${ftsfind}" "${outdir}" -mindepth 1 -ls
+
+fail=0
+for exe in "${ftsfind}" "${oldfind}"; do
+
+  # Negative tests first.  Expect the output to be empty.
+  : > $exp
+
+  # Ensure empty type arguments are rejected.
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -type '' > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Arguments to -type should contain at least one letter' $err \
+    || { cat $err; fail=1; }
+
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -xtype '' > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Arguments to -xtype should contain at least one letter' $err \
+    || { cat $err; fail=1; }
+
+  # Ensure non-separated type arguments are rejected.
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -type fd > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Must separate multiple arguments to -type' $err \
+    || { cat $err; fail=1; }
+
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -xtype fd > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Must separate multiple arguments to -xtype' $err \
+    || { cat $err; fail=1; }
+
+  # Ensure unterminated type list arguments are rejected.
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -type f, > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Last file type in list argument to -type is missing' $err \
+    ||  { cat $err; fail=1; }
+
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -xtype f, > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Last file type in list argument to -xtype is missing' $err \
+    ||  { cat $err; fail=1; }
+
+  # Ensure duplicate entries in the type list arguments are rejected.
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -type f,f > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Duplicate file type .* in the argument list to -type' $err \
+    ||  { cat $err; fail=1; }
+
+  returns_ 1 "${exe}" "${outdir}" -mindepth 1 -xtype f,f > $out 2> $err || fail=1
+  compare $exp $out || fail=1
+  grep 'Duplicate file type .* in the argument list to -xtype' $err \
+    ||  { cat $err; fail=1; }
+
+  # Continue with positive tests.
+  # Files and directories.
+  grep -e '/reg$' -e '/dir$' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -type f,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  grep -e '/reg' -e '/dir' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -xtype f,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  # Block devices.
+  grep -e '/reg$' -e '/dir$' -e '/blk$' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -type b,f,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  grep -e '/reg' -e '/dir' -e '/blk' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -xtype b,f,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  # Character devices.
+  grep -e '/reg$' -e '/dir$' -e '/chr$' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -type f,c,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  grep -e '/reg' -e '/dir' -e '/chr' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -xtype f,c,d > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  # FIFOs.
+  if [ $HAVE_FIFO = 1 ]; then
+    grep -e '/reg$' -e '/dir$' -e '/fifo$' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -type f,d,p > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+
+    grep -e '/reg' -e '/dir' -e '/fifo' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -xtype f,d,p > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+  fi
+
+  # Sockets.
+  if [ $HAVE_SOCK = 1 ]; then
+    grep -e '/reg$' -e '/dir$' -e '/sock$' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -type f,d,s > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+
+    grep -e '/reg' -e '/dir' -e '/sock' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -xtype f,d,s > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+  fi
+
+  # Symbolic links.
+  if [ $HAVE_LINK = 1 ]; then
+
+    grep -e '/reg$' -e 'link$' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -type f,l > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+
+    grep -e '/reg' -e 'dangling-link$' $all > $exp
+    "${exe}" "${outdir}" -mindepth 1 -xtype f,l > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+  fi
+
+  # -xtype: all but the dangling symlink.
+  t='f,d,b,c'
+  [ $HAVE_FIFO = 1 ] && t="$t,p"
+  [ $HAVE_SOCK = 1 ] && t="$t,s"
+  [ $HAVE_DOOR = 1 ] && t="$t,D"
+  grep -v 'dangling-link$' $all > $exp
+  "${exe}" "${outdir}" -mindepth 1 -xtype "$t" > $out || fail=1
+  sort -o $out $out
+  compare $exp $out || fail=1;
+
+  # negation
+  if [ $HAVE_LINK = 1 ]; then
+    "${exe}" "${outdir}" -mindepth 1 -not -xtype l > $out || fail=1
+    sort -o $out $out
+    compare $exp $out || fail=1;
+  fi
+
+  # Finally: full list
+  [ $HAVE_LINK = 1 ] && t="$t,l"
+  "${exe}" "${outdir}" -mindepth 1 -type "$t" > $out || fail=1
+  sort -o $out $out
+  compare $all $out || fail=1;
+
+  "${exe}" "${outdir}" -mindepth 1 -xtype "$t" > $out || fail=1
+  sort -o $out $out
+  compare $all $out || fail=1;
+done
+
+rm -rf "${outdir}" $all $exp $out $err || exit 1
+exit $fail
-- 
2.1.4

Reply via email to