From 98f6f381c8d138600c4f9732d8ffb5b608e713ee Mon Sep 17 00:00:00 2001 From: Laine Stump Date: Thu, 21 Jan 2010 00:33:43 +0100 Subject: [PATCH] New utility functions virFileCreate and virDirCreate These functions create a new file or directory with the given uid/gid. If the flag VIR_FILE_CREATE_AS_UID is given, they do this by forking a new process, calling setuid/setgid in the new process, and then creating the file. This is better than simply calling open then fchown, because in the latter case, a root-squashing nfs server would create the new file as user nobody, then refuse to allow fchown. If VIR_FILE_CREATE_AS_UID is not specified, the simpler tactic of creating the file/dir, then chowning is is used. This gives better results in cases where the parent directory isn't on a root-squashing NFS server, but doesn't give permission for the specified uid/gid to create files. (Note that if the fork/setuid method fails to create the file due to access privileges, the parent process will make a second attempt using this simpler method.) If the bit VIR_FILE_CREATE_ALLOW_EXIST is set in the flags, an existing file/directory will not cause an error; in this case, the function will simply set the permissions of the file/directory to those requested. If VIR_FILE_CREATE_ALLOW_EXIST is not specified, an existing file/directory is considered (and reported as) an error. Return from both of these functions is 0 on success, or the value of errno if there was a failure. * src/util/util.[ch]: add the 2 new util functions --- src/util/util.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/util.h | 10 ++ 2 files changed, 315 insertions(+) diff --git a/src/util/util.c b/src/util/util.c index 578d12bcd9..08d74a3704 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -1138,6 +1138,311 @@ int virFileExists(const char *path) return(0); } + +static int virFileCreateSimple(const char *path, mode_t mode, uid_t uid, gid_t gid, + unsigned int flags) { + int open_flags = O_RDWR | O_CREAT | ((flags & VIR_FILE_CREATE_ALLOW_EXIST) + ? 0 : O_EXCL); + int fd = -1; + int ret = 0; + struct stat st; + + if ((fd = open(path, open_flags, mode)) < 0) { + ret = errno; + virReportSystemError(NULL, errno, _("failed to create file '%s'"), + path); + goto error; + } + if (fstat(fd, &st) == -1) { + ret = errno; + virReportSystemError(NULL, errno, _("stat of '%s' failed"), path); + goto error; + } + if (((st.st_uid != uid) || (st.st_gid != gid)) + && (fchown(fd, uid, gid) < 0)) { + ret = errno; + virReportSystemError(NULL, errno, _("cannot chown '%s' to (%u, %u)"), + path, uid, gid); + goto error; + } + if (fchmod(fd, mode) < 0) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot set mode of '%s' to %04o"), + path, mode); + goto error; + } + if (close(fd) < 0) { + ret = errno; + virReportSystemError(NULL, errno, _("failed to close new file '%s'"), + path); + fd = -1; + goto error; + } + fd = -1; +error: + if (fd != -1) + close(fd); + return ret; +} + +static int virDirCreateSimple(const char *path, mode_t mode, uid_t uid, gid_t gid, + unsigned int flags) { + int ret = 0; + struct stat st; + + if ((mkdir(path, mode) < 0) + && !((errno == EEXIST) && (flags & VIR_FILE_CREATE_ALLOW_EXIST))) + { + ret = errno; + virReportSystemError(NULL, errno, _("failed to create directory '%s'"), + path); + goto error; + } + + if (stat(path, &st) == -1) { + ret = errno; + virReportSystemError(NULL, errno, _("stat of '%s' failed"), path); + goto error; + } + if (((st.st_uid != uid) || (st.st_gid != gid)) + && (chown(path, uid, gid) < 0)) { + ret = errno; + virReportSystemError(NULL, errno, _("cannot chown '%s' to (%u, %u)"), + path, uid, gid); + goto error; + } + if (chmod(path, mode) < 0) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot set mode of '%s' to %04o"), + path, mode); + goto error; + } +error: + return ret; +} + +#ifndef WIN32 +int virFileCreate(const char *path, mode_t mode, + uid_t uid, gid_t gid, unsigned int flags) { + struct stat st; + pid_t pid; + int waitret, status, ret = 0; + int fd; + + if ((!(flags & VIR_FILE_CREATE_AS_UID)) + || (getuid() != 0) + || ((uid == 0) && (gid == 0)) + || ((flags & VIR_FILE_CREATE_ALLOW_EXIST) && (stat(path, &st) >= 0))) { + return virFileCreateSimple(path, mode, uid, gid, flags); + } + + /* parent is running as root, but caller requested that the + * file be created as some other user and/or group). The + * following dance avoids problems caused by root-squashing + * NFS servers. */ + + if ((pid = fork()) < 0) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot fork o create file '%s'"), path); + return ret; + } + + if (pid) { /* parent */ + /* wait for child to complete, and retrieve its exit code */ + while ((waitret = waitpid(pid, &status, 0) == -1) + && (errno == EINTR)); + if (waitret == -1) { + ret = errno; + virReportSystemError(NULL, errno, + _("failed to wait for child creating '%s'"), + path); + goto parenterror; + } + ret = WEXITSTATUS(status); + if (!WIFEXITED(status) || (ret == EACCES)) { + /* fall back to the simpler method, which works better in + * some cases */ + return virFileCreateSimple(path, mode, uid, gid, flags); + } + if (ret != 0) { + goto parenterror; + } + + /* check if group was set properly by creating after + * setgid. If not, try doing it with chown */ + if (stat(path, &st) == -1) { + ret = errno; + virReportSystemError(NULL, errno, + _("stat of '%s' failed"), + path); + goto parenterror; + } + if ((st.st_gid != gid) && (chown(path, -1, gid) < 0)) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot chown '%s' to group %u"), + path, gid); + goto parenterror; + } + if (chmod(path, mode) < 0) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot set mode of '%s' to %04o"), + path, mode); + goto parenterror; + } +parenterror: + return ret; + } + + /* child - set desired uid/gid, then attempt to create the file */ + + if ((gid != 0) && (setgid(gid) != 0)) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot set gid %u creating '%s'"), + gid, path); + goto childerror; + } + if ((uid != 0) && (setuid(uid) != 0)) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot set uid %u creating '%s'"), + uid, path); + goto childerror; + } + if ((fd = open(path, O_RDWR | O_CREAT | O_EXCL, mode)) < 0) { + ret = errno; + if (ret != EACCES) { + /* in case of EACCES, the parent will retry */ + virReportSystemError(NULL, errno, + _("child failed to create file '%s'"), + path); + } + goto childerror; + } + if (close(fd) < 0) { + ret = errno; + virReportSystemError(NULL, errno, _("child failed to close new file '%s'"), + path); + goto childerror; + } +childerror: + _exit(ret); + +} + +int virDirCreate(const char *path, mode_t mode, + uid_t uid, gid_t gid, unsigned int flags) { + struct stat st; + pid_t pid; + int waitret; + int status, ret = 0; + + if ((!(flags & VIR_FILE_CREATE_AS_UID)) + || (getuid() != 0) + || ((uid == 0) && (gid == 0)) + || ((flags & VIR_FILE_CREATE_ALLOW_EXIST) && (stat(path, &st) >= 0))) { + return virDirCreateSimple(path, mode, uid, gid, flags); + } + + if ((pid = fork()) < 0) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot fork to create directory '%s'"), + path); + return ret; + } + + if (pid) { /* parent */ + /* wait for child to complete, and retrieve its exit code */ + while ((waitret = waitpid(pid, &status, 0) == -1) && (errno == EINTR)); + if (waitret == -1) { + ret = errno; + virReportSystemError(NULL, errno, + _("failed to wait for child creating '%s'"), + path); + goto parenterror; + } + ret = WEXITSTATUS(status); + if (!WIFEXITED(status) || (ret == EACCES)) { + /* fall back to the simpler method, which works better in + * some cases */ + return virDirCreateSimple(path, mode, uid, gid, flags); + } + if (ret != 0) { + goto parenterror; + } + + /* check if group was set properly by creating after + * setgid. If not, try doing it with chown */ + if (stat(path, &st) == -1) { + ret = errno; + virReportSystemError(NULL, errno, + _("stat of '%s' failed"), + path); + goto parenterror; + } + if ((st.st_gid != gid) && (chown(path, -1, gid) < 0)) { + ret = errno; + virReportSystemError(NULL, errno, + _("cannot chown '%s' to group %u"), + path, gid); + goto parenterror; + } + if (chmod(path, mode) < 0) { + virReportSystemError(NULL, errno, + _("cannot set mode of '%s' to %04o"), + path, mode); + goto parenterror; + } +parenterror: + return ret; + } + + /* child - set desired uid/gid, then attempt to create the directory */ + + if ((gid != 0) && (setgid(gid) != 0)) { + ret = errno; + virReportSystemError(NULL, errno, _("cannot set gid %u creating '%s'"), + gid, path); + goto childerror; + } + if ((uid != 0) && (setuid(uid) != 0)) { + ret = errno; + virReportSystemError(NULL, errno, _("cannot set uid %u creating '%s'"), + uid, path); + goto childerror; + } + if (mkdir(path, mode) < 0) { + ret = errno; + if (ret != EACCES) { + /* in case of EACCES, the parent will retry */ + virReportSystemError(NULL, errno, _("child failed to create directory '%s'"), + path); + } + goto childerror; + } +childerror: + _exit(ret); +} + +#else /* WIN32 */ + +int virFileCreate(const char *path, mode_t mode, + uid_t uid, gid_t gid, unsigned int flags) { + return virFileCreateSimple(path, mode, uid, gid, flags); +} + +int virDirCreate(const char *path, mode_t mode, + uid_t uid, gid_t gid, unsigned int flags) { + return virDirCreateSimple(path, mode, uid, gid, flags); +} +#endif + int virFileMakePath(const char *path) { struct stat st; diff --git a/src/util/util.h b/src/util/util.h index 5e70038e8b..b0219cc3fc 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -111,6 +111,16 @@ char *virFindFileInPath(const char *file); int virFileExists(const char *path); +enum { + VIR_FILE_CREATE_NONE = 0, + VIR_FILE_CREATE_AS_UID = (1 << 0), + VIR_FILE_CREATE_ALLOW_EXIST = (1 << 1), +}; + +int virFileCreate(const char *path, mode_t mode, uid_t uid, gid_t gid, + unsigned int flags) ATTRIBUTE_RETURN_CHECK; +int virDirCreate(const char *path, mode_t mode, uid_t uid, gid_t gid, + unsigned int flags) ATTRIBUTE_RETURN_CHECK; int virFileMakePath(const char *path) ATTRIBUTE_RETURN_CHECK; int virFileBuildPath(const char *dir, -- GitLab