cunixstatinodereaddir

Unix readdir (3) and stat (2) giving different inodes


I'm the teacher's assistant for my university's system's programming class. Lately the students have been working on an assignment that involves replicating the program pwd.

Some of the students are noticing what appears to be an inconsistency. When they read the ino from a readdir entry, it gives a different inode from when they stat the same directory. Many of them are asking why. Shouldn't the directory entry's inode point to the inode to the target directory exists at? If so, why does stat give a different inode?

Here is some sample code to demonstrate:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>

#define DIR_NAME "~redacted~"

int getReaddirInode(char* entName)
{
   DIR* directory;
   struct dirent* entry;
   ino_t result;
   if ((directory = opendir(".")) == NULL)
   {
      perror("opendir");
      exit(1);
   }
   while ((entry = readdir(directory)) != NULL)
   {
      if (strcmp(entName, entry->d_name) == 0)
      {
         result = entry->d_ino;
         break;
      }
   }
   if (entry == NULL)
   {
      fprintf(stderr, "No such directory: %s.\n", entName);
      exit(1);
   }
   if (closedir(directory) == -1)
   {
      perror("closedir");
      exit(1);
   }
   return result;
}

int getStatInode(char* entName)
{
   struct stat buf;
   if (stat(entName, &buf) == -1)
   {
      perror("stat");
      exit(1);
   }
   return buf.st_ino;
}

int main()
{
   if (chdir("/home") == -1)
   {
      perror("chdir");
      return 1;
   }
   printf("readdir (3) gives an inode of:%9d.\n", getReaddirInode(DIR_NAME));
   printf("stat (2) gives an inode of:   %9d.\n", getStatInode(DIR_NAME));
   return 0;
}

Output:

unix3:~ $ ./a.out 
readdir (3) gives an inode of:  4053392.
stat (2) gives an inode of:    69205302.

Edit: I can confirm that DIR_NAME is a mount point:

unix3:~ $ mount | grep ~redacted~
csc-na01.csc.~redacted~.edu:/student01/student01/0_t/~redacted~ on /home/~redacted~ type nfs (rw,relatime,vers=3,rsize=65536,wsize=65536,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=129.65.158.8,mountvers=3,mountport=635,mountproto=udp,local_lock=none,addr=129.65.158.8)

Edit2: The inodes only seem to change at the nfs filesystem changeover. My question is why. What inode is readdir pointing to and what inode is stat pointing to?

Both these inodes have changed in the last 4 hours since I posted this.

I do not have permission to unmount.

I checked another directory mounted from the same address and both the inodes were different from the first directory, suggesting that each directory does have two inodes which are unique to that directory, but I don't understand why.


Solution

  • A directory will have an inode:

    $ ls -li
    total 4
    264332 drwx------ 2 attie attie 4096 Nov  3 22:46 mnt
    

    In this case, the inode of the mnt directory is 264322.

    But if we now mount a filesystem on that directory, the inode will appear to change:

    $ truncate -s $((5 * 1024 * 1024)) myfs.ext2
    $ mkfs.ext2 ./myfs.ext2
    mke2fs 1.42.13 (17-May-2015)
    Discarding device blocks: done
    Creating filesystem with 5120 1k blocks and 1280 inodes
    
    Allocating group tables: done
    Writing inode tables: done
    Writing superblocks and filesystem accounting information: done
    
    $ sudo mount -o loop myfs.ext2 ./mnt
    $ ls -li
    total 265
         2 drwxr-xr-x 3 root  root     1024 Nov  3 22:49 mnt
    264339 -rw------- 1 attie attie 5242880 Nov  3 22:49 myfs.ext2
    

    Now, it would appear that the inode for mnt is 2...?! (note that the owner/group has changed too).

    If we were to run your application on this directory (removing the chdir() and changing DIR_NAME to mnt), then we get an interesting result:

    $ ./test
    readdir (3) gives an inode of:   264332.
    stat (2) gives an inode of:           2.
    

    This makes sense, because readdir() is walking through the nodes of the given directory, returning information for each (but it isn't inspecting them in full)... while stat() is returning information for the full path given, including resolving any mountpoints.

    In your case, you have an NFS mount, that remote filesystem is mounted on a directory (which has an inode) on this system... but it is also a directory (which has an inode) on the remote system.


    We can prove this further, by doing the following:

    $ ls -lia /
    total 137
          2 drwxr-xr-x  25 root root    4096 Oct 11 17:17 .
          2 drwxr-xr-x  25 root root    4096 Oct 11 17:17 ..
    2097153 drwxr-xr-x   2 root root   12288 Oct 10 13:36 bin
    [...]
    

    As you can see, the root of the filesystem mounted at / also has an inode of 2... inodes are not globally unique, but only unique within the filesystem.


    If you're involved in the marking of assignments, then I'd say that both answers are acceptable... This is a pretty subtle and complex thing for students who are probably new to this world to fully comprehend.

    The stat() answer can easily be checked with ls -i.

    I think that checking the readdir() answer is going to be fiddly without writing a little application (which isn't a problem)... You could employ a bind mount if you have permission:

    $ mkdir mnt2
    $ sudo mount -o bind . ./mnt2
    $ ls -li
    total 285
         2 drwxr-xr-x 3 root  root     1024 Nov  3 22:49 mnt
    264319 drwx------ 4 attie attie    4096 Nov  3 23:09 mnt2
    264339 -rw------- 1 attie attie 5242880 Nov  3 22:49 myfs.ext2
    264346 -rwx------ 1 attie attie    9160 Nov  3 23:00 test
    264349 -rw------- 1 attie attie     956 Nov  3 23:00 test.c
    $ ls -li mnt2
    total 288
    264332 drwx------ 2 attie attie    4096 Nov  3 22:46 mnt
    264347 drwx------ 2 attie attie    4096 Nov  3 23:09 mnt2
    264339 -rw------- 1 attie attie 5242880 Nov  3 22:49 myfs.ext2
    264346 -rwx------ 1 attie attie    9160 Nov  3 23:00 test
    264349 -rw------- 1 attie attie     956 Nov  3 23:00 test.c