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.
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
mnt
directory, on the underlying filesystem.stat()
is saying 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