f2fs: avoid abnormal behavior on broken symlink
[deliverable/linux.git] / fs / f2fs / namei.c
index e79639a9787aab0f5d8d8b27fae7354a5cd087b6..407dde3d7a92273a790aefb2ace267f3be0ece74 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/sched.h>
 #include <linux/ctype.h>
 #include <linux/dcache.h>
+#include <linux/namei.h>
 
 #include "f2fs.h"
 #include "node.h"
@@ -187,6 +188,44 @@ struct dentry *f2fs_get_parent(struct dentry *child)
        return d_obtain_alias(f2fs_iget(child->d_inode->i_sb, ino));
 }
 
+static int __recover_dot_dentries(struct inode *dir, nid_t pino)
+{
+       struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
+       struct qstr dot = QSTR_INIT(".", 1);
+       struct qstr dotdot = QSTR_INIT("..", 2);
+       struct f2fs_dir_entry *de;
+       struct page *page;
+       int err = 0;
+
+       f2fs_lock_op(sbi);
+
+       de = f2fs_find_entry(dir, &dot, &page);
+       if (de) {
+               f2fs_dentry_kunmap(dir, page);
+               f2fs_put_page(page, 0);
+       } else {
+               err = __f2fs_add_link(dir, &dot, NULL, dir->i_ino, S_IFDIR);
+               if (err)
+                       goto out;
+       }
+
+       de = f2fs_find_entry(dir, &dotdot, &page);
+       if (de) {
+               f2fs_dentry_kunmap(dir, page);
+               f2fs_put_page(page, 0);
+       } else {
+               err = __f2fs_add_link(dir, &dotdot, NULL, pino, S_IFDIR);
+       }
+out:
+       if (!err) {
+               clear_inode_flag(F2FS_I(dir), FI_INLINE_DOTS);
+               mark_inode_dirty(dir);
+       }
+
+       f2fs_unlock_op(sbi);
+       return err;
+}
+
 static struct dentry *f2fs_lookup(struct inode *dir, struct dentry *dentry,
                unsigned int flags)
 {
@@ -206,6 +245,16 @@ static struct dentry *f2fs_lookup(struct inode *dir, struct dentry *dentry,
                inode = f2fs_iget(dir->i_sb, ino);
                if (IS_ERR(inode))
                        return ERR_CAST(inode);
+
+               if (f2fs_has_inline_dots(inode)) {
+                       int err;
+
+                       err = __recover_dot_dentries(inode, dir->i_ino);
+                       if (err) {
+                               iget_failed(inode);
+                               return ERR_PTR(err);
+                       }
+               }
        }
 
        return d_splice_alias(inode, dentry);
@@ -247,6 +296,23 @@ fail:
        return err;
 }
 
+static void *f2fs_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+       struct page *page;
+
+       page = page_follow_link_light(dentry, nd);
+       if (IS_ERR(page))
+               return page;
+
+       /* this is broken symlink case */
+       if (*nd_get_link(nd) == 0) {
+               kunmap(page);
+               page_cache_release(page);
+               return ERR_PTR(-ENOENT);
+       }
+       return page;
+}
+
 static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
                                        const char *symname)
 {
@@ -276,6 +342,17 @@ static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
        d_instantiate(dentry, inode);
        unlock_new_inode(inode);
 
+       /*
+        * Let's flush symlink data in order to avoid broken symlink as much as
+        * possible. Nevertheless, fsyncing is the best way, but there is no
+        * way to get a file descriptor in order to flush that.
+        *
+        * Note that, it needs to do dir->fsync to make this recoverable.
+        * If the symlink path is stored into inline_data, there is no
+        * performance regression.
+        */
+       filemap_write_and_wait_range(inode->i_mapping, 0, symlen - 1);
+
        if (IS_DIRSYNC(dir))
                f2fs_sync_fs(sbi->sb, 1);
        return err;
@@ -693,6 +770,8 @@ static int f2fs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode)
        f2fs_unlock_op(sbi);
 
        alloc_nid_done(sbi, inode->i_ino);
+
+       stat_inc_inline_inode(inode);
        d_tmpfile(dentry, inode);
        unlock_new_inode(inode);
        return 0;
@@ -729,7 +808,7 @@ const struct inode_operations f2fs_dir_inode_operations = {
 
 const struct inode_operations f2fs_symlink_inode_operations = {
        .readlink       = generic_readlink,
-       .follow_link    = page_follow_link_light,
+       .follow_link    = f2fs_follow_link,
        .put_link       = page_put_link,
        .getattr        = f2fs_getattr,
        .setattr        = f2fs_setattr,
This page took 0.026752 seconds and 5 git commands to generate.