From cc3cadbaf3ff42a41f7f1c1ee3c31ebd344ee84e Mon Sep 17 00:00:00 2001 From: Mohamad Chaarawi Date: Tue, 22 Oct 2024 12:43:39 +0000 Subject: [PATCH] DAOS-16924 dfs: new readdir API Add a new DFS readdir API that utilizes a new DFS anchor object which can be updated to include caching parameters for future calls. This API will be more useful when client side caching for readdir is implemented to access buckets of readdir entries. Required-githooks: true Signed-off-by: Mohamad Chaarawi --- src/client/dfs/dcache.c | 6 +- src/client/dfs/dfs_internal.h | 10 +++ src/client/dfs/readdir.c | 74 ++++++++++++++++++ src/include/daos_fs.h | 73 ++++++++++++++++++ src/tests/suite/dfs_unit_test.c | 133 +++++++++++++++++--------------- 5 files changed, 233 insertions(+), 63 deletions(-) diff --git a/src/client/dfs/dcache.c b/src/client/dfs/dcache.c index 3f0aa590493..5df702f1a12 100644 --- a/src/client/dfs/dcache.c +++ b/src/client/dfs/dcache.c @@ -1,5 +1,6 @@ /** * (C) Copyright 2022-2024 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -75,7 +76,7 @@ typedef void (*drec_decref_fn_t)(dfs_dcache_t *, dfs_obj_t *); typedef void (*drec_del_at_fn_t)(dfs_dcache_t *, dfs_obj_t *); typedef int (*drec_del_fn_t)(dfs_dcache_t *, char *, dfs_obj_t *); -/** DFS directory cache */ +/** DFS dentry cache */ struct dfs_dcache { /** Cached DAOS file system */ dfs_t *dd_dfs; @@ -990,3 +991,6 @@ drec_del(dfs_dcache_t *dcache, char *path, dfs_obj_t *parent) return dcache->drec_del_fn(dcache, path, parent); } + +// dcache_readdir(dfs_dcache_t *dcache, dfs_obj_t *obj, dfs_dir_anchor_t *anchor, struct dirent dir) +// {} diff --git a/src/client/dfs/dfs_internal.h b/src/client/dfs/dfs_internal.h index 74c5a8eeb26..184646f83ba 100644 --- a/src/client/dfs/dfs_internal.h +++ b/src/client/dfs/dfs_internal.h @@ -1,5 +1,6 @@ /** * (C) Copyright 2019-2024 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -274,6 +275,15 @@ struct dfs_mnt_hdls { int type; }; +/** readdir anchor for dfs_readdir2(). If dcache is disabled, the only valid setting is the + * daos_anchor_t. */ +struct dfs_dir_anchor { + dfs_obj_t *dda_dir; + daos_anchor_t dda_anchor_int; + size_t dda_bucket_id; + off_t dda_bucket_offset; +}; + static inline bool tspec_gt(struct timespec l, struct timespec r) { diff --git a/src/client/dfs/readdir.c b/src/client/dfs/readdir.c index f2ba1112782..ccf5e6ea2f9 100644 --- a/src/client/dfs/readdir.c +++ b/src/client/dfs/readdir.c @@ -1,5 +1,6 @@ /** * (C) Copyright 2018-2024 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -9,6 +10,7 @@ #define D_LOGFAC DD_FAC(dfs) #include +#include #include "dfs_internal.h" @@ -113,6 +115,78 @@ dfs_readdirplus(dfs_t *dfs, dfs_obj_t *obj, daos_anchor_t *anchor, uint32_t *nr, return readdir_int(dfs, obj, anchor, nr, dirs, stbufs); } +int +dfs_dir_anchor_init(dfs_obj_t *obj, dfs_dir_anchor_t **_anchor) +{ + dfs_dir_anchor_t *anchor; + + if (obj == NULL || !S_ISDIR(obj->mode)) + return ENOTDIR; + + D_ALLOC_PTR(anchor); + if (anchor == NULL) + return ENOMEM; + + anchor->dda_dir = obj; + daos_anchor_init(&anchor->dda_anchor_int, 0); + anchor->dda_bucket_id = 0; + anchor->dda_bucket_offset = 0; + *_anchor = anchor; + return 0; +} + +void +dfs_dir_anchor_reset(dfs_dir_anchor_t *anchor) +{ + daos_anchor_init(&anchor->dda_anchor_int, 0); + anchor->dda_bucket_id = 0; + anchor->dda_bucket_offset = 0; +} + +bool +dfs_dir_anchor_is_eof(dfs_dir_anchor_t *anchor) +{ + return daos_anchor_is_eof(&anchor->dda_anchor_int); +} + +void +dfs_dir_anchor_destroy(dfs_dir_anchor_t *anchor) +{ + D_FREE(anchor); +} + +int +dfs_readdir_s(dfs_t *dfs, dfs_obj_t *dir, dfs_dir_anchor_t *anchor, struct dirent *entry) +{ + uint32_t nr = 1; + int rc; + + if (daos_oid_cmp(dir->oid, anchor->dda_dir->oid) != 0) + return EINVAL; + if (daos_anchor_is_eof(&anchor->dda_anchor_int)) + return -1; + + rc = readdir_int(dfs, dir, &anchor->dda_anchor_int, &nr, entry, NULL); + if (rc) + return rc; + + /** if we did not enumerate anything, try again to make sure we hit EOF */ + if (nr == 0) { + if (daos_anchor_is_eof(&anchor->dda_anchor_int)) + return -1; /** typically EOF code */ + else + return EIO; + } + return rc; +#if 0 + /** if no caching, just use the internal anchor with 1 entry */ + if (dfs->dcache == NULL) + return readdir_int(dfs, obj, &anchor->dda_anchor_int, 1, &dir, NULL); + else + dcache_readdir(dfs->dcache, obj, anchor, dir); +#endif +} + int dfs_iterate(dfs_t *dfs, dfs_obj_t *obj, daos_anchor_t *anchor, uint32_t *nr, size_t size, dfs_filler_cb_t op, void *udata) diff --git a/src/include/daos_fs.h b/src/include/daos_fs.h index 7c5ac13076d..d20563f9421 100644 --- a/src/include/daos_fs.h +++ b/src/include/daos_fs.h @@ -1,5 +1,6 @@ /* * (C) Copyright 2018-2024 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -47,6 +48,8 @@ extern "C" { typedef struct dfs_obj dfs_obj_t; /** DFS mount handle struct */ typedef struct dfs dfs_t; +/** DFS readdir anchor for dfs_readdir_s() */ +typedef struct dfs_dir_anchor dfs_dir_anchor_t; /* * Consistency modes of the DFS container. A container created with balanced @@ -741,6 +744,76 @@ int dfs_readdirplus(dfs_t *dfs, dfs_obj_t *obj, daos_anchor_t *anchor, uint32_t *nr, struct dirent *dirs, struct stat *stbufs); +/** + * Initialize a readdir anchor for dfs_readdir_s(). + * + * \param[in] dir Opened directory object for readdir. + * \param[out] anchor Pointer to initialized anchor. This muse be freed with + * dfs_dir_anchor_destroy(). + * + * \return 0 on success, errno code on failure. + */ +int +dfs_dir_anchor_init(dfs_obj_t *dir, dfs_dir_anchor_t **anchor); + +/** + * Reset an anchor the beginning of dir stream. + * + * \param[in] anchor anchor to reset + * + */ +void +dfs_dir_anchor_reset(dfs_dir_anchor_t *anchor); + +/** + * check if the anchor is at EOF. + * + * \param[in] anchor anchor to check + * + * \return true if anchor is at EOF, false otherwise. + */ +bool +dfs_dir_anchor_is_eof(dfs_dir_anchor_t *anchor); + +/** + * Destroy/free a DFS readdir anchor. + * + * \param[in] anchor anchor to free + * + */ +void +dfs_dir_anchor_destroy(dfs_dir_anchor_t *anchor); + +/** + * Directory readdir that just returns the next entry in the directory anchor stream. The stream is + * actually determined by the anchor which can be reset at anytime to start from the beginning. For + * every call of this function with the anchor, it returns 0 with the d_name of the dirent struct + * populated for the next entry. The function returns -1 if it reaches the end of stream and sets + * anchor to EOF. It is possible to get the last entry in the stream and the anchor being set to EOF + * in that call. It is also possible to get the last entry in the stream and the anchor still not + * set to EOF in that readdir call. In both situations the return code of the readdir call is 0 + * (success) and the entry is valid and should be consumed. In the latter case, the caller does not + * know that we reached the end of the directory and should make another readdir call. The next call + * in both scenarios will return -1 and the anchor will be set to EOF in that call. Further calls + * will behave similarly until the anchor is reset to the beginning. + * + * If caching is enabled on this dfs mount, this readdir call will utilize the readdir cache and may + * pull in an entire bucket of entries at once if the current stream is not in the cache. + * + * \param[in] dfs Pointer to the mounted file system. + * \param[in] obj Opened directory object. + * \param[in,out] + * anchor Anchor for the next call, it should initialized before first use. + * \param[out] dir returned dirent. Unlike the libc call, this dirent pointer is provided by + * the caller and not an internal pointer. + * + * \return 0 on success + * -1 on end of dir stream (no more entries to enumerate) + * errno code on failure + */ +int +dfs_readdir_s(dfs_t *dfs, dfs_obj_t *obj, dfs_dir_anchor_t *anchor, struct dirent *dir); + /** * User callback defined for dfs_readdir_size. */ diff --git a/src/tests/suite/dfs_unit_test.c b/src/tests/suite/dfs_unit_test.c index a5feb6c3ca5..944bee1db76 100644 --- a/src/tests/suite/dfs_unit_test.c +++ b/src/tests/suite/dfs_unit_test.c @@ -1,5 +1,6 @@ /** * (C) Copyright 2019-2024 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -2007,6 +2008,8 @@ dfs_test_async_io(void **state) par_barrier(PAR_COMM_WORLD); } +#define NUM_ENTS 10 + static void dfs_test_readdir_internal(void **state, daos_oclass_id_t obj_class) { @@ -2016,9 +2019,9 @@ dfs_test_readdir_internal(void **state, daos_oclass_id_t obj_class) char name[24]; char anchor_name[24]; daos_anchor_t anchor = {0}; - uint32_t num_ents = 10; - struct dirent ents[10]; - struct stat stbufs[10]; + uint32_t num_ents = NUM_ENTS; + struct dirent ents[NUM_ENTS]; + struct stat stbufs[NUM_ENTS]; int num_files = 0; int num_dirs = 0; int total_entries = 0; @@ -2067,7 +2070,7 @@ dfs_test_readdir_internal(void **state, daos_oclass_id_t obj_class) } total_entries++; } - num_ents = 10; + num_ents = NUM_ENTS; } assert_true(num_files == 100); @@ -2089,7 +2092,7 @@ dfs_test_readdir_internal(void **state, daos_oclass_id_t obj_class) check_first = false; } } - num_ents = 10; + num_ents = NUM_ENTS; } assert_true(total_entries == 150); @@ -2108,10 +2111,32 @@ dfs_test_readdir_internal(void **state, daos_oclass_id_t obj_class) assert_int_equal(rc, 0); for (i = 0; i < num_ents; i++) total_entries++; - num_ents = 10; + num_ents = NUM_ENTS; } assert_true(total_entries == 149); + /** use dfs anchor for readdir anchor stream */ + dfs_dir_anchor_t *dfs_anchor; + + rc = dfs_dir_anchor_init(dir, &dfs_anchor); + assert_int_equal(rc, 0); + total_entries = 0; + + while (!dfs_dir_anchor_is_eof(dfs_anchor)) { + struct dirent ent; + + rc = dfs_readdir_s(dfs_mt, dir, dfs_anchor, &ent); + if (rc == EOF) + assert_true(dfs_dir_anchor_is_eof(dfs_anchor)); + else { + assert_int_equal(rc, 0); + total_entries++; + } + } + + assert_true(total_entries == 199); + dfs_dir_anchor_destroy(dfs_anchor); + rc = dfs_release(dir); assert_int_equal(rc, 0); rc = dfs_remove(dfs_mt, NULL, dir_name, 1, NULL); @@ -3336,62 +3361,46 @@ dfs_test_pipeline_find(void **state) } static const struct CMUnitTest dfs_unit_tests[] = { - { "DFS_UNIT_TEST1: DFS mount / umount", - dfs_test_mount, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST2: DFS container modes", - dfs_test_modes, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST3: DFS lookup / lookup_rel", - dfs_test_lookup, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST4: Simple Symlinks", - dfs_test_syml, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST5: Symlinks with / without O_NOFOLLOW", - dfs_test_syml_follow, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST6: multi-threads read shared file", - dfs_test_read_shared_file, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST7: DFS lookupx", - dfs_test_lookupx, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST8: DFS IO sync error code", - dfs_test_io_error_code, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST9: DFS IO async error code", - dfs_test_io_error_code, async_enable, test_case_teardown}, - { "DFS_UNIT_TEST10: multi-threads mkdir same dir", - dfs_test_mt_mkdir, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST11: Simple rename", - dfs_test_rename, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST12: DFS API compat", - dfs_test_compat, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST13: DFS l2g/g2l_all", - dfs_test_handles, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST14: multi-threads connect to same container", - dfs_test_mt_connect, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST15: DFS chown", - dfs_test_chown, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST16: DFS stat mtime", - dfs_test_mtime, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST17: multi-threads async IO", - dfs_test_async_io_th, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST18: async IO", - dfs_test_async_io, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST19: DFS readdir", - dfs_test_readdir, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST20: dfs oclass hints", - dfs_test_oclass_hints, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST21: dfs multiple pools", - dfs_test_multiple_pools, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST22: dfs extended attributes", - dfs_test_xattrs, test_case_teardown}, - { "DFS_UNIT_TEST23: dfs MWC container checker", - dfs_test_checker, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST24: dfs MWC SB fix", - dfs_test_fix_sb, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST25: dfs MWC root fix", - dfs_test_relink_root, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST26: dfs MWC chunk size fix", - dfs_test_fix_chunk_size, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST27: dfs pipeline find", - dfs_test_pipeline_find, async_disable, test_case_teardown}, - { "DFS_UNIT_TEST28: dfs open/lookup flags", - dfs_test_oflags, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST19: DFS readdir", dfs_test_readdir, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST1: DFS mount / umount", dfs_test_mount, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST2: DFS container modes", dfs_test_modes, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST3: DFS lookup / lookup_rel", dfs_test_lookup, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST4: Simple Symlinks", dfs_test_syml, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST5: Symlinks with / without O_NOFOLLOW", dfs_test_syml_follow, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST6: multi-threads read shared file", dfs_test_read_shared_file, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST7: DFS lookupx", dfs_test_lookupx, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST8: DFS IO sync error code", dfs_test_io_error_code, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST9: DFS IO async error code", dfs_test_io_error_code, async_enable, + test_case_teardown}, + {"DFS_UNIT_TEST10: multi-threads mkdir same dir", dfs_test_mt_mkdir, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST11: Simple rename", dfs_test_rename, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST12: DFS API compat", dfs_test_compat, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST13: DFS l2g/g2l_all", dfs_test_handles, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST14: multi-threads connect to same container", dfs_test_mt_connect, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST15: DFS chown", dfs_test_chown, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST16: DFS stat mtime", dfs_test_mtime, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST17: multi-threads async IO", dfs_test_async_io_th, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST18: async IO", dfs_test_async_io, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST19: DFS readdir", dfs_test_readdir, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST20: dfs oclass hints", dfs_test_oclass_hints, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST21: dfs multiple pools", dfs_test_multiple_pools, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST22: dfs extended attributes", dfs_test_xattrs, test_case_teardown}, + {"DFS_UNIT_TEST23: dfs MWC container checker", dfs_test_checker, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST24: dfs MWC SB fix", dfs_test_fix_sb, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST25: dfs MWC root fix", dfs_test_relink_root, async_disable, test_case_teardown}, + {"DFS_UNIT_TEST26: dfs MWC chunk size fix", dfs_test_fix_chunk_size, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST27: dfs pipeline find", dfs_test_pipeline_find, async_disable, + test_case_teardown}, + {"DFS_UNIT_TEST28: dfs open/lookup flags", dfs_test_oflags, async_disable, test_case_teardown}, }; static int