rmdir, or a rename onto an empty directory, clears the directory and
drops its last link, but the node stays as long as someone holds it: a
port on it, or a process's working directory.  Nothing stopped a
create, a link or a rename through such a reference from entering a
name in it.  The name is in a directory nothing reaches; when the node
goes, its blocks are freed, and the file entered stays allocated with
no name at all (e2fsck: "Unattached inode").  With a rename, the file's
old name is gone too.

diskfs_direnter now refuses with ENOENT, as Linux does, when the
directory has no links.  Its callers already undo the link they took on
an error.  diskfs_rename_dir checks before it rewrites "..", as its
error path after the rewrite does not undo it.

How to reproduce:

  $ mkdir /tmp/d && cd /tmp/d && rmdir /tmp/d && touch x; echo $?
  0

The create in the removed directory succeeds; with this change it fails
with ENOENT.
---
 libdiskfs/dir-renamed.c | 7 +++++++
 libdiskfs/direnter.c    | 9 +++++++++
 2 files changed, 16 insertions(+)

diff --git a/libdiskfs/dir-renamed.c b/libdiskfs/dir-renamed.c
index 17efebb04..2a7ff51f6 100644
--- a/libdiskfs/dir-renamed.c
+++ b/libdiskfs/dir-renamed.c
@@ -105,6 +105,13 @@ diskfs_rename_dir (struct node *fdp, struct node *fnp, 
const char *fromname,
       fnp = NULL;
       goto out;
     }
+  /* TDP was removed (see diskfs_direnter): fail before ".." changes.  */
+  if (tdp->dn_stat.st_nlink == 0)
+    {
+      err = ENOENT;
+      fnp = NULL;
+      goto out;
+    }
   if (tnp && excl)
     {
       err = EEXIST;
diff --git a/libdiskfs/direnter.c b/libdiskfs/direnter.c
index 17cc618d2..93bc2a814 100644
--- a/libdiskfs/direnter.c
+++ b/libdiskfs/direnter.c
@@ -38,6 +38,15 @@ diskfs_direnter (struct node *dp,
 {
   error_t err;
 
+  /* A directory that was removed, by rmdir or by a rename onto it, stays
+     reachable through the ports and working directories still on it, but
+     nothing reaches what is entered in it.  */
+  if (dp->dn_stat.st_nlink == 0)
+    {
+      diskfs_drop_dirstat (dp, ds);
+      return ENOENT;
+    }
+
   err = diskfs_direnter_hard (dp, name, np, ds, cred);
   if (err)
     return err;
-- 
2.56.0


Reply via email to