| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2021. Huawei Technologies Co., Ltd. All rights reserved. |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/pagemap.h> |
| #include <linux/crc32c.h> |
| #include "euler.h" |
| #include "dht.h" |
| #include "dep.h" |
| #include "lock.h" |
| |
| /* |
| * If sbi->s_draining is set, do fsync after each namei syscall! This is much |
| * better than lock transfer for volatility quota. |
| */ |
| static void sync_on_draining(struct inode *dir, struct inode *inode) |
| { |
| struct eufs_sb_info *sbi = EUFS_SB(dir->i_sb); |
| |
| if (likely(!sbi->s_draining)) |
| return; |
| |
| /* fsync the inodes to reduce the number of dirty inodes */ |
| fsync_on_draining(dir, inode); |
| } |
| |
| static __always_inline void |
| eufs_trace_newfile(const char *prompt, struct inode *dir, struct inode *inode, |
| struct eufs_inode *pi, struct nv_dict_entry *de) |
| { |
| eufs_dbg("%s (%s): inode=%px pi=%px pi->root=%llx pi->mode=0%o de=%px de->len=%lld de->name=%6s de->nextname=%llx inode->nlink=%d pi->nlink=%d de->volatile_next=%llx de->next=%llx\n", |
| __func__, prompt, inode, pi, eufs_iread_root(pi), |
| eufs_iread_mode(pi), de, HASHLEN_LEN(de->hv), de->name, |
| de->nextname, inode->i_nlink, pi->i_nlink, de->volatile_next, |
| de->next); |
| |
| BUG_ON(inode->i_mode != pi->i_mode); |
| } |
| |
| static __always_inline void eufs_trace_delfile(const char *prompt, |
| struct inode *dir, |
| struct inode *inode, |
| struct eufs_inode *pi) |
| { |
| eufs_dbg("%s (%s): inode=%px pi=%px pi->root=%llx pi->mode=0%o inode->i_nlink=%d pi->i_nlink=%d\n", |
| __func__, prompt, inode, pi, eufs_iread_root(pi), |
| eufs_iread_mode(pi), inode->i_nlink, eufs_iread_nlink(pi)); |
| /* |
| * because inode is locked by unlink/link, so the increment/decrement |
| * of nlink should be in order and its max value is (EUFS_LINK_MAX - 1) |
| * after unlink. |
| */ |
| if ((inode->i_mode & S_IFMT) != S_IFDIR) |
| WARN(inode->i_nlink >= EUFS_LINK_MAX, |
| "unexpected nlink %d for inode 0x%lx\n", inode->i_nlink, |
| inode->i_ino); |
| } |
| |
| static __always_inline struct nv_dict_entry * |
| nv_dict_add_wrapper(struct inode *dir, u64 **nv_header, struct eufs_inode *pi, |
| hashlen_t hv, const char *name) |
| { |
| struct eufs_inode_info *vi = EUFS_I(dir); |
| |
| NV_ASSERT(pi); |
| if (!vi->i_volatile_dict) |
| vi->i_volatile_dict = eufs_zalloc_page(); |
| |
| /* insert into parent dir hash table */ |
| return nv_dict_add(dir, nv_header, hv, name, pi); |
| } |
| |
| static __always_inline struct nv_dict_entry * |
| nv_dict_del_wrapper(struct inode *dir, struct nv_dict_entry **prevde, |
| u64 **nv_header, hashlen_t hv, const char *name) |
| { |
| struct eufs_inode_info *vi = EUFS_I(dir); |
| /* Alloc for dict if necessary */ |
| if (!vi->i_volatile_dict) |
| vi->i_volatile_dict = eufs_zalloc_page(); |
| |
| /* insert into parent dir hash table */ |
| return nv_dict_delete(dir, prevde, nv_header, hv, name); |
| } |
| |
| /* |
| * Methods themselves. |
| */ |
| static struct dentry *eufs_lookup(struct inode *dir, struct dentry *dentry, |
| unsigned int flags) |
| { |
| struct inode *inode = NULL; |
| struct nv_dict_entry *de; |
| const char *name; |
| u64 hv; |
| |
| if (dentry->d_name.len > EUFS_MAX_NAME_LEN) |
| return ERR_PTR(-ENAMETOOLONG); |
| |
| hv = hash(dentry->d_name.name, dentry->d_name.len); |
| name = dentry->d_name.name; |
| de = nv_dict_find(dir, hv, name); |
| if (!de) |
| goto not_found; |
| |
| inode = eufs_iget(dir->i_sb, s2p(dir->i_sb, de->inode)); |
| if (inode == ERR_PTR(-ESTALE)) { |
| eufs_err(dir->i_sb, "deleted inode referenced: 0x%lx", |
| inode->i_ino); |
| return ERR_PTR(-EIO); |
| } |
| not_found: |
| |
| if (inode) |
| BUG_ON(atomic_read(&inode->i_count) < 1); |
| return d_splice_alias(inode, dentry); |
| } |
| |
| static int add_pinode(struct inode *dir, struct dentry *dentry, |
| struct inode *inode, bool need_unlock_inode) |
| { |
| /* Name must be checked before this is invoked. */ |
| struct eufs_inode_info *dir_vi = EUFS_I(dir); |
| struct eufs_inode *pi; |
| const char *name; |
| struct nv_dict_entry *de; |
| u64 *nv_header; |
| u64 hv; |
| struct dep_node *dep; |
| int err; |
| |
| dep = eufs_alloc_dep_node(); |
| if (!dep) |
| return -ENOMEM; |
| |
| if (need_unlock_inode) |
| eufs_inode_mark_lock_transferable(inode); |
| |
| /* Add to dict */ |
| pi = EUFS_PI(inode); |
| name = dentry->d_name.name; |
| hv = hash(name, dentry->d_name.len); |
| de = nv_dict_add_wrapper(dir, &nv_header, pi, hv, name); |
| if (IS_ERR(de)) { |
| err = PTR_ERR(de); |
| goto err_out; |
| } |
| |
| /* One more dentry */ |
| dir->i_size++; |
| eufs_dbg("diradd +> %lld of %px 0x%lx\n", dir->i_size, dir, dir->i_ino); |
| |
| /* Update dir time */ |
| dir->i_ctime = dir->i_mtime = current_time(dir); |
| |
| dep_new_insert(dep, dir, DEP_DIRADD, NULL, nv_header, de, inode, |
| dir_vi->i_next_dep_seq); |
| |
| if (need_unlock_inode) |
| eufs_inode_wait_lock_transfer_done(inode); |
| |
| dir_vi->i_next_dep_seq++; |
| |
| return 0; |
| |
| err_out: |
| if (need_unlock_inode) |
| eufs_inode_wait_lock_transfer_done(inode); |
| eufs_free_dep_node(dep); |
| return err; |
| } |
| |
| static __always_inline int del_pinode(struct inode *dir, struct dentry *dentry, |
| bool is_dir) |
| { |
| struct eufs_inode_info *dir_vi = EUFS_I(dir); |
| struct inode *inode = dentry->d_inode; |
| struct nv_dict_entry *de, *prevde; |
| u64 *nv_header; |
| const char *name; |
| u64 hv; |
| struct dep_node *dep; |
| struct eufs_inode *pi; |
| int err; |
| |
| dep = eufs_alloc_dep_node(); |
| if (!dep) |
| return -ENOMEM; |
| |
| eufs_inode_mark_lock_transferable(inode); |
| |
| /* Remove from parent dir hash table */ |
| name = dentry->d_name.name; |
| hv = hash(name, dentry->d_name.len); |
| de = nv_dict_del_wrapper(dir, &prevde, &nv_header, hv, name); |
| if (unlikely(!de)) { |
| err = -ENOENT; |
| goto err_out; |
| } |
| |
| /* Drop one dentry */ |
| dir->i_size--; |
| eufs_dbg("dirdel -> %lld of %px 0x%lx\n", dir->i_size, dir, dir->i_ino); |
| |
| /* Update parent dir time */ |
| dir->i_ctime = dir->i_mtime = current_time(dir); |
| |
| /* Update inode ctime and link */ |
| inode->i_ctime = dir->i_ctime; |
| if (is_dir) { |
| /* Update nlink and ctime for the removed inode */ |
| WARN_ON(inode->i_nlink != 2); |
| clear_nlink(inode); |
| } else if (inode->i_nlink) { |
| drop_nlink(inode); |
| } else { |
| pi = EUFS_PI(inode); |
| eufs_info("!%s!: inode=%p, inode->i_nlink=%d inode->i_mode=0%o pi=%p pi->i_nlink=%d pi->i_mode=0%o\n", |
| __func__, inode, inode->i_nlink, inode->i_mode, pi, |
| eufs_iread_nlink(pi), eufs_iread_mode(pi)); |
| BUG(); |
| } |
| |
| dep_new_insert(dep, dir, DEP_DIRREM, prevde, nv_header, de, inode, |
| dir_vi->i_next_dep_seq); |
| |
| eufs_inode_wait_lock_transfer_done(inode); |
| |
| dir_vi->i_next_dep_seq++; |
| |
| return 0; |
| |
| err_out: |
| eufs_inode_wait_lock_transfer_done(inode); |
| eufs_free_dep_node(dep); |
| return err; |
| } |
| |
| static void eufs_free_new_inode(struct inode *inode) |
| { |
| clear_nlink(inode); |
| remove_inode_hash(inode); |
| unlock_new_inode(inode); |
| iput(inode); |
| } |
| |
| /* |
| * By the time this is called, we already have created |
| * the directory cache entry for the new file, but it |
| * is so far negative - it has no inode. |
| * |
| * If the create succeeds, we fill in the inode information |
| * with d_instantiate(). |
| */ |
| static int eufs_create(struct inode *dir, struct dentry *dentry, umode_t mode, |
| bool excl) |
| { |
| struct inode *inode; |
| int err; |
| |
| /* name checks */ |
| if (unlikely(!dentry->d_name.len)) |
| return -EINVAL; |
| if (unlikely(dentry->d_name.len > EUFS_MAX_NAME_LEN)) |
| return -ENAMETOOLONG; |
| |
| inode = pre_inodes_get(dentry, dir, mode, false, 0); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| |
| err = add_pinode(dir, dentry, inode, false); |
| if (err) { |
| eufs_free_new_inode(inode); |
| return err; |
| } |
| |
| inode->i_op = &eufs_file_inode_operations; |
| inode->i_mapping->a_ops = &eufs_aops; |
| inode->i_fop = &eufs_file_operations; |
| |
| eufs_trace_newfile("!create!", dir, inode, EUFS_PI(inode), NULL); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| d_instantiate(dentry, inode); |
| unlock_new_inode(inode); |
| |
| sync_on_draining(dir, NULL); |
| |
| return 0; |
| } |
| |
| static int eufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, |
| dev_t rdev) |
| { |
| struct inode *inode; |
| int err; |
| |
| if (unlikely(!dentry->d_name.len)) |
| return -EINVAL; |
| if (unlikely(dentry->d_name.len > EUFS_MAX_NAME_LEN)) |
| return -ENAMETOOLONG; |
| |
| inode = pre_inodes_get(dentry, dir, mode, true, rdev); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| |
| err = add_pinode(dir, dentry, inode, false); |
| if (err) { |
| eufs_free_new_inode(inode); |
| return err; |
| } |
| |
| inode->i_op = &eufs_special_inode_operations; |
| |
| eufs_trace_newfile("!mknode!", dir, inode, EUFS_PI(inode), NULL); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| d_instantiate(dentry, inode); |
| unlock_new_inode(inode); |
| |
| sync_on_draining(dir, NULL); |
| |
| return 0; |
| } |
| |
| static int eufs_symlink(struct inode *dir, struct dentry *dentry, |
| const char *symname) |
| { |
| struct inode *inode = NULL; |
| struct eufs_inode *pi; |
| u32 len = strlen(symname); |
| void *pi_root; |
| int err; |
| |
| /* name checks */ |
| if (unlikely(!dentry->d_name.len)) |
| return -EINVAL; |
| if (unlikely(dentry->d_name.len > EUFS_MAX_NAME_LEN)) |
| return -ENAMETOOLONG; |
| if (unlikely(len > EUFS_MAX_SYMLINK_LEN)) |
| return -ENAMETOOLONG; |
| |
| /* alloc vfs inode and xxfs inode */ |
| inode = pre_inodes_get(dentry, dir, S_IFLNK | S_IRWXUGO, false, 0); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| |
| pi = EUFS_FRESH_PI(EUFS_PI(inode)); |
| |
| pi_root = o2p(dir->i_sb, eufs_iread_root(pi)); |
| |
| /* copy the symname */ |
| *((u64 *)pi_root) = hash(symname, len); |
| memcpy(((char *)pi_root) + sizeof(u64), symname, len); |
| BUG_ON(!eufs_access_ok(inode->i_sb, pi_root, PAGE_SIZE)); |
| |
| /* update the size */ |
| inode->i_size = len; |
| |
| err = add_pinode(dir, dentry, inode, false); |
| if (err) { |
| eufs_free_new_inode(inode); |
| return err; |
| } |
| |
| inode->i_op = &eufs_symlink_inode_operations; |
| inode->i_mapping->a_ops = &eufs_aops; |
| |
| eufs_trace_newfile("!symlink!", dir, inode, pi, NULL); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| d_instantiate(dentry, inode); |
| unlock_new_inode(inode); |
| |
| sync_on_draining(dir, NULL); |
| |
| return 0; |
| } |
| |
| static int eufs_link(struct dentry *dest_dentry, struct inode *dir, |
| struct dentry *dentry) |
| { |
| struct inode *inode = dest_dentry->d_inode; |
| struct eufs_inode *pi = EUFS_PI(inode); |
| struct nv_dict_entry *de; |
| int err; |
| |
| /* name checks */ |
| if (unlikely(!dentry->d_name.len)) |
| return -EINVAL; |
| if (unlikely(dentry->d_name.len > EUFS_MAX_NAME_LEN)) |
| return -ENAMETOOLONG; |
| /* nlink check */ |
| if (unlikely(inode->i_nlink >= EUFS_LINK_MAX)) |
| return -EMLINK; |
| |
| ihold(inode); |
| |
| err = add_pinode(dir, dentry, inode, true); |
| if (unlikely(err)) { |
| iput(inode); |
| return err; |
| } |
| |
| /* update inode ctime */ |
| inode->i_ctime = current_time(inode); |
| inc_nlink(inode); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| d_instantiate(dentry, inode); |
| |
| eufs_trace_newfile("!link!", dir, inode, pi, de); |
| |
| /* inode_lock() has been acquired */ |
| sync_on_draining(dir, inode); |
| |
| return 0; |
| } |
| |
| static int eufs_unlink(struct inode *dir, struct dentry *dentry) |
| { |
| struct inode *inode = dentry->d_inode; |
| int ret; |
| |
| ret = del_pinode(dir, dentry, false); |
| if (ret < 0) |
| return ret; |
| |
| eufs_trace_delfile("!unlink!", dir, inode, EUFS_PI(inode)); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| |
| sync_on_draining(dir, inode); |
| |
| return 0; |
| } |
| |
| /* NOTE: do not count the link for directories */ |
| static int eufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) |
| { |
| struct inode *inode; |
| struct eufs_inode *dir_pi = EUFS_PI(dir); |
| struct eufs_inode_info *vi; |
| struct eufs_inode *pi; |
| int err; |
| |
| /* name checks */ |
| if (unlikely(!dentry->d_name.len)) |
| return -EINVAL; |
| |
| if (unlikely(dentry->d_name.len > EUFS_MAX_NAME_LEN)) |
| return -ENAMETOOLONG; |
| |
| /* alloc vfs inode and xxfs inode */ |
| inode = pre_inodes_get(dentry, dir, S_IFDIR | mode, false, 0); |
| if (IS_ERR(inode)) |
| return PTR_ERR(inode); |
| |
| inode->i_op = &eufs_dir_inode_operations; |
| inode->i_fop = &eufs_dir_operations; |
| inode->i_mapping->a_ops = &eufs_aops; |
| /* We have to mimic the nlink number */ |
| inc_nlink(inode); |
| |
| /* alloc & init dir hash table for new inode */ |
| pi = EUFS_FRESH_PI(EUFS_PI(inode)); |
| vi = EUFS_I(inode); |
| vi->i_dotdot = p2o(dir->i_sb, dir_pi); |
| pi->i_dotdot = cpu_to_le64(vi->i_dotdot); |
| |
| err = add_pinode(dir, dentry, inode, false); |
| if (err) { |
| eufs_free_new_inode(inode); |
| return err; |
| } |
| |
| /* We have to mimic the nlink number */ |
| inc_nlink(dir); |
| |
| eufs_trace_newfile("!mkdir!", dir, inode, pi, NULL); |
| |
| vi->i_is_dirty = true; |
| d_instantiate(dentry, inode); |
| unlock_new_inode(inode); |
| |
| sync_on_draining(dir, NULL); |
| |
| PRINT_PINODE(pi, "FINAL-CHECK: "); |
| |
| BUG_ON(atomic_read(&dir->i_count) < 1); |
| if (inode) |
| BUG_ON(atomic_read(&inode->i_count) < 1); |
| |
| return 0; |
| } |
| |
| /* |
| * routine to check that the specified directory is empty (for rmdir) |
| */ |
| static __always_inline int eufs_empty_dir(struct inode *inode) |
| { |
| return !inode->i_size; |
| } |
| |
| static int eufs_rmdir(struct inode *dir, struct dentry *dentry) |
| { |
| struct inode *inode = dentry->d_inode; |
| int ret; |
| |
| /* checks before rmdir */ |
| if (!inode) |
| return -ENOENT; |
| if (!eufs_empty_dir(inode)) |
| return -ENOTEMPTY; |
| |
| ret = del_pinode(dir, dentry, true); |
| if (ret < 0) |
| return ret; |
| |
| /* We have to mimic the nlink number */ |
| drop_nlink(dir); |
| |
| EUFS_I(inode)->i_is_dirty = true; |
| |
| eufs_trace_delfile("!rmdir!", dir, inode, EUFS_PI(inode)); |
| |
| sync_on_draining(dir, inode); |
| |
| return 0; |
| } |
| |
| /* |
| * Precondition: old_dentry exists in the old directory |
| */ |
| static int eufs_rename(struct inode *old_dir, struct dentry *old_dentry, |
| struct inode *new_dir, struct dentry *new_dentry, |
| unsigned int flags) |
| { |
| struct inode *old_inode = old_dentry->d_inode; |
| struct inode *new_inode = new_dentry->d_inode; |
| /* |
| * TODO: need to lock old_inode |
| * If old_inode is a directory, its inode lock will |
| * not be acquired, so the offset of newest physical node |
| * may be changed during the rename procedure. |
| */ |
| struct eufs_inode *pi = EUFS_FRESH_PI(EUFS_PI(old_inode)); |
| struct eufs_inode *old_dir_pi; |
| struct eufs_inode *new_dir_pi; |
| struct inode *locked_inodes[EUFS_INODE_CNT_IN_RENAME] = { |
| old_dir, new_dir, old_inode, new_inode |
| }; |
| struct super_block *sb = old_inode->i_sb; |
| |
| struct nv_dict_entry *new_de; |
| u64 *new_dir_nv_header; |
| struct nv_dict_entry *old_de, *old_prevde; |
| u64 *old_dir_nv_header; |
| bool in_same_dir = (old_dir == new_dir); |
| |
| const char *name; |
| struct eufs_renamej *renamej; |
| u64 old_hv, new_hv; |
| struct nv_dict_entry **vde; |
| int cpu; |
| void *buffer[16]; |
| |
| NV_ASSERT(pi->i_mode == old_inode->i_mode); |
| |
| if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT)) |
| return -EOPNOTSUPP; |
| |
| /* checks */ |
| if (new_inode) { |
| eufs_dbg( |
| "!new_inode=%px new_inode->i_count=%d new_dentry->d_lockref.count=%d\n", |
| new_inode, atomic_read(&new_inode->i_count), |
| new_dentry->d_lockref.count); |
| if (S_ISDIR(new_inode->i_mode) && !eufs_empty_dir(new_inode)) |
| return -ENOTEMPTY; |
| eufs_dbg("rename overwrites! newinode=%px newpi=%px newinode->i_mode=0%o, newinode->root=%px oldinode=%px oldpi=%px oldinode->i_mode=0%o, oldinode->root=%px\n", |
| new_inode, EUFS_PI(new_inode), new_inode->i_mode, |
| EUFS_I(new_inode)->i_volatile_root, old_inode, |
| EUFS_PI(old_inode), old_inode->i_mode, |
| EUFS_I(old_inode)->i_volatile_root); |
| BUG_ON(new_inode->i_mode != EUFS_PI(new_inode)->i_mode); |
| } |
| eufs_dbg("%s: rename %s to %s before fsync, old_pi=%px new_pi=%px\n", |
| __func__, old_dentry->d_name.name, new_dentry->d_name.name, |
| EUFS_PI(old_inode), |
| new_inode ? EUFS_PI(new_inode) : (void *)-1); |
| |
| eufs_dbg("old: dir=%px inode=%px; new: dir=%px inode=%px\n", old_dir, |
| old_inode, new_dir, new_inode); |
| |
| if (S_ISDIR(old_inode->i_mode)) |
| locked_inodes[2] = NULL; |
| if (locked_inodes[0] == locked_inodes[1]) |
| locked_inodes[1] = NULL; |
| |
| if (locked_inodes[0]) |
| BUG_ON(!inode_is_locked(locked_inodes[0])); |
| if (locked_inodes[1]) |
| BUG_ON(!inode_is_locked(locked_inodes[1])); |
| if (locked_inodes[2]) |
| BUG_ON(!inode_is_locked(locked_inodes[2])); |
| if (locked_inodes[3]) |
| BUG_ON(!inode_is_locked(locked_inodes[3])); |
| |
| fsync_rename_inodes(old_dir, new_dir, locked_inodes); |
| |
| /* |
| * get the newer inodes after fsync_rename_inodes() completes |
| * which may update the offset of the newer inodes |
| */ |
| old_dir_pi = EUFS_FRESH_PI(EUFS_PI(old_dir)); |
| new_dir_pi = EUFS_FRESH_PI(EUFS_PI(new_dir)); |
| |
| /* -------------- get new dentry info -------------- */ |
| /* get new filename */ |
| new_hv = hash(new_dentry->d_name.name, new_dentry->d_name.len); |
| name = new_dentry->d_name.name; |
| eufs_dbg("%s: rename %s to %s\n", __func__, old_dentry->d_name.name, |
| new_dentry->d_name.name); |
| |
| /* -------------- insertion ---------------- */ |
| /* insert into parent dir hash table */ |
| if (new_inode) { |
| new_de = nv_dict_find(new_dir, new_hv, name); |
| if (!new_de) |
| return -ENOENT; |
| /* Delay the actual write */ |
| BUG_ON(!new_inode->i_nlink); |
| ihold(new_inode); |
| /* We have new_inode in hand */ |
| if (S_ISDIR(new_inode->i_mode)) { |
| WARN_ON(new_inode->i_nlink != 2); |
| clear_nlink(new_inode); |
| } else { |
| drop_nlink(new_inode); |
| } |
| new_dir_nv_header = NULL; |
| } else { |
| new_de = nv_dict_add_wrapper(new_dir, &new_dir_nv_header, |
| EUFS_HEAD_PI(pi), new_hv, name); |
| if (IS_ERR(new_de)) |
| return PTR_ERR(new_de); |
| if (unlikely(!new_de)) |
| return -EEXIST; |
| /* We have no dep in rename. Just release the header lock */ |
| inode_header_unlock(new_dir); |
| |
| if (!in_same_dir) { |
| new_dir->i_size++; |
| if (S_ISDIR(old_inode->i_mode)) |
| inc_nlink(new_dir); |
| } |
| eufs_dbg("rename diradd +> %lld of %px 0x%lx\n", |
| new_dir->i_size, new_dir, new_dir->i_ino); |
| } |
| /* update dir time */ |
| new_dir->i_ctime = new_dir->i_mtime = current_time(new_dir); |
| |
| /* -------------- get old dentry info -------------- */ |
| /* get old filename */ |
| old_hv = hash(old_dentry->d_name.name, old_dentry->d_name.len); |
| name = old_dentry->d_name.name; |
| if (!name) { |
| BUG(); |
| return -ENOENT; |
| } |
| |
| /* -------------- removal ---------------- */ |
| old_de = nv_dict_del_wrapper(old_dir, &old_prevde, &old_dir_nv_header, |
| old_hv, name); |
| if (unlikely(!old_de)) { |
| BUG(); |
| return -ENOENT; |
| } |
| /* We have no dep in rename. Just release the header lock */ |
| inode_header_unlock(old_dir); |
| |
| if (!in_same_dir || new_inode) { |
| old_dir->i_size--; |
| if (S_ISDIR(old_inode->i_mode)) |
| drop_nlink(old_dir); |
| } |
| if (old_dir != new_dir) |
| old_dir->i_ctime = old_dir->i_mtime = new_dir->i_ctime; |
| |
| eufs_dbg("rename dirdel -> %lld of %px 0x%lx\n", old_dir->i_size, |
| old_dir, old_dir->i_ino); |
| |
| /* old_inode may NOT be locked ? */ |
| /* update ctime of source inode */ |
| old_inode->i_ctime = new_dir->i_ctime; |
| if (!in_same_dir && S_ISDIR(old_inode->i_mode)) { |
| /* update parent pointer of source inode */ |
| struct eufs_inode_info *vi = EUFS_I(old_inode); |
| |
| vi->i_dotdot = p2o(sb, EUFS_HEAD_PI(new_dir_pi)); |
| } |
| |
| NV_ASSERT(new_de->inode == old_de->inode); |
| NV_ASSERT(eufs_valid_inode_in_de(old_de, old_inode)); |
| NV_ASSERT(old_inode->i_mode == |
| eufs_iread_mode(EUFS_FRESH_PI( |
| (struct eufs_inode *)(s2p(sb, old_de->inode))))); |
| |
| if (!new_inode) { |
| struct alloc_batch ab; |
| |
| ab.n_used = 0; |
| ab.size = 16; |
| ab.batch = buffer; |
| |
| eufs_alloc_batch_add(old_dir->i_sb, &ab, new_de); |
| persist_name(old_dir->i_sb, new_de, &ab); |
| eufs_dentry_clr_not_persist_flag(new_de); |
| persist_dentry(new_de); |
| |
| eufs_alloc_batch_persist_reset(old_dir->i_sb, &ab); |
| } |
| |
| cpu = get_cpu(); |
| /* RenameJ is redo log */ |
| renamej = eufs_get_renamej(old_dir->i_sb, cpu); |
| renamej->crc = 0; |
| renamej->flags = 0; |
| /* address to put old_de->next */ |
| renamej->addr_of_oldnext = |
| p2s(sb, (old_prevde ? &old_prevde->next : |
| (void *)old_dir_nv_header)); |
| /* the value: old_de->next */ |
| renamej->oldnext = |
| (old_prevde ? |
| old_de->next : |
| old_de->next == EUFS_DIR_EOC ? |
| NULL_VAL : |
| COMPOSE_DICT_HEAD_le64(sb, s2p(sb, old_de->next))); |
| |
| /* address to put new_de if necessary */ |
| renamej->addr_of_newde = p2s(sb, new_dir_nv_header); |
| /* the value: new_de */ |
| renamej->composed_newde = COMPOSE_DICT_HEAD_le64(sb, new_de); |
| /* the value: new_de->inode */ |
| renamej->newde_inode = p2s(sb, EUFS_HEAD_PI(pi)); |
| |
| /* dir pi */ |
| renamej->old_dir_pi = p2s(sb, EUFS_HEAD_PI(old_dir_pi)); |
| renamej->new_dir_pi = p2s(sb, EUFS_HEAD_PI(new_dir_pi)); |
| /* inode attributes */ |
| renamej->time = cpu_to_le64(new_dir->i_ctime.tv_sec); |
| renamej->time_nsec = cpu_to_le32(new_dir->i_ctime.tv_nsec); |
| renamej->old_link = cpu_to_le16(old_dir->i_nlink); |
| renamej->new_link = cpu_to_le16(new_dir->i_nlink); |
| renamej->old_size = cpu_to_le32(old_dir->i_size); |
| renamej->new_size = cpu_to_le32(new_dir->i_size); |
| memset(renamej->pad, 0, sizeof(renamej->pad)); |
| |
| renamej->flags = EUFS_RENAME_IN_ACTION; |
| renamej->crc = cpu_to_le32( |
| crc32c(EUFS_CRC_SEED, (char *)renamej + sizeof(renamej->crc), |
| sizeof(*renamej) - sizeof(renamej->crc))); |
| |
| eufs_flush_cacheline((char *)renamej + CACHELINE_SIZE); |
| eufs_flush_cacheline(renamej); |
| |
| if (old_prevde) { |
| old_prevde->next = old_de->next; |
| } else { |
| if (old_de->next == EUFS_DIR_EOC) |
| *old_dir_nv_header = NULL_VAL; |
| else |
| *old_dir_nv_header = COMPOSE_DICT_HEAD_le64( |
| sb, s2p(sb, old_de->next)); |
| } |
| eufs_flush_cacheline(old_prevde ? (void *)&old_prevde->next : |
| (void *)old_dir_nv_header); |
| |
| vde = &(EUFS_I(old_dir)->i_volatile_dict->table[INDEX(old_hv)]); |
| if (*vde) { |
| bool vbool = (*vde == NULL || *vde == (void *)EUFS_DIR_EOC); |
| bool pbool = (*old_dir_nv_header == NULL_VAL || |
| *old_dir_nv_header == EUFS_DIR_EOC); |
| BUG_ON(vbool != pbool); |
| *vde = NULL; |
| } |
| |
| if (new_inode) { |
| new_de->inode = p2s(sb, EUFS_HEAD_PI(pi)); |
| eufs_flush_cacheline(new_de); |
| } else { |
| *new_dir_nv_header = COMPOSE_DICT_HEAD_le64(sb, new_de); |
| eufs_flush_cacheline(new_dir_nv_header); |
| vde = &EUFS_I(new_dir)->i_volatile_dict->table[INDEX(new_hv)]; |
| if (*vde) { |
| bool vbool = |
| (*vde == NULL || *vde == (void *)EUFS_DIR_EOC); |
| bool pbool = (*new_dir_nv_header == NULL_VAL || |
| *new_dir_nv_header == EUFS_DIR_EOC); |
| BUG_ON(vbool != pbool); |
| *vde = NULL; |
| } |
| } |
| |
| eufs_iwrite_size(old_dir_pi, old_dir->i_size); |
| eufs_iwrite_nlink(old_dir_pi, old_dir->i_nlink); |
| eufs_iwrite_ctime_mtime(old_dir_pi, old_dir); |
| eufs_flush_pi(old_dir_pi); |
| |
| if (old_dir != new_dir) { |
| eufs_iwrite_size(new_dir_pi, new_dir->i_size); |
| eufs_iwrite_nlink(new_dir_pi, new_dir->i_nlink); |
| eufs_iwrite_ctime_mtime(new_dir_pi, new_dir); |
| eufs_flush_pi(new_dir_pi); |
| } |
| |
| eufs_iwrite_ctime(pi, old_inode->i_ctime.tv_sec); |
| eufs_iwrite_ctime_nsec(pi, old_inode->i_ctime.tv_nsec); |
| if (!in_same_dir && S_ISDIR(old_inode->i_mode)) { |
| struct eufs_inode_info *vi = EUFS_I(old_inode); |
| |
| eufs_iwrite_dotdot(pi, vi->i_dotdot); |
| } |
| eufs_flush_pi(pi); |
| |
| renamej->flags = 0; |
| eufs_flush_cacheline(renamej); |
| put_cpu(); |
| |
| /* remove overwritten inode */ |
| if (new_inode) |
| iput(new_inode); |
| |
| /* remove the source dentry */ |
| eufs_free_name(old_dir->i_sb, old_de); |
| nv_free(old_dir->i_sb, old_de); |
| |
| eufs_dbg("%s: renamed %s to %s , old_pi=%llx new_pi=%llx\n", __func__, |
| old_dentry->d_name.name, new_dentry->d_name.name, |
| old_de->inode, new_de->inode); |
| |
| return 0; |
| } |
| |
| const struct inode_operations eufs_dir_inode_operations = { |
| .create = eufs_create, |
| .lookup = eufs_lookup, |
| .link = eufs_link, |
| .unlink = eufs_unlink, |
| .symlink = eufs_symlink, |
| .mkdir = eufs_mkdir, |
| .rmdir = eufs_rmdir, |
| .mknod = eufs_mknod, |
| .rename = eufs_rename, |
| .setattr = eufs_notify_change, |
| }; |
| |
| const struct inode_operations eufs_special_inode_operations = { |
| .setattr = eufs_notify_change, |
| }; |