3

I'm trying to understand how removing the current working directory works from a C program. I'm using the Advanced Programming in Unix Enviroment book as a reference. First, I make the following sequence of calls (/tmp is an existing directory):

//  1. Create a test directory
if (mkdir("/tmp/foo", DIR_PERM) < 0){
    printf("mkdir error\n");
}
else 
    printf("mkdir succeeded\n");

// 2. Change cwd to the test directory
if (chdir("/tmp/foo") < 0)
    printf("chdir error\n");
else
    printf("chdir succeeded\n");

// (2.5) Check the cwd
if (getcwd(cwd, sizeof(cwd)) != NULL)
    printf("Current working directory: %s\n", cwd);
else
    printf("getcwd error\n");

// 3. Remove the test directory
if (rmdir("/tmp/foo") < 0)
    printf("rmdir error\n");
else
    printf("rmdir succeeded\n");

// (3.5) Check current working directory
if (getcwd(cwd, sizeof(cwd)) != NULL)
    printf("Current working directory: %s\n", cwd);
else
    printf("getcwd error\n");

This works as I would expect. mkdir, chdir, and rmdir succeed, getcwd in (2.5) returns the /tmp/foo pathname and getcwd in (3.5) fails.

What I don't understand is the behavior of using opendir afterward on the /tmp/foo directory using ".", "../foo", and "/tmp/foo" as pathnames:

// 4. Open the removed directory using "." 
if ((dp = opendir(".")) == NULL)
    printf("1. opendir error\n");
else{
    printf("1. opendir succeeded\n");
    while((dirp = readdir(dp)) != NULL)
        printf("%s\n", dirp->d_name);
}

// 5. Open the removed directory using "../foo"
if ((dp = opendir("../foo")) == NULL)
    printf("2. opendir error\n");
else
    printf("2. opendir succeeded\n");

// 6. Open the removed directory using "/tmp/foo"
if ((dp=opendir("/tmp/foo")) == NULL)
    printf("3. error\n");
else
    printf("3. opendir succeeded\n");

The output of the above code snippet is

  1. opendir succeeded
  2. opendir error
  3. opendir error

Why does opendir(".") work but opendir("../foo") doesn't? As I understand it, the link count of the /tmp/foo directory becomes 0 after rmdir. The dot and dot-dot entries are removed before that function returns and reading the directory in 4. after the successful opendir call doesn't print anything. How does opendir know what dot stands for if that entry was removed during rmdir? And why wouldn't it resolve dot-dot if it can resolve dot?

  • 1
    I think that's because the inode for the just-deleted `~/tmp/foo` still exists as it's reference count is greater than zero (your program keeps the inode alive). – Cristik Mar 22 '22 at 05:58
  • 1
    Some good explanations here: https://unix.stackexchange.com/questions/434417/what-happens-when-the-current-directory-is-deleted – Cristik Mar 22 '22 at 06:01
  • The link discussing procedural semantics above provides a hint. Once you change to `"."` and then remove `"~/tmp/foo"` there must be a way for you to traverse out of your cwd. While `opendir()` knows `"~/tmp/foo"` is unlinked, it doesn't have the same information on `"."`. I don't know the exact sequence of events, but as long as you are in what was `~/tmp/foo` `"."` will remain valid (to an extent) until all references to it can be destroyed. That can't happen while it is your cwd. – David C. Rankin Mar 22 '22 at 06:09
  • 1
    Does `opendir("..")` work after you remove the `foo` directory`? – chqrlie Mar 22 '22 at 06:54
  • 3
    Did `rmdir("~/tmp/foo")` really work? It shouldn't. System calls don't understand `~`. Only the shell does. – user207421 Mar 22 '22 at 07:17
  • 1
    @chqrlie It does. In part (4) I changed ```(".")``` to ```("..")``` and that ```readdir``` ```while``` loop prints both dot and dot-dot entries. That is really interesting since at that point the ```.``` and ```..``` entries don't exist as directory entries in ```~/tmp/foo``` and as I understand it the only reference to that pathname is in the process enviroment variable for the current working directory. – death-crips Mar 22 '22 at 07:26
  • 1
    @user207421 You are correct. I just used the tilde as a shorthand for my home directory. The actual program has the full pathnames specified. – death-crips Mar 22 '22 at 07:30
  • 1
    Your code would be less misleading and more like an [MCVE] if you removed the tiles and left absolute pathname visible using `/tmp` as the directory name. – Jonathan Leffler Mar 22 '22 at 11:59
  • @JonathanLeffler I changed the pathnames as per your suggestion. What did you mean by tiles? – death-crips Mar 22 '22 at 15:20
  • The spell mangler on my iPhone decided I could not possibly mean what I typed and converted “tildes” into “tiles” and I didn't spot it. Sorry. – Jonathan Leffler Mar 22 '22 at 15:22

1 Answers1

0

Why does opendir(".") work but opendir("../foo") doesn't?

the link count of the /tmp/foo directory becomes 0 after rmdir

No, your program's current working directory still points there. It's like an open inode.

$ mkdir -p /tmp/foo/abc
$ cd /tmp/foo
$ ls -la /proc/self/cwd
lrwxrwxrwx 1 kamil kamil 0 mar 22 18:35 /proc/self/cwd -> /tmp/foo/
$ rmdir /tmp/foo
$ ls -la /proc/self/cwd
lrwxrwxrwx 1 kamil kamil 0 mar 22 18:36 /proc/self/cwd -> '/tmp/foo (deleted)'

How does opendir know what dot stands for if that entry was removed during rmdir?

Kernel keeps track of processes and keeps track of processes current working directories and it knows what is ..

why wouldn't it resolve dot-dot if it can resolve dot?

It resolves ... It does not resolve ../foo, there is no foo at .. after it was deleted. opendir('..') should still work.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks. A couple things I'm still unclear about: 1. I checked the link count for ```.``` after ```rmdir``` returns and it was 0 although I understand that that the directory is not freed until the last process closes it. 2. Following from the first point, I'm still not sure how ```..``` is resolved since the process only tracks the current working directory ```.``` and the parent directory is not present as a directory entry anymore after ```rmdir``` returns. – death-crips Mar 22 '22 at 18:10