[libvirt] [PATCH 2/3] New utility functions virFileCreate and virDirCreate
Daniel P. Berrange
berrange at redhat.com
Wed Jan 13 10:36:21 UTC 2010
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 :|
More information about the libvir-list
mailing list