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

On Unix (but not on Windows), lstat and stat on the directories can help.

(Actually, this works for every file, but df only looks at mountpoints, i.e. directories.)

#!/usr/bin/perl use strict; use warnings; for my $d (qw( / /bin /sbin /lib /usr /usr/bin /usr/sbin /usr/lib /pro +c /sys /run /tmp /var/run /var/tmp )) { print "$d: on device ",(lstat($d))[0]; if (-l $d) { print " (symlink to ",readlink($d),")"; } print "\n"; }

Running that on a recent debian in a VM shows:

$ perl lstat.pl /: on device 1795 /bin: on device 1795 (symlink to usr/bin) /sbin: on device 1795 (symlink to usr/sbin) /lib: on device 1795 (symlink to usr/lib) /usr: on device 1795 /usr/bin: on device 1795 /usr/sbin: on device 1795 /usr/lib: on device 1795 /proc: on device 99 /sys: on device 100 /run: on device 105 /tmp: on device 1795 /var/run: on device 1795 (symlink to /run) /var/tmp: on device 1795 $

Most stuff is on device 1795, the root filesystem of this VM. /proc is a completely different device (that of the virtual proc filesystem), and so are /run (105) and /sys (100). /var/run is a symlink stored on device 1795 (root filesystem), it points to /run, which is on device 105.

Due to the way symlinks work, everything that appears to be in /var/run/ is really in /run.

Changing lstat() to stat() allows the system to follow symlinks instead of working on them:

#!/usr/bin/perl use strict; use warnings; for my $d (qw( / /bin /sbin /lib /usr /usr/bin /usr/sbin /usr/lib /pro +c /sys /run /tmp /var/run /var/tmp )) { print "$d: on device ",(stat($d))[0]; if (-l $d) { print " (followed symlink to ",readlink($d),")"; } print "\n"; }
$ perl stat.pl /: on device 1795 /bin: on device 1795 (followed symlink to usr/bin) /sbin: on device 1795 (followed symlink to usr/sbin) /lib: on device 1795 (followed symlink to usr/lib) /usr: on device 1795 /usr/bin: on device 1795 /usr/sbin: on device 1795 /usr/lib: on device 1795 /proc: on device 99 /sys: on device 100 /run: on device 105 /tmp: on device 1795 /var/run: on device 105 (followed symlink to /run) /var/tmp: on device 1795 $

Alexander

--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

Replies are listed 'Best First'.
Re^3: An anomaly with Filesys::DfPortable, I need your eyes
by Intrepid (Curate) on Mar 12, 2026 at 19:49 UTC

    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

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