diff mbox series

[PULL,13/28] file-posix: Prepare permission code for fd switching

Message ID 20190312173025.3843-14-kwolf@redhat.com
State New
Headers show
Series [PULL,01/28] gluster: Handle changed glfs_ftruncate signature | expand

Commit Message

Kevin Wolf March 12, 2019, 5:30 p.m. UTC
In order to be able to dynamically reopen the file read-only or
read-write, depending on the users that are attached, we need to be able
to switch to a different file descriptor during the permission change.

This interacts with reopen, which also creates a new file descriptor and
performs permission changes internally. In this case, the permission
change code must reuse the reopen file descriptor instead of creating a
third one.

In turn, reopen can drop its code to copy file locks to the new file
descriptor because that is now done when applying the new permissions.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 block/file-posix.c | 96 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 85 insertions(+), 11 deletions(-)

Comments

Peter Maydell March 14, 2019, 5:27 p.m. UTC | #1
On Tue, 12 Mar 2019 at 17:30, Kevin Wolf <kwolf@redhat.com> wrote:
>
> In order to be able to dynamically reopen the file read-only or
> read-write, depending on the users that are attached, we need to be able
> to switch to a different file descriptor during the permission change.
>
> This interacts with reopen, which also creates a new file descriptor and
> performs permission changes internally. In this case, the permission
> change code must reuse the reopen file descriptor instead of creating a
> third one.
>
> In turn, reopen can drop its code to copy file locks to the new file
> descriptor because that is now done when applying the new permissions.

Hi -- Coverity doesn't like this function (CID 1399712).
I think this may be a false positive, but could you confirm?

> @@ -2696,12 +2695,78 @@ static QemuOptsList raw_create_opts = {
>  static int raw_check_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared,
>                            Error **errp)
>  {
> -    return raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
> +    BDRVRawState *s = bs->opaque;
> +    BDRVRawReopenState *rs = NULL;
> +    int open_flags;
> +    int ret;
> +
> +    if (s->perm_change_fd) {
> +        /*
> +         * In the context of reopen, this function may be called several times
> +         * (directly and recursively while change permissions of the parent).
> +         * This is even true for children that don't inherit from the original
> +         * reopen node, so s->reopen_state is not set.
> +         *
> +         * Ignore all but the first call.
> +         */
> +        return 0;
> +    }
> +
> +    if (s->reopen_state) {
> +        /* We already have a new file descriptor to set permissions for */
> +        assert(s->reopen_state->perm == perm);
> +        assert(s->reopen_state->shared_perm == shared);
> +        rs = s->reopen_state->opaque;
> +        s->perm_change_fd = rs->fd;
> +    } else {
> +        /* We may need a new fd if auto-read-only switches the mode */
> +        ret = raw_reconfigure_getfd(bs, bs->open_flags, &open_flags,
> +                                    false, errp);

Coverity says that raw_reconfigure_getfd() returns an fd in 'ret' here...

> +        if (ret < 0) {
> +            return ret;
> +        } else if (ret != s->fd) {
> +            s->perm_change_fd = ret;
> +        }
> +    }
> +
> +    /* Prepare permissions on old fd to avoid conflicts between old and new,
> +     * but keep everything locked that new will need. */
> +    ret = raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);

...but this call overwrites that fd, so we might never close it.

I think the answer is that either:
 * ret == s->fd and we'll close s->fd later
 * or we save ret into s->perm_change_fd

and Coverity just isn't clever enough to realise that if
ret == s->fd then we haven't lost the handle.

Is that right? If so I'll mark it as a false-positive in the UI.

thanks
-- PMM
Kevin Wolf March 15, 2019, 10:44 a.m. UTC | #2
Am 14.03.2019 um 18:27 hat Peter Maydell geschrieben:
> On Tue, 12 Mar 2019 at 17:30, Kevin Wolf <kwolf@redhat.com> wrote:
> >
> > In order to be able to dynamically reopen the file read-only or
> > read-write, depending on the users that are attached, we need to be able
> > to switch to a different file descriptor during the permission change.
> >
> > This interacts with reopen, which also creates a new file descriptor and
> > performs permission changes internally. In this case, the permission
> > change code must reuse the reopen file descriptor instead of creating a
> > third one.
> >
> > In turn, reopen can drop its code to copy file locks to the new file
> > descriptor because that is now done when applying the new permissions.
> 
> Hi -- Coverity doesn't like this function (CID 1399712).
> I think this may be a false positive, but could you confirm?
> 
> > @@ -2696,12 +2695,78 @@ static QemuOptsList raw_create_opts = {
> >  static int raw_check_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared,
> >                            Error **errp)
> >  {
> > -    return raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
> > +    BDRVRawState *s = bs->opaque;
> > +    BDRVRawReopenState *rs = NULL;
> > +    int open_flags;
> > +    int ret;
> > +
> > +    if (s->perm_change_fd) {
> > +        /*
> > +         * In the context of reopen, this function may be called several times
> > +         * (directly and recursively while change permissions of the parent).
> > +         * This is even true for children that don't inherit from the original
> > +         * reopen node, so s->reopen_state is not set.
> > +         *
> > +         * Ignore all but the first call.
> > +         */
> > +        return 0;
> > +    }
> > +
> > +    if (s->reopen_state) {
> > +        /* We already have a new file descriptor to set permissions for */
> > +        assert(s->reopen_state->perm == perm);
> > +        assert(s->reopen_state->shared_perm == shared);
> > +        rs = s->reopen_state->opaque;
> > +        s->perm_change_fd = rs->fd;
> > +    } else {
> > +        /* We may need a new fd if auto-read-only switches the mode */
> > +        ret = raw_reconfigure_getfd(bs, bs->open_flags, &open_flags,
> > +                                    false, errp);
> 
> Coverity says that raw_reconfigure_getfd() returns an fd in 'ret' here...
> 
> > +        if (ret < 0) {
> > +            return ret;
> > +        } else if (ret != s->fd) {
> > +            s->perm_change_fd = ret;
> > +        }
> > +    }
> > +
> > +    /* Prepare permissions on old fd to avoid conflicts between old and new,
> > +     * but keep everything locked that new will need. */
> > +    ret = raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
> 
> ...but this call overwrites that fd, so we might never close it.
> 
> I think the answer is that either:
>  * ret == s->fd and we'll close s->fd later
>  * or we save ret into s->perm_change_fd
> 
> and Coverity just isn't clever enough to realise that if
> ret == s->fd then we haven't lost the handle.
> 
> Is that right? If so I'll mark it as a false-positive in the UI.

raw_reconfigure_getfd() returns a file descriptor that works for the
given parameters. This can be the existing one (the ret == s->fd case) or
a new one. We only own the reference and need to store it if it's a new
one.

So yes, I think it is a false positive.

Kevin
diff mbox series

Patch

diff --git a/block/file-posix.c b/block/file-posix.c
index 932cc8e58c..e41e0779c6 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -144,6 +144,7 @@  typedef struct BDRVRawState {
     uint64_t locked_perm;
     uint64_t locked_shared_perm;
 
+    int perm_change_fd;
     BDRVReopenState *reopen_state;
 
 #ifdef CONFIG_XFS
@@ -845,7 +846,8 @@  static int raw_handle_perm_lock(BlockDriverState *bs,
 }
 
 static int raw_reconfigure_getfd(BlockDriverState *bs, int flags,
-                                 int *open_flags, Error **errp)
+                                 int *open_flags, bool force_dup,
+                                 Error **errp)
 {
     BDRVRawState *s = bs->opaque;
     int fd = -1;
@@ -871,6 +873,11 @@  static int raw_reconfigure_getfd(BlockDriverState *bs, int flags,
     assert((s->open_flags & O_ASYNC) == 0);
 #endif
 
+    if (!force_dup && *open_flags == s->open_flags) {
+        /* We're lucky, the existing fd is fine */
+        return s->fd;
+    }
+
     if ((*open_flags & ~fcntl_flags) == (s->open_flags & ~fcntl_flags)) {
         /* dup the original fd */
         fd = qemu_dup(s->fd);
@@ -935,7 +942,7 @@  static int raw_reopen_prepare(BDRVReopenState *state,
     qemu_opts_to_qdict(opts, state->options);
 
     rs->fd = raw_reconfigure_getfd(state->bs, state->flags, &rs->open_flags,
-                                   &local_err);
+                                   true, &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
         ret = -1;
@@ -951,14 +958,6 @@  static int raw_reopen_prepare(BDRVReopenState *state,
             ret = -EINVAL;
             goto out_fd;
         }
-
-        /* Copy locks to the new fd */
-        ret = raw_apply_lock_bytes(NULL, rs->fd, s->locked_perm,
-                                   s->locked_shared_perm, false, errp);
-        if (ret < 0) {
-            ret = -EINVAL;
-            goto out_fd;
-        }
     }
 
     s->reopen_state = state;
@@ -2696,12 +2695,78 @@  static QemuOptsList raw_create_opts = {
 static int raw_check_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared,
                           Error **errp)
 {
-    return raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
+    BDRVRawState *s = bs->opaque;
+    BDRVRawReopenState *rs = NULL;
+    int open_flags;
+    int ret;
+
+    if (s->perm_change_fd) {
+        /*
+         * In the context of reopen, this function may be called several times
+         * (directly and recursively while change permissions of the parent).
+         * This is even true for children that don't inherit from the original
+         * reopen node, so s->reopen_state is not set.
+         *
+         * Ignore all but the first call.
+         */
+        return 0;
+    }
+
+    if (s->reopen_state) {
+        /* We already have a new file descriptor to set permissions for */
+        assert(s->reopen_state->perm == perm);
+        assert(s->reopen_state->shared_perm == shared);
+        rs = s->reopen_state->opaque;
+        s->perm_change_fd = rs->fd;
+    } else {
+        /* We may need a new fd if auto-read-only switches the mode */
+        ret = raw_reconfigure_getfd(bs, bs->open_flags, &open_flags,
+                                    false, errp);
+        if (ret < 0) {
+            return ret;
+        } else if (ret != s->fd) {
+            s->perm_change_fd = ret;
+        }
+    }
+
+    /* Prepare permissions on old fd to avoid conflicts between old and new,
+     * but keep everything locked that new will need. */
+    ret = raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    /* Copy locks to the new fd */
+    if (s->perm_change_fd) {
+        ret = raw_apply_lock_bytes(NULL, s->perm_change_fd, perm, ~shared,
+                                   false, errp);
+        if (ret < 0) {
+            raw_handle_perm_lock(bs, RAW_PL_ABORT, 0, 0, NULL);
+            goto fail;
+        }
+    }
+    return 0;
+
+fail:
+    if (s->perm_change_fd && !s->reopen_state) {
+        qemu_close(s->perm_change_fd);
+    }
+    s->perm_change_fd = 0;
+    return ret;
 }
 
 static void raw_set_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared)
 {
     BDRVRawState *s = bs->opaque;
+
+    /* For reopen, we have already switched to the new fd (.bdrv_set_perm is
+     * called after .bdrv_reopen_commit) */
+    if (s->perm_change_fd && s->fd != s->perm_change_fd) {
+        qemu_close(s->fd);
+        s->fd = s->perm_change_fd;
+    }
+    s->perm_change_fd = 0;
+
     raw_handle_perm_lock(bs, RAW_PL_COMMIT, perm, shared, NULL);
     s->perm = perm;
     s->shared_perm = shared;
@@ -2709,6 +2774,15 @@  static void raw_set_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared)
 
 static void raw_abort_perm_update(BlockDriverState *bs)
 {
+    BDRVRawState *s = bs->opaque;
+
+    /* For reopen, .bdrv_reopen_abort is called afterwards and will close
+     * the file descriptor. */
+    if (s->perm_change_fd && !s->reopen_state) {
+        qemu_close(s->perm_change_fd);
+    }
+    s->perm_change_fd = 0;
+
     raw_handle_perm_lock(bs, RAW_PL_ABORT, 0, 0, NULL);
 }