diff options
Diffstat (limited to 'fs/ubifs/dir.c')
-rw-r--r-- | fs/ubifs/dir.c | 239 |
1 files changed, 222 insertions, 17 deletions
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 64902702b17d..c8f60df2733e 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -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) @@ -972,7 +1067,9 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry, 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; @@ -997,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) @@ -1021,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 @@ -1091,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); @@ -1129,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) { @@ -1183,13 +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, .listxattr = ubifs_listxattr, #ifdef CONFIG_UBIFS_ATIME_SUPPORT .update_time = ubifs_update_time, #endif + .tmpfile = ubifs_tmpfile, }; const struct file_operations ubifs_dir_operations = { |