[ovs-dev] [PATCH v4 1/3] chutil: introduce a new change-utils lib

Aaron Conole aconole at redhat.com
Fri Aug 19 23:48:12 UTC 2016


It will be useful in the future to be able to set ownership and permissions
on files which Open vSwitch creates. Allowing the specification of such
ownership and permissions using the standard user:group, uog+-rwxs, and
numerical forms commonly associated with those actions.

This patch introduces a new chutil library, currently with a posix command
implementation. WIN32 support does not exist at this time, but could be added
in the future.

As part of this, the daemon-unix.c was refactored to move the ownership
parsing code to the chutil library. A new set of tests was added, and the
fchmod and fchown calls are implemented.

Signed-off-by: Aaron Conole <aconole at redhat.com>
Acked-by: Ben Pfaff <blp at ovn.org>
---
 lib/automake.mk     |   2 +
 lib/chutil-unix.c   | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/chutil.h        |  32 +++++
 lib/daemon-unix.c   | 149 +---------------------
 tests/automake.mk   |   2 +
 tests/library.at    |   5 +
 tests/test-chutil.c | 243 ++++++++++++++++++++++++++++++++++++
 7 files changed, 637 insertions(+), 144 deletions(-)
 create mode 100644 lib/chutil-unix.c
 create mode 100644 lib/chutil.h
 create mode 100644 tests/test-chutil.c

diff --git a/lib/automake.mk b/lib/automake.mk
index 165e6a8..9a2e303 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -35,6 +35,7 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/byteq.h \
 	lib/cfm.c \
 	lib/cfm.h \
+	lib/chutil.h \
 	lib/classifier.c \
 	lib/classifier.h \
 	lib/classifier-private.h \
@@ -308,6 +309,7 @@ lib_libopenvswitch_la_SOURCES += \
 	lib/strsep.c
 else
 lib_libopenvswitch_la_SOURCES += \
+	lib/chutil-unix.c \
 	lib/daemon-unix.c \
 	lib/latch-unix.c \
 	lib/signals.c \
diff --git a/lib/chutil-unix.c b/lib/chutil-unix.c
new file mode 100644
index 0000000..f0fd3c9
--- /dev/null
+++ b/lib/chutil-unix.c
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "chutil.h"
+
+#include <errno.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "daemon.h"
+#include "util.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(chutil_unix);
+
+#ifndef S_ISLNK
+#define S_ISLNK(mode) (0)
+#endif
+
+#define USR_MODES (S_ISUID | S_IRWXU)
+#define GRP_MODES (S_ISGID | S_IRWXG)
+#define OTH_MODES (S_IRWXO)
+#define ALL_MODES (USR_MODES | GRP_MODES | OTH_MODES)
+
+#define READ_MODES  (S_IRUSR | S_IRGRP | S_IROTH)
+#define WRITE_MODES (S_IWUSR | S_IWGRP | S_IWOTH)
+#define EXEC_MODES  (S_IXUSR | S_IXGRP | S_IXOTH)
+
+#define SUID_MODES  (S_ISUID | S_ISGID)
+
+/* Convert a chown-style string to uid/gid; supports numeric arguments
+ * as well as usernames. */
+int
+ovs_strtousr(const char *user_spec, uid_t *uid, char **user, gid_t *gid,
+             bool validate_user_group)
+{
+    char *pos = strchr(user_spec, ':');
+    size_t bufsize = 0;
+    user_spec += strspn(user_spec, " \t\r\n");
+
+    size_t len = pos ? pos - user_spec : strlen(user_spec);
+    char *buf = NULL;
+    struct passwd pwd, *res = NULL;
+    int e;
+
+    buf = x2nrealloc(NULL, &bufsize, sizeof pwd);
+    char *user_search = NULL;
+    uid_t uid_search = getuid();
+    if (len) {
+        user_search = xmemdup0(user_spec, len);
+        if (!strcspn(user_search, "0123456789")) {
+            uid_search = strtoul(user_search, NULL, 10);
+            free(user_search);
+            user_search = NULL;
+        }
+    }
+
+    if (user_search) {
+        while ((e = getpwnam_r(user_search, &pwd, buf,
+                               bufsize * sizeof pwd, &res)) == ERANGE) {
+            buf = x2nrealloc(buf, &bufsize, sizeof pwd);
+        }
+    } else {
+        while ((e = getpwuid_r(uid_search, &pwd, buf, bufsize * sizeof pwd,
+                               &res)) == ERANGE) {
+            buf = x2nrealloc(buf, &bufsize, sizeof pwd);
+        }
+    }
+
+    if (!res && !e) {
+        e = ENOENT;
+    }
+
+    if (e) {
+        VLOG_ERR("Failed to retrieve user pwentry (%s), aborting.",
+                 ovs_strerror(e));
+        goto release;
+    }
+
+    if (!user_search) {
+        user_search = xstrdup(pwd.pw_name);
+    }
+
+    if (user) {
+        *user = user_search;
+    }
+
+    if (uid) {
+        *uid = pwd.pw_uid;
+    }
+
+    if (gid) {
+        *gid = pwd.pw_gid;
+    }
+
+    if (pos) {
+        gid_t tmpgid = pwd.pw_gid;
+        char *grpstr = pos + 1;
+        grpstr += strspn(grpstr, " \t\r\n");
+
+        if (*grpstr) {
+            struct group grp, *res;
+
+            bufsize = 1;
+            buf = x2nrealloc(buf, &bufsize, sizeof grp);
+
+            if (strcspn(grpstr, "0123456789")) {
+                while ((e = getgrnam_r(grpstr, &grp, buf,
+                                       bufsize * sizeof grp, &res))
+                       == ERANGE) {
+                    buf = x2nrealloc(buf, &bufsize, sizeof grp);
+                }
+            } else {
+                gid_t grpgid = strtoul(grpstr, NULL, 10);
+                while ((e = getgrgid_r(grpgid, &grp, buf,
+                                       bufsize * sizeof grp, &res))
+                       == ERANGE) {
+                    buf = x2nrealloc(buf, &bufsize, sizeof grp);
+                }
+            }
+
+            if (!res && !e) {
+                e = ENOENT;
+            }
+
+            if (e) {
+                VLOG_ERR("Failed to get group entry for %s (%s), aborting.",
+                         grpstr, ovs_strerror(e));
+                goto release;
+            }
+
+            if (tmpgid != grp.gr_gid) {
+                char **mem;
+
+                for (mem = grp.gr_mem; *mem; ++mem) {
+                    if (!strcmp(*mem, user_search)) {
+                         break;
+                     }
+                }
+
+                if (!*mem && validate_user_group) {
+                    VLOG_ERR("Invalid user str %s (user %s is not in "
+                             "group %s), aborting.", user_spec,
+                             user_search, grpstr);
+                    e = EINVAL;
+                    goto release;
+                }
+                if (gid) {
+                    *gid = grp.gr_gid;
+                }
+            }
+        }
+    }
+
+release:
+    free(buf);
+    if (e || !user) {
+        free(user_search);
+    }
+    return e;
+}
+
+/* Convert a chmod style string (or set of comma separated chmod style
+ * strings) to a mode_t.
+ */
+static mode_t
+chmod_getmode(const char *mode, mode_t oldmode)
+{
+    mode_t ret = oldmode & ALL_MODES;
+    if (*mode >= '0' && *mode <= '7') {
+        ret = 0;
+
+        while (*mode >= '0' && *mode <= '7') {
+            ret = (ret << 3) | (*mode++ - '0');
+        }
+
+        if (*mode) {
+            errno = EINVAL;
+            return 0;
+        }
+    } else {
+        while (*mode) {
+            mode_t actors_mask = 0, perms_mask = 0;
+            char action = 0;
+            while (*mode && !action) {
+                switch (*mode++) {
+                case 'a':
+                    actors_mask |= ALL_MODES;
+                    break;
+                case 'u':
+                    actors_mask |= USR_MODES;
+                    break;
+                case 'g':
+                    actors_mask |= GRP_MODES;
+                    break;
+                case 'o':
+                    actors_mask |= OTH_MODES;
+                    break;
+                case '+':
+                case '-':
+                case '=':
+                    action = *(mode-1);
+                    break;
+                default:
+                    errno = EINVAL;
+                    return 0;
+                }
+            }
+            if (!actors_mask) {
+                actors_mask = USR_MODES;
+            }
+            while (*mode) {
+                switch(*mode++) {
+                case 'r':
+                    perms_mask |= READ_MODES;
+                    break;
+                case 'w':
+                    perms_mask |= WRITE_MODES;
+                    break;
+                case 'x':
+                    perms_mask |= EXEC_MODES;
+                    break;
+                case 's':
+                    perms_mask |= SUID_MODES;
+                    break;
+                case ',':
+                    goto actions;
+                default:
+                    errno = EINVAL;
+                    return 0;
+                }
+            }
+actions:
+            if (action == '+') {
+                ret |= actors_mask & perms_mask;
+            } else if (action == '-') {
+                ret &= ~(actors_mask & perms_mask);
+            } else if (action == '=') {
+                ret &= ~(actors_mask & (READ_MODES | WRITE_MODES
+                                        | EXEC_MODES));
+                ret |= actors_mask & perms_mask;
+            }
+        }
+    }
+    return ret;
+}
+
+
+/* Changes the mode of a file to the mode specified.  Accepts chmod style
+ * comma-separated strings.  Returns 0 on success, otherwise a positive errno
+ * value. */
+int
+ovs_fchmod(int fd, const char *mode)
+{
+    mode_t new_mode;
+    struct stat st;
+    int err;
+
+    if (fstat(fd, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_fchown: fstat (%s)", ovs_strerror(errno));
+        return err;
+    }
+
+    if (S_ISLNK(st.st_mode)) {
+        errno = EINVAL;
+        err = errno;
+        VLOG_ERR("ovs_fchown: unable to change ownership of symlink");
+        return err;
+    }
+
+    errno = 0;
+    new_mode = chmod_getmode(mode, st.st_mode);
+    if (errno) {
+        err = errno;
+        VLOG_ERR("ovs_fchmod bad mode (%s) specified (%s)", mode,
+                 ovs_strerror(errno));
+        return err;
+    }
+
+    if (fchmod(fd, new_mode)) {
+        VLOG_ERR("ovs_fchmod: chmod error (%s) with mode %s",
+                 ovs_strerror(errno), mode);
+        return errno;
+    }
+    return 0;
+}
+
+
+/* Changes the ownership of a file to the mode specified.  Accepts chown style
+ * user:group strings.  Returns 0 on success.  Non-zero results contain
+ * errno. */
+int
+ovs_fchown(int fd, const char *owner)
+{
+    struct stat st;
+    uid_t user;
+    gid_t group;
+    int err;
+
+    if (fstat(fd, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_fchmod: lstat (%s)", ovs_strerror(errno));
+        return err;
+    }
+
+    if (S_ISLNK(st.st_mode) || S_ISDIR(st.st_mode)) {
+        errno = EINVAL;
+        err = errno;
+        VLOG_ERR("ovs_fchmod: changing symlink / directory modes is not "
+                 "supported.");
+        return err;
+    }
+
+    if (ovs_strtousr(owner, &user, NULL, &group, true)) {
+        err = errno;
+        VLOG_ERR("ovs_fchown: unknown user or group - bailing");
+        return err;
+    }
+
+    if (fchown(fd, user, group)) {
+        err = errno;
+        VLOG_ERR("ovs_fchown: chown error (%s)", ovs_strerror(errno));
+        return err;
+    }
+
+    return 0;
+}
diff --git a/lib/chutil.h b/lib/chutil.h
new file mode 100644
index 0000000..cdd4d52
--- /dev/null
+++ b/lib/chutil.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHUTIL_H
+#define CHUTIL_H 1
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "compiler.h"
+
+#ifndef WIN32
+int ovs_fchmod(int fd, const char *mode) OVS_WARN_UNUSED_RESULT;
+int ovs_fchown(int fd, const char *usrstr) OVS_WARN_UNUSED_RESULT;
+int ovs_strtousr(const char *user_spec, uid_t *uid, char **user,
+                 gid_t *gid, bool validate_user_group) OVS_WARN_UNUSED_RESULT;
+#endif
+
+#endif
diff --git a/lib/daemon-unix.c b/lib/daemon-unix.c
index 28f76da..775999e 100644
--- a/lib/daemon-unix.c
+++ b/lib/daemon-unix.c
@@ -15,6 +15,7 @@
  */
 
 #include <config.h>
+#include "chutil.h"
 #include "daemon.h"
 #include "daemon-private.h"
 #include <errno.h>
@@ -874,58 +875,6 @@ daemon_become_new_user(bool access_datapath)
     }
 }
 
-/* Return the maximun suggested buffer size for both getpwname_r()
- * and getgrnam_r().
- *
- * This size may still not be big enough. in case getpwname_r()
- * and friends return ERANGE, a larger buffer should be supplied to
- * retry. (The man page did not specify the max size to stop at, we
- * will keep trying with doubling the buffer size for each round until
- * the size wrapps around size_t.  */
-static size_t
-get_sysconf_buffer_size(void)
-{
-    size_t bufsize, pwd_bs = 0, grp_bs = 0;
-    const size_t default_bufsize = 1024;
-
-    errno = 0;
-    if ((pwd_bs = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) {
-        if (errno) {
-            VLOG_FATAL("%s: Read initial passwordd struct size "
-                       "failed (%s), aborting. ", pidfile,
-                       ovs_strerror(errno));
-        }
-    }
-
-    if ((grp_bs = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) {
-        if (errno) {
-            VLOG_FATAL("%s: Read initial group struct size "
-                       "failed (%s), aborting. ", pidfile,
-                       ovs_strerror(errno));
-        }
-    }
-
-    bufsize = MAX(pwd_bs, grp_bs);
-    return bufsize ? bufsize : default_bufsize;
-}
-
-/* Try to double the size of '*buf', return true
- * if successful, and '*sizep' will be updated with
- * the new size. Otherwise, return false.  */
-static bool
-enlarge_buffer(char **buf, size_t *sizep)
-{
-    size_t newsize = *sizep * 2;
-
-    if (newsize > *sizep) {
-        *buf = xrealloc(*buf, newsize);
-        *sizep = newsize;
-        return true;
-    }
-
-    return false;
-}
-
 /* Parse and sanity check user_spec.
  *
  * If successful, set global variables 'uid' and 'gid'
@@ -940,10 +889,6 @@ enlarge_buffer(char **buf, size_t *sizep)
 void
 daemon_set_new_user(const char *user_spec)
 {
-    char *pos = strchr(user_spec, ':');
-    size_t init_bufsize, bufsize;
-
-    init_bufsize = get_sysconf_buffer_size();
     uid = getuid();
     gid = getgid();
 
@@ -951,94 +896,10 @@ daemon_set_new_user(const char *user_spec)
         VLOG_FATAL("%s: only root can use --user option", pidfile);
     }
 
-    user_spec += strspn(user_spec, " \t\r\n");
-    size_t len = pos ? pos - user_spec : strlen(user_spec);
-    char *buf;
-    struct passwd pwd, *res;
-    int e;
-
-    bufsize = init_bufsize;
-    buf = xmalloc(bufsize);
-    if (len) {
-        user = xmemdup0(user_spec, len);
-
-        while ((e = getpwnam_r(user, &pwd, buf, bufsize, &res)) == ERANGE) {
-            if (!enlarge_buffer(&buf, &bufsize)) {
-                break;
-            }
-        }
-
-        if (e != 0) {
-            VLOG_FATAL("%s: Failed to retrive user %s's uid (%s), aborting.",
-                       pidfile, user, ovs_strerror(e));
-        }
-        if (res == NULL) {
-            VLOG_FATAL("%s: user %s not found, aborting.", pidfile, user);
-        }
+    if (!ovs_strtousr(user_spec, &uid, &user, &gid, true)) {
+        switch_user = true;
     } else {
-        /* User name is not specified, use current user.  */
-        while ((e = getpwuid_r(uid, &pwd, buf, bufsize, &res)) == ERANGE) {
-            if (!enlarge_buffer(&buf, &bufsize)) {
-                break;
-            }
-        }
-
-        if (e != 0) {
-            VLOG_FATAL("%s: Failed to retrive current user's name "
-                       "(%s), aborting.", pidfile, ovs_strerror(e));
-        }
-        user = xstrdup(pwd.pw_name);
+        VLOG_FATAL("%s: Failed --user option with %s, aborting.", pidfile,
+                   user_spec);
     }
-
-    uid = pwd.pw_uid;
-    gid = pwd.pw_gid;
-    free(buf);
-
-    if (pos) {
-        char *grpstr = pos + 1;
-        grpstr += strspn(grpstr, " \t\r\n");
-
-        if (*grpstr) {
-            struct group grp, *res;
-
-            bufsize = init_bufsize;
-            buf = xmalloc(bufsize);
-            while ((e = getgrnam_r(grpstr, &grp, buf, bufsize, &res))
-                         == ERANGE) {
-                if (!enlarge_buffer(&buf, &bufsize)) {
-                    break;
-                }
-            }
-
-            if (e) {
-                VLOG_FATAL("%s: Failed to get group entry for %s, "
-                           "(%s), aborting.", pidfile, grpstr,
-                           ovs_strerror(e));
-            }
-            if (res == NULL) {
-                VLOG_FATAL("%s: group %s not found, aborting.", pidfile,
-                           grpstr);
-            }
-
-            if (gid != grp.gr_gid) {
-                char **mem;
-
-                for (mem = grp.gr_mem; *mem; ++mem) {
-                    if (!strcmp(*mem, user)) {
-                        break;
-                    }
-                }
-
-                if (!*mem) {
-                    VLOG_FATAL("%s: Invalid --user option %s (user %s is "
-                               "not in group %s), aborting.", pidfile,
-                               user_spec, user, grpstr);
-                }
-                gid = grp.gr_gid;
-            }
-            free(buf);
-        }
-    }
-
-    switch_user = true;
 }
diff --git a/tests/automake.mk b/tests/automake.mk
index 5d12ae5..75d9040 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -174,6 +174,7 @@ valgrind_wrappers = \
 	tests/valgrind/test-atomic \
 	tests/valgrind/test-bundle \
 	tests/valgrind/test-byte-order \
+	tests/valgrind/test-chutil \
 	tests/valgrind/test-classifier \
 	tests/valgrind/test-ccmap \
 	tests/valgrind/test-cmap \
@@ -361,6 +362,7 @@ tests_ovstest_SOURCES = \
 
 if !WIN32
 tests_ovstest_SOURCES += \
+	tests/test-chutil.c \
 	tests/test-unix-socket.c
 endif
 
diff --git a/tests/library.at b/tests/library.at
index bbf1e9d..0e2da1a 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -240,3 +240,8 @@ AT_CLEANUP
 AT_SETUP([rcu])
 AT_CHECK([ovstest test-rcu-quiesce], [0], [])
 AT_CLEANUP
+
+AT_SETUP([test chutil functions])
+AT_SKIP_IF([test "$IS_WIN32" = "yes"])
+AT_CHECK([ovstest test-chutil], [0], [ignore], [ignore])
+AT_CLEANUP
diff --git a/tests/test-chutil.c b/tests/test-chutil.c
new file mode 100644
index 0000000..dce07b4
--- /dev/null
+++ b/tests/test-chutil.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* A non-exhaustive test for some of the functions and macros declared in
+ * the change-utils suite in chutil.h. */
+
+#include <config.h>
+#undef NDEBUG
+
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "chutil.h"
+#include "ovstest.h"
+#include "util.h"
+
+static int
+get_mode(const char *pathname, mode_t *mode)
+{
+    struct stat st;
+    if (stat(pathname, &st)) {
+        return -1;
+    }
+    *mode = st.st_mode & 0x7ff;
+    return 0;
+}
+
+static int
+with_temp_file(int (*fn)(const char *pathname, int fd))
+{
+    char filepath[PATH_MAX] = "/tmp/test_chutil_wtfXXXXXX";
+    mode_t old_mask = umask(0777);
+    int fd = mkstemp(filepath);
+    umask(old_mask);
+    assert(fd >= 0);
+    int result = fn(filepath, fd);
+    close(fd);
+    unlink(filepath);
+    return result;
+}
+
+static int
+run_chmod_bad_parsing(const char *pathname, int fd)
+{
+    static char users[] = "bcdefhijklmnpqrstvwxyz";
+    static char perms[] = "abcdefghijklmnopqtuvyz";
+    static char actions[] = "~`!@#$%^&*()_";
+
+    char *itest;
+
+    mode_t pathmode;
+    if (get_mode(pathname, &pathmode)) {
+        return -1;
+    }
+
+    for (itest = users; itest != users + strlen(users); ++itest) {
+        char buf[256] = {0};
+        mode_t testmode;
+        snprintf(buf, sizeof(buf), "%c+rwx", *itest);
+        if (!ovs_fchmod(fd, buf) || get_mode(pathname, &testmode)
+            || testmode != pathmode) {
+            printf("F(%s)", buf);
+            return -1;
+        }
+    }
+
+    for (itest = perms; itest != perms + strlen(perms); ++itest) {
+        char buf[256] = {0};
+        mode_t testmode;
+        snprintf(buf, sizeof(buf), "u+%c", *itest);
+        if (!ovs_fchmod(fd, buf) || get_mode(pathname, &testmode)
+            || testmode != pathmode) {
+            printf("F(%s)", buf);
+            return -1;
+        }
+    }
+
+    for (itest = actions; itest != actions + strlen(actions); ++itest) {
+        char buf[256] = {0};
+        mode_t testmode;
+        snprintf(buf, sizeof(buf), "u%crw", *itest);
+        if (!ovs_fchmod(fd, buf) || get_mode(pathname, &testmode)
+            || testmode != pathmode) {
+            printf("F(%s)", buf);
+            return -1;
+        }
+    }
+    printf(".");
+    return 0;
+}
+
+/* Skip suid and sgid for now. */
+static int
+run_chmod_str_successes(const char *pathname, int fd)
+{
+    const char *users[] = { "u", "g", "o", "a", "ug", "uo", "go" };
+    const char *perms[] = { "r", "w", "x", "rw", "rx", "wx" };
+    size_t iusers, iperms;
+    mode_t chkmode;
+
+    if (get_mode(pathname, &chkmode)) {
+        return -1;
+    }
+
+    for (iusers = 0; iusers < ARRAY_SIZE(users); ++iusers) {
+        for (iperms = 0; iperms < ARRAY_SIZE(perms); ++iperms) {
+            mode_t pathmode;
+            char buf[256] = {0};
+            snprintf(buf, sizeof(buf), "%s+%s", users[iusers], perms[iperms]);
+            if (ovs_fchmod(fd, buf) || get_mode(pathname, &pathmode)) {
+                printf("run_chmod_successes:E(%s)\n", buf);
+                return -1;
+            }
+            /* XXX: Check the actual mode here */
+            snprintf(buf, sizeof(buf), "%s-%s", users[iusers], perms[iperms]);
+            if (ovs_fchmod(fd, buf) || get_mode(pathname, &pathmode)
+                || pathmode != chkmode) {
+                printf("run_chmod_successes:F(%s:%x:%x)\n", buf, pathmode,
+                       chkmode);
+                return -1;
+            }
+        }
+    }
+
+    mode_t pmode;
+    if (ovs_fchmod(fd, "u-rwx,g-rwx,o-rwx")
+        || get_mode(pathname, &pmode) || pmode != 0) {
+        printf("run_chmod_successes:csvF\n");
+        return -1;
+    }
+
+    if (ovs_fchmod(fd, "u=rx,g=w") || get_mode(pathname, &pmode)
+        || pmode != (S_IRUSR | S_IXUSR | S_IWGRP)) {
+        printf("run_chmod_successes:assignF\n");
+        return -1;
+    }
+    return 0;
+}
+
+static int
+run_chmod_numeric_successes(const char *pathname, int fd)
+{
+    const char *modestrs[] = {"0755", "0644", "0600", "11", "20", "755",
+                              "640"};
+    const mode_t expectedmode[] = {
+        S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH,
+        S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR,
+        S_IRUSR | S_IWUSR,
+        S_IXOTH | S_IXGRP,
+        S_IWGRP,
+        S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH,
+        S_IRUSR | S_IRGRP | S_IWUSR,
+    };
+    size_t imodes;
+    for (imodes = 0; imodes < ARRAY_SIZE(modestrs); ++imodes) {
+        mode_t newmode;
+        if (ovs_fchmod(fd, modestrs[imodes]) ||
+           get_mode(pathname, &newmode)) {
+            printf("run_chmod_numeric_successes:F(%s)\n", modestrs[imodes]);
+            return -1;
+        }
+        if (newmode != expectedmode[imodes]) {
+            printf("run_chmod_numeric_successes:F(%x:%x)\n", newmode,
+                   expectedmode[imodes]);
+            return -1;
+        }
+        if (ovs_fchmod(fd, "0000")) {
+            printf("run_chmod_numeric_successes:E(%s)\n", modestrs[imodes]);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+run_ovs_strtouser_successes(void)
+{
+    /* seems this is the only user:group combination to exist? */
+    const char *ugparses[] = {"root:", "root:root", "0:0", "0:", "nobody:",
+                              "root", "nobody"};
+    size_t iugstr;
+    for (iugstr = 0; iugstr < ARRAY_SIZE(ugparses); ++iugstr) {
+        uid_t ui = 1;
+        gid_t gi = 1;
+        char *user = NULL;
+        if (ovs_strtousr(ugparses[iugstr], &ui, &user, &gi, true) ||
+           !user || (strcmp("root", user) && strcmp("nobody", user))) {
+            printf("run_ovs_strtouser_successes:F(%s)\n", ugparses[iugstr]);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+run_ovs_strtouser_failures(void)
+{
+    /* If any of these are successful, you have a poorly configured system
+     * so this test 'failing' is the least of your worries. */
+    const char *ugparses[] = {"nobody:root", "THISUSERBETTERNOTEXSIST:",
+                              ":THISGROUPBETTERNOTEXIST"};
+    size_t iugstr;
+    for (iugstr = 0; iugstr < ARRAY_SIZE(ugparses); ++iugstr) {
+        if (!ovs_strtousr(ugparses[iugstr], NULL, NULL, NULL, true)) {
+            printf("run_ovs_strtouser_failures:F(%s)\n", ugparses[iugstr]);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static void
+test_chutil_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    assert(!with_temp_file(run_chmod_bad_parsing));
+    assert(!with_temp_file(run_chmod_str_successes));
+    assert(!with_temp_file(run_chmod_numeric_successes));
+    assert(!run_ovs_strtouser_successes());
+    assert(!run_ovs_strtouser_failures());
+    printf("\n");
+}
+
+OVSTEST_REGISTER("test-chutil", test_chutil_main);
-- 
2.5.5




More information about the dev mailing list