On Wed, Jan 13, 2010 at 01:25:06AM -0500, Laine Stump wrote: > 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.) > > Return from both of these functions is 0 on success, or the value of > errno if there was a failure. > --- > src/util/util.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/util/util.h | 9 ++ > 2 files changed, 256 insertions(+), 0 deletions(-) > > diff --git a/src/util/util.c b/src/util/util.c > index 1d493de..1cb29f4 100644 > --- a/src/util/util.c > +++ b/src/util/util.c > @@ -1126,6 +1126,253 @@ int virFileExists(const char *path) > return(0); > } > > + > +static int virFileCreateSimple(const char *path, mode_t mode, uid_t uid, gid_t gid) { > + int fd = -1; > + int ret = 0; > + > + if ((fd = open(path, O_RDWR | O_CREAT | O_EXCL, mode)) < 0) { > + ret = errno; > + virReportSystemError(NULL, errno, _("failed to create file '%s'"), > + path); > + goto error; > + } > + if ((getuid() == 0) && ((uid != 0) || (gid != 0))) { > + if (fchown(fd, uid, gid) < 0) { > + ret = errno; > + virReportSystemError(NULL, errno, _("cannot chown '%s' to (%u, %u)"), > + path, uid, gid); > + close(fd); > + goto error; > + } > + } > + if (close(fd) < 0) { > + ret = errno; > + virReportSystemError(NULL, errno, _("failed to close new file '%s'"), > + path); > + goto error; > + } > + fd = -1; > +error: > + return ret; > +} > + > +static int virDirCreateSimple(const char *path, mode_t mode, uid_t uid, gid_t gid) { > + int ret = 0; > + > + if (mkdir(path, mode) < 0) { > + ret = errno; > + virReportSystemError(NULL, errno, _("failed to create directory '%s'"), > + path); > + goto error; > + } > + > + if ((getuid() == 0) && ((uid != 0) || (gid != getgid()))) { > + if (chown(path, uid, gid) < 0) { > + ret = errno; > + virReportSystemError(NULL, errno, _("cannot chown '%s' to (%u, %u)"), > + path, uid, gid); > + goto error; > + } > + } > +error: > + return ret; > +} > + > +#ifndef WIN32 > +int virFileCreate(const char *path, mode_t mode, > + uid_t uid, gid_t gid, unsigned int flags) { > + pid_t pid; > + int waitret, status, ret = 0; > + int fd = -1; > + > + if ((!(flags & VIR_FILE_CREATE_AS_UID)) > + || (getuid() != 0) > + || ((uid == 0) && (gid == 0))) { > + return virFileCreateSimple(path, mode, uid, gid); > + } > + > + /* 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); > + return ret; > + } > + ret = WEXITSTATUS(status); > + if (!WIFEXITED(status) || (ret == EACCES)) { > + /* fall back to the simpler method, which works better in > + * some cases */ > + /* Should we log a warning here? */ > + return virFileCreateSimple(path, mode, uid, gid); > + } > + if ((ret == 0) && (gid != 0)) { > + /* check if group was set properly by creating after > + * setgid. If not, try doing it with chown */ > + struct stat st; > + if (stat(path, &st) == -1) { > + ret = errno; > + virReportSystemError(NULL, errno, > + _("stat of '%s' failed"), > + path); > + return ret; > + } > + if ((st.st_gid != gid) && (chown(path, -1, gid) < 0)) { > + ret = errno; > + virReportSystemError(NULL, errno, _("cannot chown '%s' to group %u"), > + path, gid); > + } > + } > + 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) { > + pid_t pid; > + int waitret; > + int status, ret = 0; > + > + if ((!(flags & VIR_FILE_CREATE_AS_UID)) > + || (getuid() != 0) > + || ((uid == 0) && (gid == 0))) { > + return virDirCreateSimple(path, mode, uid, gid); > + } > + > + 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); > + return ret; > + } > + ret = WEXITSTATUS(status); > + if (!WIFEXITED(status) || (ret == EACCES)) { > + /* fall back to the simpler method, which works better in some cases */ > + /* Should we log a warning here? */ > + return virDirCreateSimple(path, mode, uid, gid); > + } > + if ((ret == 0) && (gid != 0)) { > + /* check if group was set properly by creating after > + * setgid. If not, try doing it with chown */ > + struct stat st; > + if (stat(path, &st) == -1) { > + ret = errno; > + virReportSystemError(NULL, errno, > + _("stat of '%s' failed"), > + path); > + return ret; > + } > + if ((st.st_gid != gid) && (chown(path, -1, gid) < 0)) { > + ret = errno; > + virReportSystemError(NULL, errno, _("cannot chown '%s' to group %u"), > + path, gid); > + } > + } > + > + 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); > +} > + > +int virDirCreate(const char *path, mode_t mode, > + uid_t uid, gid_t gid, unsigned int flags) { > + return virDirCreateSimple(path, mode, uid, gid); > +} > +#endif > + > int virFileMakePath(const char *path) > { > struct stat st; > diff --git a/src/util/util.h b/src/util/util.h > index 5e70038..2762862 100644 > --- a/src/util/util.h > +++ b/src/util/util.h > @@ -111,6 +111,15 @@ char *virFindFileInPath(const char *file); > > int virFileExists(const char *path); > > +enum { > + VIR_FILE_CREATE_NONE = 0, > + VIR_FILE_CREATE_AS_UID = (1 << 0), > +}; > + > +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, ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :| -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list