Morgan Wilde Morgan Wilde - 3 months ago 11
C Question

Why does `.` and `..` have different inode numbers inside `/` on a Mac?

I was playing around with implementing my own

pwd
command. To find the entire path, I need to traverse the
inode
tree until I reach the root, and the way to tell that I've hit the root is by checking for equal
inode
numbers stored for
.
and
..
.

But on my Mac that seems to not be case, at least if you look at this table below.

dirent | stat | link to
-----------+------------+--------
34078072 | 34078072 | self
31103058 | 31103058 | parent
31103058 | 31103058 | self
31103020 | 31103020 | parent
31103020 | 31103020 | self
613497 | 613497 | parent
613497 | 613497 | self
603204 | 603204 | parent
603204 | 603204 | self
157433 | 157433 | parent
157433 | 157433 | self
2 | 2 | parent
2 | 2 | self // This is root aka /
1 | 2 | parent // There is something above it?


This was generated using the code below. The
stat
struct seems to be doing fine, but
dirent
has a different value when it comes to
/
. Why is that the case? Shouldn't
dirent
and
stat
have the same values for inode number? Why is it different on a Mac?

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

void inodes();

int main()
{
printf(" dirent | stat | link to\n");
printf("-----------+------------+--------\n");
inodes();
return 0;
}

void inodes()
{
DIR* directory = opendir(".");
struct dirent* entry = NULL;
struct stat status;
ino_t self = -1;
ino_t parent = -1;

while ((entry = readdir(directory))) {
stat(entry->d_name, &status);
if (strcmp(entry->d_name, ".") == 0) {
self = status.st_ino;
printf("%10.llu | %10.llu | self\n", entry->d_ino, self);
}
if (strcmp(entry->d_name, "..") == 0) {
parent = status.st_ino;
printf("%10.llu | %10.llu | parent\n", entry->d_ino, parent);
}
}
if (self != parent) {
if (chdir("..") != -1) {
inodes();
}
}
}

Answer

On the Macintosh HFS+ file system, every file and folder has a unique "file ID". This file system is described in Apple's "Technical Note TN1150 – HFS Plus Volume Format".

In particular, the root folder always has the file ID 2 and the parent ID 1. In the TN1150, these are documented as

enum {
    kHFSRootParentID            = 1,
    kHFSRootFolderID            = 2,
    ...
}

kHFSRootParentID
    Parent ID of the root folder.
kHFSRootFolderID
    Folder ID of the root folder.

The inode on a HFS+ file system reflects exactly the file ID. This might explain why readdir() reports the inode 2 for the "." entry of the root folder, and the inode 1 for the ".." entry of the root folder. (But I do not have a definite reference for this fact. One could try to find the source code at http://www.opensource.apple.com :)

On the other hand, ".." in the root folder is always a link to the root folder itself. Therefore, when

stat(entry->d_name, &status);

is executed for the ".." entry, a stat() on the root folder is done, so this gives the inode 2 again.