user/dorphan.c
About this file
dorphan is the directory version of user/forphan.c. It creates a directory, makes
it its current directory, deletes the directory’s name, and then sleeps forever. A
process’s current directory holds a reference to the directory’s inode, just as an
open file does, so the directory now exists with link count (nlink) 0 and no name: an
orphaned inode.
test-xv6.py (test-xv6.py:156) waits for the line wait for kill and reclaim,
kills QEMU, boots again and waits for ireclaim: orphaned inode N, printed by
ireclaim when it frees the directory’s inode and its one data block. Run by
hand at this commit, the next boot printed ireclaim: orphaned inode 24.
A directory is a harder case than a file because the kernel must also refuse to put
anything into a directory that has been unlinked: create and
sys_link both check dp->nlink == 0 (kernel/sysfile.c:269,
kernel/sysfile.c:161), and any path lookup through an unlinked directory already
fails in namex (kernel/fs.c:707). Otherwise a new file could be linked into the orphan and
leak with it. user/grind.c exercises that case at random (its operation 20).
Added with user/forphan.c in commit 52efd73. Read before:
user/forphan.c. Read next: sys_unlink and ireclaim.
Headers, purpose and an unused buffer
The comment states the purpose. BUFSZ, buf and the include of
kernel/fcntl.h are not used; like user/forphan.c, this file appears to have
started as a copy of user/logstress.c.
Create a directory
mkdir (sys_mkdir → create with type T_DIR) allocates an
inode, writes the entries . and .. into it, and enters dd in the current
directory (the root, when run from the shell). The new directory’s link count is 1
(only its name in the parent; . deliberately does not count), and the parent’s
count goes up by 1 for the new ...
If dd is left over from an earlier run, mkdir fails and the program stops; the
test script always starts from a fresh disk image.
The program’s name, for error messages.
Create the directory dd. Link count 1.
Make it the current directory
chdir (sys_chdir) stores the directory’s inode in p->cwd, which holds
a reference to it (namei took one). That reference plays the role of the
open file in user/forphan.c: as long as it exists, the inode cannot be freed.
Make dd the current directory; p->cwd now holds a reference to its inode.
Delete the directory's name while standing in it
../dd is resolved from inside dd: .. leads back to the parent, and the last
element is dd, so sys_unlink removes the entry dd from the parent.
This is allowed because the directory is empty (only . and .., checked by
isdirempty). Note that xv6 refuses to unlink a name that is . or .., but
here the last element is dd, so the check does not apply.
sys_unlink clears the entry, decrements the parent’s link count (the .. inside
dd no longer counts) and decrements dd’s count from 1 to 0. All of this is one
transaction. Because p->cwd still references dd, iput does not
free it: the directory is now an orphan, and the process is standing in it.
Remove the name dd from the parent. Link count 0, but the current-directory
reference keeps the inode allocated.
Announce the orphan and wait to be killed
The printed line is the signal test-xv6.py waits for. The program then sleeps,
100 seconds at a time (sys_pause), forever. As with user/forphan.c,
it must not exit: kexit calls iput(p->cwd) (kernel/proc.c:343),
which would free the directory normally and leave nothing for ireclaim to find.
When QEMU is killed instead, the disk holds an inode of type T_DIR with
nlink == 0. At the next boot ireclaim gets it, and iput
truncates it (freeing the data block that held . and ..) and marks it free.
Nothing else needs fixing, because the parent’s link count was already decremented
by the unlink. If ireclaim failed, no ireclaim: line would appear and the
script would time out and print FAIL.
Tell the test script the orphan is ready.
Sleep about 100 seconds, forever, still inside the orphaned directory.