3

/proc/self/mountinfo gives a list of mount points. I suspect they are sorted with the most recently created mount last (and mount --move has no effect on them).

Then is it possible to check if a mount has become inaccessible, due to a more recently moved or created mount?

man proc shows that the first field of /proc/self/mountinfo is a "mount ID". But it doesn't explain any way to check the uppermost mount ID on a given path.

statvfs() (/usr/bin/stat -f) can retrieve something else, a "filesystem ID". I see apparently unique values in it per filesystem... and although it wouldn't help the question I've asked here, it would also be sufficient for my original problem... if there was also a way to associate a filesystem ID with the mount paths and all mount options. (statvfs() returns "mount flags", but not filesystem-specific options, unlike /proc/self/mountinfo).

A second interest in this is that I reported incomplete overmount detection as a lack in df.

I think FSINFO_ATTR_MOUNT_INFO would answer this question nicely. However, that patch has not been accepted into the current kernel.

sourcejedi
  • 50,249

3 Answers3

2

It seems like you should be able to work out all the details of the mount tree by looking at the "parent ID" field in mountinfo.

Consider two mounts /dir/sub and /dir. If the parent mount of /dir/sub is not /dir, but instead /, then /dir/sub must be masked by /dir.

Or if you have two mounts both for the path /, one will be a parent of the other, and the child mount will be the uppermost (accessible) one.

If you have two or more mounts on the exact same path, the uppermost of those mounts is the one which is not a parent to any of the others.

So try this:

  1. Look at your mount path P. If there is a mount at P that is a child of your mount M, then your mount M is hidden (stop).

  2. Look at your mount path P. If there is a mount at P that is a parent of your mount M, then set M to that parent mount and repeat 2.

  3. Look at the longest subprefix PRE of your path P, which has at least one mount on it. Look for the parent mount PAR of your mount M. If your parent mount PAR is not there, then your mount M is hidden (stop).

  4. Recursively: if your parent mount PAR is hidden, then your original mount is also hidden.

Note: the path / has no smaller prefixes. If you reach step 2 and your path P is equal to /, then you know the mount M is not hidden. Stop.

sourcejedi
  • 50,249
  • Does your rule 2 cover the situation where there is an overmount higher up (towards the root) in the path, so your parent mount is there, but unfortunately hidden? – TheDiveO Mar 20 '21 at 22:06
  • 1
    I think I missed a step. Edited. What you called step 2 is now numbered step 3. I think you have the right idea, yes. So long as you mean an overmount higher up than M but lower than P. – sourcejedi Mar 21 '21 at 11:26
  • The algo gives false positives when mounting 1:/a, 2:/a/b, 3:/a, and then 4:/a/b and determines 4:/a/b as hidden. Pondering for a long time, but reckon a bottom-up algo is the wrong approach. Considering how VFS mounts and path resolution works on an abstract level I reckon that false positives can only be avoided by downward(!) traversing the tree of all mount points and recursively determining visibility. That is, it seems to be impossible to correctly determine hidden-ess for a single mount point by just traversing up its hierarchy of prefixes and parent mount points. – TheDiveO Mar 25 '21 at 17:17
  • 1
    My attempt using a top-down tree traversing approach including test cases: https://github.com/TheDiveO/lxkns/blob/feature/mountinfo/mounts/mount.go – TheDiveO Mar 25 '21 at 17:18
1

After implementing sourcejedi's algorithm I hit a major roadblock as it gave me false positives, that is, declaring a visible mountpoint to be hidden. Let's take this set of mount points with their paths, IDs and parent IDs:

  • ID:2, "/"
  • ID:3, "/a/b", ParentID: 2
  • ID:4, "/a/b/c", ParentID: 3
  • ID:5, "/a/b/c/e/f", ParentID: 4
  • ID:40, "/a/b/c", ParentID: 4
  • ID:5, "/a", ParentID: 2
  • ID:50, "/a/b/c/e/f", ParentID: 5

Only mount points with ID:2 "/", ID:5, "/a" and ID:50 "/a/b/c/e/f" should be visible. In my implementation of sourcejedi's algorithm step 3 gives me a false positive for mountpoint ID:50. I pondered for a long while how to correctly get PRE and PAR in this case, assuming that PRE is to be calculated on the "VFS view", that is, PRE is a mount path that may relate to multiple mount points. Moreover, there is a PRE that might reference only mount points from a different hierarchy and this combination might make step 3 trip.

Considering that the Linux kernel's VFS resolves paths into inodes by marching along the VFS nodes starting from the root "downwards" it is thus probably a safer bet to do the same for determining if a mount point is hidden or not (as opposed to figuring it out by going up the hierarchy, starting at the mount point in question). The downside is that we (more or less) need to determine overmounting for the complete tree of mount points before we can safely determine whether a specific mount point really is overmounted/hidden.

My implementation (in Golang) is as follows and successfully passes the test case above; it basically works as follows:

  • Starting with the mount point at mount path "/":
    • check for an "in-place" overmount, where the path P(child(MP)) of one of the children of the current mount point is the same as the path P(MP) of the current mount point.
      • yes: mark all child mount points (recursively with their children) of the current mount point hidden, except for the child mount point that is overmounting the current mount point. Then recursively check the overmounting child mount point and be done afterwards.
      • no: proceed
    • check for children overmounting other children by comparing their paths: ISPREFIX(P(ith-child(MP)),P(jth-child(MP))):
      • yes: recursively mark jth-child(MP) as hidden (overmounted), as well as all its (grand)children.
      • no: proceed.
    • for all child(MP) which aren't hidden (overmounted): recursively check them using this algorithm.

The implementation linked to has some mild optimizations, such as building a linked tree of mount points based on mount point IDs and sorting child mount points by their length(!) of mount paths. For instance, this allows to simplify the check for a child mount point overmounting its parent mount point to just looking at the first child mount point, if there is any. It also halves the O² prefix overmount search in the set of child mount points, which admittedly doesn't gain much in view of O²...

TheDiveO
  • 1,317
  • 1
  • 11
  • 26
0

If you have access to chroot, there is a hack which works on recent kernels. At least it works on Linux v4.17. I don't think it's shell-friendly, but python is ok.

(Note, you can gain the ability to chroot if you have access to user namespaces. Use unshare -rm --propagation slave or equivalent.)

# mount --bind / /mnt
# mount --make-slave /mnt    # don't propagate submounts back to /
# mount --bind / /mnt

Now we have an overmount, let's investigate it.

# python3
...
>>> import os
>>> os.chdir("/")
>>> os.system("grep mnt proc/self/mountinfo")
231 73 253:0 / /mnt rw,relatime shared:1 - ext4 /dev/mapper/alan_dell_2016-fedora rw,seclabel
352 231 253:0 / /mnt rw,relatime shared:281 - ext4 /dev/mapper/alan_dell_2016-fedora rw,seclabel
0

>>> os.chroot("/mnt")
>>> os.system("cat proc/self/mountinfo")
352 231 253:0 / / rw,relatime shared:281 - ext4 /dev/mapper/alan_dell_2016-fedora rw,seclabel
0

This result shows that there is an accessible filesystem mounted on /mnt, and it is the filesystem with mount ID 352 etc.

sourcejedi
  • 50,249