Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mountinfo.Mounted(): fix #90

Merged
merged 3 commits into from
Nov 3, 2021
Merged

mountinfo.Mounted(): fix #90

merged 3 commits into from
Nov 3, 2021

Conversation

kolyshkin
Copy link
Collaborator

@kolyshkin kolyshkin commented Oct 28, 2021

Fix Mounted() and its tests.

  1. In case the argument does not exist, Mounted now returns an
    appropriate error. Previously, this condition was treated as
    "not a mount point", with no error returned, but in fact such
    path could be a mount point, overshadowed by another mount.
    Since the mount is still unreachable, it can not be unmounted
    anyway, so we do not fall back to mountedByMountinfo, returning
    an error instead.

    ⚠️ In case callers are not interested in ENOENT, they should filter it
    out. Amend the function doc accordingly.

  2. There are some corner cases in which mountedByOpenat2 or mountedByStat
    do not work properly. In particular,

    • neither work if there's an ending slash;
    • neither work for a symlink to a mounted directory;
    • mountedByStat do not work a parent of path is a symlink residing on
      different filesystem.

    To solve these cases, call normalizePath() before path is used for any of
    mountedBy* functions, not just before mountedByMountinfo
    like it was done earlier.

  3. Fix TestMountedBy

    The way prepareMounts was written was very complicated from the
    maintainability perspective, with all the test cases in the same basket.

    Rewrite this in a much cleaner way, with individual subtests.

    Add some more test cases that were failing before this PR.

Fixes: #88

@kolyshkin
Copy link
Collaborator Author

In case callers are not interested in ENOENT, they should filter it out.

I briefly looked through moby/moby repo code (except *_test.go files) and it looks like there are no users of mountinfo.Mounted that need to be modified because of this change.

Same for github.com/moby/sys/mount, the only user is MakeMount and looks like it will work equally well with this change.

@kolyshkin
Copy link
Collaborator Author

@mkimuram PTAL 🙏🏻 (last two commits) if you have time

@kolyshkin kolyshkin marked this pull request as ready for review October 28, 2021 15:57
@kolyshkin
Copy link
Collaborator Author

No longer a draft; PTAL @thaJeztah

@kolyshkin kolyshkin mentioned this pull request Oct 28, 2021
@thaJeztah
Copy link
Member

I was wondering if the graph driver needed to be updated; do you think we need a draft PR that vendors this branch? 🤔

--- a/daemon/graphdriver/driver_linux.go
+++ b/daemon/graphdriver/driver_linux.go
@@ -1,6 +1,9 @@
 package graphdriver // import "github.com/docker/docker/daemon/graphdriver"

 import (
+       "errors"
+       "os"
+
        "github.com/moby/sys/mountinfo"
        "golang.org/x/sys/unix"
 )
@@ -113,7 +116,10 @@ type defaultChecker struct {
 }

 func (c *defaultChecker) IsMounted(path string) bool {
-       m, _ := mountinfo.Mounted(path)
+       m, err := mountinfo.Mounted(path)
+       if err != nil && errors.Is(err, os.ErrNotExist) {
+               return false
+       }
        return m
 }

@kolyshkin
Copy link
Collaborator Author

I was wondering if the graph driver needed to be updated

Looks like this patch is not needed, since mountinfo.Mounted() always returns false when there's an error (although it's not enforced in the code).

do you think we need a draft PR that vendors this branch? 

Maybe for tests, yes (I haven't looked in how unit tests use Mounted()).

@kolyshkin
Copy link
Collaborator Author

mountinfo.Mounted() always returns false when there's an error

Perhaps we need to document and test it. Let me update the last commit.

@thaJeztah
Copy link
Member

I saw some places where tests were using it; I may try a draft PR (or if you have time, feel free to open one)

@thaJeztah
Copy link
Member

That said; probably doesn't need to be a blocker before merging

1. In case the argument does not exist, Mounted now returns an
   appropriate error. Previously, this condition was treated as
   "not a mount point", with no error returned, but in fact such
   path could be a mount point, overshadowed by another mount.
   Since the mount is still unreachable, it can not be unmounted
   anyway, so we do not fall back to mountedByMountinfo, returning
   an error instead.

   In case callers are not interested in ENOENT, they should filter it
   out. Amend the function doc accordingly.

2. There are some corner cases in which mountedByOpenat2 or mountedByStat
   do not work properly. In particular,

    - neither work if there's an ending slash;
    - neither work for a symlink to a mounted directory;
    - mountedByStat do not work a parent of path is a symlink residing on
      different filesystem.

   To solve these cases, we have to call normalizePath() before path is used
   for any of mountedBy* functions, not just before mountedByMountinfo
   like it was done earlier.

   More tests are to be added by the next commit.

Signed-off-by: Kir Kolyshkin <[email protected]>
The way prepareMounts was written was very complicated from the
maintainability perspective, with all the test cases in the same basket.

Rewrite this in a much cleaner way, with individual subtests.

Add some more test cases that were failing before the previous commit.

Signed-off-by: Kir Kolyshkin <[email protected]>
In case of any error, Mounted() always returns false. Document and check
that.

Signed-off-by: Kir Kolyshkin <[email protected]>
@kolyshkin
Copy link
Collaborator Author

mountinfo.Mounted() always returns false when there's an error

Perhaps we need to document and test it. Let me update the last commit.

Done.

Copy link

@mkimuram mkimuram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kolyshkin Really clear PR description, patch comments, and codes. Just two comments:

Comment on lines +16 to +18
// The non-existent path returns an error. If a caller is not interested
// in this particular error, it should handle it separately using e.g.
// errors.Is(err, os.ErrNotExist).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that the errors that are returned when neither original path nor resolved real path found will likely be the errors that normalizePath returns. If my understanding is correct, how can users handle error to exclude particular errors, as described?

(normalizePath seems to return wrapped errors, not original errors. And errors.Is doesn't seem to handle the wrapped error the same to the original one).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, errors.Is unwraps the error, that's the beauty of it.

Demo code: https://play.golang.org/p/yp5-3eOa9Fa

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that %w is a special directive to wrap an error so it can be unwrapped.

Updated demo code with fmt.Errorf("%w"): https://play.golang.org/p/XQR6weZc-fj

},
},
{
desc: "path whose parent is a symlink to directory on another device",
Copy link

@mkimuram mkimuram Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When thinking as closed-box testing, bind mount version of this case, like "path whose parent is a bind mount to directory (on another device)", may be interesting. However, if thinking as open-box testing, it would clearly work, so it may not be needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I added this mostly to check that it fails on mountedByStat when a call to normalizePath is commented out.

With normalizePath in place, this indeed does not make much sense.

@thaJeztah
Copy link
Member

CI on my test PR (moby/moby#42980) didn't explode, so LGTM

@mkimuram @kolyshkin are any changes still needed, for the discussion on #90 (comment) above, or is this good to go?

@mkimuram
Copy link

mkimuram commented Nov 3, 2021

Sorry, I should have clearly commented that it is good to go. It looks good to me.

@thaJeztah
Copy link
Member

Thanks! Just double-checking 😅

Thanks for helping review these changes; it's always good to have more eyes 🤗

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mountinfo.Mounted: incorrect results for some corner cases
3 participants