5

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.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Thomas
  • 871
  • 2
  • 8
  • 21
  • Is `DIR_NAME` a mount point? What file system is it on, if not? Either way, I think you’d have better luck asking on the [Unix and Linux Stack Exchange](https://unix.stackexchange.com/) than here. – Daniel H Nov 03 '17 at 17:43
  • Yes, it is a mount point. I added that to my original post. I will probably leave it here for a bit, and move it if I don't get a good response. Thanks for the idea. – Thomas Nov 03 '17 at 17:49
  • 1
    The answers _[here](https://stackoverflow.com/a/29094555/645128)_, and _[here](https://stackoverflow.com/questions/39429803/how-to-list-first-level-directories-only-in-c/39430337#39430337https://stackoverflow.com/questions/39429803/how-to-list-first-level-directories-only-in-c/39430337#39430337)_, along with associated discussion might have some relevance. – ryyker Nov 03 '17 at 17:51
  • Careful... some IPs are not anonymous... you've carefully redacted the DNS, but not the IP... many Universities have large blocks of public IPs - California Polytechnic State University by any chance? [beep](https://www.speedguide.net/ip/129.65.158.8) – Attie Nov 03 '17 at 17:52
  • @user2752635 If you edit the question quickly, I think it might not save the old version, or maybe that window has passed. Either way I think you’re creating more anonymity than you need, but that’s your business. – Daniel H Nov 03 '17 at 17:58
  • 1
    Have you tried on something that isn't a mountpoint? Are the values static? Do you have the ability to unmount it? I'd guess that `stat()` is giving the inode from the local filesystem, while `readdir()` is giving the inode from the server. – Attie Nov 03 '17 at 17:58
  • I suspect it's a matter of perspective - looking at the directory from outside / the root, or looking at the directory from within the directory (I don't have an NFS setup at hand to test) – Attie Nov 03 '17 at 17:59
  • @Attie What do you mean the inode from the local filesystem? Does the one directory have two inodes? My assumption would be that it has one inode and one device id, and those two combined would point to the data. – Thomas Nov 03 '17 at 22:24
  • @user2752635 I had a play, see my answer. – Attie Nov 03 '17 at 23:03
  • `ino_t result;` is not initialized. – wildplasser Nov 03 '17 at 23:29
  • @wildplasser True, that could cause a problem if the program cannot find a directory with that name. But seeing as I'm running this under extremely controlled circumstances, and this is purely P.O.C. code, I'm not too worried about that. – Thomas Nov 03 '17 at 23:31
  • Well:I am worried about that. Did you compile with `-Wall`, or the equivalent? – wildplasser Nov 03 '17 at 23:36
  • Interestingly enough, `-Wall` complained about string.h missing, but not the uninitialized variable. – Thomas Nov 03 '17 at 23:50

1 Answers1

6

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.
  • readdir() is saying 264332
    • Which is the inode of the mnt directory, on the underlying filesystem.
  • stat() is saying 2
    • Which is the inode of the root of the mounted filesystem.

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
Attie
  • 6,690
  • 2
  • 24
  • 34
  • 1
    I'm not actually writing any assignments. Your comments earlier actually prompted me to do a little inspecting of this on my own and I found something similar. But I could never have gone into such detail about the matter. – Thomas Nov 03 '17 at 23:35
  • 1
    I don't know who downvoted this or why. It seems perfectly robust to me. – Thomas Nov 03 '17 at 23:35
  • no worries, I always appreciate feedback if I've got something wrong... thanks for accepting :) – Attie Nov 03 '17 at 23:37
  • I downvoted. The answer may be correct (textually, theoretically) but itis missing the point that the original program fails to initialize the inode number. – wildplasser Nov 03 '17 at 23:47
  • 2
    Well, you are both right. `ino_t result;` is indeed uninitialized, but will have no effect unless the global variable `DIR_NAME` is set to something other than a valid directory name and is not `NULL`. Other than that ancillary point, @Attie's answer is fine. – David C. Rankin Nov 04 '17 at 00:04
  • 3
    @wildplasser thanks for the feedback, but that is incredibly pedantic and has no bearing on the question or discussion. The OP wasn't after a review of their sample application (which in the controlled circumstances wasn't presenting a bug), but rather an insight into what is going on with inodes. – Attie Nov 04 '17 at 00:12
  • 2
    Unless wildplasser was being wildly sarcastic about, "missing the point," I don't know what they though the point was. If they were, it seems awfully rude to down-vote such a through answer for a joke. – Thomas Nov 04 '17 at 00:40
  • Ok, I re-upvoted. I still think that obvious errors in a program should be corrected before diving into a sea of theory. Even if the program is only intended as a POC, it *could* fail to detect the concept it is aiming at. – wildplasser Nov 04 '17 at 09:41