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

Aaron Conole aconole at redhat.com
Fri May 20 20:32:05 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
chmod side is implemented.

Signed-off-by: Aaron Conole <aconole at redhat.com>
---
v1->v2:
* Rebased to latest
* Added multi- and assign- actions to ovs_chmod
* Re-ran checkpatch with the latest round of enhancments

 lib/automake.mk     |   2 +
 lib/chutil-unix.c   | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/chutil.h        |  32 +++++
 lib/daemon-unix.c   | 149 +----------------------
 tests/automake.mk   |   2 +
 tests/library.at    |   5 +
 tests/test-chutil.c | 242 +++++++++++++++++++++++++++++++++++++
 7 files changed, 628 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 e196ada..00adc97 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 \
@@ -295,6 +296,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..208ce6f
--- /dev/null
+++ b/lib/chutil-unix.c
@@ -0,0 +1,340 @@
+/*
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "chutil.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.
+ */
+int
+ovs_chmod(const char *path, const char *mode)
+{
+    struct stat st;
+    int err;
+    mode_t new_mode;
+
+    if (lstat(path, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_chmod: lstat (%s) for file (%s)", ovs_strerror(errno),
+                 path);
+        return err;
+    }
+
+    errno = 0;
+    new_mode = chmod_getmode(mode, st.st_mode);
+    if (errno) {
+        err = errno;
+        VLOG_ERR("ovs_chmod bad mode (%s) specified (%s)", mode,
+                 ovs_strerror(errno));
+        return err;
+    }
+
+    if (S_ISLNK(st.st_mode) || S_ISDIR(st.st_mode)) {
+        err = EINVAL;
+        VLOG_ERR("ovs_chmod: changing symlink / directory modes is not "
+                 "supported.");
+        return err;
+    }
+
+    if (chmod(path, new_mode)) {
+        err = errno;
+        VLOG_ERR("ovs_chmod: chmod error (%s) for file %s with mode %s",
+                 ovs_strerror(errno), path, mode);
+        return err;
+    }
+    return 0;
+}
+
+/* Changes the ownership of a file to the mode specified.  Accepts chown style
+ * user:group strings.
+ */
+int
+ovs_chown(const char *path, const char *owner)
+{
+    struct stat st;
+    int err;
+
+    if (lstat(path, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_chown: lstat (%s) for file %s", ovs_strerror(errno),
+                 path);
+        return err;
+    }
+
+    uid_t user;
+    gid_t group;
+    if (ovs_strtousr(owner, &user, NULL, &group, true)) {
+        err = errno;
+        VLOG_ERR("ovs_chown: unknown user or group - bailing");
+        return err;
+    }
+
+    if (chown(path, user, group)) {
+        err = errno;
+        VLOG_ERR("ovs_chown: 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..3e91775
--- /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_chmod(const char *path, const char *mode) OVS_WARN_UNUSED_RESULT;
+int ovs_chown(const char *path, 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 7af7f69..a8ce5cc 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -168,6 +168,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 \
@@ -354,6 +355,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 8e6d602..9b363d2 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -240,3 +240,8 @@ AT_CLEANUP
 AT_SETUP([test 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..9057220
--- /dev/null
+++ b/tests/test-chutil.c
@@ -0,0 +1,242 @@
+/*
+ * 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))
+{
+    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);
+    close(fd);
+    unlink(filepath);
+    return result;
+}
+
+static int
+run_chmod_bad_parsing(const char *pathname) {
+    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_chmod(pathname, 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_chmod(pathname, 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_chmod(pathname, 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)
+{
+    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_chmod(pathname, 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_chmod(pathname, 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_chmod(pathname, "u-rwx,g-rwx,o-rwx")
+        || get_mode(pathname, &pmode) || pmode != 0) {
+        printf("run_chmod_successes:csvF\n");
+        return -1;
+    }
+
+    if (ovs_chmod(pathname, "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)
+{
+    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_chmod(pathname, 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_chmod(pathname, "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