As far as I understand, the "read" permission on directories allows one to list all names under that directory, and no more than that.

Based on common understanding of FS design, if a directory is stored as a file, its content should at least contain, in addition to the file names, a reference to their inodes. Using the "everything is a file" philosophy of Unix, the "read" permission grants access to the "file content" of the directory itself, and thus should be sufficient to retrieve the inode numbers of each file. However, this is not the case.

What am I missing here?

  • You mean something like ls -i -1 *, or stat -c '%i %n' * ? (Much of what stat can report is in the inode itself, but obviously the inode number cannot be). Commented Apr 14, 2023 at 8:27
  • @Paul_Pedant it seems that more can be done than stat and ls can do. Probably not much more. See muru`s answer. Commented Apr 14, 2023 at 14:33

4 Answers 4


As @muru has shown, you can definitely get the inode numbers (on any POSIX system, not just Linux) by reading the directory contents (whether directories are implemented as normal files like in the early Unices or not).

What probably caused you to think you couldn't was that you tried

ls -i dir

On a directory for which you had read but not search permission and saw:

file1: Permission denied
file2: Permission denied

That's because your ls implementation did try to do a lstat() on each of the entries returned by readdir() instead of just printing the d_ino in the entry.

Not all implementations do that. For instance, the ls builtin of ksh93 (if included at build time and enabled with builtin ls) does print the d_ino as returned by readdir() rather than lstat()ing each file.

One reason why most ls implementations use lstat() to retrieve the inode number may be that otherwise, you generally get the wrong inode numbers for files that are mount points:

% ksh93 -o emacs
$ builtin ls
$ /bin/ls -i /
    21 bin        28 home-blank       6 lost+found      22 root       5 tmp
     2 boot        9 home-test       26 media            1 run        2 u
     8 cdrom      14 lib             17 mnt             15 sbin      13 usr
     1 dev        23 lib32       855691 opt             25 snap  229376 var
    19 etc        20 lib64           34 pool0           18 srv
    34 home        4 libx32           1 proc             1 sys
$ ls -i /
    21 bin        28 home-blank       6 lost+found      22 root       5 tmp
    24 boot        9 home-test       26 media           12 run        2 u
     8 cdrom      14 lib             17 mnt             15 sbin      13 usr
     3 dev        23 lib32       855691 opt             25 snap  229376 var
    19 etc        20 lib64           11 pool0           18 srv
265966 home        4 libx32           7 proc            16 sys

See how for files that are mountpoints (like /boot, /proc, /sys above), ksh93's builtin ls gives the inode numbers of the files in the root filesystem, while /bin/ls (in my case GNU ls) gives the effective inode numbers of those files, that is that of the root directory of the mounted file systems.

In any case, ls -id some/file will do a lstat() on the file (ksh93's ls actually does a stat() making it non-POSIX compliant).

$ ls -id /boot
     2 /boot

(with both ksh93 and GNU ls).


It does, at least on a Linux system I tried it on:


mkdir foo
touch foo/bar
# remove as much permission as possible, so that the program still works (only leave `r`).
chmod ugo-wx foo
chmod ugo+r #incase your umask, caused it not to be set
sudo chown root:root foo -R

And with:

#include <iostream>
#include <dirent.h>

int main(int argc, char* argv[])
    for (int i = 1; i < argc; i++)
        auto dir = opendir(argv[i]);
        for(auto direntry = readdir(dir); direntry; direntry = readdir(dir))
            std::cout << direntry->d_ino << ' ' << direntry->d_name << '\n';

I get:

% ./readinode foo
3933790 .
3932162 ..
3933910 bar
  • 4
    It looks like ls and stat are doing more than this program. They then trigger an error and bail-out. Commented Apr 14, 2023 at 14:31
  • Why the chown? And why the -w? Neither of these are needed to demonstrate what you are saying. I repeated your experiment, and got same results (different inode numbers), without root or removing write permission. Commented Apr 14, 2023 at 14:35
  • 3
    Yes, neither are strictly necessary, but I'm just demonstrating that this works with minimal access.
    – muru
    Commented Apr 14, 2023 at 14:42

"What am I missing"

Don't think the proposition that you don't get inode information is wrong - though the idea of inodes map differently to non-UNIX file systems.

But what I think you're missing is that you confuse userland interface ideals ("everything's a file") with implementation detail:
I'd say you're simply trying to take the assumptions on the internal structure of the file system's notion of a directory too far.

No matter how directories are implemented, the access semantics are as they are defined to be – there's really nothing to understand (or, conversely, misunderstand) there; that's just how they are defined to be. The internals of the file system have no bearing on that.

Personal Remark

I'd note that "the directory is stored as a file" is one way a file system might implement directories. It's not the only one. This article explains how one of the oldest POSIX-style file systems in the Linux kernel doesn't handle small directory entry lists like files at all – the directory entries aren't stored in data extents, but where usually the list of data extent descriptors would reside for small directories, and for large directories, it's a B+-tree, which itself is stored in extents (XFS Algorithms & Data Structures 3rd ed, Sec. 17.5 and 16.2) – but nothing like a file as a map of contiguous extents.

So, the assumption "a directory is just a file internally" really doesn't hold.
I honestly don't know when the last time it held – XFS is from 1993/94, and has been in Linux for 22 years now; ext3 and subsequently ext4 have a similar tree-of-blocks-approach to directory, as well, and ext3 has been around also since ca 2001. I honestly don't know whether thinking about ext2 makes much sense here – I've not set up an ext2 file system since ca 2008, since ext3 without journalling is always a strict advantage over that, and I see Linux distros and embedded tools tend to see it the same way.

  • 3
    (Now, I still don't see what that "everything is a file" has to do with the original question they posted. I suppose on the filesystem level, directories would need to contain the filenames and inode numbers of their contained files, even if the directory itself was stored in some special way completely different from regular files. But for generality, esp. with allowing arbitrary nesting levels, it does seem to make lots of sense to store directories exactly like files as far as possible. Though, returning the inode numbers to userspace from readdir() might not be that necessary.)
    – ilkkachu
    Commented Apr 14, 2023 at 19:42
  • 1
    well, malloc() is wholly a user-space construct, though, so I'd exempt that. :) sleep() could be implemented as a write() to a special file that just takes a varying time to complete depending on what you wrote there, and similarly gettimeofday() should be replaced with a read() of a similar special file... Yes, some sockets have their own namespaces, and worse, the ones that do share the filesystem namespace can't be open()ed... Oh but a mantra it still is, sadly, just not a very accurate one, but one that people should be, hmm, taught to leave behind (to put it nicely).
    – ilkkachu
    Commented Apr 14, 2023 at 20:00
  • 4
    The socket issue: "Everything is a file; some files are just more file than others." Commented Apr 14, 2023 at 20:06
  • 1
    please stay on your path :) there's so much beauty here in the sun of not gaf about eisaf! Commented Apr 14, 2023 at 20:07
  • 1
    anyway... that ext4 doc describes a few of the directory structures with references to filesystem data blocks, like how "Interior nodes [are] also the full length of a data block", so it's not like they're fully disconnected. But then it makes sense, since directory handling is a bit more tightly tied with the filesystem operations that some random old text file is, and the FS developers are smart enough to realize that there might be performance benefits in doing full-block accesses. Which is probably something userland databases may also think of.
    – ilkkachu
    Commented Apr 14, 2023 at 20:11

It is like at the dog park: Everything is a dog. This does not mean that there is one regular dog, and the others are just this dog but with different masks on. It means they all have some sort of dog property.

  • They all have names.
  • They can all have their name changed.
  • They all hangout in a dog-park / directory.
  • They can be teleported to another directory (on the same file-system).
  • They all contain data (but not necessarily in the same way). Some files are more like others than others. Such as a block device file is more like a regular file than to a directory. Both regular files and block devices are like pipes, streams, sockets, and character devices (but not the other way around).

There are a set of interfaces that a file may have. These include (I probably missed something):

  • A file name, (lives in the directory tree): block device, character device, regular file, directory, symlink, named pipe, named socket.
  • Sequential readable/writable: pipe, socket, character device, block device, character device, regular file, named pipe, named socket,
  • Random accessible: regular file, block device.

I missed out stuff in /proc, stuff in /sys, stuff in /net. These are pretending to be the others.

  • You forgot links! Hardlinks: some of suspiciously similar dogs are in fact one dog. You can duplicate any dog, and kill all but one of the clones without any harm to the dog that they all is. Softlinks: other dogs, yes, those that don't look exactly opaque, are only looking like dogs, but in fact point to other dogs. And these, transparent, almost invisible dogs, are similar, but are pointing to dogs that in fact don't exist, or unmounted, or even may have never existed! Someone told me, there once was a dog that pointed to itself, but when they looked at it, the Matrix rebooted... Commented Apr 15, 2023 at 6:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .