Commit 75a6f82a authored by Al Viro's avatar Al Viro Committed by Al Viro

freeing unlinked file indefinitely delayed

	Normally opening a file, unlinking it and then closing will have
the inode freed upon close() (provided that it's not otherwise busy and
has no remaining links, of course).  However, there's one case where that
does *not* happen.  Namely, if you open it by fhandle with cold dcache,
then unlink() and close().

	In normal case you get d_delete() in unlink(2) notice that dentry
is busy and unhash it; on the final dput() it will be forcibly evicted from
dcache, triggering iput() and inode removal.  In this case, though, we end
up with *two* dentries - disconnected (created by open-by-fhandle) and
regular one (used by unlink()).  The latter will have its reference to inode
dropped just fine, but the former will not - it's considered hashed (it
is on the ->s_anon list), so it will stay around until the memory pressure
will finally do it in.  As the result, we have the final iput() delayed
indefinitely.  It's trivial to reproduce -

void flush_dcache(void)
{
        system("mount -o remount,rw /");
}

static char buf[20 * 1024 * 1024];

main()
{
        int fd;
        union {
                struct file_handle f;
                char buf[MAX_HANDLE_SZ];
        } x;
        int m;

        x.f.handle_bytes = sizeof(x);
        chdir("/root");
        mkdir("foo", 0700);
        fd = open("foo/bar", O_CREAT | O_RDWR, 0600);
        close(fd);
        name_to_handle_at(AT_FDCWD, "foo/bar", &x.f, &m, 0);
        flush_dcache();
        fd = open_by_handle_at(AT_FDCWD, &x.f, O_RDWR);
        unlink("foo/bar");
        write(fd, buf, sizeof(buf));
        system("df .");			/* 20Mb eaten */
        close(fd);
        system("df .");			/* should've freed those 20Mb */
        flush_dcache();
        system("df .");			/* should be the same as #2 */
}

will spit out something like
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/root         322023 303843      1131 100% /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/root         322023 303843      1131 100% /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/root         322023 283282     21692  93% /
- inode gets freed only when dentry is finally evicted (here we trigger
than by remount; normally it would've happened in response to memory
pressure hell knows when).

Cc: stable@vger.kernel.org # v2.6.38+; earlier ones need s/kill_it/unhash_it/
Acked-by: default avatarJ. Bruce Fields <bfields@fieldses.org>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 9391dd00
...@@ -642,7 +642,7 @@ static inline bool fast_dput(struct dentry *dentry) ...@@ -642,7 +642,7 @@ static inline bool fast_dput(struct dentry *dentry)
/* /*
* If we have a d_op->d_delete() operation, we sould not * If we have a d_op->d_delete() operation, we sould not
* let the dentry count go to zero, so use "put__or_lock". * let the dentry count go to zero, so use "put_or_lock".
*/ */
if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) if (unlikely(dentry->d_flags & DCACHE_OP_DELETE))
return lockref_put_or_lock(&dentry->d_lockref); return lockref_put_or_lock(&dentry->d_lockref);
...@@ -697,7 +697,7 @@ static inline bool fast_dput(struct dentry *dentry) ...@@ -697,7 +697,7 @@ static inline bool fast_dput(struct dentry *dentry)
*/ */
smp_rmb(); smp_rmb();
d_flags = ACCESS_ONCE(dentry->d_flags); d_flags = ACCESS_ONCE(dentry->d_flags);
d_flags &= DCACHE_REFERENCED | DCACHE_LRU_LIST; d_flags &= DCACHE_REFERENCED | DCACHE_LRU_LIST | DCACHE_DISCONNECTED;
/* Nothing to do? Dropping the reference was all we needed? */ /* Nothing to do? Dropping the reference was all we needed? */
if (d_flags == (DCACHE_REFERENCED | DCACHE_LRU_LIST) && !d_unhashed(dentry)) if (d_flags == (DCACHE_REFERENCED | DCACHE_LRU_LIST) && !d_unhashed(dentry))
...@@ -776,6 +776,9 @@ void dput(struct dentry *dentry) ...@@ -776,6 +776,9 @@ void dput(struct dentry *dentry)
if (unlikely(d_unhashed(dentry))) if (unlikely(d_unhashed(dentry)))
goto kill_it; goto kill_it;
if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED))
goto kill_it;
if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) { if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) {
if (dentry->d_op->d_delete(dentry)) if (dentry->d_op->d_delete(dentry))
goto kill_it; goto kill_it;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment