Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
        }
    }
}
like image 859
Morgan Wilde Avatar asked Sep 26 '22 07:09

Morgan Wilde


1 Answers

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.

like image 77
Martin R Avatar answered Sep 30 '22 06:09

Martin R