diff mbox

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

Message ID 1462296400-8711-2-git-send-email-aconole@redhat.com
State Changes Requested
Headers show

Commit Message

Aaron Conole May 3, 2016, 5:26 p.m. UTC
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@redhat.com>
---
 configure.ac        |   2 +-
 lib/automake.mk     |   2 +
 lib/chutil-unix.c   | 360 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/chutil.h        |  32 +++++
 lib/daemon-unix.c   | 141 +-------------------
 lib/util.c          |  17 +++
 lib/util.h          |   2 +
 tests/automake.mk   |   2 +
 tests/library.at    |   5 +
 tests/test-chutil.c | 228 +++++++++++++++++++++++++++++++++
 10 files changed, 654 insertions(+), 137 deletions(-)
 create mode 100644 lib/chutil-unix.c
 create mode 100644 lib/chutil.h
 create mode 100644 tests/test-chutil.c

Comments

Ben Pfaff May 18, 2016, 11:22 p.m. UTC | #1
On Tue, May 03, 2016 at 01:26:39PM -0400, Aaron Conole wrote:
> 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@redhat.com>

This is very thorough.  I am impressed.

I wish we could just use the appropriate code from gnulib for this but,
alas, licenses.

I'm not sure that it's worth adding a check or a fallback for lstat
given that chutil-unix.c is only built on unixlike systems (also, OVS
already uses lstat elsewhere on unixlikes without checking).

I'm not sure whether it's worth both using sysconf() to check for buffer
sizes and dynamically resizing as necessary.  The latter is necessary
anyhow so it seems like it's enough on its own.

An equivalent of enlarge_buffer() is already available as x2nrealloc().

Please always write {} around 'if' and 'while' statements, e.g.
        if (!res) {
            e = ENOENT;
        }
instead of:
        if (!res) e = ENOENT;
in accordance with CodingStyle.md.

However, the logic here seems slightly wrong to me anyway:
    if (e != 0 || !res) {
        if (!res) e = ENOENT;
        VLOG_ERR("Failed to retrieve user pwentry (%s), aborting.",
                 ovs_strerror(e));
        goto release;
    }
I guess the logic is supposed to be that, if there was no error but no
result was returned, assume ENOENT.  But for that wouldn't one write
something more like this:
        if (!e) {
            e = ENOENT;
        }
or even ovs_strerror(e ? e : ENOENT).

Similar comments for the group parsing code in the same function.

In chmod_getmode() here, I hadn't observed before that ('0' & 7) == 0
but really it's better to write -'0' since that's completely portable
and more obviously correct:
            ret = (ret << 3) | (*mode++ & 0x7);

It looks like chmod_getmode() doesn't allow multiple symbolic modes,
e.g. u+rw,g+r, or nonincremental modes, e.g. o=rwx.  I don't think it
would be difficult to add those, can you do it?

In ovs_chmod() and ovs_chown(), often it doesn't read well to
interpolate error strings into messages directly, because you end up
with things like
        ovs_chmod: stat error No such file or directory for file nonexistent
so ordinarily we'd separate them grammatically, e.g.:
        ovs_chmod: stat error for file nonexistent (No such file or directory)

It's odd that ovs_chmod() and ovs_chown() set errno to 0 initially, is
that necessary?

The header guard in chutil.h should come first, instead of after the
#includes.

In the tests, please write the { that opens a function on a line by
itself.
Aaron Conole May 19, 2016, 6:27 p.m. UTC | #2
Ben Pfaff <blp@ovn.org> writes:

> On Tue, May 03, 2016 at 01:26:39PM -0400, Aaron Conole wrote:
>> 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@redhat.com>
>
> This is very thorough.  I am impressed.

You can't take it back, now.  It's on the internet. :)  Also, thanks so
much for the review, Ben.

> I wish we could just use the appropriate code from gnulib for this but,
> alas, licenses.

+1

> I'm not sure that it's worth adding a check or a fallback for lstat
> given that chutil-unix.c is only built on unixlike systems (also, OVS
> already uses lstat elsewhere on unixlikes without checking).

okay, I can just drop it.

> I'm not sure whether it's worth both using sysconf() to check for buffer
> sizes and dynamically resizing as necessary.  The latter is necessary
> anyhow so it seems like it's enough on its own.
>
> An equivalent of enlarge_buffer() is already available as x2nrealloc().

okay.  I moved it over from the daemon code, and figured I should
preserve it.  But I'll drop it, and just use the x2nrealloc() code
instead.

> Please always write {} around 'if' and 'while' statements, e.g.
>         if (!res) {
>             e = ENOENT;
>         }
> instead of:
>         if (!res) e = ENOENT;
> in accordance with CodingStyle.md.

Expect a possible checkpatch.py patch coming soon for this.  Oops.

> However, the logic here seems slightly wrong to me anyway:
>     if (e != 0 || !res) {
>         if (!res) e = ENOENT;
>         VLOG_ERR("Failed to retrieve user pwentry (%s), aborting.",
>                  ovs_strerror(e));
>         goto release;
>     }
> I guess the logic is supposed to be that, if there was no error but no
> result was returned, assume ENOENT.  But for that wouldn't one write
> something more like this:
>         if (!e) {
>             e = ENOENT;
>         }
> or even ovs_strerror(e ? e : ENOENT).
>
> Similar comments for the group parsing code in the same function.

Okay, yep you got the idea.  I'll rewrite it so that it looks like:

/* This is a case where no result was returned, but an error was not
   signaled by the get*ent api */
if (!e && !res) {
    e = ENOENT;
}

if (e != 0) {
    ...
}

I'll also clean up the error message.

> In chmod_getmode() here, I hadn't observed before that ('0' & 7) == 0
> but really it's better to write -'0' since that's completely portable
> and more obviously correct:
>             ret = (ret << 3) | (*mode++ & 0x7);

Sorry, I thought it was clever :).  I can switch, but it will make the
code a bit larger (because it still needs to mask the bits).

> It looks like chmod_getmode() doesn't allow multiple symbolic modes,
> e.g. u+rw,g+r, or nonincremental modes, e.g. o=rwx.  I don't think it
> would be difficult to add those, can you do it?

ack.

> In ovs_chmod() and ovs_chown(), often it doesn't read well to
> interpolate error strings into messages directly, because you end up
> with things like
>         ovs_chmod: stat error No such file or directory for file nonexistent
> so ordinarily we'd separate them grammatically, e.g.:
>         ovs_chmod: stat error for file nonexistent (No such file or directory)
>
> It's odd that ovs_chmod() and ovs_chown() set errno to 0 initially, is
> that necessary?

Yes, at least according to the manpages.

> The header guard in chutil.h should come first, instead of after the
> #includes.

Okay.

> In the tests, please write the { that opens a function on a line by
> itself.
d'oh!  Will do.
Ben Pfaff May 20, 2016, 2:46 a.m. UTC | #3
On Thu, May 19, 2016 at 02:27:38PM -0400, Aaron Conole wrote:
> Ben Pfaff <blp@ovn.org> writes:
> 
> > On Tue, May 03, 2016 at 01:26:39PM -0400, Aaron Conole wrote:
> >> 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@redhat.com>
> >
> > This is very thorough.  I am impressed.
> 
> You can't take it back, now.  It's on the internet. :)  

Hahaha.

> > I'm not sure whether it's worth both using sysconf() to check for buffer
> > sizes and dynamically resizing as necessary.  The latter is necessary
> > anyhow so it seems like it's enough on its own.
> >
> > An equivalent of enlarge_buffer() is already available as x2nrealloc().
> 
> okay.  I moved it over from the daemon code, and figured I should
> preserve it.  But I'll drop it, and just use the x2nrealloc() code
> instead.

I didn't notice that this was just moved code.

I still think x2nrealloc() is better.

> > Please always write {} around 'if' and 'while' statements, e.g.
> >         if (!res) {
> >             e = ENOENT;
> >         }
> > instead of:
> >         if (!res) e = ENOENT;
> > in accordance with CodingStyle.md.
> 
> Expect a possible checkpatch.py patch coming soon for this.  Oops.

Great, that's a good idea.

> > In chmod_getmode() here, I hadn't observed before that ('0' & 7) == 0
> > but really it's better to write -'0' since that's completely portable
> > and more obviously correct:
> >             ret = (ret << 3) | (*mode++ & 0x7);
> 
> Sorry, I thought it was clever :).  I can switch, but it will make the
> code a bit larger (because it still needs to mask the bits).

Why?  To quote another line:
        while (*mode >= '0' && *mode <= '7')
            ret = (ret << 3) | (*mode++ & 0x7);
Since *mode >= '0' && *mode <= '7', (*mode - '0') >= 0 && (*mode - '0')
<= 7, so adding & 7 will have no additional effect.

> > It's odd that ovs_chmod() and ovs_chown() set errno to 0 initially, is
> > that necessary?
> 
> Yes, at least according to the manpages.

Which manpages?

Thanks,

Ben.
diff mbox

Patch

diff --git a/configure.ac b/configure.ac
index 05d80d5..128fe23 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,7 +105,7 @@  AC_CHECK_DECLS([sys_siglist], [], [], [[#include <signal.h>]])
 AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec],
   [], [], [[#include <sys/stat.h>]])
 AC_CHECK_MEMBERS([struct ifreq.ifr_flagshigh], [], [], [[#include <net/if.h>]])
-AC_CHECK_FUNCS([mlockall strnlen getloadavg statvfs getmntent_r])
+AC_CHECK_FUNCS([mlockall strnlen getloadavg statvfs getmntent_r lstat])
 AC_CHECK_HEADERS([mntent.h sys/statvfs.h linux/types.h linux/if_ether.h stdatomic.h])
 AC_CHECK_HEADERS([net/if_mib.h], [], [], [[#include <sys/types.h>
 #include <net/if.h>]])
diff --git a/lib/automake.mk b/lib/automake.mk
index affbb5c..f48f0b1 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 \
@@ -288,6 +289,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..0132ffa
--- /dev/null
+++ b/lib/chutil-unix.c
@@ -0,0 +1,360 @@ 
+/*
+ * 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
+
+#ifndef HAVE_LSTAT
+static int
+lstat(const char *pathname, struct stat *buf)
+{
+    return stat(pathname, buf);
+}
+#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)
+
+/* 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("Read initial passwordd struct size "
+                       "failed (%s), aborting. ",
+                       ovs_strerror(errno));
+        }
+    }
+
+    if ((grp_bs = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) {
+        if (errno) {
+            VLOG_FATAL("Read initial group struct size "
+                       "failed (%s), aborting. ",
+                       ovs_strerror(errno));
+        }
+    }
+
+    bufsize = MAX(pwd_bs, grp_bs);
+    return bufsize ? bufsize : default_bufsize;
+}
+
+/* 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, init_bufsize = get_sysconf_buffer_size();
+    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;
+
+    bufsize = init_bufsize;
+    buf = xmalloc(bufsize);
+    char *user_search = NULL;
+    if (len) {
+        user_search = xmemdup0(user_spec, len);
+        if (strcspn(user_search, "0123456789")) {
+            while ((e = getpwnam_r(user_search, &pwd, buf, bufsize, &res))
+                   == ERANGE) {
+                if (!enlarge_buffer(&buf, &bufsize)) {
+                    break;
+                }
+            }
+        } else {
+            uid_t uid_search = strtoul(user_search, NULL, 10);
+            free(user_search);
+            user_search = NULL;
+            while ((e = getpwuid_r(uid_search, &pwd, buf, bufsize, &res))
+                   == ERANGE) {
+                if (!enlarge_buffer(&buf, &bufsize)) {
+                    break;
+                }
+            }
+        }
+    } else {
+        while ((e = getpwuid_r(getuid(), &pwd, buf, bufsize, &res))
+               == ERANGE) {
+            if (!enlarge_buffer(&buf, &bufsize)) {
+                break;
+            }
+        }
+    }
+
+    if (e != 0 || !res) {
+        if (!res) e = ENOENT;
+        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;
+
+    free(buf);
+    buf = NULL;
+
+    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 = init_bufsize;
+            buf = xmalloc(bufsize);
+
+            if (strcspn(grpstr, "0123456789")) {
+                while ((e = getgrnam_r(grpstr, &grp, buf, bufsize, &res))
+                       == ERANGE) {
+                    if (!enlarge_buffer(&buf, &bufsize)) {
+                        break;
+                    }
+                }
+            } else {
+                gid_t grpgid = strtoul(grpstr, NULL, 10);
+                while ((e = getgrgid_r(grpgid, &grp, buf, bufsize, &res))
+                       == ERANGE) {
+                    if (!enlarge_buffer(&buf, &bufsize)) {
+                        break;
+                    }
+                }
+            }
+
+            if (e || !res) {
+                if (!res) e = ENOENT;
+                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;
+}
+
+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++ & 0x7);
+        if (*mode) {
+            errno = EINVAL;
+            return 0;
+        }
+    } else {
+        mode_t actors_mask = USR_MODES, 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 '-':
+                action = *(mode-1);
+                break;
+            default:
+                errno = EINVAL;
+                return 0;
+            }
+        }
+        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;
+            default:
+                errno = EINVAL;
+                return 0;
+            }
+        }
+        if (action == '+')
+            ret |= actors_mask & perms_mask;
+        else if (action == '-')
+            ret &= ~(actors_mask & perms_mask);
+    }
+    return ret;
+}
+
+int
+ovs_chmod(const char *path, const char *mode)
+{
+    struct stat st;
+    int err;
+    mode_t new_mode;
+    errno = 0;
+
+    if (lstat(path, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_chmod: stat error %s for file %s", ovs_strerror(errno),
+                 path);
+        return err;
+    }
+
+    new_mode = chmod_getmode(mode, st.st_mode);
+    if (errno) {
+        err = errno;
+        VLOG_ERR("ovs_chmod has bad mode (%s) specified", mode);
+        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;
+}
+
+int
+ovs_chown(const char *path, const char *owner)
+{
+    struct stat st;
+    int err;
+    errno = 0;
+
+    if (lstat(path, &st)) {
+        err = errno;
+        VLOG_ERR("ovs_chown: stat error %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..f151c6e
--- /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.
+ */
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "compiler.h"
+
+#ifndef CHUTIL_H
+#define CHUTIL_H 1
+
+#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 182f76b..84a21e0 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,87 +896,11 @@  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 (!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);
-    }
-
-    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 (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);
-        }
+        VLOG_FATAL("%s: Failed --user option with %s, aborting.", pidfile,
+                   user_spec);
     }
 
-    switch_user = true;
 }
diff --git a/lib/util.c b/lib/util.c
index 94311ac..c7d0b8b 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -250,6 +250,23 @@  free_cacheline(void *p)
 #endif
 }
 
+/* Try to double the size of '*buf', return true
+ * if successful, and '*sizep' will be updated with
+ * the new size. Otherwise, return false.  */
+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;
+}
+
 char *
 xasprintf(const char *format, ...)
 {
diff --git a/lib/util.h b/lib/util.h
index f631bdf..8c29457 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -120,6 +120,8 @@  void *xmalloc_cacheline(size_t) MALLOC_LIKE;
 void *xzalloc_cacheline(size_t) MALLOC_LIKE;
 void free_cacheline(void *);
 
+bool enlarge_buffer(char **buf, size_t *sizep);
+
 void ovs_strlcpy(char *dst, const char *src, size_t size);
 void ovs_strzcpy(char *dst, const char *src, size_t size);
 
diff --git a/tests/automake.mk b/tests/automake.mk
index 0b77617..7e994c1 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -164,6 +164,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-cmap \
 	tests/valgrind/test-csum \
@@ -341,6 +342,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 8b1c443..608356b 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -234,3 +234,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..8743ce6
--- /dev/null
+++ b/tests/test-chutil.c
@@ -0,0 +1,228 @@ 
+/*
+ * 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;
+            }
+        }
+    }
+
+    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);