Merge tag 'upstream-4.9-rc1' of git://git.infradead.org/linux-ubifs
[sfrench/cifs-2.6.git] / fs / ubifs / dir.c
index 4b86d3a738e1807c667fb6736f1deaa28a93d429..c8f60df2733eba516b189716a6b3bb3ab64520d1 100644 (file)
@@ -301,6 +301,95 @@ out_budg:
        return err;
 }
 
+static int do_tmpfile(struct inode *dir, struct dentry *dentry,
+                     umode_t mode, struct inode **whiteout)
+{
+       struct inode *inode;
+       struct ubifs_info *c = dir->i_sb->s_fs_info;
+       struct ubifs_budget_req req = { .new_ino = 1, .new_dent = 1};
+       struct ubifs_budget_req ino_req = { .dirtied_ino = 1 };
+       struct ubifs_inode *ui, *dir_ui = ubifs_inode(dir);
+       int err, instantiated = 0;
+
+       /*
+        * Budget request settings: new dirty inode, new direntry,
+        * budget for dirtied inode will be released via writeback.
+        */
+
+       dbg_gen("dent '%pd', mode %#hx in dir ino %lu",
+               dentry, mode, dir->i_ino);
+
+       err = ubifs_budget_space(c, &req);
+       if (err)
+               return err;
+
+       err = ubifs_budget_space(c, &ino_req);
+       if (err) {
+               ubifs_release_budget(c, &req);
+               return err;
+       }
+
+       inode = ubifs_new_inode(c, dir, mode);
+       if (IS_ERR(inode)) {
+               err = PTR_ERR(inode);
+               goto out_budg;
+       }
+       ui = ubifs_inode(inode);
+
+       if (whiteout) {
+               init_special_inode(inode, inode->i_mode, WHITEOUT_DEV);
+               ubifs_assert(inode->i_op == &ubifs_file_inode_operations);
+       }
+
+       err = ubifs_init_security(dir, inode, &dentry->d_name);
+       if (err)
+               goto out_inode;
+
+       mutex_lock(&ui->ui_mutex);
+       insert_inode_hash(inode);
+
+       if (whiteout) {
+               mark_inode_dirty(inode);
+               drop_nlink(inode);
+               *whiteout = inode;
+       } else {
+               d_tmpfile(dentry, inode);
+       }
+       ubifs_assert(ui->dirty);
+
+       instantiated = 1;
+       mutex_unlock(&ui->ui_mutex);
+
+       mutex_lock(&dir_ui->ui_mutex);
+       err = ubifs_jnl_update(c, dir, &dentry->d_name, inode, 1, 0);
+       if (err)
+               goto out_cancel;
+       mutex_unlock(&dir_ui->ui_mutex);
+
+       ubifs_release_budget(c, &req);
+
+       return 0;
+
+out_cancel:
+       mutex_unlock(&dir_ui->ui_mutex);
+out_inode:
+       make_bad_inode(inode);
+       if (!instantiated)
+               iput(inode);
+out_budg:
+       ubifs_release_budget(c, &req);
+       if (!instantiated)
+               ubifs_release_budget(c, &ino_req);
+       ubifs_err(c, "cannot create temporary file, error %d", err);
+       return err;
+}
+
+static int ubifs_tmpfile(struct inode *dir, struct dentry *dentry,
+                        umode_t mode)
+{
+       return do_tmpfile(dir, dentry, mode, NULL);
+}
+
 /**
  * vfs_dent_type - get VFS directory entry type.
  * @type: UBIFS directory entry type
@@ -927,37 +1016,43 @@ out_budg:
 }
 
 /**
- * lock_3_inodes - a wrapper for locking three UBIFS inodes.
+ * lock_4_inodes - a wrapper for locking three UBIFS inodes.
  * @inode1: first inode
  * @inode2: second inode
  * @inode3: third inode
+ * @inode4: fouth inode
  *
  * This function is used for 'ubifs_rename()' and @inode1 may be the same as
- * @inode2 whereas @inode3 may be %NULL.
+ * @inode2 whereas @inode3 and @inode4 may be %NULL.
  *
  * We do not implement any tricks to guarantee strict lock ordering, because
  * VFS has already done it for us on the @i_mutex. So this is just a simple
  * wrapper function.
  */
-static void lock_3_inodes(struct inode *inode1, struct inode *inode2,
-                         struct inode *inode3)
+static void lock_4_inodes(struct inode *inode1, struct inode *inode2,
+                         struct inode *inode3, struct inode *inode4)
 {
        mutex_lock_nested(&ubifs_inode(inode1)->ui_mutex, WB_MUTEX_1);
        if (inode2 != inode1)
                mutex_lock_nested(&ubifs_inode(inode2)->ui_mutex, WB_MUTEX_2);
        if (inode3)
                mutex_lock_nested(&ubifs_inode(inode3)->ui_mutex, WB_MUTEX_3);
+       if (inode4)
+               mutex_lock_nested(&ubifs_inode(inode4)->ui_mutex, WB_MUTEX_4);
 }
 
 /**
- * unlock_3_inodes - a wrapper for unlocking three UBIFS inodes for rename.
+ * unlock_4_inodes - a wrapper for unlocking three UBIFS inodes for rename.
  * @inode1: first inode
  * @inode2: second inode
  * @inode3: third inode
+ * @inode4: fouth inode
  */
-static void unlock_3_inodes(struct inode *inode1, struct inode *inode2,
-                           struct inode *inode3)
+static void unlock_4_inodes(struct inode *inode1, struct inode *inode2,
+                           struct inode *inode3, struct inode *inode4)
 {
+       if (inode4)
+               mutex_unlock(&ubifs_inode(inode4)->ui_mutex);
        if (inode3)
                mutex_unlock(&ubifs_inode(inode3)->ui_mutex);
        if (inode1 != inode2)
@@ -966,12 +1061,15 @@ static void unlock_3_inodes(struct inode *inode1, struct inode *inode2,
 }
 
 static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
-                       struct inode *new_dir, struct dentry *new_dentry)
+                       struct inode *new_dir, struct dentry *new_dentry,
+                       unsigned int flags)
 {
        struct ubifs_info *c = old_dir->i_sb->s_fs_info;
        struct inode *old_inode = d_inode(old_dentry);
        struct inode *new_inode = d_inode(new_dentry);
+       struct inode *whiteout = NULL;
        struct ubifs_inode *old_inode_ui = ubifs_inode(old_inode);
+       struct ubifs_inode *whiteout_ui = NULL;
        int err, release, sync = 0, move = (new_dir != old_dir);
        int is_dir = S_ISDIR(old_inode->i_mode);
        int unlink = !!new_inode;
@@ -984,6 +1082,9 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
        struct timespec time;
        unsigned int uninitialized_var(saved_nlink);
 
+       if (flags & ~RENAME_NOREPLACE)
+               return -EINVAL;
+
        /*
         * Budget request settings: deletion direntry, new direntry, removing
         * the old inode, and changing old and new parent directory inodes.
@@ -993,15 +1094,13 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
         * separately.
         */
 
-       dbg_gen("dent '%pd' ino %lu in dir ino %lu to dent '%pd' in dir ino %lu",
+       dbg_gen("dent '%pd' ino %lu in dir ino %lu to dent '%pd' in dir ino %lu flags 0x%x",
                old_dentry, old_inode->i_ino, old_dir->i_ino,
-               new_dentry, new_dir->i_ino);
-       ubifs_assert(inode_is_locked(old_dir));
-       ubifs_assert(inode_is_locked(new_dir));
+               new_dentry, new_dir->i_ino, flags);
+
        if (unlink)
                ubifs_assert(inode_is_locked(new_inode));
 
-
        if (unlink && is_dir) {
                err = check_dir_empty(c, new_inode);
                if (err)
@@ -1017,7 +1116,32 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
                return err;
        }
 
-       lock_3_inodes(old_dir, new_dir, new_inode);
+       if (flags & RENAME_WHITEOUT) {
+               union ubifs_dev_desc *dev = NULL;
+
+               dev = kmalloc(sizeof(union ubifs_dev_desc), GFP_NOFS);
+               if (!dev) {
+                       ubifs_release_budget(c, &req);
+                       ubifs_release_budget(c, &ino_req);
+                       return -ENOMEM;
+               }
+
+               err = do_tmpfile(old_dir, old_dentry, S_IFCHR | WHITEOUT_MODE, &whiteout);
+               if (err) {
+                       ubifs_release_budget(c, &req);
+                       ubifs_release_budget(c, &ino_req);
+                       kfree(dev);
+                       return err;
+               }
+
+               whiteout->i_state |= I_LINKABLE;
+               whiteout_ui = ubifs_inode(whiteout);
+               whiteout_ui->data = dev;
+               whiteout_ui->data_len = ubifs_encode_dev(dev, MKDEV(0, 0));
+               ubifs_assert(!whiteout_ui->dirty);
+       }
+
+       lock_4_inodes(old_dir, new_dir, new_inode, whiteout);
 
        /*
         * Like most other Unix systems, set the @i_ctime for inodes on a
@@ -1087,12 +1211,34 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
                if (unlink && IS_SYNC(new_inode))
                        sync = 1;
        }
-       err = ubifs_jnl_rename(c, old_dir, old_dentry, new_dir, new_dentry,
+
+       if (whiteout) {
+               struct ubifs_budget_req wht_req = { .dirtied_ino = 1,
+                               .dirtied_ino_d = \
+                               ALIGN(ubifs_inode(whiteout)->data_len, 8) };
+
+               err = ubifs_budget_space(c, &wht_req);
+               if (err) {
+                       ubifs_release_budget(c, &req);
+                       ubifs_release_budget(c, &ino_req);
+                       kfree(whiteout_ui->data);
+                       whiteout_ui->data_len = 0;
+                       iput(whiteout);
+                       return err;
+               }
+
+               inc_nlink(whiteout);
+               mark_inode_dirty(whiteout);
+               whiteout->i_state &= ~I_LINKABLE;
+               iput(whiteout);
+       }
+
+       err = ubifs_jnl_rename(c, old_dir, old_dentry, new_dir, new_dentry, whiteout,
                               sync);
        if (err)
                goto out_cancel;
 
-       unlock_3_inodes(old_dir, new_dir, new_inode);
+       unlock_4_inodes(old_dir, new_dir, new_inode, whiteout);
        ubifs_release_budget(c, &req);
 
        mutex_lock(&old_inode_ui->ui_mutex);
@@ -1125,12 +1271,74 @@ out_cancel:
                                inc_nlink(old_dir);
                }
        }
-       unlock_3_inodes(old_dir, new_dir, new_inode);
+       if (whiteout) {
+               drop_nlink(whiteout);
+               iput(whiteout);
+       }
+       unlock_4_inodes(old_dir, new_dir, new_inode, whiteout);
        ubifs_release_budget(c, &ino_req);
        ubifs_release_budget(c, &req);
        return err;
 }
 
+static int ubifs_xrename(struct inode *old_dir, struct dentry *old_dentry,
+                       struct inode *new_dir, struct dentry *new_dentry)
+{
+       struct ubifs_info *c = old_dir->i_sb->s_fs_info;
+       struct ubifs_budget_req req = { .new_dent = 1, .mod_dent = 1,
+                               .dirtied_ino = 2 };
+       int sync = IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir);
+       struct inode *fst_inode = d_inode(old_dentry);
+       struct inode *snd_inode = d_inode(new_dentry);
+       struct timespec time;
+       int err;
+
+       ubifs_assert(fst_inode && snd_inode);
+
+       lock_4_inodes(old_dir, new_dir, NULL, NULL);
+
+       time = ubifs_current_time(old_dir);
+       fst_inode->i_ctime = time;
+       snd_inode->i_ctime = time;
+       old_dir->i_mtime = old_dir->i_ctime = time;
+       new_dir->i_mtime = new_dir->i_ctime = time;
+
+       if (old_dir != new_dir) {
+               if (S_ISDIR(fst_inode->i_mode) && !S_ISDIR(snd_inode->i_mode)) {
+                       inc_nlink(new_dir);
+                       drop_nlink(old_dir);
+               }
+               else if (!S_ISDIR(fst_inode->i_mode) && S_ISDIR(snd_inode->i_mode)) {
+                       drop_nlink(new_dir);
+                       inc_nlink(old_dir);
+               }
+       }
+
+       err = ubifs_jnl_xrename(c, old_dir, old_dentry, new_dir, new_dentry,
+                               sync);
+
+       unlock_4_inodes(old_dir, new_dir, NULL, NULL);
+       ubifs_release_budget(c, &req);
+
+       return err;
+}
+
+static int ubifs_rename2(struct inode *old_dir, struct dentry *old_dentry,
+                       struct inode *new_dir, struct dentry *new_dentry,
+                       unsigned int flags)
+{
+       if (flags & ~(RENAME_NOREPLACE | RENAME_WHITEOUT | RENAME_EXCHANGE))
+               return -EINVAL;
+
+       ubifs_assert(inode_is_locked(old_dir));
+       ubifs_assert(inode_is_locked(new_dir));
+
+       if (flags & RENAME_EXCHANGE)
+               return ubifs_xrename(old_dir, old_dentry, new_dir, new_dentry);
+
+       return ubifs_rename(old_dir, old_dentry, new_dir, new_dentry, flags);
+}
+
 int ubifs_getattr(struct vfsmount *mnt, struct dentry *dentry,
                  struct kstat *stat)
 {
@@ -1179,16 +1387,14 @@ const struct inode_operations ubifs_dir_inode_operations = {
        .mkdir       = ubifs_mkdir,
        .rmdir       = ubifs_rmdir,
        .mknod       = ubifs_mknod,
-       .rename      = ubifs_rename,
+       .rename      = ubifs_rename2,
        .setattr     = ubifs_setattr,
        .getattr     = ubifs_getattr,
-       .setxattr    = generic_setxattr,
-       .getxattr    = generic_getxattr,
        .listxattr   = ubifs_listxattr,
-       .removexattr = generic_removexattr,
 #ifdef CONFIG_UBIFS_ATIME_SUPPORT
        .update_time = ubifs_update_time,
 #endif
+       .tmpfile     = ubifs_tmpfile,
 };
 
 const struct file_operations ubifs_dir_operations = {