stat and lstat can do another trick, 100% not my invention: Find out if a directory is a mountpoint or not.
That trick - once you have seen it - is really obvious, but at least I never thought of it.
I'll start slowly:
If you look at a directory (on a Unix-like system), it always contains two subdirectory entries named "." and "..", representing the directory itself and its parent directory. Because they both start with a ".", they are hidden by default, but ls -a shows them. I'll also add the -l flag for more details and the -i flag to show inode numbers, and use head to limit the output to just the first few lines:
alex@desktop:/usr/bin$ ls -ali | head
total 169256
11099 drwxr-xr-x 2 root root 40960 Mar 14 13:29 .
18 drwxr-xr-x 12 root root 4096 Feb 18 2024 ..
11047 -rwxr-xr-x 1 root root 68496 Sep 20 2022 [
45297 lrwxrwxrwx 1 root root 25 Mar 18 2022 aclocal -> /etc
+/alternatives/aclocal
27338 -rwxr-xr-x 1 root root 36020 Mar 18 2022 aclocal-1.16
51916 -rwxr-xr-x 1 root root 14656 Jul 14 2024 acyclic
45514 -rwxr-xr-x 1 root root 31040 Nov 21 2024 addpart
37014 lrwxrwxrwx 1 root root 10 Sep 18 2024 add-patch -> ed
+it-patch
13872 lrwxrwxrwx 1 root root 26 Jan 14 2023 addr2line -> x8
+6_64-linux-gnu-addr2line
alex@desktop:/usr/bin$
So, it seems the /usr/bin directory on this Debian VM uses inode 11099, and its parent directory uses inode 18.
There is another little detail: "." and ".." are hard links. Their link count is larger than 1, and it must be: "/usr/bin/." is just another name (a hard link) for "/usr/bin", and "/usr/bin/.." is just another name for "/usr". Want to see it?
alex@desktop:/usr/bin$ ls -ali /usr | head
total 108
18 drwxr-xr-x 12 root root 4096 Feb 18 2024 .
2 drwxr-xr-x 18 root root 4096 Apr 1 22:29 ..
11099 drwxr-xr-x 2 root root 40960 Mar 14 13:29 bin
38 drwxr-xr-x 2 root root 4096 Mar 19 2022 games
19 drwxr-xr-x 38 root root 16384 Jan 13 19:52 include
11639 drwxr-xr-x 68 root root 4096 Jan 13 19:52 lib
13375 drwxr-xr-x 2 root root 4096 Sep 13 2025 lib64
13372 drwxr-xr-x 8 root root 4096 Jan 13 2024 libexec
24 drwxr-xr-x 10 root root 4096 Mar 28 2022 local
alex@desktop:/usr/bin$
Compare the inode numbers: Inode 18 is "/usr/." a.k.a. "/usr/bin/..". And inode 11099 is "/usr/bin" a.k.a. "/usr/bin/.". The hard link count also matches.
That should not be very surprising. (Having "." and ".." in a directory is not universal, Windows does not always have them.)
There is also a stat command that can be invoked from the shell, that just calls the same API functions as stat() or lstat(). So let's look at the output of that tool:
alex@desktop:/usr/bin$ stat /usr/bin
File: /usr/bin
Size: 40960 Blocks: 88 IO Block: 4096 directory
Device: 7,3 Inode: 11099 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ roo
+t)
Access: 2026-04-01 03:34:50.514938532 +0200
Modify: 2026-03-14 13:29:15.836767197 +0100
Change: 2026-03-14 13:29:15.836767197 +0100
Birth: 2022-11-01 19:51:10.238946811 +0100
alex@desktop:/usr/bin$
The same boring details, in a different representation. How does it look for /usr?
alex@desktop:/usr/bin$ stat /usr
File: /usr
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 7,3 Inode: 18 Links: 12
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ roo
+t)
Access: 2026-04-01 03:39:35.774067015 +0200
Modify: 2024-02-18 15:08:41.370981344 +0100
Change: 2024-02-18 15:08:41.370981344 +0100
Birth: 2022-11-01 19:51:08.739001035 +0100
alex@desktop:/usr/bin$
Old news. How does "/" look like?
alex@desktop:/usr/bin$ stat /
File: /
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 7,3 Inode: 2 Links: 18
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ roo
+t)
Access: 2026-04-01 23:57:05.412225271 +0200
Modify: 2026-04-01 22:29:21.690873175 +0200
Change: 2026-04-01 22:29:21.690873175 +0200
Birth: 2022-11-01 19:51:06.000000000 +0100
What about "/run"?
alex@desktop:/usr/bin$ stat /run
File: /run
Size: 580 Blocks: 0 IO Block: 4096 directory
Device: 0,106 Inode: 1 Links: 20
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ roo
+t)
Access: 2026-04-01 22:29:13.508184057 +0200
Modify: 2026-04-01 23:57:03.205307793 +0200
Change: 2026-04-01 23:57:03.205307793 +0200
Birth: 2026-04-01 22:29:13.508184057 +0200
alex@desktop:/usr/bin$
Already bored?
Did you notice that the two Device numbers changed? That's no accident. That's the trick!
The device field (first list element returned in perl, st_dev in struct stat in libc) contains the device number (major and minor device number) of the device containing the filesystem.
"/", "/usr", and "/usr/bin" are all on device "7,3", which happens to be the root device of this VM. "/run" is on a tmpfs in RAM, using a different device number ("0,106").
So, to test if a directory is a mount point, you compare the device field of the directory with that one of its parent directory. If they are different, the directory is a mount point.
But ... - that's not the entire truth:
alex@desktop:/usr/bin$ ls -ali / | head
total 76
2 drwxr-xr-x 18 root root 4096 Apr 1 22:29 .
2 drwxr-xr-x 18 root root 4096 Apr 1 22:29 ..
13 lrwxrwxrwx 1 root root 7 Mar 28 2022 bin -> usr/b
+in
131073 drwxr-xr-x 2 root root 4096 Mar 19 2022 boot
58982403 drwxr-xr-x 3 root root 4096 Jan 26 2024 data
1 drwxr-xr-x 7 root root 500 Apr 1 22:29 dev
393217 drwxr-xr-x 104 root root 12288 Apr 1 22:29 etc
130 lrwxrwxrwx 1 root root 9 Jan 25 2024 home -> data
+/home
14 lrwxrwxrwx 1 root root 7 Mar 28 2022 lib -> usr/l
+ib
alex@desktop:/usr/bin$
The root directory is its own parent directory (same inode number 2 for both "." and ".."), so its device field and the one of its parent are always the same. But the root directory is by definition a mount point, where the root device is mounted.
So the real condition for a moint point needs to be extended. A directory is a mount point if its device is different from the device of its parent directory, or if directoy and its parent have the same pair of device and inode numbers.
There is a mountpoint tool for exactly this test. You can find a very minimal implementation (about 100 lines of commented C code) in busybox.
And of course, you could simply use lstat() in perl.
Alexander
--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
|