// 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 #include #include #include #include #include "euler.h" #include "dht.h" #include "dep.h" #define GET_CRASH_VER(ptr) (((unsigned long)ptr) >> 56) void *fix_table(struct super_block *sb, struct nv_dict *dict, u32 idx) { struct nv_dict_entry *he; void *real_head; u64 head_off; head_off = le64_to_cpu(dict->table[idx]); if (!head_off) return NULL; if (head_off == EUFS_DIR_EOC) return NULL; real_head = o2p(sb, DICT_HEAD_REAL_OFF(head_off)); if (likely(GET_CRASH_VER(head_off) == EUFS_SB(sb)->s_crash_ver)) { /* No need fix */ return real_head; } he = real_head; BUG_ON(he == EUFS_DIR_EOC_PTR); while (he && he != EUFS_DIR_EOC_PTR) { if (he->volatile_next) { he->volatile_next = NULL_VAL; eufs_flush_cacheline(he); } he = s2p(sb, he->next); } dict->table[idx] = COMPOSE_DICT_HEAD_le64(sb, real_head); eufs_flush_cacheline(&dict->table[idx]); eufs_pbarrier(); return real_head; } /* * Insert to nv_dict using spinlocks. * NOTICE: No resizing supported yet! * Previous dentry is always the pointer */ struct nv_dict_entry *nv_dict_add(struct inode *dir, u64 **nv_header, u64 h, const char *key, struct eufs_inode *pi) { struct super_block *sb = dir->i_sb; struct nv_dict __pmem *dict = o2p(sb, eufs_iread_dict(EUFS_FRESH_PI(EUFS_PI(dir)))); struct v_dict *volatile_dict = EUFS_I(dir)->i_volatile_dict; u32 idx; long err; struct nv_dict_entry __pmem *de; idx = INDEX(h); NV_ASSERT(dict); NV_ASSERT(volatile_dict); /* NOTICE: simplified version w/o resizing */ de = eufs_malloc_dentry(sb); if (!de) return ERR_PTR(-ENOSPC); err = copy_filename(sb, de, h, key); if (IS_ERR_VALUE(err)) { nv_free(sb, de); return ERR_PTR(-ENOSPC); } WARN_ON(!EUFS_IS_HEAD_PI(pi)); de->inode = p2s(sb, pi); de->next = p2s(sb, (volatile_dict->table[idx]) ? (volatile_dict->table[idx] == EUFS_DIR_EOC_PTR ? NULL : volatile_dict->table[idx]) : fix_table(sb, dict, idx)); eufs_dentry_set_not_persist_flag(de); PRINT_DENTRY(de, "new dentry: "); PRINT_PINODE(de->inode, "inode within dentry: "); *nv_header = &dict->table[idx]; /* Lock the header. It's to be released right after dep is locked. */ inode_header_lock(dir); volatile_dict->table[idx] = de; return de; } /* * Find from nv_dict with the protection of spinlock. * No resizing support yet! */ struct nv_dict_entry *nv_dict_find(struct inode *dir, hashlen_t h, const char *key) { struct super_block *sb = dir->i_sb; struct nv_dict __pmem *dict = o2p(sb, eufs_iread_dict(EUFS_FRESH_PI(EUFS_PI(dir)))); struct v_dict *volatile_dict = EUFS_I(dir)->i_volatile_dict; struct nv_dict_entry *he; unsigned int idx; idx = INDEX(h); /* * volatile_dict->table[idx] can be EOC after * all entries have been deleted */ if (volatile_dict && volatile_dict->table[idx]) he = volatile_dict->table[idx]; else he = fix_table(sb, dict, idx); while (he && he != EUFS_DIR_EOC_PTR) { __le64 vnext; if (key_equals(sb, key, h, he)) break; vnext = eufs_dentry_vnext(he); he = s2p(sb, vnext ? vnext : he->next); } if (he == EUFS_DIR_EOC_PTR) he = NULL; return he; } /* * Delete from nv_dict w/ spinlocks. * No resizing support yet! * Previous dentry is also returned */ struct nv_dict_entry *nv_dict_delete(struct inode *dir, struct nv_dict_entry **prevde, u64 **nv_header, hashlen_t h, const char *key) { struct super_block *sb = dir->i_sb; struct nv_dict __pmem *dict = o2p(sb, eufs_iread_dict(EUFS_FRESH_PI(EUFS_PI(dir)))); struct eufs_inode_info *dir_vi = EUFS_I(dir); struct v_dict *volatile_dict = dir_vi->i_volatile_dict; struct nv_dict_entry *he; struct nv_dict_entry *prev = NULL; unsigned int idx; __le64 vnext; NV_ASSERT(dict); NV_ASSERT(volatile_dict); idx = INDEX(h); he = volatile_dict->table[idx] ? volatile_dict->table[idx] : fix_table(sb, dict, idx); while (he && he != EUFS_DIR_EOC_PTR) { if (key_equals(sb, key, h, he)) break; prev = he; vnext = eufs_dentry_vnext(he); /* EOC is not NULL, so it's okay. */ he = s2p(sb, vnext ? vnext : he->next); } if (he && he != EUFS_DIR_EOC_PTR) { /* Lock the header. It's to be released right after dep is locked. */ inode_header_lock(dir); vnext = eufs_dentry_vnext(he); if (!prev) { /* * the first dentry (head of the chain). * If the target is the end of chain, it is the only * dentry in the chain, then either its volatile_next * is EOC, or its next is NULL. */ volatile_dict->table[idx] = s2p(sb, vnext ? vnext : he->next); if (volatile_dict->table[idx] == NULL) volatile_dict->table[idx] = EUFS_DIR_EOC_PTR; } else { bool persist_prev = !eufs_dentry_is_not_persist(prev); if (!persist_prev) { /* * Protect against the persistence of prev dentry * by background persister. */ spin_lock(&dir_vi->i_dentry_persist_lock); persist_prev = !eufs_dentry_is_not_persist(prev); if (!persist_prev) { /* * Prev is a newly created dentry, * Keep the property, * Two pointers are updated together, * no need to worry about the EOC. */ prev->next = vnext ? (vnext == EUFS_DIR_EOC ? NULL_VAL : vnext) : he->next; eufs_dentry_set_not_persist_flag(prev); } spin_unlock(&dir_vi->i_dentry_persist_lock); } if (persist_prev) prev->volatile_next = vnext ? vnext : (he->next ? he->next : EUFS_DIR_EOC); } if (eufs_dentry_is_not_persist(he)) he->volatile_next = EUFS_DIR_DELNEW; *prevde = prev; /* table[idx] must have been fixed, so it's OK to return it. */ *nv_header = &dict->table[idx]; } else if (he == EUFS_DIR_EOC_PTR) { he = NULL; } return he; } void nv_dict_scan_via_ptr(struct inode *dir, u64 pos, int (*fn)(void *privdata, const struct nv_dict_entry *de), void *privdata) { struct super_block *sb = dir->i_sb; struct nv_dict __pmem *dict = o2p(sb, eufs_iread_dict(EUFS_FRESH_PI(EUFS_PI(dir)))); struct v_dict *volatile_dict = EUFS_I(dir)->i_volatile_dict; const struct nv_dict_entry *de = 0; u64 idx; u64 i; u64 skip; struct dir_scan_data *data = (struct dir_scan_data *)privdata; struct dir_context *ctx = data->ctx; int err; if (ctx->pos == EUFS_DIR_DOTDOT) { idx = 0; skip = 0; } else { idx = CURSOR_IDX(pos); skip = CURSOR_CNT(pos); } /* Next to emit: the skip-th element in dict->table[idx] */ while (idx < NV_DICT_CAPACITY) { if (!de) { eufs_ptr_fast_check(dict); eufs_ptr_fast_check(dict->table); de = (volatile_dict && volatile_dict->table[idx]) ? volatile_dict->table[idx] : fix_table(sb, dict, idx); } i = 0; while (de && de != EUFS_DIR_EOC_PTR) { __le64 vnext; /* current is the i-th de in list */ /* skip de's remaining to skip */ if (skip == 0) { err = fn(privdata, de); if (err) return; /* ctx->pos points to the next de */ ctx->pos = CURSOR(idx, i + 1); } else skip--; i++; vnext = eufs_dentry_vnext(de); de = s2p(sb, vnext ? vnext : de->next); } if (de == EUFS_DIR_EOC_PTR) de = NULL; idx++; /* next idx */ skip = 0; } ctx->pos = EUFS_DIR_EODIR; }