35

Rather than using mount | grep, I'd like to use mount -l -t bind, but that doesn't work, and -t none shows all mounts.

l0b0
  • 51,350

5 Answers5

40

Maybe this could do the trick:

findmnt | grep  "\["

Example:

$ mkdir /tmp/foo
$ sudo mount --bind /media/ /tmp/foo
$ findmnt | grep  "\["
│ └─/tmp/foo                     /dev/sda2[/media] ext4            rw,relatime,data=ordered
l0b0
  • 51,350
olopopo
  • 501
  • 1
    Apparently, this only works when a subdirectory of a mountpoint is bind mounted. If / itself is bind mounted, for example, the output doesn't have a [...]. – muru Mar 14 '18 at 01:59
  • 5
    findmnt uses /proc/self/mountinfo so you can check there directly as well. – tmm1 Oct 07 '20 at 17:43
  • 1
    careful with this command. There will be false positives if subvolumes from btrfs are mounted. So be aware. Still a good and valid way to find bind mounts. – Ben Nov 16 '22 at 10:40
37

Bind mounts are not a filesystem type, nor a parameter of a mounted filesystem; they're parameters of a mount operation. As far as I know, the following sequences of commands lead to essentially identical system states as far as the kernel is concerned:

mount /dev/foo /mnt/one; mount --bind /mnt/one /mnt/two
mount /dev/foo /mnt/two; mount --bind /mnt/two /mnt/one

So the only way to remember what mounts were bind mounts is the log of mount commands left in /etc/mtab. A bind mount operation is indicated by the bind mount option (which causes the filesystem type to be ignored). But mount has no option to list only filesystems mounted with a particular set of sets of options. Therefore you need to do your own filtering.

mount | grep -E '[,(]bind[,)]'
</etc/mtab awk '$4 ~ /(^|,)bind(,|$)/'

Note that /etc/mtab is only useful here if it's a text file maintained by mount. Some distributions set up /etc/mtab as a symbolic link to /proc/mounts instead; /proc/mounts is mostly equivalent to /etc/mtab but does have a few differences, one of which is not tracking bind mounts.

One piece of information that is retained by the kernel, but not shown in /proc/mounts, is when a mount point only shows a part of the directory tree on the mounted filesystem. In practice this mostly happens with bind mounts:

mount --bind /mnt/one/sub /mnt/partial

In /proc/mounts, the entries for /mnt/one and /mnt/partial have the same device, the same filesystem type and the same options. The information that /mnt/partial only shows the part of the filesystem that's rooted at /sub is visible in the per-process mount point information in /proc/$pid/mountinfo (column 4). Entries there look like this:

12 34 56:78 / /mnt/one rw,relatime - ext3 /dev/foo rw,errors=remount-ro,data=ordered
12 34 56:78 /sub /mnt/partial rw,relatime - ext3 /dev/foo rw,errors=remount-ro,data=ordered
  • 6
    @Gilles Actually, you can do this simply using findmnt | fgrep [ as explained here. – aculich Mar 06 '12 at 16:39
  • 1
    @Gilles What mount --version are you using that records any bind information in /etc/mtab? I am using version 2.20.1 and I looked at the latest sources and in neither case do I see bind information recorded anywhere that would allow you to grep for bind. On the other hand, what I suggested in my answer does in fact list bind mounts created with --bind as well as using the bind option. – aculich Mar 06 '12 at 18:16
  • @aculich </etc/mtab awk … is POSIX-compliant (I forget whether it's supported in Bourne). Please check your facts. I can confirm that /etc/mtab has the bind option for a filesystem mounted with mount --bind /source /target on Debian stable (mount from util-linux-ng 2.17.2). – Gilles 'SO- stop being evil' Mar 06 '12 at 21:10
  • @Gilles I deleted my errant comment to remove confusion. You're right, it is indeed POSIX-compliant. Also now I understand the reason we are seeing different behavior of mount and /etc/mtab. You are using Debian stable which has the older version of util-linux-ng; I am using Debian testing which has a newer version that no longer seems to have the same /etc/mtab behavior, which is maybe why @rozcietrzewiacz did not see bind in in /etc/mtab if his distribution is also using a newer version? – aculich Mar 06 '12 at 21:44
  • @aculich I see the same results on mount from util-linux 2.20.1 on Debian testing. Kernel 2.6.32 in both cases, I don't think it matters for the /etc/mtab content but I'm not sure. – Gilles 'SO- stop being evil' Mar 06 '12 at 21:47
  • Incidentaly, '[,(]bind[,)]' is also the monster who ate your shield in the original Legend of Zelda. – orb Oct 23 '13 at 15:16
  • 1
    @aculich You should post findmnt as an answer. It only works if the target directory is not another mount point, by the way. Try for example sudo mount --bind / foo && findmnt | grep foo – l0b0 Nov 19 '13 at 22:56
  • You say that binds mounts are parameters of a "mount operation", but I don't see how that would prevent the kernel from maintaining any record of a particular mount being a bind mount. In fact, it must retain some record of it or store some flag that indicates a particular mount is a bind mount in order for it to know to connect a certain directory to another as if it were a traditional mount. – Melab Mar 23 '16 at 23:12
20

The kernel doesn't handle bind mounts different from normal mounts after the fact. The only differ in what happens while mount runs.

When you mount a filesystem (eg. with mount -t ext4 /dev/sda1 /mnt) the kernel (a bit simplified) performs three steps:

  1. The kernel looks for a filesystem driver for the specified filesystem type (if you omit -t or use -t auto mount guesses the type for you and provides the guessed type to the kernel)
  2. The kernel instructs the filesystem driver to access the filesystem using the source path and any provided options. At this point the filesystem is only identified by a major:minor number pair.
  3. The filesystem is bound to a path (the mountpoint). The kernel also uses some of the mount options here. (nodev for example is an option on the mountpoint, not on the filesystem. You can have a bind mount with nodev and one without)

If you perform a bind mount (eg. with mount --bind /a /b) the following happens:

  1. The kernel resolves which filesystem contains the source path and the relative path from the mountpoint to the directory.
  2. The filesystem is bound to the new mountpoint using the options and the relative path.

(I'll skip mount --move, because it's not relevant to the question.)

This quite is similar to how files are created on Linux:

  1. The kernel resolves which filesystem is responsible for the directory in which the file should be created.
  2. A new file in the filesystem is created. At this point the file only has an inode number.
  3. The new file is linked to a filename in the directory.

If you make a hard link the following happens:

  1. The kernel resolves the inode number of the source file.
  2. The file is linked to the destination filename.

As you can see, the created file and the hard link are indistinguishable:

$ touch first
$ ln first second
$ ls -li
1184243 -rw-rw-r-- 2 cg909 cg909 0 Feb 20 23:56 /tmp/first
1184243 -rw-rw-r-- 2 cg909 cg909 0 Feb 20 23:56 /tmp/second

But, as you can identify all hardlinks to a file by comparing the inode numbers, you can identify all mounts to a filesystem by comparing the major:minor numbers of mounts.

You can do this with findmnt -o TARGET,MAJ:MIN or by directly looking at /proc/self/mountinfo (see the Linux kernel documentation for more information).

The following Python script lists all bind mounts. It assumes that the oldest mount point with the shortest relative path to the root of the mounted file system is the original mount.

#!/usr/bin/python3

import os.path, re
from collections import namedtuple

MountInfo = namedtuple('MountInfo', ['mountid', 'parentid', 'devid', 'root', 'mountpoint', 'mountoptions', 'extra', 'fstype', 'source', 'fsoptions'])

mounts = {}

def unescape(string):
    return re.sub(r'\\([0-7]{3})', (lambda m: chr(int(m.group(1), 8))), string)

with open('/proc/self/mountinfo', 'r') as f:
    for line in f:
        # Parse line
        mid, pid, devid, root, mp, mopt, *tail = line.rstrip().split(' ')
        extra = []
        for item in tail:
            if item != '-':
                extra.append(item)
            else:
                break
        fstype, src, fsopt = tail[len(extra)+1:]
        # Save mount info
        mount = MountInfo(int(mid), int(pid), devid, unescape(root), unescape(mp), mopt, extra, fstype, unescape(src), fsopt)
        mounts.setdefault(devid, []).append(mount)

for devid, mnts in mounts.items():
    # Skip single mounts
    if len(mnts) <= 1:
        continue
    # Sort list to get the first mount of the device's root dir (if still mounted)
    mnts.sort(key=lambda x: x.root)
    src, *binds = mnts
    # Print bind mounts
    for bindmount in binds:
        if src.root == bindmount.root:
            srcstring = src.mountpoint
        else:
            srcstring = src.mountpoint+':/'+os.path.relpath(bindmount.root, src.root)
        print('{0} -> {1.mountpoint} ({1.mountoptions})'.format(srcstring, bindmount))
cg909
  • 7,082
3

This is similar to the other findmnt answer, but avoids the formatting issue.

To show all submounts:

findmnt --kernel -n --list | grep '\['

To show all submounts of filesystems of type ext4:

findmnt --kernel -t ext4 -n --list | grep '\['

To show all mounts excluding submounts:

findmnt --kernel -n --list | grep -v '\['

To show all mounts of filesystems of type ext4 excluding submounts:

findmnt --kernel -t ext4 -n --list | grep -v '\['

The "-n" removes the headers and the "--list" removes the lines of the "tree" format.

Tested on Debian stretch.

sg23
  • 41
  • 1
1
unset DONE1FSES
FSES=$(findmnt -vUPno SOURCE,FSROOT,TARGET,MAJ:MIN)
FSES=${FSES//MAJ:MIN/MAJ_MIN}
while read SEARCH1FS
do
  unset DONE2FSES
  eval "$SEARCH1FS"
  SEARCH1SOURCE=$SOURCE
  SEARCH1FSROOT=$FSROOT
  SEARCH1TARGET=$TARGET
  SEARCH1MAJMIN=$MAJ_MIN

  FS1WASHANDLED=0
  while read DONE1FS 
  do
    if [[ $DONE1FS == $MAJ_MIN ]]
    then
      FS1WASHANDLED=1
      break
    fi
  done < <(echo "$DONE1FSES")


  if [[ ($SEARCH1FSROOT == /) && ($FS1WASHANDLED == 0) ]]
  then
  DONE1FSES+=$MAJ_MIN$'\n'
  while read SEARCH2FS
  do
    eval "$SEARCH2FS"
    SEARCH2SOURCE=$SOURCE
    SEARCH2FSROOT=$FSROOT
    SEARCH2TARGET=$TARGET
    SEARCH2MAJMIN=$MAJ_MIN

    FS2WASHANDLED=0
    while read DONE2FS 
    do
      if [[ $DONE2FS == $SEARCH2FS ]]
      then
        FS2WASHANDLED=1
        break
      fi
    done < <(echo "$DONE2FSES")

    if [[ ($SEARCH1MAJMIN == $SEARCH2MAJMIN)  && ($SEARCH1TARGET != $SEARCH2TARGET )  && ($FS2WASHANDLED == 0 ) ]]
    then
      DONE2FSES+=$SEARCH2FS$'\n'
      echo "$SEARCH1TARGET$SEARCH2FSROOT   --> $SEARCH2TARGET"
    fi

  done < <(echo "$FSES")


  fi
done   < <(echo "$FSES")
n3rdopolis
  • 121
  • 4