From nobody Tue Apr 7 06:02:28 2026 Received: from mail-yw1-f171.google.com (mail-yw1-f171.google.com [209.85.128.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EACEA33122A for ; Mon, 16 Mar 2026 02:20:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773627615; cv=none; b=rLiTNLYILKL6iSHSmnbSDCIP6+5KovCRQhEBjnTfrpqh6mYBN6NQqDjM3X6TWpmGGWNFx4i43GCBIxOzMoxFPKF4x8FXvpfoSDaIlBPk7JGCVj08swM92MavqtP0S//sCT3WCzXvPVOjUobovvcrQuDw5+uvcdglWWn1qA64W4M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773627615; c=relaxed/simple; bh=42MquAxGtlvevN1u/3R4ChkKdJ/yVfcsptKK2I3NZUg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lNx0jf9/LCZ1uk6dAtXIf63NjotkhEG2h0UR/Egf0LPhTjthZTLtdWZhqkX8hxT78aGVxXmr+oShTFvkTt9+Tr491+jIu0+gXku22EDhbAW6jJk68SecJV/tMQR22JLb+wXb3snRAngnyUkJJRxHQ7+V4JzSqOU9Jc8c65BG9KI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=dubeyko.com; spf=pass smtp.mailfrom=dubeyko.com; dkim=pass (2048-bit key) header.d=dubeyko-com.20230601.gappssmtp.com header.i=@dubeyko-com.20230601.gappssmtp.com header.b=KPLZlfVq; arc=none smtp.client-ip=209.85.128.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=dubeyko.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=dubeyko.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=dubeyko-com.20230601.gappssmtp.com header.i=@dubeyko-com.20230601.gappssmtp.com header.b="KPLZlfVq" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-7982c3b7da9so34762647b3.1 for ; Sun, 15 Mar 2026 19:20:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dubeyko-com.20230601.gappssmtp.com; s=20230601; t=1773627606; x=1774232406; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Gy66Wdjab2k0pSpgemHi1McaislayxDtLmSv/5I8Ok0=; b=KPLZlfVqu34XSRRDJYM7hmLDjmEGA+ksHAsa9RcHgpnB4zojvnBURp6uNwY7UX1za4 w1UlUhuw2x93HVZKmGH+LdRVIBEge5QCr79Mw3fJdBmfX45wReWrMM0/I9HNkxU/e4Ba O7wYI2S2Nt3iFxMahB3D11UOKAs5Fs7H+o7BMFP2pelv43YmgKYNP8GE5+gc7LoSSsnL GPjzVBb18oBqyIzTwj5ot92ST95FRRT8ZzCO++qeQPL49ABiaj2cNbe0hjOcURM956TD 3He6k3lHcyGH+4Mv/ORAr+8y7I8yR+XyjW8DQDqkj7zCJXT74LdOwrMY1d+pH4A6Hokh g/8w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773627606; x=1774232406; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Gy66Wdjab2k0pSpgemHi1McaislayxDtLmSv/5I8Ok0=; b=i2KR/n4g6KabpHQRD6LDXZszamwkq3TFjjvJA70g/I2qiTuS3qduUvEsRzAhJaTENH iNX3Hjh9ZMUQPF0/woiwxnD2+uf9ymEs3tpl6AvsTZl8j2qfs+iqtaYMzD2Dg1URojxq TYBJAvESdGMQQcFfpuvYUCbqRS0PCExPj8VywHvKwWjn9JtUmZjQH6kTTKxG0AZOMnJ5 aKO/2lOyaJzh/1qhIOmkhy5l2hVrfOoursmGVYN3l1nfF34B4x0BM7Ii3Dy3Xobf4B3p 5/h577H2wK1+6tFqfIfaJm26S9WdX0VfcS95/b84BNlqiyjTO3zGfUN1f6vQQjx/AyLJ I0sw== X-Gm-Message-State: AOJu0Yw7hFdHVdTbPULHudP2swH8+aPSXlz/c+AflWSU+aKxLuu2nu+i mJ/pmosF/vTArvzGihxq0AlkCKr2bkFzUQsO32DnLE49VWMCrEAKM5F9h1RCFMdKy1bZezotYM2 OvCjd X-Gm-Gg: ATEYQzwFKOWVhCBfw216QEiGSYIlcEe7fAVwivT91MsI8BrZOsEEhbLnmHjobC1h9A/ FzwXRjMrTuHRPHZ5eOL9nvz92LGdAxoEPkC5qW29+5G+bq57RbwtKYETjK8lAVGT+r0tf+3+bGO lgVqLrwdz5g44+pXFnCtSTpCFXmGWNkUnc0DIOd3digewV9SRgT/K8lDzWWMe4vBsnpZBJQuekp li3Zj5cIifXA0VrZi9w2ZozBmDxKdFMpTVdK6ZRL5GyW0fxjxim4WjRufmJbCRh4dJJU7Q+LfnF jNUOtj4dhnxhxhxu+rL+3rUVQsYznIsWTL0Z/A2k98/ToxPdgjK1jGBydJrKE9VoVIRmYdzCgQv Dhfm1pEpk+gCu9jeW+qBaLtB4DW5rlEeRs5IpMwGPdxNe6jTAGM2/zDzYtXLem5Wl41sNshTQrK V6ogseOK71yTemI3eZFNGAYEvs1FDoztv9Tt1HawE0YRzBcGaseDzbvrJ7MOHNNdvGx2qzHzn1j HinbeDqRzDVXpPkvScSJhRb X-Received: by 2002:a05:690c:7245:b0:797:a27b:864c with SMTP id 00721157ae682-79a1c184589mr116442047b3.38.1773627605425; Sun, 15 Mar 2026 19:20:05 -0700 (PDT) Received: from pop-os.attlocal.net ([2600:1700:6476:1430:6939:3e01:5e8f:6093]) by smtp.gmail.com with ESMTPSA id 00721157ae682-79917deb69asm79721617b3.10.2026.03.15.19.20.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Mar 2026 19:20:04 -0700 (PDT) From: Viacheslav Dubeyko To: linux-fsdevel@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Viacheslav Dubeyko Subject: [PATCH v2 76/79] ssdfs: implement directory operations support Date: Sun, 15 Mar 2026 19:17:59 -0700 Message-ID: <20260316021800.1694650-33-slava@dubeyko.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260316021800.1694650-1-slava@dubeyko.com> References: <20260316021800.1694650-1-slava@dubeyko.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Complete patchset is available here: https://github.com/dubeyko/ssdfs-driver/tree/master/patchset/linux-kernel-6= .18.0 Implement directory operations. Signed-off-by: Viacheslav Dubeyko --- fs/ssdfs/dir.c | 2197 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2197 insertions(+) create mode 100644 fs/ssdfs/dir.c diff --git a/fs/ssdfs/dir.c b/fs/ssdfs/dir.c new file mode 100644 index 000000000000..2565bfbfdf57 --- /dev/null +++ b/fs/ssdfs/dir.c @@ -0,0 +1,2197 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause-Clear + * + * SSDFS -- SSD-oriented File System. + * + * fs/ssdfs/dir.c - folder operations. + * + * Copyright (c) 2019-2026 Viacheslav Dubeyko + * http://www.ssdfs.org/ + * All rights reserved. + * + * Authors: Viacheslav Dubeyko + */ + +#include +#include +#include +#include +#include + +#include "peb_mapping_queue.h" +#include "peb_mapping_table_cache.h" +#include "folio_vector.h" +#include "ssdfs.h" +#include "btree_search.h" +#include "btree_node.h" +#include "btree.h" +#include "dentries_tree.h" +#include "shared_dictionary.h" +#include "xattr.h" +#include "acl.h" + +#include + +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING +atomic64_t ssdfs_dir_folio_leaks; +atomic64_t ssdfs_dir_memory_leaks; +atomic64_t ssdfs_dir_cache_leaks; +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ + +/* + * void ssdfs_dir_cache_leaks_increment(void *kaddr) + * void ssdfs_dir_cache_leaks_decrement(void *kaddr) + * void *ssdfs_dir_kmalloc(size_t size, gfp_t flags) + * void *ssdfs_dir_kzalloc(size_t size, gfp_t flags) + * void *ssdfs_dir_kcalloc(size_t n, size_t size, gfp_t flags) + * void ssdfs_dir_kfree(void *kaddr) + * struct folio *ssdfs_dir_alloc_folio(gfp_t gfp_mask, + * unsigned int order) + * struct folio *ssdfs_dir_add_batch_folio(struct folio_batch *batch, + * unsigned int order) + * void ssdfs_dir_free_folio(struct folio *folio) + * void ssdfs_dir_folio_batch_release(struct folio_batch *batch) + */ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + SSDFS_MEMORY_LEAKS_CHECKER_FNS(dir) +#else + SSDFS_MEMORY_ALLOCATOR_FNS(dir) +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ + +void ssdfs_dir_memory_leaks_init(void) +{ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + atomic64_set(&ssdfs_dir_folio_leaks, 0); + atomic64_set(&ssdfs_dir_memory_leaks, 0); + atomic64_set(&ssdfs_dir_cache_leaks, 0); +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ +} + +void ssdfs_dir_check_memory_leaks(void) +{ +#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING + if (atomic64_read(&ssdfs_dir_folio_leaks) !=3D 0) { + SSDFS_ERR("DIR: " + "memory leaks include %lld folios\n", + atomic64_read(&ssdfs_dir_folio_leaks)); + } + + if (atomic64_read(&ssdfs_dir_memory_leaks) !=3D 0) { + SSDFS_ERR("DIR: " + "memory allocator suffers from %lld leaks\n", + atomic64_read(&ssdfs_dir_memory_leaks)); + } + + if (atomic64_read(&ssdfs_dir_cache_leaks) !=3D 0) { + SSDFS_ERR("DIR: " + "caches suffers from %lld leaks\n", + atomic64_read(&ssdfs_dir_cache_leaks)); + } +#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */ +} + +static unsigned char +ssdfs_filetype_table[SSDFS_FT_MAX] =3D { + [SSDFS_FT_UNKNOWN] =3D DT_UNKNOWN, + [SSDFS_FT_REG_FILE] =3D DT_REG, + [SSDFS_FT_DIR] =3D DT_DIR, + [SSDFS_FT_CHRDEV] =3D DT_CHR, + [SSDFS_FT_BLKDEV] =3D DT_BLK, + [SSDFS_FT_FIFO] =3D DT_FIFO, + [SSDFS_FT_SOCK] =3D DT_SOCK, + [SSDFS_FT_SYMLINK] =3D DT_LNK, +}; + +int ssdfs_inode_by_name(struct inode *dir, + const struct qstr *child, + ino_t *ino) +{ + struct ssdfs_inode_info *ii =3D SSDFS_I(dir); + struct ssdfs_btree_search *search; + struct ssdfs_dir_entry *raw_dentry; + size_t dentry_size =3D sizeof(struct ssdfs_dir_entry); + int private_flags; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!rwsem_is_locked(&ii->lock)); + + SSDFS_DBG("dir_ino %lu, target_name %s\n", + (unsigned long)dir->i_ino, + child->name); +#endif /* CONFIG_SSDFS_DEBUG */ + + *ino =3D 0; + private_flags =3D atomic_read(&ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + if (!ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree absent!!!\n"); + goto finish_search_dentry; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_search_dentry; + } + + ssdfs_btree_search_init(search); + + err =3D ssdfs_dentries_tree_find(ii->dentries_tree, + child->name, + child->len, + search); + if (err =3D=3D -ENODATA) { + err =3D -ENOENT; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu hasn't child %s\n", + (unsigned long)dir->i_ino, + child->name); +#endif /* CONFIG_SSDFS_DEBUG */ + goto dentry_is_not_available; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find the dentry: " + "dir %lu, child %s\n", + (unsigned long)dir->i_ino, + child->name); + goto dentry_is_not_available; + } + + if (search->result.state !=3D SSDFS_BTREE_SEARCH_VALID_ITEM) { + err =3D -ERANGE; + SSDFS_ERR("invalid result's state %#x\n", + search->result.state); + goto dentry_is_not_available; + } + + switch (search->result.raw_buf.state) { + case SSDFS_BTREE_SEARCH_INLINE_BUFFER: + case SSDFS_BTREE_SEARCH_EXTERNAL_BUFFER: + /* expected state */ + break; + + default: + err =3D -ERANGE; + SSDFS_ERR("invalid buffer state %#x\n", + search->result.raw_buf.state); + goto dentry_is_not_available; + } + + if (!search->result.raw_buf.place.ptr) { + err =3D -ERANGE; + SSDFS_ERR("buffer is absent\n"); + goto dentry_is_not_available; + } + + if (search->result.raw_buf.size < dentry_size) { + err =3D -ERANGE; + SSDFS_ERR("buf_size %zu < dentry_size %zu\n", + search->result.raw_buf.size, + dentry_size); + goto dentry_is_not_available; + } + + raw_dentry =3D + (struct ssdfs_dir_entry *)search->result.raw_buf.place.ptr; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(le64_to_cpu(raw_dentry->ino) >=3D U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + *ino =3D (ino_t)le64_to_cpu(raw_dentry->ino); + +dentry_is_not_available: + ssdfs_btree_search_free(search); + } else { + err =3D -ENOENT; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dentries tree is absent: " + "ino %lu\n", + (unsigned long)dir->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + } + +finish_search_dentry: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; +} + +/* + * The ssdfs_lookup() is called when the VFS needs + * to look up an inode in a parent directory. + */ +static struct dentry *ssdfs_lookup(struct inode *dir, struct dentry *targe= t, + unsigned int flags) +{ + struct inode *inode =3D NULL; + ino_t ino; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, flags %#x\n", (unsigned long)dir->i_ino, flags); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (target->d_name.len > SSDFS_MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + down_read(&SSDFS_I(dir)->lock); + err =3D ssdfs_inode_by_name(dir, &target->d_name, &ino); + up_read(&SSDFS_I(dir)->lock); + + if (err =3D=3D -ENOENT) { + err =3D 0; + ino =3D 0; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find the inode: " + "err %d\n", + err); + return ERR_PTR(err); + } + + if (ino) { + inode =3D ssdfs_iget(dir->i_sb, ino); + if (inode =3D=3D ERR_PTR(-ESTALE)) { + SSDFS_ERR("deleted inode referenced: %lu\n", + (unsigned long)ino); + return ERR_PTR(-EIO); + } + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return d_splice_alias(inode, target); +} + +static int ssdfs_add_link(struct inode *dir, struct dentry *dentry, + struct inode *inode) +{ + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(dir->i_sb); + struct ssdfs_inode_info *dir_ii =3D SSDFS_I(dir); + struct ssdfs_inode_info *ii =3D SSDFS_I(inode); + struct ssdfs_btree_search *search; + int private_flags; + struct timespec64 cur_time; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!rwsem_is_locked(&dir_ii->lock)); + + SSDFS_DBG("Created ino %lu with mode %o, nlink %d, nrpages %ld\n", + (unsigned long)inode->i_ino, inode->i_mode, + inode->i_nlink, inode->i_mapping->nrpages); +#endif /* CONFIG_SSDFS_DEBUG */ + + private_flags =3D atomic_read(&dir_ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + if (!dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree absent!!!\n"); + goto finish_add_link; + } + } else { + if (dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree exists unexpectedly!!!\n"); + goto finish_create_dentries_tree; + } else { + err =3D ssdfs_dentries_tree_create(fsi, dir_ii); + if (unlikely(err)) { + SSDFS_ERR("fail to create the dentries tree: " + "ino %lu, err %d\n", + dir->i_ino, err); + goto finish_create_dentries_tree; + } + + atomic_or(SSDFS_INODE_HAS_INLINE_DENTRIES, + &dir_ii->private_flags); + } + +finish_create_dentries_tree: + if (unlikely(err)) + goto finish_add_link; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_add_link; + } + + ssdfs_btree_search_init(search); + + err =3D ssdfs_dentries_tree_add(dir_ii->dentries_tree, + &dentry->d_name, + ii, search); + if (unlikely(err)) { + SSDFS_ERR("fail to add the dentry: " + "ino %lu, err %d\n", + inode->i_ino, err); + } else { + cur_time =3D current_time(dir); + inode_set_mtime_to_ts(dir, cur_time); + inode_set_ctime_to_ts(dir, cur_time); + mark_inode_dirty(dir); + } + + ssdfs_btree_search_free(search); + +finish_add_link: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; +} + +static int ssdfs_add_nondir(struct inode *dir, struct dentry *dentry, + struct inode *inode) +{ + struct ssdfs_inode_info *dir_ii =3D SSDFS_I(dir); + int private_flags; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("Created ino %lu with mode %o, nlink %d, nrpages %ld\n", + (unsigned long)inode->i_ino, inode->i_mode, + inode->i_nlink, inode->i_mapping->nrpages); +#endif /* CONFIG_SSDFS_DEBUG */ + + private_flags =3D atomic_read(&dir_ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_read(&dir_ii->lock); + } else { + down_write(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_write(&dir_ii->lock); + } + + if (err) { + inode_dec_link_count(inode); + iget_failed(inode); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; + } + + unlock_new_inode(inode); + d_instantiate(dentry, inode); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return 0; +} + +/* + * The ssdfs_create() is called by the open(2) and + * creat(2) system calls. + */ +int ssdfs_create(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *dentry, + umode_t mode, bool excl) +{ + struct inode *inode; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, mode %o\n", (unsigned long)dir->i_ino, mode); +#endif /* CONFIG_SSDFS_DEBUG */ + + inode =3D ssdfs_new_inode(idmap, dir, mode, &dentry->d_name); + if (IS_ERR(inode)) { + err =3D PTR_ERR(inode); + goto failed_create; + } + + mark_inode_dirty(inode); + err =3D ssdfs_add_nondir(dir, dentry, inode); + +failed_create: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; +} + +/* + * The ssdfs_mknod() is called by the mknod(2) system call + * to create a device (char, block) inode or a named pipe + * (FIFO) or socket. + */ +static int ssdfs_mknod(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *dentry, + umode_t mode, dev_t rdev) +{ + struct inode *inode; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, mode %o, rdev %#x\n", + (unsigned long)dir->i_ino, mode, rdev); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (dentry->d_name.len > SSDFS_MAX_NAME_LEN) + return -ENAMETOOLONG; + + inode =3D ssdfs_new_inode(idmap, dir, mode, &dentry->d_name); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + init_special_inode(inode, mode, rdev); + + mark_inode_dirty(inode); + err =3D ssdfs_add_nondir(dir, dentry, inode); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * Create symlink. + * The ssdfs_symlink() is called by the symlink(2) system call. + */ +static int ssdfs_symlink(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *dentry, + const char *target) +{ + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(dir->i_sb); + struct inode *inode; + size_t target_len =3D strlen(target) + 1; + size_t raw_inode_size; + size_t inline_len; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, target_len %zu\n", + (unsigned long)dir->i_ino, target_len); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (target_len > dir->i_sb->s_blocksize) + return -ENAMETOOLONG; + + down_read(&fsi->volume_sem); + raw_inode_size =3D le16_to_cpu(fsi->vs->inodes_btree.desc.item_size); + up_read(&fsi->volume_sem); + + inline_len =3D offsetof(struct ssdfs_inode, internal); + + if (raw_inode_size <=3D inline_len) { + SSDFS_ERR("invalid raw inode size %zu\n", + raw_inode_size); + return -EFAULT; + } + + inline_len =3D raw_inode_size - inline_len; + + inode =3D ssdfs_new_inode(idmap, dir, S_IFLNK | S_IRWXUGO, &dentry->d_nam= e); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + if (target_len > inline_len) { + /* slow symlink */ + inode_nohighmem(inode); + + err =3D page_symlink(inode, target, target_len); + if (err) + goto out_fail; + } else { + /* fast symlink */ + down_write(&SSDFS_I(inode)->lock); + inode->i_link =3D (char *)SSDFS_I(inode)->raw_inode.internal; + memcpy(inode->i_link, target, target_len); + inode->i_size =3D target_len - 1; + atomic_or(SSDFS_INODE_HAS_INLINE_FILE, + &SSDFS_I(inode)->private_flags); + up_write(&SSDFS_I(inode)->lock); + } + + mark_inode_dirty(inode); + err =3D ssdfs_add_nondir(dir, dentry, inode); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; + +out_fail: + inode_dec_link_count(inode); + iget_failed(inode); + return err; +} + +/* + * Create hardlink. + * The ssdfs_link() is called by the link(2) system call. + */ +static int ssdfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *inode =3D d_inode(old_dentry); + struct ssdfs_inode_info *dir_ii =3D SSDFS_I(dir); + int private_flags; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, inode %lu\n", + (unsigned long)dir->i_ino, (unsigned long)inode->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (inode->i_nlink >=3D SSDFS_LINK_MAX) + return -EMLINK; + + if (!S_ISREG(inode->i_mode)) + return -EPERM; + + inode_set_ctime_to_ts(inode, current_time(inode)); + inode_inc_link_count(inode); + ihold(inode); + + private_flags =3D atomic_read(&dir_ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_read(&dir_ii->lock); + } else { + down_write(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_write(&dir_ii->lock); + } + + if (err) { + inode_dec_link_count(inode); + iput(inode); + return err; + } + + d_instantiate(dentry, inode); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return 0; +} + +/* + * Set the first fragment of directory. + */ +static int ssdfs_make_empty(struct inode *inode, struct inode *parent) +{ + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_inode_info *ii =3D SSDFS_I(inode); + struct ssdfs_inode_info *parent_ii =3D SSDFS_I(parent); + struct ssdfs_btree_search *search; + int private_flags; + struct qstr dot =3D QSTR_INIT(".", 1); + struct qstr dotdot =3D QSTR_INIT("..", 2); + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("Created ino %lu with mode %o, nlink %d, nrpages %ld\n", + (unsigned long)inode->i_ino, inode->i_mode, + inode->i_nlink, inode->i_mapping->nrpages); +#endif /* CONFIG_SSDFS_DEBUG */ + + private_flags =3D atomic_read(&ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&ii->lock); + + if (!ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree absent!!!\n"); + goto finish_make_empty_dir; + } + } else { + down_write(&ii->lock); + + if (ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree exists unexpectedly!!!\n"); + goto finish_create_dentries_tree; + } else { + err =3D ssdfs_dentries_tree_create(fsi, ii); + if (unlikely(err)) { + SSDFS_ERR("fail to create the dentries tree: " + "ino %lu, err %d\n", + inode->i_ino, err); + goto finish_create_dentries_tree; + } + + atomic_or(SSDFS_INODE_HAS_INLINE_DENTRIES, + &ii->private_flags); + } + +finish_create_dentries_tree: + downgrade_write(&ii->lock); + + if (unlikely(err)) + goto finish_make_empty_dir; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_make_empty_dir; + } + + ssdfs_btree_search_init(search); + + err =3D ssdfs_dentries_tree_add(ii->dentries_tree, + &dot, ii, search); + if (unlikely(err)) { + SSDFS_ERR("fail to add dentry: " + "ino %lu, err %d\n", + inode->i_ino, err); + goto free_search_object; + } + + err =3D ssdfs_dentries_tree_add(ii->dentries_tree, + &dotdot, parent_ii, + search); + if (unlikely(err)) { + SSDFS_ERR("fail to add dentry: " + "ino %lu, err %d\n", + parent->i_ino, err); + goto free_search_object; + } + +free_search_object: + ssdfs_btree_search_free(search); + +finish_make_empty_dir: + up_read(&ii->lock); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +static int __ssdfs_mkdir(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *dentry, umode_t mode) +{ + struct inode *inode; + struct ssdfs_inode_info *dir_ii =3D SSDFS_I(dir); + int private_flags; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, mode %o\n", + (unsigned long)dir->i_ino, mode); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (dentry->d_name.len > SSDFS_MAX_NAME_LEN) + return -ENAMETOOLONG; + + inode_inc_link_count(dir); + + inode =3D ssdfs_new_inode(idmap, dir, S_IFDIR | mode, &dentry->d_name); + err =3D PTR_ERR(inode); + if (IS_ERR(inode)) + goto out_dir; + + inode_inc_link_count(inode); + + err =3D ssdfs_make_empty(inode, dir); + if (err) + goto out_fail; + + private_flags =3D atomic_read(&dir_ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_read(&dir_ii->lock); + } else { + down_write(&dir_ii->lock); + err =3D ssdfs_add_link(dir, dentry, inode); + up_write(&dir_ii->lock); + } + + if (err) + goto out_fail; + + d_instantiate(dentry, inode); + unlock_new_inode(inode); + return 0; + +out_fail: + inode_dec_link_count(inode); + inode_dec_link_count(inode); + unlock_new_inode(inode); + iput(inode); +out_dir: + inode_dec_link_count(dir); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * Create subdirectory. + * The ssdfs_mkdir() is called by the mkdir(2) system call. + */ +static struct dentry *ssdfs_mkdir(struct mnt_idmap *idmap, struct inode *d= ir, + struct dentry *dentry, umode_t mode) +{ + return ERR_PTR(__ssdfs_mkdir(idmap, dir, dentry, mode)); +} + +/* + * Delete inode. + * The ssdfs_unlink() is called by the unlink(2) system call. + */ +static int ssdfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct ssdfs_inode_info *ii =3D SSDFS_I(dir); + struct inode *inode =3D d_inode(dentry); + struct ssdfs_btree_search *search; + int private_flags; + u64 name_hash; + struct timespec64 cur_time; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, inode %lu\n", + (unsigned long)dir->i_ino, (unsigned long)inode->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + + trace_ssdfs_unlink_enter(dir, dentry); + + private_flags =3D atomic_read(&ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&ii->lock); + + if (!ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_WARN("dentries tree absent!!!\n"); + goto finish_delete_dentry; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_delete_dentry; + } + + ssdfs_btree_search_init(search); + + name_hash =3D ssdfs_generate_name_hash(&dentry->d_name); + if (name_hash >=3D U64_MAX) { + err =3D -ERANGE; + SSDFS_ERR("invalid name hash\n"); + goto dentry_is_not_available; + } + + err =3D ssdfs_dentries_tree_delete(ii->dentries_tree, + name_hash, + inode->i_ino, + search); + if (unlikely(err)) { + SSDFS_ERR("fail to delete the dentry: " + "name_hash %llx, ino %lu, err %d\n", + name_hash, inode->i_ino, err); + goto dentry_is_not_available; + } + +dentry_is_not_available: + ssdfs_btree_search_free(search); + +finish_delete_dentry: + up_read(&ii->lock); + + if (unlikely(err)) + goto finish_unlink; + } else { + err =3D -ENOENT; + SSDFS_ERR("dentries tree is absent\n"); + goto finish_unlink; + } + + mark_inode_dirty(dir); + mark_inode_dirty(inode); + + cur_time =3D current_time(dir); + inode_set_ctime_to_ts(inode, cur_time); + inode_set_mtime_to_ts(dir, cur_time); + inode_set_ctime_to_ts(dir, cur_time); + + inode_dec_link_count(inode); + +finish_unlink: + trace_ssdfs_unlink_exit(inode, err); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +static inline bool ssdfs_empty_dir(struct inode *dir) +{ + struct ssdfs_inode_info *ii =3D SSDFS_I(dir); + bool is_empty =3D false; + int private_flags; + u64 dentries_count; + u64 threshold =3D 2; /* . and .. */ + + private_flags =3D atomic_read(&ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&ii->lock); + + if (!ii->dentries_tree) { + SSDFS_WARN("dentries tree absent!!!\n"); + is_empty =3D true; + } else { + dentries_count =3D + atomic64_read(&ii->dentries_tree->dentries_count); + + if (dentries_count > threshold) { + /* not empty folder */ + is_empty =3D false; + } else if (dentries_count < threshold) { + SSDFS_WARN("unexpected dentries count %llu\n", + dentries_count); + is_empty =3D true; + } else + is_empty =3D true; + } + + up_read(&ii->lock); + } else { + /* dentries tree is absent */ + is_empty =3D true; + } + + return is_empty; +} + +/* + * Delete subdirectory. + * The ssdfs_rmdir() is called by the rmdir(2) system call. + */ +static int ssdfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode =3D d_inode(dentry); + int err =3D -ENOTEMPTY; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dir %lu, subdir %lu\n", + (unsigned long)dir->i_ino, (unsigned long)inode->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (ssdfs_empty_dir(inode)) { + err =3D ssdfs_unlink(dir, dentry); + if (!err) { + inode->i_size =3D 0; + inode_dec_link_count(inode); + inode_dec_link_count(dir); + } + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +enum { + SSDFS_FIRST_INODE_LOCK =3D 0, + SSDFS_SECOND_INODE_LOCK, + SSDFS_THIRD_INODE_LOCK, + SSDFS_FOURTH_INODE_LOCK, +}; + +static void lock_4_inodes(struct inode *inode1, struct inode *inode2, + struct inode *inode3, struct inode *inode4) +{ + down_write_nested(&SSDFS_I(inode1)->lock, SSDFS_FIRST_INODE_LOCK); + + if (inode2 !=3D inode1) { + down_write_nested(&SSDFS_I(inode2)->lock, + SSDFS_SECOND_INODE_LOCK); + } + + if (inode3) { + down_write_nested(&SSDFS_I(inode3)->lock, + SSDFS_THIRD_INODE_LOCK); + } + + if (inode4) { + down_write_nested(&SSDFS_I(inode4)->lock, + SSDFS_FOURTH_INODE_LOCK); + } +} + +static void unlock_4_inodes(struct inode *inode1, struct inode *inode2, + struct inode *inode3, struct inode *inode4) +{ + if (inode4) + up_write(&SSDFS_I(inode4)->lock); + if (inode3) + up_write(&SSDFS_I(inode3)->lock); + if (inode1 !=3D inode2) + up_write(&SSDFS_I(inode2)->lock); + up_write(&SSDFS_I(inode1)->lock); +} + +/* + * Regular rename. + */ +static int ssdfs_rename_target(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry, + unsigned int flags) +{ + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(old_dir->i_sb); + struct ssdfs_inode_info *old_dir_ii =3D SSDFS_I(old_dir); + struct ssdfs_inode_info *new_dir_ii =3D SSDFS_I(new_dir); + struct inode *old_inode =3D d_inode(old_dentry); + struct ssdfs_inode_info *old_ii =3D SSDFS_I(old_inode); + struct inode *new_inode =3D d_inode(new_dentry); + struct ssdfs_btree_search *search; + struct qstr dotdot =3D QSTR_INIT("..", 2); + bool is_dir =3D S_ISDIR(old_inode->i_mode); + bool move =3D (new_dir !=3D old_dir); + bool unlink =3D new_inode !=3D NULL; + ino_t old_ino, old_parent_ino, new_ino; + struct timespec64 time; + u64 name_hash; + int err =3D -ENOENT; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_dir %lu, old_inode %lu, " + "new_dir %lu, new_inode %p\n", + (unsigned long)old_dir->i_ino, + (unsigned long)old_inode->i_ino, + (unsigned long)new_dir->i_ino, + new_inode); +#endif /* CONFIG_SSDFS_DEBUG */ + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto out; + } + + ssdfs_btree_search_init(search); + + lock_4_inodes(old_dir, new_dir, old_inode, new_inode); + + err =3D ssdfs_inode_by_name(old_dir, &old_dentry->d_name, &old_ino); + if (unlikely(err)) { + SSDFS_ERR("fail to find old dentry: err %d\n", err); + goto finish_target_rename; + } else if (old_ino !=3D old_inode->i_ino) { + err =3D -ERANGE; + SSDFS_ERR("invalid ino: found ino %lu !=3D requested ino %lu\n", + old_ino, old_inode->i_ino); + goto finish_target_rename; + } + + if (S_ISDIR(old_inode->i_mode)) { + err =3D ssdfs_inode_by_name(old_inode, &dotdot, &old_parent_ino); + if (unlikely(err)) { + SSDFS_ERR("fail to find parent dentry: err %d\n", err); + goto finish_target_rename; + } else if (old_parent_ino !=3D old_dir->i_ino) { + err =3D -ERANGE; + SSDFS_ERR("invalid ino: " + "found ino %lu !=3D requested ino %lu\n", + old_parent_ino, old_dir->i_ino); + goto finish_target_rename; + } + } + + if (!old_dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("old dir hasn't dentries tree\n"); + goto finish_target_rename; + } + + if (!new_dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("new dir hasn't dentries tree\n"); + goto finish_target_rename; + } + + if (S_ISDIR(old_inode->i_mode) && !old_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("old inode hasn't dentries tree\n"); + goto finish_target_rename; + } + + if (flags & RENAME_WHITEOUT) { + /* TODO: implement support */ + SSDFS_WARN("TODO: implement support of RENAME_WHITEOUT\n"); + } + + if (new_inode) { + err =3D -ENOTEMPTY; + if (is_dir && !ssdfs_empty_dir(new_inode)) + goto finish_target_rename; + + err =3D ssdfs_inode_by_name(new_dir, &new_dentry->d_name, + &new_ino); + if (unlikely(err)) { + SSDFS_ERR("fail to find new dentry: err %d\n", err); + goto finish_target_rename; + } else if (new_ino !=3D new_inode->i_ino) { + err =3D -ERANGE; + SSDFS_ERR("invalid ino: " + "found ino %lu !=3D requested ino %lu\n", + new_ino, new_inode->i_ino); + goto finish_target_rename; + } + + name_hash =3D ssdfs_generate_name_hash(&new_dentry->d_name); + + err =3D ssdfs_dentries_tree_change(new_dir_ii->dentries_tree, + name_hash, + new_inode->i_ino, + &old_dentry->d_name, + old_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_target_rename; + } + } else { + err =3D ssdfs_add_link(new_dir, new_dentry, old_inode); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to add the link: err %d\n", + err); + goto finish_target_rename; + } + } + + name_hash =3D ssdfs_generate_name_hash(&old_dentry->d_name); + + err =3D ssdfs_dentries_tree_delete(old_dir_ii->dentries_tree, + name_hash, + old_inode->i_ino, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to delete the dentry: " + "name_hash %llx, ino %lu, err %d\n", + name_hash, old_inode->i_ino, err); + goto finish_target_rename; + } + + if (is_dir && move) { + /* update ".." directory entry info of old dentry */ + name_hash =3D ssdfs_generate_name_hash(&dotdot); + err =3D ssdfs_dentries_tree_change(old_ii->dentries_tree, + name_hash, old_dir->i_ino, + &dotdot, new_dir_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_target_rename; + } + } + + old_ii->parent_ino =3D new_dir->i_ino; + + /* + * Like most other Unix systems, set the @i_ctime for inodes on a + * rename. + */ + time =3D current_time(old_dir); + inode_set_ctime_to_ts(old_inode, time); + mark_inode_dirty(old_inode); + + /* We must adjust parent link count when renaming directories */ + if (is_dir) { + if (move) { + /* + * @old_dir loses a link because we are moving + * @old_inode to a different directory. + */ + inode_dec_link_count(old_dir); + /* + * @new_dir only gains a link if we are not also + * overwriting an existing directory. + */ + if (!unlink) + inode_inc_link_count(new_dir); + } else { + /* + * @old_inode is not moving to a different directory, + * but @old_dir still loses a link if we are + * overwriting an existing directory. + */ + if (unlink) + inode_dec_link_count(old_dir); + } + } + + inode_set_mtime_to_ts(old_dir, time); + inode_set_ctime_to_ts(old_dir, time); + inode_set_mtime_to_ts(new_dir, time); + inode_set_ctime_to_ts(new_dir, time); + + /* + * And finally, if we unlinked a direntry which happened to have the + * same name as the moved direntry, we have to decrement @i_nlink of + * the unlinked inode and change its ctime. + */ + if (unlink) { + /* + * Directories cannot have hard-links, so if this is a + * directory, just clear @i_nlink. + */ + if (is_dir) { + clear_nlink(new_inode); + mark_inode_dirty(new_inode); + } else + inode_dec_link_count(new_inode); + inode_set_ctime_to_ts(new_inode, time); + } + +finish_target_rename: + unlock_4_inodes(old_dir, new_dir, old_inode, new_inode); + ssdfs_btree_search_free(search); + +out: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; +} + +/* + * Cross-directory rename. + */ +static int ssdfs_cross_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(old_dir->i_sb); + struct ssdfs_inode_info *old_dir_ii =3D SSDFS_I(old_dir); + struct ssdfs_inode_info *new_dir_ii =3D SSDFS_I(new_dir); + struct inode *old_inode =3D d_inode(old_dentry); + struct ssdfs_inode_info *old_ii =3D SSDFS_I(old_inode); + struct inode *new_inode =3D d_inode(new_dentry); + struct ssdfs_inode_info *new_ii =3D SSDFS_I(new_inode); + struct ssdfs_btree_search *search; + struct qstr dotdot =3D QSTR_INIT("..", 2); + ino_t old_ino, new_ino; + struct timespec64 time; + u64 name_hash; + int err =3D -ENOENT; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_dir %lu, old_inode %lu, new_dir %lu\n", + (unsigned long)old_dir->i_ino, + (unsigned long)old_inode->i_ino, + (unsigned long)new_dir->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto out; + } + + ssdfs_btree_search_init(search); + + lock_4_inodes(old_dir, new_dir, old_inode, new_inode); + + err =3D ssdfs_inode_by_name(old_dir, &old_dentry->d_name, &old_ino); + if (unlikely(err)) { + SSDFS_ERR("fail to find old dentry: err %d\n", err); + goto finish_cross_rename; + } else if (old_ino !=3D old_inode->i_ino) { + err =3D -ERANGE; + SSDFS_ERR("invalid ino: found ino %lu !=3D requested ino %lu\n", + old_ino, old_inode->i_ino); + goto finish_cross_rename; + } + + err =3D ssdfs_inode_by_name(new_dir, &new_dentry->d_name, &new_ino); + if (unlikely(err)) { + SSDFS_ERR("fail to find new dentry: err %d\n", err); + goto finish_cross_rename; + } else if (new_ino !=3D new_inode->i_ino) { + err =3D -ERANGE; + SSDFS_ERR("invalid ino: found ino %lu !=3D requested ino %lu\n", + new_ino, new_inode->i_ino); + goto finish_cross_rename; + } + + if (!old_dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("old dir hasn't dentries tree\n"); + goto finish_cross_rename; + } + + if (!new_dir_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("new dir hasn't dentries tree\n"); + goto finish_cross_rename; + } + + if (S_ISDIR(old_inode->i_mode) && !old_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("old inode hasn't dentries tree\n"); + goto finish_cross_rename; + } + + if (S_ISDIR(new_inode->i_mode) && !new_ii->dentries_tree) { + err =3D -ERANGE; + SSDFS_ERR("new inode hasn't dentries tree\n"); + goto finish_cross_rename; + } + + name_hash =3D ssdfs_generate_name_hash(&dotdot); + + /* update ".." directory entry info of old dentry */ + if (S_ISDIR(old_inode->i_mode)) { + err =3D ssdfs_dentries_tree_change(old_ii->dentries_tree, + name_hash, old_dir->i_ino, + &dotdot, new_dir_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_cross_rename; + } + } + + /* update ".." directory entry info of new dentry */ + if (S_ISDIR(new_inode->i_mode)) { + err =3D ssdfs_dentries_tree_change(new_ii->dentries_tree, + name_hash, new_dir->i_ino, + &dotdot, old_dir_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_cross_rename; + } + } + + /* update directory entry info of old dir inode */ + name_hash =3D ssdfs_generate_name_hash(&old_dentry->d_name); + + err =3D ssdfs_dentries_tree_change(old_dir_ii->dentries_tree, + name_hash, old_inode->i_ino, + &new_dentry->d_name, new_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_cross_rename; + } + + /* update directory entry info of new dir inode */ + name_hash =3D ssdfs_generate_name_hash(&new_dentry->d_name); + + err =3D ssdfs_dentries_tree_change(new_dir_ii->dentries_tree, + name_hash, new_inode->i_ino, + &old_dentry->d_name, old_ii, + search); + if (unlikely(err)) { + ssdfs_fs_error(fsi->sb, __FILE__, __func__, __LINE__, + "fail to update dentry: err %d\n", + err); + goto finish_cross_rename; + } + + old_ii->parent_ino =3D new_dir->i_ino; + new_ii->parent_ino =3D old_dir->i_ino; + + time =3D current_time(old_dir); + inode_set_ctime_to_ts(old_inode, time); + inode_set_ctime_to_ts(new_inode, time); + inode_set_mtime_to_ts(old_dir, time); + inode_set_ctime_to_ts(old_dir, time); + inode_set_mtime_to_ts(new_dir, time); + inode_set_ctime_to_ts(new_dir, time); + + if (old_dir !=3D new_dir) { + if (S_ISDIR(old_inode->i_mode) && + !S_ISDIR(new_inode->i_mode)) { + inode_inc_link_count(new_dir); + inode_dec_link_count(old_dir); + } + else if (!S_ISDIR(old_inode->i_mode) && + S_ISDIR(new_inode->i_mode)) { + inode_dec_link_count(new_dir); + inode_inc_link_count(old_dir); + } + } + + mark_inode_dirty(old_inode); + mark_inode_dirty(new_inode); + +finish_cross_rename: + unlock_4_inodes(old_dir, new_dir, old_inode, new_inode); + ssdfs_btree_search_free(search); + +out: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * The ssdfs_rename() is called by the rename(2) system call + * to rename the object to have the parent and name given by + * the second inode and dentry. + */ +static int ssdfs_rename(struct mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_dir %lu, old_inode %lu, new_dir %lu\n", + (unsigned long)old_dir->i_ino, + (unsigned long)old_dentry->d_inode->i_ino, + (unsigned long)new_dir->i_ino); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT)) { + SSDFS_ERR("invalid flags %#x\n", flags); + return -EINVAL; + } + + if (flags & RENAME_EXCHANGE) { + return ssdfs_cross_rename(old_dir, old_dentry, + new_dir, new_dentry); + } + + return ssdfs_rename_target(old_dir, old_dentry, new_dir, new_dentry, + flags); +} + +static +int ssdfs_dentries_tree_get_start_hash(struct ssdfs_dentries_btree_info *t= ree, + u64 *start_hash) +{ + struct ssdfs_btree_index *index; + struct ssdfs_dir_entry *cur_dentry; + u64 dentries_count; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!tree || !start_hash); + + SSDFS_DBG("tree %p, start_hash %p\n", + tree, start_hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + *start_hash =3D U64_MAX; + + switch (atomic_read(&tree->state)) { + case SSDFS_DENTRIES_BTREE_CREATED: + case SSDFS_DENTRIES_BTREE_INITIALIZED: + case SSDFS_DENTRIES_BTREE_DIRTY: + /* expected state */ + break; + + default: + SSDFS_ERR("invalid dentries tree's state %#x\n", + atomic_read(&tree->state)); + return -ERANGE; + }; + + dentries_count =3D atomic64_read(&tree->dentries_count); + + if (dentries_count < 2) { + SSDFS_WARN("folder is corrupted: " + "dentries_count %llu\n", + dentries_count); + return -ERANGE; + } else if (dentries_count =3D=3D 2) + return -ENOENT; + + switch (atomic_read(&tree->type)) { + case SSDFS_INLINE_DENTRIES_ARRAY: + down_read(&tree->lock); + + if (!tree->inline_dentries) { + err =3D -ERANGE; + SSDFS_ERR("inline tree's pointer is empty\n"); + goto finish_process_inline_tree; + } + + cur_dentry =3D &tree->inline_dentries[0]; + *start_hash =3D le64_to_cpu(cur_dentry->hash_code); + +finish_process_inline_tree: + up_read(&tree->lock); + + if (*start_hash >=3D U64_MAX) { + /* warn about invalid hash code */ + SSDFS_WARN("inline array: hash_code is invalid\n"); + } + break; + + case SSDFS_PRIVATE_DENTRIES_BTREE: + down_read(&tree->lock); + + if (!tree->root) { + err =3D -ERANGE; + SSDFS_ERR("root node pointer is NULL\n"); + goto finish_get_start_hash; + } + + index =3D &tree->root->indexes[SSDFS_ROOT_NODE_LEFT_LEAF_NODE]; + *start_hash =3D le64_to_cpu(index->hash); + +finish_get_start_hash: + up_read(&tree->lock); + + if (*start_hash >=3D U64_MAX) { + /* warn about invalid hash code */ + SSDFS_WARN("private dentry: hash_code is invalid\n"); + } + break; + + default: + err =3D -ERANGE; + SSDFS_ERR("invalid tree type %#x\n", + atomic_read(&tree->type)); + break; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +static +int ssdfs_dentries_tree_get_next_hash(struct ssdfs_dentries_btree_info *tr= ee, + struct ssdfs_btree_search *search, + u64 *next_hash) +{ + u64 old_hash; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!tree || !search || !next_hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + old_hash =3D le64_to_cpu(search->node.found_index.index.hash); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("search %p, next_hash %p, old (node %u, hash %llx)\n", + search, next_hash, search->node.id, old_hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (atomic_read(&tree->type)) { + case SSDFS_INLINE_DENTRIES_ARRAY: + SSDFS_DBG("inline dentries array is unsupported\n"); + return -ENOENT; + + case SSDFS_PRIVATE_DENTRIES_BTREE: + /* expected tree type */ + break; + + default: + SSDFS_ERR("invalid tree type %#x\n", + atomic_read(&tree->type)); + return -ERANGE; + } + + down_read(&tree->lock); + err =3D ssdfs_btree_get_next_hash(tree->generic_tree, search, next_hash); + up_read(&tree->lock); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +static +int ssdfs_dentries_tree_node_hash_range(struct ssdfs_dentries_btree_info *= tree, + struct ssdfs_btree_search *search, + u64 *start_hash, u64 *end_hash, + u16 *items_count) +{ + struct ssdfs_dir_entry *cur_dentry; + u64 dentries_count; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!search || !start_hash || !end_hash || !items_count); + + SSDFS_DBG("search %p, start_hash %p, " + "end_hash %p, items_count %p\n", + search, start_hash, end_hash, items_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + *start_hash =3D *end_hash =3D U64_MAX; + *items_count =3D 0; + + switch (atomic_read(&tree->state)) { + case SSDFS_DENTRIES_BTREE_CREATED: + case SSDFS_DENTRIES_BTREE_INITIALIZED: + case SSDFS_DENTRIES_BTREE_DIRTY: + /* expected state */ + break; + + default: + SSDFS_ERR("invalid dentries tree's state %#x\n", + atomic_read(&tree->state)); + return -ERANGE; + }; + + switch (atomic_read(&tree->type)) { + case SSDFS_INLINE_DENTRIES_ARRAY: + dentries_count =3D atomic64_read(&tree->dentries_count); + if (dentries_count >=3D U16_MAX) { + err =3D -ERANGE; + SSDFS_ERR("unexpected dentries count %llu\n", + dentries_count); + goto finish_extract_hash_range; + } + + *items_count =3D (u16)dentries_count; + + if (*items_count =3D=3D 0) + goto finish_extract_hash_range; + + down_read(&tree->lock); + + if (!tree->inline_dentries) { + err =3D -ERANGE; + SSDFS_ERR("inline tree's pointer is empty\n"); + goto finish_process_inline_tree; + } + + cur_dentry =3D &tree->inline_dentries[0]; + *start_hash =3D le64_to_cpu(cur_dentry->hash_code); + + if (dentries_count > SSDFS_INLINE_DENTRIES_COUNT) { + err =3D -ERANGE; + SSDFS_ERR("dentries_count %llu > max_value %u\n", + dentries_count, + SSDFS_INLINE_DENTRIES_COUNT); + goto finish_process_inline_tree; + } + + cur_dentry =3D &tree->inline_dentries[dentries_count - 1]; + *end_hash =3D le64_to_cpu(cur_dentry->hash_code); + +finish_process_inline_tree: + up_read(&tree->lock); + break; + + case SSDFS_PRIVATE_DENTRIES_BTREE: + err =3D ssdfs_btree_node_get_hash_range(search, + start_hash, + end_hash, + items_count); + if (unlikely(err)) { + SSDFS_ERR("fail to get hash range: err %d\n", + err); + goto finish_extract_hash_range; + } + break; + + default: + SSDFS_ERR("invalid tree type %#x\n", + atomic_read(&tree->type)); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("start_hash %llx, end_hash %llx, items_count %u\n", + *start_hash, *end_hash, *items_count); +#endif /* CONFIG_SSDFS_DEBUG */ + +finish_extract_hash_range: + return err; +} + +static +int ssdfs_dentries_tree_check_search_result(struct ssdfs_btree_search *sea= rch) +{ + size_t dentry_size =3D sizeof(struct ssdfs_dir_entry); + u16 items_count; + size_t buf_size; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!search); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (search->result.state) { + case SSDFS_BTREE_SEARCH_VALID_ITEM: + /* expected state */ + break; + + default: + SSDFS_ERR("unexpected result's state %#x\n", + search->result.state); + return -ERANGE; + } + + switch (search->result.raw_buf.state) { + case SSDFS_BTREE_SEARCH_INLINE_BUFFER: + case SSDFS_BTREE_SEARCH_EXTERNAL_BUFFER: + if (!search->result.raw_buf.place.ptr) { + SSDFS_ERR("buffer pointer is NULL\n"); + return -ERANGE; + } + break; + + default: + SSDFS_ERR("unexpected buffer's state\n"); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(search->result.raw_buf.items_count >=3D U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + items_count =3D (u16)search->result.raw_buf.items_count; + + if (items_count =3D=3D 0) { + SSDFS_ERR("items_in_buffer %u\n", + items_count); + return -ENOENT; + } else if (items_count !=3D search->result.count) { + SSDFS_ERR("items_count %u !=3D search->result.count %u\n", + items_count, search->result.count); + return -ERANGE; + } + + buf_size =3D dentry_size * items_count; + + if (buf_size !=3D search->result.raw_buf.size) { + SSDFS_ERR("buf_size %zu !=3D search->result.raw_buf.size %zu\n", + buf_size, + search->result.raw_buf.size); + return -ERANGE; + } + + return 0; +} + +static +bool is_invalid_dentry(struct ssdfs_dir_entry *dentry) +{ + u8 name_len; + bool is_invalid =3D false; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!dentry); + + SSDFS_DBG("dentry_type %#x, file_type %#x, " + "flags %#x, name_len %u, " + "hash_code %llx, ino %llu\n", + dentry->dentry_type, dentry->file_type, + dentry->flags, dentry->name_len, + le64_to_cpu(dentry->hash_code), + le64_to_cpu(dentry->ino)); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (dentry->dentry_type) { + case SSDFS_INLINE_DENTRY: + case SSDFS_REGULAR_DENTRY: + /* expected dentry type */ + break; + + default: + is_invalid =3D true; + SSDFS_ERR("invalid dentry type %#x\n", + dentry->dentry_type); + goto finish_check; + } + + if (dentry->file_type <=3D SSDFS_FT_UNKNOWN || + dentry->file_type >=3D SSDFS_FT_MAX) { + is_invalid =3D true; + SSDFS_ERR("invalid file type %#x\n", + dentry->file_type); + goto finish_check; + } + + if (dentry->flags & ~SSDFS_DENTRY_FLAGS_MASK) { + is_invalid =3D true; + SSDFS_ERR("invalid set of flags %#x\n", + dentry->flags); + goto finish_check; + } + + name_len =3D dentry->name_len; + + if (name_len > SSDFS_MAX_NAME_LEN) { + is_invalid =3D true; + SSDFS_ERR("invalid name_len %u\n", + name_len); + goto finish_check; + } + + if (le64_to_cpu(dentry->hash_code) >=3D U64_MAX) { + is_invalid =3D true; + SSDFS_ERR("invalid hash_code\n"); + goto finish_check; + } + + if (le64_to_cpu(dentry->ino) >=3D U32_MAX) { + is_invalid =3D true; + SSDFS_ERR("ino %llu is too huge\n", + le64_to_cpu(dentry->ino)); + goto finish_check; + } + +finish_check: + if (is_invalid) { + SSDFS_ERR("dentry_type %#x, file_type %#x, " + "flags %#x, name_len %u, " + "hash_code %llx, ino %llu\n", + dentry->dentry_type, dentry->file_type, + dentry->flags, dentry->name_len, + le64_to_cpu(dentry->hash_code), + le64_to_cpu(dentry->ino)); + } + + return is_invalid; +} + +/* + * The ssdfs_readdir() is called when the VFS needs + * to read the directory contents. + */ +static int ssdfs_readdir(struct file *file, struct dir_context *ctx) +{ + struct inode *inode =3D file_inode(file); + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_inode_info *ii =3D SSDFS_I(inode); + struct qstr dot =3D QSTR_INIT(".", 1); + u64 dot_hash; + struct qstr dotdot =3D QSTR_INIT("..", 2); + u64 dotdot_hash; + struct ssdfs_shared_dict_btree_info *dict; + struct ssdfs_btree_search *search; + struct ssdfs_dir_entry *dentry; + size_t dentry_size =3D sizeof(struct ssdfs_dir_entry); + int private_flags; + u64 start_hash =3D U64_MAX; + u64 end_hash =3D U64_MAX; + u64 hash =3D U64_MAX; + u64 start_pos; + u16 items_count; + ino_t ino; + int i; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("file %p, ctx %p\n", file, ctx); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (ctx->pos < 0) { + SSDFS_DBG("ctx->pos %lld\n", ctx->pos); + return 0; + } + + dict =3D fsi->shdictree; + if (!dict) { + SSDFS_ERR("shared dictionary is absent\n"); + return -ERANGE; + } + + dot_hash =3D ssdfs_generate_name_hash(&dot); + dotdot_hash =3D ssdfs_generate_name_hash(&dotdot); + + private_flags =3D atomic_read(&ii->private_flags); + + if (private_flags & SSDFS_INODE_HAS_INLINE_DENTRIES || + private_flags & SSDFS_INODE_HAS_DENTRIES_BTREE) { + down_read(&ii->lock); + if (!ii->dentries_tree) + err =3D -ERANGE; + up_read(&ii->lock); + + if (unlikely(err)) { + SSDFS_WARN("dentries tree is absent\n"); + return -ERANGE; + } + } else { + if (!S_ISDIR(inode->i_mode)) { + SSDFS_WARN("this is not folder!!!\n"); + return -EINVAL; + } + + down_read(&ii->lock); + if (ii->dentries_tree) + err =3D -ERANGE; + up_read(&ii->lock); + + if (unlikely(err)) { + SSDFS_WARN("dentries tree exists!!!!\n"); + return err; + } + } + + start_pos =3D ctx->pos; + + if (ctx->pos =3D=3D 0) { + down_read(&ii->lock); + err =3D ssdfs_inode_by_name(inode, &dot, &ino); + up_read(&ii->lock); + + if (unlikely(err)) { + SSDFS_ERR("fail to find dentry: err %d\n", err); + goto out; + } + + if (!dir_emit_dot(file, ctx)) { + err =3D -ERANGE; + SSDFS_ERR("fail to emit dentry\n"); + goto out; + } + + ctx->pos =3D 1; + } + + if (ctx->pos =3D=3D 1) { + down_read(&ii->lock); + err =3D ssdfs_inode_by_name(inode, &dotdot, &ino); + up_read(&ii->lock); + + if (unlikely(err)) { + SSDFS_ERR("fail to find dentry: err %d\n", err); + goto out; + } + + if (!dir_emit_dotdot(file, ctx)) { + err =3D -ERANGE; + SSDFS_ERR("fail to emit dentry\n"); + goto out; + } + + ctx->pos =3D 2; + } + + if (ctx->pos >=3D 2) { + down_read(&ii->lock); + err =3D ssdfs_dentries_tree_get_start_hash(ii->dentries_tree, + &start_hash); + up_read(&ii->lock); + + if (err =3D=3D -ENOENT) { + err =3D 0; + ctx->pos =3D 2; + goto out; + } else if (unlikely(err)) { + SSDFS_ERR("fail to get start root hash: err %d\n", err); + goto out; + } else if (start_hash >=3D U64_MAX) { + err =3D -ERANGE; + SSDFS_ERR("invalid hash value\n"); + goto out; + } + + ctx->pos =3D 2; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto out; + } + + do { + ssdfs_btree_search_init(search); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ctx->pos %llu, start_hash %llx\n", + (u64)ctx->pos, start_hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + /* allow readdir() to be interrupted */ + if (fatal_signal_pending(current)) { + err =3D -ERESTARTSYS; + goto out_free; + } + cond_resched(); + + down_read(&ii->lock); + + err =3D ssdfs_dentries_tree_find_leaf_node(ii->dentries_tree, + start_hash, + search); + if (err =3D=3D -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find a leaf node: " + "hash %llx, err %d\n", + start_hash, err); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_tree_processing; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find a leaf node: " + "hash %llx, err %d\n", + start_hash, err); + goto finish_tree_processing; + } + + err =3D ssdfs_dentries_tree_node_hash_range(ii->dentries_tree, + search, + &start_hash, + &end_hash, + &items_count); + if (unlikely(err)) { + SSDFS_ERR("fail to get node's hash range: " + "err %d\n", err); + goto finish_tree_processing; + } + + if (items_count =3D=3D 0) { + err =3D -ENOENT; + SSDFS_DBG("empty leaf node\n"); + goto finish_tree_processing; + } + + if (start_hash > end_hash) { + err =3D -ENOENT; + goto finish_tree_processing; + } + + err =3D ssdfs_dentries_tree_extract_range(ii->dentries_tree, + 0, items_count, + search); + if (unlikely(err)) { + SSDFS_ERR("fail to extract the range: " + "items_count %u, err %d\n", + items_count, err); + goto finish_tree_processing; + } + +finish_tree_processing: + up_read(&ii->lock); + + if (err =3D=3D -ENODATA) { + err =3D 0; + goto out_free; + } else if (unlikely(err)) + goto out_free; + + err =3D ssdfs_dentries_tree_check_search_result(search); + if (unlikely(err)) { + SSDFS_ERR("corrupted search result: " + "err %d\n", err); + goto out_free; + } + + items_count =3D search->result.count; + + for (i =3D 0; i < items_count; i++) { + u8 *start_ptr =3D (u8 *)search->result.raw_buf.place.ptr; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("start_pos %llu, ctx->pos %llu\n", + start_pos, ctx->pos); +#endif /* CONFIG_SSDFS_DEBUG */ + + dentry =3D (struct ssdfs_dir_entry *)(start_ptr + + (i * dentry_size)); + hash =3D le64_to_cpu(dentry->hash_code); + + if (ctx->pos < start_pos) { + if (dot_hash =3D=3D hash || dotdot_hash =3D=3D hash) { + /* skip counting */ + continue; + } else { + ctx->pos++; + continue; + } + } + + if (is_invalid_dentry(dentry)) { + err =3D -EIO; + SSDFS_ERR("found corrupted dentry\n"); + goto out_free; + } + + if (dot_hash =3D=3D hash || dotdot_hash =3D=3D hash) { + /* + * These items were created already. + * Simply skip the case. + */ + } else if (dentry->flags & + SSDFS_DENTRY_HAS_EXTERNAL_STRING) { + err =3D ssdfs_shared_dict_get_name(dict, hash, + &search->name.string); + if (unlikely(err)) { + SSDFS_ERR("fail to extract the name: " + "hash %llx, err %d\n", + hash, err); + goto out_free; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ctx->pos %llu, name %s, " + "name_len %zu, " + "ino %llu, hash %llx\n", + ctx->pos, + search->name.string.str, + search->name.string.len, + le64_to_cpu(dentry->ino), + hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!dir_emit(ctx, + search->name.string.str, + search->name.string.len, + (ino_t)le64_to_cpu(dentry->ino), + ssdfs_filetype_table[dentry->file_type])) { + /* stopped by some reason */ + err =3D 1; + goto out_free; + } else + ctx->pos++; + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ctx->pos %llu, name %s, " + "name_len %u, " + "ino %llu, hash %llx\n", + ctx->pos, + dentry->inline_string, + dentry->name_len, + le64_to_cpu(dentry->ino), + hash); + SSDFS_DBG("dentry %p, name %p\n", + dentry, dentry->inline_string); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!dir_emit(ctx, + dentry->inline_string, + dentry->name_len, + (ino_t)le64_to_cpu(dentry->ino), + ssdfs_filetype_table[dentry->file_type])) { + /* stopped by some reason */ + err =3D 1; + goto out_free; + } else + ctx->pos++; + } + } + + if (hash !=3D end_hash) { + err =3D -ERANGE; + SSDFS_ERR("hash %llx < end_hash %llx\n", + hash, end_hash); + goto out_free; + } + + start_hash =3D end_hash + 1; + + down_read(&ii->lock); + err =3D ssdfs_dentries_tree_get_next_hash(ii->dentries_tree, + search, + &start_hash); + up_read(&ii->lock); + + ssdfs_btree_search_forget_parent_node(search); + ssdfs_btree_search_forget_child_node(search); + + if (err =3D=3D -ENOENT) { + err =3D 0; + ctx->pos =3D U64_MAX; + SSDFS_DBG("no more items in the folder\n"); + goto out_free; + } else if (unlikely(err)) { + SSDFS_ERR("fail to get next hash: err %d\n", + err); + goto out_free; + } + } while (start_hash < U64_MAX); + +out_free: + ssdfs_btree_search_free(search); + +out: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; +} + +const struct inode_operations ssdfs_dir_inode_operations =3D { + .create =3D ssdfs_create, + .lookup =3D ssdfs_lookup, + .link =3D ssdfs_link, + .unlink =3D ssdfs_unlink, + .symlink =3D ssdfs_symlink, + .mkdir =3D ssdfs_mkdir, + .rmdir =3D ssdfs_rmdir, + .mknod =3D ssdfs_mknod, + .rename =3D ssdfs_rename, + .setattr =3D ssdfs_setattr, + .listxattr =3D ssdfs_listxattr, + .get_inode_acl =3D ssdfs_get_acl, + .set_acl =3D ssdfs_set_acl, +}; + +const struct file_operations ssdfs_dir_operations =3D { + .read =3D generic_read_dir, + .iterate_shared =3D ssdfs_readdir, + .unlocked_ioctl =3D ssdfs_ioctl, + .fsync =3D ssdfs_fsync, + .llseek =3D generic_file_llseek, +}; --=20 2.34.1