Print this page
7656 unlinking directory on tmpfs can cause kernel panic
@@ -19,14 +19,14 @@
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ *
+ * Copyright 2016 RackTop Systems.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
-
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/time.h>
@@ -523,16 +523,24 @@
gethrestime(&now);
dir->tn_mtime = now;
dir->tn_ctime = now;
tp->tn_ctime = now;
+ /*
+ * If this is a _REMOVE (unlink) operation there may
+ * be other links to the directory entry.
+ */
ASSERT(tp->tn_nlink > 0);
DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
- if (op == DR_RMDIR && tp->tn_type == VDIR) {
+ if (op == DR_RMDIR || (op == DR_REMOVE && tp->tn_type == VDIR)) {
+ if (tp->tn_nlink > 1) {
+ ASSERT(op == DR_REMOVE);
+ } else {
tdirtrunc(tp);
ASSERT(tp->tn_nlink == 0);
}
+ }
return (0);
}
/*
* tdirinit is used internally to initialize a directory (dir)
@@ -613,10 +621,11 @@
struct tdirent *tdp;
struct tmpnode *tp;
size_t namelen;
timestruc_t now;
int isvattrdir, isdotdot, skip_decr;
+ int lock_held;
ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
ASSERT(dir->tn_type == VDIR);
isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
@@ -651,11 +660,34 @@
tmpfs_hash_out(tdp);
tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
dir->tn_size -= (sizeof (struct tdirent) + namelen);
dir->tn_dirents--;
+
+ /*
+ * This directory entry may itself be a directory with
+ * entries and removing it may have created orphans.
+ * On a normal filesystem like UFS this wouldn't be
+ * a huge problem because fcsk can reclaim them. For
+ * TMPFS which resides in RAM however, it means we
+ * end up leaking memory.
+ *
+ * To avoid this we also truncate child directories,
+ * but only if they have no other links to them.
+ */
+ if (!isdotdot && tp->tn_type == VDIR && tp != dir) {
+ if (tp->tn_nlink > 1)
+ continue;
+ lock_held = RW_WRITE_HELD(&tp->tn_rwlock);
+ if (!lock_held)
+ rw_enter(&tp->tn_rwlock, RW_WRITER);
+ tdirtrunc(tp);
+ if (!lock_held)
+ rw_exit(&tp->tn_rwlock);
+ ASSERT(tp->tn_nlink == 0);
}
+ }
gethrestime(&now);
dir->tn_mtime = now;
dir->tn_ctime = now;