From a7c3e7def6ed18e6760be9cdf7cbf8b928f8e36a Mon Sep 17 00:00:00 2001
From: Matt Whitlock <findutils@mattwhitlock.name>
Date: Mon, 22 Apr 2019 23:21:00 -0400
Subject: [PATCH 2/2] find: support -attr only if OS supports inode flags
 (e.g., Linux)

Also, quash some compiler warnings.
---
 NEWS           |  5 +++--
 configure.ac   |  2 ++
 doc/find.texi  |  2 +-
 find/defs.h    |  6 ++++++
 find/find.1    |  2 ++
 find/ftsfind.c | 14 ++++++++++++--
 find/parser.c  | 37 ++++++++++++++++++++++++-------------
 find/pred.c    | 19 +++++++++++++++----
 find/tree.c    | 11 ++++++++++-
 find/util.c    | 28 ++++++++++++++++++++++------
 10 files changed, 97 insertions(+), 29 deletions(-)

diff --git a/NEWS b/NEWS
index c91a613d..ff20859b 100644
--- a/NEWS
+++ b/NEWS
@@ -45,8 +45,9 @@ an existing file.
 find now supports the debug option '-D all' to include all of the other
 debug options at once.
 
-find now implements an '-attr' test to match files based on inode flags,
-which are file attributes you can access with chattr(1) and lsattr(1).
+find now implements an '-attr' test (on Linux only) to match files based
+on inode flags, which are the file attributes that you can manipulate
+using the chattr(1) and lsattr(1) commands.
 
 xargs now supports the -o, --open-tty option to reopen stdin as /dev/tty
 in the child process before executing the command; useful to run an
diff --git a/configure.ac b/configure.ac
index de651d7b..14e767e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -214,6 +214,8 @@ dnl TODO: it's possible gnulib eliminates the need for AC_HEADER_DIRENT.
 AC_HEADER_DIRENT
 AC_HEADER_STAT
 AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([linux/fs.h],
+  AC_DEFINE([HAVE_INODE_FLAGS],1,[Define to 1 if your system has inode flags.]))
 
 
 dnl Checks for typedefs, structures, and compiler characteristics.
diff --git a/doc/find.texi b/doc/find.texi
index d8c1d576..153db3be 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -1285,7 +1285,7 @@ SELinux support and only when SELinux is enabled.
 @end deffn
 
 @node Attributes
-@section File Attributes
+@section File Attributes (Linux only)
 
 The @samp{-attr} test checks that a file's attributes, also known as
 its @dfn{inode flags}, match a specified pattern.
diff --git a/find/defs.h b/find/defs.h
index 387ff8e7..72c703e4 100644
--- a/find/defs.h
+++ b/find/defs.h
@@ -303,8 +303,10 @@ struct predicate
   /* True if this predicate node requires knowledge of the inode number. */
   bool need_inum;
 
+#if HAVE_INODE_FLAGS
   /* True if this predicate node requires knowledge of the inode flags. */
   bool need_iflags;
+#endif
 
   enum EvaluationCost p_cost;
 
@@ -656,8 +658,10 @@ struct state
   /* Current depth; 0 means current path is a command line arg. */
   int curdepth;
 
+#if HAVE_INODE_FLAGS
   /* If true, we have fetched the inode flags for the current path. */
   bool have_iflags;
+#endif
 
   /* If true, we have called stat on the current path. */
   bool have_stat;
@@ -666,9 +670,11 @@ struct state
   bool have_type;
   mode_t type;			/* this is the actual type */
 
+#if HAVE_INODE_FLAGS
   /* The actual inode flags for the current path.
    * Valid if and only if have_iflags is true.  */
   int iflags;
+#endif
 
   /* The file being operated on, relative to the current directory.
      Used for stat, readlink, remove, and opendir.  */
diff --git a/find/find.1 b/find/find.1
index c53a383f..f38a9772 100644
--- a/find/find.1
+++ b/find/find.1
@@ -617,6 +617,7 @@ Any of the inode attributes in \fIflags\fR are set for the file.
 The \fIflags\fR argument may contain any attribute characters
 described in
 .BR chattr (1).
+This test is available on Linux only.
 
 .IP "\-attr [\-+]\fIflags\fR"
 The inode attributes in \fIflags\fR have the specified state.
@@ -624,6 +625,7 @@ For example,
 .B "\-attr +ad\-S"
 will match files that have the append and nodump flags set
 but not the sync flag.
+This test is available on Linux only.
 
 .IP "\-cmin \fIn\fR"
 File's status was last changed \fIn\fR minutes ago.
diff --git a/find/ftsfind.c b/find/ftsfind.c
index 511ac5b0..23a6e855 100644
--- a/find/ftsfind.c
+++ b/find/ftsfind.c
@@ -497,10 +497,18 @@ consider_visiting (FTS *p, FTSENT *ent)
   if (options.debug_options & DebugSearch)
     fprintf (stderr,
 	     "consider_visiting (late): %s: "
-	     "fts_info=%-6s, isdir=%d ignore=%d have_stat=%d have_type=%d have_iflags=%d\n",
+	     "fts_info=%-6s, isdir=%d ignore=%d have_stat=%d have_type=%d"
+#if HAVE_INODE_FLAGS
+	     " have_iflags=%d"
+#endif
+	     "\n",
 	     quotearg_n_style (0, options.err_quoting_style, ent->fts_path),
 	     get_fts_info_name (ent->fts_info),
-	     isdir, ignore, state.have_stat, state.have_type, state.have_iflags);
+	     isdir, ignore, state.have_stat, state.have_type
+#if HAVE_INODE_FLAGS
+	     , state.have_iflags
+#endif
+	     );
 
   if (!ignore)
     {
@@ -581,8 +589,10 @@ find (char *arg)
 	  state.have_stat = false;
 	  state.have_type = !!ent->fts_statp->st_mode;
 	  state.type = state.have_type ? ent->fts_statp->st_mode : 0;
+#if HAVE_INODE_FLAGS
 	  state.have_iflags = false;
 	  state.iflags = 0;
+#endif
 	  consider_visiting (p, ent);
 	}
       /* fts_read returned NULL; distinguish between "finished" and "error". */
diff --git a/find/parser.c b/find/parser.c
index 33215757..57dda3de 100644
--- a/find/parser.c
+++ b/find/parser.c
@@ -24,13 +24,16 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <grp.h>
-#include <linux/fs.h>
 #include <math.h>
 #include <pwd.h>
 #include <regex.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
+#ifdef HAVE_LINUX_FS_H
+# include <linux/fs.h>
+#endif
+
 
 /* gnulib headers. */
 #include "error.h"
@@ -231,7 +234,9 @@ static struct parser_table const parse_table[] =
   PARSE_PUNCTUATION("and",                   and),		/* GNU */
   PARSE_TEST       ("anewer",                anewer),	     /* GNU */
   {ARG_TEST,       "atime",                  parse_time, pred_atime}, /* POSIX */
-  PARSE_TEST       ("attr",                  attr),	     /* GNU, 4.7.0+ */
+#if HAVE_INODE_FLAGS
+  PARSE_TEST       ("attr",                  attr),	     /* GNU, 4.7.0+, Linux */
+#endif
   PARSE_TEST       ("cmin",                  cmin),	     /* GNU */
   PARSE_TEST       ("cnewer",                cnewer),	     /* GNU */
   {ARG_TEST,       "ctime",                  parse_time, pred_ctime}, /* POSIX */
@@ -800,6 +805,8 @@ parse_anewer (const struct parser_table* entry, char **argv, int *arg_ptr)
   return false;
 }
 
+#if HAVE_INODE_FLAGS
+
 static int _GL_ATTRIBUTE_CONST
 attr_to_iflag (char attr)
 {
@@ -921,10 +928,12 @@ parse_attr (const struct parser_table* entry, char **argv, int *arg_ptr)
 	  else if (*p == '-')
 	    ++p, on = false;
 	  if (!(iflag = attr_to_iflag (*p)))
-	    if (*p)
-	      goto bad_flag;
-	    else
-	      goto bad_expr;
+	    {
+	      if (*p)
+		goto bad_flag;
+	      else
+		goto bad_expr;
+	    }
 	  if (mask.and_mask & iflag)
 	    goto bad_expr;
 	  mask.and_mask |= iflag;
@@ -944,13 +953,13 @@ parse_attr (const struct parser_table* entry, char **argv, int *arg_ptr)
   else
     goto bad_expr;
 
-    our_pred = insert_primary (entry, expr);
-    our_pred->need_stat = our_pred->need_type = false;
-    our_pred->need_iflags = true;
-    our_pred->p_cost = NeedsStatInfo;
-    our_pred->est_success_rate = rate;
-    our_pred->args.intmask = mask;
-    return true;
+  our_pred = insert_primary (entry, expr);
+  our_pred->need_stat = our_pred->need_type = false;
+  our_pred->need_iflags = true;
+  our_pred->p_cost = NeedsStatInfo;
+  our_pred->est_success_rate = rate;
+  our_pred->args.intmask = mask;
+  return true;
 
 bad_expr:
   die (EXIT_FAILURE, 0,
@@ -965,6 +974,8 @@ bad_flag:
   return false; /* unreachable */
 }
 
+#endif /* HAVE_INODE_FLAGS */
+
 bool
 parse_closeparen (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
diff --git a/find/pred.c b/find/pred.c
index 91ed69fe..927fd9ff 100644
--- a/find/pred.c
+++ b/find/pred.c
@@ -74,7 +74,9 @@ struct pred_assoc pred_table[] =
   {pred_and, "and     "},
   {pred_anewer, "anewer  "},
   {pred_atime, "atime   "},
+#if HAVE_INODE_FLAGS
   {pred_attr, "attr    "},
+#endif
   {pred_closeparen, ")       "},
   {pred_cmin, "cmin    "},
   {pred_cnewer, "cnewer  "},
@@ -243,6 +245,7 @@ pred_atime (const char *pathname, struct stat *stat_buf, struct predicate *pred_
   return pred_timewindow (get_stat_atime(stat_buf), pred_ptr, DAYSECS);
 }
 
+#if HAVE_INODE_FLAGS
 bool
 pred_attr (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
@@ -251,11 +254,12 @@ pred_attr (const char *pathname, struct stat *stat_buf, struct predicate *pred_p
   (void) &stat_buf;
 
   assert (state.have_iflags);
-  ret = (state.iflags & pred_ptr->args.intmask.and_mask ^ pred_ptr->args.intmask.xor_mask) != 0;
+  ret = ((state.iflags & pred_ptr->args.intmask.and_mask) ^ pred_ptr->args.intmask.xor_mask) != 0;
   if (pred_ptr->args.intmask.negate)
     ret = !ret;
   return ret;
 }
+#endif /* HAVE_INODE_FLAGS */
 
 bool
 pred_closeparen (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
@@ -1330,11 +1334,18 @@ print_optlist (FILE *fp, const struct predicate *p)
     {
       print_parenthesised (fp, p->pred_left);
       fprintf (fp,
-               "%s%s%s%s",
+               "%s%s%s"
+#if HAVE_INODE_FLAGS
+               "%s"
+#endif
+               ,
                p->need_stat ? "[call stat] " : "",
                p->need_type ? "[need type] " : "",
-               p->need_inum ? "[need inum] " : "",
-               p->need_iflags ? "[get iflags] " : "");
+               p->need_inum ? "[need inum] " : ""
+#if HAVE_INODE_FLAGS
+               , p->need_iflags ? "[get iflags] " : ""
+#endif
+               );
       print_predicate (fp, p);
       fprintf (fp, " [est success rate %.4g] ", p->est_success_rate);
       if (options.debug_options & DebugSuccessRates)
diff --git a/find/tree.c b/find/tree.c
index c3f6d74b..81a92c6e 100644
--- a/find/tree.c
+++ b/find/tree.c
@@ -926,7 +926,9 @@ static struct pred_cost_lookup costlookup[] =
     { pred_and       ,  NeedsNothing,        },
     { pred_anewer    ,  NeedsStatInfo,       },
     { pred_atime     ,  NeedsStatInfo,       },
+#if HAVE_INODE_FLAGS
     { pred_attr      ,  NeedsStatInfo        },
+#endif
     { pred_closeparen,  NeedsNothing         },
     { pred_cmin      ,  NeedsStatInfo,       },
     { pred_cnewer    ,  NeedsStatInfo,       },
@@ -1659,7 +1661,11 @@ print_tree (FILE *fp, struct predicate *node, int indent)
 	   node->est_success_rate,
 	   (node->side_effects ? "" : "no "));
 
-  if (node->need_stat || node->need_type || node->need_inum || node->need_iflags)
+  if (node->need_stat || node->need_type || node->need_inum
+#if HAVE_INODE_FLAGS
+      || node->need_iflags
+#endif
+     )
     {
       int comma = 0;
 
@@ -1677,11 +1683,14 @@ print_tree (FILE *fp, struct predicate *node, int indent)
       if (node->need_type)
 	{
 	  fprintf (fp, "%stype", comma ? "," : "");
+	  comma = 1;
 	}
+#if HAVE_INODE_FLAGS
       if (node->need_iflags)
 	{
 	  fprintf (fp, "%siflags", comma ? "," : "");
 	}
+#endif
     }
   fprintf (fp, "\n");
 
diff --git a/find/util.c b/find/util.c
index b03e776e..9a73931e 100644
--- a/find/util.c
+++ b/find/util.c
@@ -24,13 +24,16 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <linux/fs.h>
 #include <string.h>
-#include <sys/ioctl.h>
 #include <sys/stat.h> /* for fstatat() */
 #include <sys/time.h>
 #include <sys/utsname.h>
 
+#if HAVE_LINUX_FS_H
+# include <linux/fs.h>
+# include <sys/ioctl.h>
+#endif
+
 /* gnulib headers. */
 #include "error.h"
 #include "fdleak.h"
@@ -274,17 +277,18 @@ get_statinfo (const char *pathname, const char *name, struct stat *p)
   return 0;
 }
 
+#if HAVE_INODE_FLAGS
 /* Get the inode flags for the current file, if they are not already known.
  * Returns 0 on success (or if we did nothing).
  */
 static int
-get_iflags ()
+get_iflags (void)
 {
   int ret = 0;
 
   if (!state.have_iflags)
     {
-      int fd = -1, ret;
+      int fd = -1;
       assert (state.have_type);
       if (!S_ISREG(state.type) && !S_ISDIR(state.type))
 	return -1;
@@ -294,6 +298,7 @@ get_iflags ()
 	{
 	case SYMLINK_DEREF_ARGSONLY:
 	  if (state.curdepth == 0)
+	  /* fall through */
 	case SYMLINK_ALWAYS_DEREF:
 	    if ((fd = openat (state.cwd_dir_fd, state.rel_pathname,
 	                      O_RDONLY | O_CLOEXEC | O_NOATIME | O_NOCTTY)) >= 0)
@@ -312,6 +317,7 @@ get_iflags ()
 
   return ret;
 }
+#endif /* HAVE_INODE_FLAGS */
 
 /* Get the stat/type/inode information for a file, if it is not
  * already known.   Returns 0 on success (or if we did nothing).
@@ -330,7 +336,11 @@ get_info (const char *pathname,
     {
       todo = true;		/* need full stat info */
     }
-  else if ((pred_ptr->need_type || pred_ptr->need_iflags) && !state.have_type)
+  else if (pred_ptr->need_type && !state.have_type
+#if HAVE_INODE_FLAGS
+           || pred_ptr->need_iflags && !state.have_iflags
+#endif
+          )
     {
       todo = true;		/* need to stat to get the type */
     }
@@ -1137,14 +1147,20 @@ apply_predicate(const char *pathname, struct stat *stat_buf, struct predicate *p
 {
   ++p->perf.visits;
 
-  if (p->need_stat || p->need_type || p->need_inum || p->need_iflags)
+  if (p->need_stat || p->need_type || p->need_inum
+#if HAVE_INODE_FLAGS
+      || p->need_iflags
+#endif
+     )
     {
       /* We may need a stat here. */
       if (get_info(pathname, stat_buf, p) != 0)
 	    return false;
     }
+#if HAVE_INODE_FLAGS
   if (p->need_iflags && get_iflags () != 0)
     return false;
+#endif
   if ((p->pred_func)(pathname, stat_buf, p))
     {
       ++(p->perf.successes);
-- 
2.21.0

