Skip to content

Commit

Permalink
ubifs: Implement RENAME_WHITEOUT
Browse files Browse the repository at this point in the history
Adds RENAME_WHITEOUT support to UBIFS, we implement
it in the same way as ext4 and xfs do.
For an overview of other ways to implement it please
refere to commit 7dcf5c3 ("xfs: add RENAME_WHITEOUT support").

Signed-off-by: Richard Weinberger <richard@nod.at>
  • Loading branch information
richardweinberger committed Oct 2, 2016
1 parent 474b937 commit 9e0a1ff
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 33 deletions.
120 changes: 101 additions & 19 deletions fs/ubifs/dir.c
Expand Up @@ -301,8 +301,8 @@ static int ubifs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
return err;
}

static int ubifs_tmpfile(struct inode *dir, struct dentry *dentry,
umode_t mode)
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;
Expand Down Expand Up @@ -336,14 +336,27 @@ static int ubifs_tmpfile(struct inode *dir, struct dentry *dentry,
}
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);
d_tmpfile(dentry, 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);

Expand Down Expand Up @@ -371,6 +384,12 @@ static int ubifs_tmpfile(struct inode *dir, struct dentry *dentry,
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
Expand Down Expand Up @@ -997,37 +1016,43 @@ static int ubifs_symlink(struct inode *dir, struct dentry *dentry,
}

/**
* 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)
Expand All @@ -1036,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;
Expand All @@ -1063,15 +1091,18 @@ 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);
new_dentry, new_dir->i_ino, flags);

if (flags & ~(RENAME_NOREPLACE | RENAME_WHITEOUT))
return -EINVAL;

ubifs_assert(inode_is_locked(old_dir));
ubifs_assert(inode_is_locked(new_dir));
if (unlink)
ubifs_assert(inode_is_locked(new_inode));


if (unlink && is_dir) {
err = check_dir_empty(c, new_inode);
if (err)
Expand All @@ -1087,7 +1118,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
Expand Down Expand Up @@ -1157,12 +1213,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);
Expand Down Expand Up @@ -1195,7 +1273,11 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
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;
Expand Down Expand Up @@ -1249,7 +1331,7 @@ const struct inode_operations ubifs_dir_inode_operations = {
.mkdir = ubifs_mkdir,
.rmdir = ubifs_rmdir,
.mknod = ubifs_mknod,
.rename = ubifs_rename,
.rename2 = ubifs_rename,
.setattr = ubifs_setattr,
.getattr = ubifs_getattr,
.setxattr = generic_setxattr,
Expand Down
43 changes: 30 additions & 13 deletions fs/ubifs/journal.c
Expand Up @@ -917,14 +917,15 @@ int ubifs_jnl_delete_inode(struct ubifs_info *c, const struct inode *inode)
* @sync: non-zero if the write-buffer has to be synchronized
*
* This function implements the re-name operation which may involve writing up
* to 3 inodes and 2 directory entries. It marks the written inodes as clean
* to 4 inodes and 2 directory entries. It marks the written inodes as clean
* and returns zero on success. In case of failure, a negative error code is
* returned.
*/
int ubifs_jnl_rename(struct ubifs_info *c, const struct inode *old_dir,
const struct dentry *old_dentry,
const struct inode *new_dir,
const struct dentry *new_dentry, int sync)
const struct dentry *new_dentry,
const struct inode *whiteout, int sync)
{
void *p;
union ubifs_key key;
Expand Down Expand Up @@ -980,13 +981,19 @@ int ubifs_jnl_rename(struct ubifs_info *c, const struct inode *old_dir,
zero_dent_node_unused(dent);
ubifs_prep_grp_node(c, dent, dlen1, 0);

/* Make deletion dent */
dent2 = (void *)dent + aligned_dlen1;
dent2->ch.node_type = UBIFS_DENT_NODE;
dent_key_init_flash(c, &dent2->key, old_dir->i_ino,
&old_dentry->d_name);
dent2->inum = 0;
dent2->type = DT_UNKNOWN;

if (whiteout) {
dent2->inum = cpu_to_le64(whiteout->i_ino);
dent2->type = get_dent_type(whiteout->i_mode);
} else {
/* Make deletion dent */
dent2->inum = 0;
dent2->type = DT_UNKNOWN;
}
dent2->nlen = cpu_to_le16(old_dentry->d_name.len);
memcpy(dent2->name, old_dentry->d_name.name, old_dentry->d_name.len);
dent2->name[old_dentry->d_name.len] = '\0';
Expand Down Expand Up @@ -1035,16 +1042,26 @@ int ubifs_jnl_rename(struct ubifs_info *c, const struct inode *old_dir,
if (err)
goto out_ro;

err = ubifs_add_dirt(c, lnum, dlen2);
if (err)
goto out_ro;
offs += aligned_dlen1;
if (whiteout) {
dent_key_init(c, &key, old_dir->i_ino, &old_dentry->d_name);
err = ubifs_tnc_add_nm(c, &key, lnum, offs, dlen2, &old_dentry->d_name);
if (err)
goto out_ro;

dent_key_init(c, &key, old_dir->i_ino, &old_dentry->d_name);
err = ubifs_tnc_remove_nm(c, &key, &old_dentry->d_name);
if (err)
goto out_ro;
ubifs_delete_orphan(c, whiteout->i_ino);
} else {
err = ubifs_add_dirt(c, lnum, dlen2);
if (err)
goto out_ro;

dent_key_init(c, &key, old_dir->i_ino, &old_dentry->d_name);
err = ubifs_tnc_remove_nm(c, &key, &old_dentry->d_name);
if (err)
goto out_ro;
}

offs += aligned_dlen1 + aligned_dlen2;
offs += aligned_dlen2;
if (new_inode) {
ino_key_init(c, &key, new_inode->i_ino);
err = ubifs_tnc_add(c, &key, lnum, offs, ilen);
Expand Down
4 changes: 3 additions & 1 deletion fs/ubifs/ubifs.h
Expand Up @@ -157,6 +157,7 @@ enum {
WB_MUTEX_1 = 0,
WB_MUTEX_2 = 1,
WB_MUTEX_3 = 2,
WB_MUTEX_4 = 3,
};

/*
Expand Down Expand Up @@ -1523,7 +1524,8 @@ int ubifs_jnl_delete_inode(struct ubifs_info *c, const struct inode *inode);
int ubifs_jnl_rename(struct ubifs_info *c, const struct inode *old_dir,
const struct dentry *old_dentry,
const struct inode *new_dir,
const struct dentry *new_dentry, int sync);
const struct dentry *new_dentry,
const struct inode *whiteout, int sync);
int ubifs_jnl_truncate(struct ubifs_info *c, const struct inode *inode,
loff_t old_size, loff_t new_size);
int ubifs_jnl_delete_xattr(struct ubifs_info *c, const struct inode *host,
Expand Down

0 comments on commit 9e0a1ff

Please sign in to comment.