in reply to Re^2: An anomaly with Filesys::DfPortable, I need your eyes
in thread An anomaly with Filesys::DfPortable, I need your eyes

Alexander, I want to acknowledge your very substantial reply, I've been busy with other things the last few days and didn't have time to do that. And still don't right now. So, I am going to edit this node when I have time to do your suggestions justice, rather than rush through it. Thanks for your thoughts.

EDIT


Ok, I've had a chance to look at Alexander's post, and download and run the code.

Here's how the data looks from running your first script, on one of my Gnu/Linux systems.

$ perl stat-and-friends.pl
/: on device 2082
/bin: on device 2082 (symlink to usr/bin)
/sbin: on device 2082 (symlink to usr/sbin)
/lib: on device 2082 (symlink to usr/lib)
/usr: on device 2082
/usr/bin: on device 2082
/usr/sbin: on device 2082
/usr/lib: on device 2082
/proc: on device 23
/sys: on device 22
/run: on device 25
/tmp: on device 2082
/var/run: on device 2082 (symlink to /run)
/var/tmp: on device 2082

So. Interesting. This seems useful for getting a picture of how resources are being allocated to the various filesystems. I don't think I've ever actually used perl's stat or lstat in a program I've written. I'm going to have to remember that perl has these built-in functions and they could be useful.

Thanks for waiting a long time for my reply, Alexander :-)


    – Soren
Apr 01, 2026 at 16:31 UTC

  • Comment on Re^3: An anomaly with Filesys::DfPortable, I need your eyes

Replies are listed 'Best First'.
Re^4: An anomaly with Filesys::DfPortable, I need your eyes
by afoken (Chancellor) on Apr 01, 2026 at 23:01 UTC

    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". ;-)