I have put together a patch that adds -executable, -readable, and
-writable to /usr/bin/find.

When I first started working on this patch, I implemented the access
check by checking the stat of the file like so:

    int
    is_permission(const FTSENT *entry, mode_t umode, mode_t gmode,
                  mode_t omode)
    {
        struct stat *stat = entry->fts_statp;

        if (getuid() == stat->st_uid && (stat->st_mode & umode) != 0)
            return 1;
        if (getgid() == stat->st_gid && (stat->st_mode & gmode) != 0)
            return 1;
        if ((stat->st_mode & omode) != 0)
            return 1;

        return 0;
    }

    int is_readable(const FTSENT *entry)
    {
        return is_permission(entry, S_IRUSR, S_IRGRP, S_IROTH);
    }

    int is_writable(const FTSENT *entry)
    {
        return is_permission(entry, S_IWUSR, S_IWGRP, S_IWOTH);
    }

    int is_executable(const FTSENT *entry)
    {
        return is_permission(entry, S_IXUSR, S_IXGRP, S_IXOTH);
    }

This appears to work just fine. However, when I read the Linux manpage
for find, they use access(2):

    This test makes use of the access(2) system call, and so can be
    fooled by NFS servers which do UID mapping (or root-squashing),
    since many systems implement access(2) in the client's kernel and so
    cannot make use of the UID mapping information held on the server.
    Because this test is based only on the result of the access(2)
    system call, there is no guarantee that a file for which this test
    succeeds can actually be executed.

Because I'm unfamiliar with OpenBSD I worried that maybe there was more to
access(2) than the checks I created above, I implemented my patch using it.
However, I had some difficulty finding the source of access(2) so I could not
vet by assumptions.

Any guidance is greatly appreciated. I have been finding small-footprint
patches like this one to become familiar with OpenBSD.

Index: extern.h
===================================================================
RCS file: /cvs/src/usr.bin/find/extern.h,v
retrieving revision 1.22
diff -u -p -u -p -r1.22 extern.h
--- extern.h    3 Jan 2017 21:31:16 -0000    1.22
+++ extern.h    1 Apr 2023 02:15:15 -0000
@@ -58,6 +58,7 @@ PLAN    *c_depth(char *, char ***, int);
 PLAN    *c_empty(char *, char ***, int);
 PLAN    *c_exec(char *, char ***, int);
 PLAN    *c_execdir(char *, char ***, int);
+PLAN    *c_executable(char *, char ***, int);
 PLAN    *c_flags(char *, char ***, int);
 PLAN    *c_follow(char *, char ***, int);
 PLAN    *c_fstype(char *, char ***, int);
@@ -78,9 +79,11 @@ PLAN    *c_perm(char *, char ***, int);
 PLAN    *c_print(char *, char ***, int);
 PLAN    *c_print0(char *, char ***, int);
 PLAN    *c_prune(char *, char ***, int);
+PLAN    *c_readable(char *, char ***, int);
 PLAN    *c_size(char *, char ***, int);
 PLAN    *c_type(char *, char ***, int);
 PLAN    *c_user(char *, char ***, int);
+PLAN    *c_writable(char *, char ***, int);
 PLAN    *c_xdev(char *, char ***, int);
 PLAN    *c_openparen(char *, char ***, int);
 PLAN    *c_closeparen(char *, char ***, int);
@@ -88,5 +91,5 @@ PLAN    *c_mtime(char *, char ***, int);
 PLAN    *c_not(char *, char ***, int);
 PLAN    *c_or(char *, char ***, int);

-extern int ftsoptions, isdelete, isdepth, isoutput, isxargs;
+extern int ftsoptions, isdelete, isdepth, isoutput, isxargs, accessmode;
 extern int mayexecve;
Index: find.1
===================================================================
RCS file: /cvs/src/usr.bin/find/find.1,v
retrieving revision 1.101
diff -u -p -u -p -r1.101 find.1
--- find.1    31 Mar 2022 17:27:24 -0000    1.101
+++ find.1    1 Apr 2023 02:15:15 -0000
@@ -264,6 +264,9 @@ The filename substituted for the string
 .Qq {}
 is not qualified.
 .Pp
+.It Fl executable
+True if the file is executable by the user running the command.
+.Pp
 .It Xo
 .Ic -flags
 .Oo - Oc Ns Ar flags
@@ -450,6 +453,9 @@ primary has no effect if the
 .Fl d
 option was specified.
 .Pp
+.It Fl readable
+True if the file is readable by the user running the command.
+.Pp
 .It Ic -size Ar n Ns Op Cm c
 True if the file's size, rounded up, in 512-byte blocks is
 .Ar n .
@@ -491,6 +497,9 @@ If
 is numeric and there is no such user name, then
 .Ar uname
 is treated as a user ID.
+.Pp
+.It Fl writable
+True if the file is writable by the user running the command.
 .Pp
 .It Ic -xdev
 This primary always evaluates to true.
Index: find.h
===================================================================
RCS file: /cvs/src/usr.bin/find/find.h,v
retrieving revision 1.18
diff -u -p -u -p -r1.18 find.h
--- find.h    3 Jan 2017 21:31:16 -0000    1.18
+++ find.h    1 Apr 2023 02:15:15 -0000
@@ -37,12 +37,12 @@
 enum ntype {
     N_AND = 1,                 /* must start > 0 */
     N_AMIN, N_ANEWER, N_ATIME, N_CLOSEPAREN, N_CMIN, N_CNEWER, N_CTIME,
-    N_DELETE, N_DEPTH, N_EMPTY, N_EXEC, N_EXECDIR, N_EXPR,
+    N_DELETE, N_DEPTH, N_EMPTY, N_EXEC, N_EXECDIR, N_EXECUTABLE, N_EXPR,
     N_FLAGS, N_FOLLOW, N_FSTYPE, N_GROUP, N_INAME, N_INUM, N_LINKS, N_LS,
     N_MMIN, N_MAXDEPTH,
     N_MINDEPTH, N_MTIME, N_NAME, N_NEWER, N_NOGROUP, N_NOT, N_NOUSER,
     N_OK, N_OPENPAREN, N_OR, N_PATH, N_PERM, N_PRINT, N_PRINT0, N_PRUNE,
-    N_SIZE, N_TYPE, N_USER, N_XDEV
+    N_READABLE, N_SIZE, N_TYPE, N_USER, N_WRITABLE, N_XDEV
 };

 /* node definition */
Index: function.c
===================================================================
RCS file: /cvs/src/usr.bin/find/function.c,v
retrieving revision 1.53
diff -u -p -u -p -r1.53 function.c
--- function.c    8 Mar 2023 04:43:10 -0000    1.53
+++ function.c    1 Apr 2023 02:15:15 -0000
@@ -100,6 +100,7 @@ int    f_nogroup(PLAN *, FTSENT *);
 int    f_nouser(PLAN *, FTSENT *);
 int    f_path(PLAN *, FTSENT *);
 int    f_perm(PLAN *, FTSENT *);
+int    f_accesscheck(PLAN *, FTSENT *);
 int    f_print(PLAN *, FTSENT *);
 int    f_print0(PLAN *, FTSENT *);
 int    f_prune(PLAN *, FTSENT *);
@@ -1448,6 +1449,45 @@ PLAN *
 c_prune(char *ignore, char ***ignored, int unused)
 {
     return (palloc(N_PRUNE, f_prune));
+}
+
+/*
+ * -permissions functions --
+ *
+ *      These functions check if a file has the specified permissions,
+ *      indicated by the corresponding R_OK, W_OK, or X_OK flags, as used in
+ *      access(2). The created PLAN node uses the f_accesscheck function to
+ *      perform the actual permissions check. Requested permissions are stored
+ *      in accessmode.
+ */
+#define DEFINE_PERMISSION_FUNC(func_name, perm_name, perm_flag) \
+    PLAN *func_name(char *ignore, char ***ignored, int unused) \
+    { \
+        ftsoptions &= ~FTS_NOSTAT; \
+        accessmode |= perm_flag; \
+        return (palloc(perm_name, f_accesscheck)); \
+    }
+
+DEFINE_PERMISSION_FUNC(c_readable, N_READABLE, R_OK)
+DEFINE_PERMISSION_FUNC(c_writable, N_WRITABLE, W_OK)
+DEFINE_PERMISSION_FUNC(c_executable, N_EXECUTABLE, X_OK)
+
+int
+f_accesscheck(PLAN *plan, FTSENT *entry)
+{
+    int result = access(entry->fts_accpath, accessmode);
+    if (result == -1) {
+        switch (errno) {
+        case EACCES:
+        case EPERM:
+        case EROFS:
+            return 0;
+        default:
+            warn("Error checking permissions for file '%s'",
+                entry->fts_accpath);
+        }
+    }
+    return result == 0;
 }

 /*
Index: main.c
===================================================================
RCS file: /cvs/src/usr.bin/find/main.c,v
retrieving revision 1.33
diff -u -p -u -p -r1.33 main.c
--- main.c    4 Dec 2022 23:50:48 -0000    1.33
+++ main.c    1 Apr 2023 02:15:15 -0000
@@ -52,6 +52,7 @@ int isdelete;            /* user specified -delet
 int isdepth;            /* do directories on post-order visit */
 int isoutput;            /* user specified output operator */
 int isxargs;            /* don't permit xargs delimiting chars */
+int accessmode;            /* mode for checking perms via access(2) */

 __dead static void usage(void);

Index: option.c
===================================================================
RCS file: /cvs/src/usr.bin/find/option.c,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 option.c
--- option.c    6 Dec 2018 17:45:14 -0000    1.21
+++ option.c    1 Apr 2023 02:15:15 -0000
@@ -64,6 +64,7 @@ static OPTION options[] = {
     { "-empty",    N_EMPTY,    c_empty,    O_ZERO },
     { "-exec",    N_EXEC,        c_exec,        O_ARGVP },
     { "-execdir",    N_EXECDIR,    c_execdir,    O_ARGVP },
+    { "-executable",N_EXECUTABLE,    c_executable,    O_ZERO },
     { "-flags",    N_FLAGS,    c_flags,    O_ARGV },
     { "-follow",    N_FOLLOW,    c_follow,    O_ZERO },
     { "-fstype",    N_FSTYPE,    c_fstype,    O_ARGV },
@@ -90,9 +91,11 @@ static OPTION options[] = {
     { "-print",    N_PRINT,    c_print,    O_ZERO },
     { "-print0",    N_PRINT0,    c_print0,    O_ZERO },
     { "-prune",    N_PRUNE,    c_prune,    O_ZERO },
+    { "-readable",    N_READABLE,    c_readable,    O_ZERO },
     { "-size",    N_SIZE,        c_size,        O_ARGV },
     { "-type",    N_TYPE,        c_type,        O_ARGV },
     { "-user",    N_USER,        c_user,        O_ARGV },
+    { "-writable",    N_WRITABLE,    c_writable,    O_ZERO },
     { "-xdev",    N_XDEV,        c_xdev,        O_ZERO },
 };

Reply via email to