From patchwork Sat May 3 19:59:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 62381 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9239FC369C2 for ; Sat, 3 May 2025 20:02:03 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.web11.17576.1746302520574263206 for ; Sat, 03 May 2025 13:02:00 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.14.1/8.14.1) with ESMTP id 543JxuJv008895; Sat, 3 May 2025 14:59:57 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: skandigraun@gmail.com, landervanloock@gmail.com, richard.purdie@linuxfoundation.org, fntoth@gmail.com Subject: [pseudo][PATCH v3 1/3] Move ftw and ftw64 to calling ntfw and nftw64 Date: Sat, 3 May 2025 14:59:53 -0500 Message-Id: <1746302395-8723-2-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sat, 03 May 2025 20:02:03 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/1481 From: Mark Hatle The 4 functions are very similar to each other: nftw-nftw64 and ftw-ftw64 are identical to their pair, except for the stat struct they use (stat vs stat64). nftw is a "superset" of ftw: nftw is backwards compatible with ftw, but it also accepts a number of extra flags to modify its behavior. Since all the 4 functions are so similar, the same codebase is used to implement all (which is also fairly similar to their implementation in glibc). [1]: https://linux.die.net/man/3/nftw Re-implemented, but based on the ideas from Gyorgy Sarvari Signed-off-by: Mark Hatle --- guts/README | 4 ++-- ports/linux/guts/ftw64.c | 9 ++++++++- ports/unix/guts/ftw.c | 10 +++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/guts/README b/guts/README index 0a1fe5f..bb2e573 100644 --- a/guts/README +++ b/guts/README @@ -54,6 +54,8 @@ other functions: __xmknod: __xmknodat __xstat64: __fxstatat64 __xstat: __fxstatat + ftw: nftw + ftw64: nftw64 The following functions are full implementations: @@ -129,8 +131,6 @@ calling the underlying routine. eaccess euidaccess fts_open - ftw64 - ftw glob64 glob lutimes diff --git a/ports/linux/guts/ftw64.c b/ports/linux/guts/ftw64.c index 48adb80..72e80f1 100644 --- a/ports/linux/guts/ftw64.c +++ b/ports/linux/guts/ftw64.c @@ -9,7 +9,14 @@ * int rc = -1; */ - rc = real_ftw64(path, fn, nopenfd); + // 1. Set the flag argument to 0, just like glibc does. + // 2. The difference between ftw and nftw callback + // is only the last parameter: struct FTW is only used + // by nftw(), and it is missing from ftw(). + // However since otherwise the stacklayout for the + // functions is the same, this cast should work just the + // way we want it. This is also borrowed from glibc. + rc = wrap_nftw64(path, (void *)fn, nopenfd, 0); /* return rc; * } diff --git a/ports/unix/guts/ftw.c b/ports/unix/guts/ftw.c index 58945a1..12a17f4 100644 --- a/ports/unix/guts/ftw.c +++ b/ports/unix/guts/ftw.c @@ -9,7 +9,15 @@ * int rc = -1; */ - rc = real_ftw(path, fn, nopenfd); + // 1. Set the flag argument to 0, just like glibc does. + // 2. The difference between ftw and nftw callback + // is only the last parameter: struct FTW is only used + // by nftw(), and it is missing from ftw(). + // However since otherwise the stacklayout for the + // functions is the same, this cast should work just the + // way we want it. This is also borrowed from glibc. + + rc = wrap_nftw(path, (void *)fn, nopenfd, 0); /* return rc; * } From patchwork Sat May 3 19:59:54 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 62382 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93030C3ABB0 for ; Sat, 3 May 2025 20:02:03 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.web11.17579.1746302522581255012 for ; Sat, 03 May 2025 13:02:02 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.14.1/8.14.1) with ESMTP id 543JxuJw008895; Sat, 3 May 2025 14:59:57 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: skandigraun@gmail.com, landervanloock@gmail.com, richard.purdie@linuxfoundation.org, fntoth@gmail.com Subject: [pseudo][PATCH v3 2/3] nftw, nftw64: add wrapper Date: Sat, 3 May 2025 14:59:54 -0500 Message-Id: <1746302395-8723-3-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sat, 03 May 2025 20:02:03 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/1484 From: "Gyorgy Sarvari via lists.yoctoproject.org" Add a wrapper for nftw and ftw[1] calls (along with nftw64 and ftw64). The call in brief: it accepts a path, which it walks. For every entries it finds, it calls a user-specified callback function, and passes some information about the entry to this callback. The implementation saves the callback from the nftw call, and subtitutes it with its own "fake_callback". When the real nftw calls the fake_callback, it corrects the stat struct it received with information queried from pseudo. Afterwards it calls the original callback and passes the now corrected information to it. Since nftw and nftw64 are so similar, the same codebase is used to implement both (which is also fairly similar to their implementation in glibc). [1]: https://linux.die.net/man/3/nftw Signed-off-by: Gyorgy Sarvari Rework nftw_wrapper_base to provide a 'pseudo_' function for the generated wrapper functions to call. This is cleaner for debugging and profiling in the future as the standard wrapper is now being used. Reworded the commit message above to remove references to a chunk that is no longer applicable. Added error checking around chdir and fchdir function calls. Signed-off-by: Mark Hatle --- guts/README | 4 +- ports/linux/guts/nftw64.c | 2 +- ports/linux/pseudo_wrappers.c | 10 ++ ports/unix/guts/nftw.c | 2 +- ports/unix/guts/nftw_wrapper_base.c | 195 ++++++++++++++++++++++++++++ ports/unix/pseudo_wrappers.c | 10 ++ 6 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 ports/unix/guts/nftw_wrapper_base.c diff --git a/guts/README b/guts/README index bb2e573..a1f30f5 100644 --- a/guts/README +++ b/guts/README @@ -96,6 +96,8 @@ wrappers: freopen64 mkstemp mkstemp64 + nftw64 + nftw fcntl fork link @@ -136,8 +138,6 @@ calling the underlying routine. lutimes mkdtemp mktemp - nftw64 - nftw opendir pathconf readlinkat diff --git a/ports/linux/guts/nftw64.c b/ports/linux/guts/nftw64.c index 816faba..8946109 100644 --- a/ports/linux/guts/nftw64.c +++ b/ports/linux/guts/nftw64.c @@ -9,7 +9,7 @@ * int rc = -1; */ - rc = real_nftw64(path, fn, nopenfd, flag); + rc = pseudo_nftw64(path, fn, nopenfd, flag); /* return rc; * } diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c index 7659897..c39cf20 100644 --- a/ports/linux/pseudo_wrappers.c +++ b/ports/linux/pseudo_wrappers.c @@ -148,3 +148,13 @@ static int wrap_prctl(int option, va_list ap) { (void) ap; return -1; } + +#undef NFTW_NAME +#undef NFTW_STAT_NAME +#undef NFTW_STAT_STRUCT +#undef NFTW_LSTAT_NAME +#define NFTW_NAME nftw64 +#define NFTW_STAT_NAME stat64 +#define NFTW_STAT_STRUCT stat64 +#define NFTW_LSTAT_NAME lstat64 +#include "../unix/guts/nftw_wrapper_base.c" diff --git a/ports/unix/guts/nftw.c b/ports/unix/guts/nftw.c index dac3106..7a81acf 100644 --- a/ports/unix/guts/nftw.c +++ b/ports/unix/guts/nftw.c @@ -9,7 +9,7 @@ * int rc = -1; */ - rc = real_nftw(path, fn, nopenfd, flag); + rc = pseudo_nftw(path, fn, nopenfd, flag); /* return rc; * } diff --git a/ports/unix/guts/nftw_wrapper_base.c b/ports/unix/guts/nftw_wrapper_base.c new file mode 100644 index 0000000..d13712b --- /dev/null +++ b/ports/unix/guts/nftw_wrapper_base.c @@ -0,0 +1,195 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-only + */ + +/* + * Whatever includes this is expected to defind the four items + * + * #define NFTW_NAME nftw64 + * #define NFTW_STAT_STRUCT stat64 + * #define NFTW_STAT_NAME stat64 + * #define NFTW_LSTAT_NAME lstat64 + */ + +#define NFTW_CONCAT_EXPANDED(prefix, value) prefix ## value +#define NFTW_CONCAT(prefix, value) NFTW_CONCAT_EXPANDED(prefix, value) + +#define NFTW_PSEUDO_NAME NFTW_CONCAT(pseudo_, NFTW_NAME) +#define NFTW_REAL_NAME NFTW_CONCAT(real_, NFTW_NAME) + +#define NFTW_CALLBACK_NAME NFTW_CONCAT(wrap_callback_, NFTW_NAME) +#define NFTW_STORAGE_STRUCT_NAME NFTW_CONCAT(storage_struct_, NFTW_NAME) +#define NFTW_STORAGE_ARRAY_SIZE NFTW_CONCAT(storage_size_, NFTW_NAME) +#define NFTW_MUTEX_NAME NFTW_CONCAT(mutex_, NFTW_NAME) +#define NFTW_STORAGE_ARRAY_NAME NFTW_CONCAT(storage_array_, NFTW_NAME) +#define NFTW_APPEND_FN_NAME NFTW_CONCAT(append_to_array_, NFTW_NAME) +#define NFTW_DELETE_FN_NAME NFTW_CONCAT(delete_from_array_, NFTW_NAME) +#define NFTW_FIND_FN_NAME NFTW_CONCAT(find_in_array_, NFTW_NAME) + +struct NFTW_STORAGE_STRUCT_NAME { + int (*callback)(const char *, const struct NFTW_STAT_STRUCT *, int, struct FTW *); + int flags; + pthread_t tid; +}; + +static struct NFTW_STORAGE_STRUCT_NAME *NFTW_STORAGE_ARRAY_NAME; +size_t NFTW_STORAGE_ARRAY_SIZE = 0; +static pthread_mutex_t NFTW_MUTEX_NAME = PTHREAD_MUTEX_INITIALIZER; + +static void NFTW_APPEND_FN_NAME(struct NFTW_STORAGE_STRUCT_NAME *data_to_append){ + NFTW_STORAGE_ARRAY_NAME = realloc(NFTW_STORAGE_ARRAY_NAME, ++NFTW_STORAGE_ARRAY_SIZE * sizeof(*data_to_append)); + memcpy(&NFTW_STORAGE_ARRAY_NAME[NFTW_STORAGE_ARRAY_SIZE - 1], data_to_append, sizeof(*data_to_append)); +} + +int NFTW_FIND_FN_NAME(struct NFTW_STORAGE_STRUCT_NAME* target) { + pthread_t tid = pthread_self(); + + // return the last one, not the first + for (ssize_t i = NFTW_STORAGE_ARRAY_SIZE - 1; i >= 0; --i){ + if (NFTW_STORAGE_ARRAY_NAME[i].tid == tid){ + // need to dereference it, as next time this array + // may be realloc'd, making the original pointer + // invalid + *target = NFTW_STORAGE_ARRAY_NAME[i]; + return 1; + } + } + + return 0; +} + +static void NFTW_DELETE_FN_NAME() { + pthread_t tid = pthread_self(); + + if (NFTW_STORAGE_ARRAY_SIZE == 1) { + if (NFTW_STORAGE_ARRAY_NAME[0].tid == tid) { + free(NFTW_STORAGE_ARRAY_NAME); + NFTW_STORAGE_ARRAY_NAME = NULL; + --NFTW_STORAGE_ARRAY_SIZE; + } else { + pseudo_diag("%s: Invalid callback storage content, can't find corresponding data", __func__); + } + return; + } + + int found_idx = -1; + for (ssize_t i = NFTW_STORAGE_ARRAY_SIZE - 1; i >= 0; --i) { + if (NFTW_STORAGE_ARRAY_NAME[i].tid == tid) { + found_idx = i; + break; + } + } + + if (found_idx == -1) { + pseudo_diag("%s: Invalid callback storage content, can't find corresponding data", __func__); + return; + } + + // delete the item we just found + for (size_t i = found_idx + 1; i < NFTW_STORAGE_ARRAY_SIZE; ++i) + NFTW_STORAGE_ARRAY_NAME[i - 1] = NFTW_STORAGE_ARRAY_NAME[i]; + + NFTW_STORAGE_ARRAY_NAME = realloc(NFTW_STORAGE_ARRAY_NAME, --NFTW_STORAGE_ARRAY_SIZE * sizeof(struct NFTW_STORAGE_STRUCT_NAME)); + +} + +static int NFTW_CALLBACK_NAME(const char* fpath, const struct NFTW_STAT_STRUCT __attribute__((unused)) *sb, int typeflag, struct FTW *ftwbuf) { + int orig_cwd_fd = -1; + char *orig_cwd = NULL; + char *target_dir = NULL; + struct NFTW_STORAGE_STRUCT_NAME saved_details; + struct NFTW_STAT_STRUCT pseudo_sb; + + if (!NFTW_FIND_FN_NAME(&saved_details)) { + pseudo_diag("%s: Could not find corresponding callback!", __func__); + return -1; + } + + // This flag is handled by nftw, however the actual directory change happens + // outside of pseudo, so it doesn't have any effect. To mitigate this, handle + // it here also explicitly. + // + // This is very similar to what glibc is doing: keep an open FD for the + // current working directory, process the entry (determine the flags, etc), + // call the callback, and then switch back to the original folder - in the same + // process. Glibc doesn't seem to take any further thread-safety measures nor + // other special steps. + // + // See io/ftw.c in glibc source + if (saved_details.flags & FTW_CHDIR) { + orig_cwd_fd = open(".", O_RDONLY | O_DIRECTORY); + if (orig_cwd_fd == -1) { + orig_cwd = getcwd(NULL, 0); + } + + // If it is a folder that's content has been already walked with the + // FTW_DEPTH flag, then switch into this folder, instead of the parent of + // it. This matches the behavior of the real nftw in this special case. + // This seems to be undocumented - it was derived by observing this behavior. + if (typeflag == FTW_DP) { + if (chdir(fpath) == -1) + return -1; + } else { + target_dir = malloc(ftwbuf->base + 1); + memset(target_dir, 0, ftwbuf->base + 1); + strncpy(target_dir, fpath, ftwbuf->base); + if (chdir(target_dir) == -1) + return -1; + } + } + + // This is the main point of this call. Instead of the stat that + // came from real_nftw, use the stat returned by pseudo. + // + // We use our own stat memory as the stat from nftw is labeled const + // and while it would probably be safe to re-use, there is a + // chance it won't be. + // + // If the target can't be stat'd (DNR), then just we clear the + // stat memory as no information can be retrieved of it anyway. + if (typeflag != FTW_DNR) { + (saved_details.flags & FTW_PHYS) ? NFTW_LSTAT_NAME(fpath, &pseudo_sb) : NFTW_STAT_NAME(fpath, &pseudo_sb); + } else { + /* Clear memory so we're not passing something we shouldn't */ + memset(&pseudo_sb, 0, sizeof(pseudo_sb)); + } + + int ret = saved_details.callback(fpath, &pseudo_sb, typeflag, ftwbuf); + + if (saved_details.flags & FTW_CHDIR) { + if (orig_cwd_fd != -1) { + if (fchdir(orig_cwd_fd) == -1) + return -1; + close(orig_cwd_fd); + } else if (orig_cwd != NULL) { + if (chdir(orig_cwd) == -1) + return -1; + } + free(target_dir); + } + + return ret; +} + +static int +NFTW_PSEUDO_NAME(const char *path, int (*fn)(const char *, const struct NFTW_STAT_STRUCT *, int, struct FTW *), int nopenfd, int flag) { + int rc = -1; + + struct NFTW_STORAGE_STRUCT_NAME saved_details; + + saved_details.tid = pthread_self(); + saved_details.flags = flag; + saved_details.callback = fn; + + pthread_mutex_lock(&NFTW_MUTEX_NAME); + NFTW_APPEND_FN_NAME(&saved_details); + pthread_mutex_unlock(&NFTW_MUTEX_NAME); + + // Call the real function, but use our callback to intercept the answer + rc = NFTW_REAL_NAME(path, NFTW_CALLBACK_NAME, nopenfd, flag); + + pthread_mutex_lock(&NFTW_MUTEX_NAME); + NFTW_DELETE_FN_NAME(); + pthread_mutex_unlock(&NFTW_MUTEX_NAME); + return rc; +} diff --git a/ports/unix/pseudo_wrappers.c b/ports/unix/pseudo_wrappers.c index bf69aa9..41398e4 100644 --- a/ports/unix/pseudo_wrappers.c +++ b/ports/unix/pseudo_wrappers.c @@ -52,3 +52,13 @@ wrap_popen(const char *command, const char *mode) { return rc; } + +#undef NFTW_NAME +#undef NFTW_STAT_NAME +#undef NFTW_STAT_STRUCT +#undef NFTW_LSTAT_NAME +#define NFTW_NAME nftw +#define NFTW_STAT_NAME stat +#define NFTW_STAT_STRUCT stat +#define NFTW_LSTAT_NAME lstat +#include "guts/nftw_wrapper_base.c" From patchwork Sat May 3 19:59:55 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 62383 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93E98C3DA4A for ; Sat, 3 May 2025 20:02:03 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.web11.17578.1746302520851545378 for ; Sat, 03 May 2025 13:02:01 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.14.1/8.14.1) with ESMTP id 543JxuJx008895; Sat, 3 May 2025 14:59:58 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: skandigraun@gmail.com, landervanloock@gmail.com, richard.purdie@linuxfoundation.org, fntoth@gmail.com Subject: [pseudo][PATCH v3 3/3] ftw, nftw, ftw64 and nftw64: add tests Date: Sat, 3 May 2025 14:59:55 -0500 Message-Id: <1746302395-8723-4-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1746302395-8723-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sat, 03 May 2025 20:02:03 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/1483 From: "Gyorgy Sarvari via lists.yoctoproject.org" Add tests for nftw, ftw, nftw64 and ftw64 calls. Signed-off-by: Gyorgy Sarvari Signed-off-by: Mark Hatle --- Makefile.in | 4 +- test/ftw-test-impl.c | 226 ++++++++++++++++++++++++++++++++++++++++ test/nftw-test-impl.c | 236 ++++++++++++++++++++++++++++++++++++++++++ test/test-ftw.c | 4 + test/test-ftw64.c | 4 + test/test-nftw.c | 4 + test/test-nftw.sh | 90 ++++++++++++++++ test/test-nftw64.c | 4 + 8 files changed, 570 insertions(+), 2 deletions(-) create mode 100644 test/ftw-test-impl.c create mode 100644 test/nftw-test-impl.c create mode 100644 test/test-ftw.c create mode 100644 test/test-ftw64.c create mode 100644 test/test-nftw.c create mode 100755 test/test-nftw.sh create mode 100644 test/test-nftw64.c diff --git a/Makefile.in b/Makefile.in index 983a7cf..3a248ca 100644 --- a/Makefile.in +++ b/Makefile.in @@ -55,7 +55,7 @@ GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN))) SOURCES=$(wildcard *.c) OBJS=$(subst .c,.o,$(SOURCES)) -TESTS=$(patsubst %.c,%,$(wildcard test/*.c)) +TESTS=$(patsubst %.c,%,$(wildcard test/test-*.c)) SHOBJS=pseudo_tables.o pseudo_util.o DBOBJS=pseudo_db.o @@ -78,7 +78,7 @@ all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) test: all $(TESTS) | $(BIN) $(LIB) ./run_tests.sh -v -test/%: test/%.c +test/test-%: test/test-%.c $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $@ $< install-lib: $(LIBPSEUDO) diff --git a/test/ftw-test-impl.c b/test/ftw-test-impl.c new file mode 100644 index 0000000..3ef7f5c --- /dev/null +++ b/test/ftw-test-impl.c @@ -0,0 +1,226 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#define LAST_VAL 999 +#define LAST_PATH "LAST_SENTINEL" + +#define TEST_WITH_PSEUDO 1 +#define TEST_WITHOUT_PSEUDO 0 + +static int current_idx = 0; +static int* current_responses; +static char** expected_fpaths; + +static int pseudo_active; + +static unsigned int expected_gid; +static unsigned int expected_uid; + +static int current_recursion_level = 0; +static int max_recursion = 0; + + +static int callback(const char* fpath, const struct FTW_STAT_STRUCT *sb, int __attribute__ ((unused)) typeflag){ + if (current_recursion_level < max_recursion) { + ++current_recursion_level; + if (FTW_NAME("./walking/a1", callback, 10) != 0) { + printf("Recursive call failed\n"); + exit(1); + } + } + + + int ret = current_responses[current_idx]; + // printf("idx: %d, path: %s, ret: %d\n", current_idx, fpath, ret); + + if (ret == LAST_VAL){ + printf("Unexpected callback, it should have stopped already! fpath: %s\n", fpath); + return FTW_STOP; + } + + char* expected_fpath_ending = expected_fpaths[current_idx]; + + if (strcmp(expected_fpath_ending, LAST_PATH) == 0){ + printf("Unexpected fpath received: %s\n", fpath); + return FTW_STOP; + } + + const char* actual_fpath_ending = fpath + strlen(fpath) - strlen(expected_fpath_ending); + + if (strcmp(actual_fpath_ending, expected_fpath_ending) != 0){ + printf("Incorrect fpath received. Expected: %s, actual: %s\n", expected_fpath_ending, actual_fpath_ending); + return FTW_STOP; + } + + if (pseudo_active) { + if (sb->st_gid != 0 || sb->st_uid != 0) { + printf("Invalid uid/gid! Gid (act/exp): %d/%d, Uid (act/exp): %d/%d\n", sb->st_gid, 0, sb->st_uid, 0); + return FTW_STOP; + } + } else if (sb->st_gid != expected_gid || sb->st_uid != expected_uid) { + printf("Invalid uid/gid! Gid (act/exp): %d/%d, Uid (act/exp): %d/%d\n", sb->st_gid, expected_gid, sb->st_uid, expected_uid); + return FTW_STOP; + } + + ++current_idx; + return ret; +} + +static int run_test(int* responses, char** fpaths, int expected_retval, int with_pseudo) { + int ret; + current_responses = responses; + expected_fpaths = fpaths; + pseudo_active = with_pseudo; + + ret = FTW_NAME("./walking", callback, 10); + current_responses = NULL; + expected_fpaths = NULL; + + if (ret != expected_retval){ + printf("Incorrect return value. Expected: %d, actual: %d\n", expected_retval, ret); + return 1; + } + + if (responses[current_idx] != LAST_VAL){ + printf("Not all expected paths were walked!\n"); + return 1; + } + return 0; +} + +/* + * This test just walks the whole test directory structure, and verifies that + * all expected files are returned. + */ +static int test_walking(int with_pseudo){ + int responses[] = {FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, LAST_VAL}; + + char* fpaths[] = {"walking", + "walking/a1", + "walking/a1/b2", + "walking/a1/b2/file5", + "walking/a1/b2/file4", + "walking/a1/b1", + "walking/a1/b1/c1", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1/file3", + "walking/a1/b1/c1/file2", + "walking/a1/b3", + LAST_PATH}; + + int expected_retval = 0; + + return run_test(responses, fpaths, expected_retval, with_pseudo); +} + +/* + * This test is very similar to test_walking(), but the callback at the + * start also calls ftw(), "max_recursion" times. + * It is trying to test pseudo's implementation of handling multiple + * concurrent (n)ftw calls in the same thread. + */ +static int test_walking_recursion(int with_pseudo){ + max_recursion = 3; + + int responses[] = {FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, + FTW_CONTINUE, LAST_VAL}; + + char* fpaths[] = {"walking/a1", + "walking/a1/b2", + "walking/a1/b2/file5", + "walking/a1/b2/file4", + "walking/a1/b1", + "walking/a1/b1/c1", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1/file3", + "walking/a1/b1/c1/file2", + "walking/a1/b3", + "walking/a1", + "walking/a1/b2", + "walking/a1/b2/file5", + "walking/a1/b2/file4", + "walking/a1/b1", + "walking/a1/b1/c1", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1/file3", + "walking/a1/b1/c1/file2", + "walking/a1/b3", + "walking/a1", + "walking/a1/b2", + "walking/a1/b2/file5", + "walking/a1/b2/file4", + "walking/a1/b1", + "walking/a1/b1/c1", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1/file3", + "walking/a1/b1/c1/file2", + "walking/a1/b3", + "walking", + "walking/a1", + "walking/a1/b2", + "walking/a1/b2/file5", + "walking/a1/b2/file4", + "walking/a1/b1", + "walking/a1/b1/c1", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1/file3", + "walking/a1/b1/c1/file2", + "walking/a1/b3", + LAST_PATH}; + int expected_retval = 0; + + return run_test(responses, fpaths, expected_retval, with_pseudo); +} + +/* + * Arguments: + * argv[1]: always the test name + * argv[2], argv[3]: in case the test name refers to a test without using + * pseudo (no_pseudo), then they should be the gid and uid + * of the current user. Otherwise these arguments are ignored. + * + * ftw64 call only exists on Linux in case __USE_LARGEFILE64 is defined. + * If this is not the case, just skip this test. + */ +int main(int argc, char* argv[]) +{ +#if !defined(__USE_LARGEFILE64) && FTW_NAME == ftw64 +return 0; +#endif + if (argc < 2) { + printf("Need a test name as argument\n"); + return 1; + } + + if (strcmp(argv[1], "pseudo_no_recursion") == 0) { + return test_walking(TEST_WITH_PSEUDO); + } else if (strcmp(argv[1], "no_pseudo_no_recursion") == 0) { + expected_gid = atoi(argv[2]); + expected_uid = atoi(argv[3]); + return test_walking(TEST_WITHOUT_PSEUDO); + } if (strcmp(argv[1], "pseudo_recursion") == 0) { + return test_walking_recursion(TEST_WITH_PSEUDO); + } if (strcmp(argv[1], "no_pseudo_recursion") == 0) { + expected_gid = atoi(argv[2]); + expected_uid = atoi(argv[3]); + return test_walking_recursion(TEST_WITHOUT_PSEUDO); + } else { + printf("Unknown test name: %s\n", argv[1]); + return 1; + } +} diff --git a/test/nftw-test-impl.c b/test/nftw-test-impl.c new file mode 100644 index 0000000..df520cb --- /dev/null +++ b/test/nftw-test-impl.c @@ -0,0 +1,236 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#define PATH_MAX 1024 +#define LAST_VAL 999 +#define LAST_PATH "LAST_SENTINEL" + +#define TEST_WITH_PSEUDO 1 +#define TEST_WITHOUT_PSEUDO 0 + +#define TEST_CHDIR 1 +#define TEST_NO_CHDIR 0 + +static int current_idx = 0; +static int* current_responses; +static char** expected_fpaths; + +static int pseudo_active; +static int verify_folder = 0; +static char* base_dir = NULL; + +static unsigned int expected_gid; +static unsigned int expected_uid; + +static int compare_paths(const char *path1, const char *path2){ + char full_path1[PATH_MAX] = {0}; + char full_path2[PATH_MAX] = {0}; + + if (path1[0] == '.'){ + strcat(full_path1, base_dir); + strcat(full_path1, path1 + 1); + } else { + strcpy(full_path1, path1); + } + + if (path2[0] == '.'){ + strcat(full_path2, base_dir); + strcat(full_path2, path2 + 1); + } else { + strcpy(full_path2, path2); + } + + return strcmp(full_path1, full_path2); +} + +static int callback(const char* fpath, const struct NFTW_STAT_STRUCT *sb, int typeflag, struct FTW *ftwbuf){ + int ret = current_responses[current_idx]; +// printf("path: %s, ret: %d\n", fpath, ret); + + if (ret == LAST_VAL){ + printf("Unexpected callback, it should have stopped already! fpath: %s\n", fpath); + return FTW_STOP; + } + + char* expected_fpath_ending = expected_fpaths[current_idx]; + + if (strcmp(expected_fpath_ending, LAST_PATH) == 0){ + printf("Unexpected fpath received: %s\n", fpath); + return FTW_STOP; + } + + const char* actual_fpath_ending = fpath + strlen(fpath) - strlen(expected_fpath_ending); + + if (strcmp(actual_fpath_ending, expected_fpath_ending) != 0){ + printf("Incorrect fpath received. Expected: %s, actual: %s\n", expected_fpath_ending, actual_fpath_ending); + return FTW_STOP; + } + + if (pseudo_active) { + if (sb->st_gid != 0 || sb->st_uid != 0) { + printf("Invalid uid/gid! Gid (act/exp): %d/%d, Uid (act/exp): %d/%d\n", sb->st_gid, 0, sb->st_uid, 0); + return FTW_STOP; + } + } else if (sb->st_gid != expected_gid || sb->st_uid != expected_uid) { + printf("Invalid uid/gid! Gid (act/exp): %d/%d, Uid (act/exp): %d/%d\n", sb->st_gid, expected_gid, sb->st_uid, expected_uid); + return FTW_STOP; + } + + if (verify_folder) { + int res; + char* cwd = NULL; + cwd = getcwd(NULL, 0); + + char* exp_cwd = NULL; + if (typeflag == FTW_DP){ + res = compare_paths(fpath, cwd); + } else { + char* exp_cwd = malloc(ftwbuf->base); + memset(exp_cwd, 0, ftwbuf->base); + strncpy(exp_cwd, fpath, ftwbuf->base - 1); + res = compare_paths(cwd, exp_cwd); + } + + free(cwd); + free(exp_cwd); + + if (res != 0) { + printf("Incorrect folder for %s\n", fpath); + return FTW_STOP; + } + } + + ++current_idx; + return ret; +} + +static int run_test(int* responses, char** fpaths, int expected_retval, int with_pseudo, int flags) { + int ret; + current_responses = responses; + expected_fpaths = fpaths; + pseudo_active = with_pseudo; + + ret = NFTW_NAME("./walking", callback, 10, flags); + current_responses = NULL; + expected_fpaths = NULL; + + if (ret != expected_retval){ + printf("Incorrect return value. Expected: %d, actual: %d\n", expected_retval, ret); + return 1; + } + + if (responses[current_idx] != LAST_VAL){ + printf("Not all expected paths were walked!\n"); + return 1; + } + return 0; +} + +static int test_skip_siblings_file_depth_walking(int with_pseudo, int change_dir){ + int responses[] = {FTW_SKIP_SIBLINGS, FTW_CONTINUE, FTW_SKIP_SIBLINGS, FTW_CONTINUE, + FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, FTW_CONTINUE, LAST_VAL}; + char* fpaths[] = {"walking/a1/b2/file5", + "walking/a1/b2", + "walking/a1/b1/c1/file", + "walking/a1/b1/c1", + "walking/a1/b1", + "walking/a1/b3", + "walking/a1", + "walking", + LAST_PATH}; + int expected_retval = 0; + int flags = FTW_ACTIONRETVAL | FTW_DEPTH; + + // store base_dir, because the fpath returned by (n)ftw can be relative to this + // folder - that way a full absolute path can be constructed and compared, + // if needed. + if (change_dir){ + flags |= FTW_CHDIR; + base_dir = getcwd(NULL, 0); + verify_folder = 1; + } + + return run_test(responses, fpaths, expected_retval, with_pseudo, flags); +} + +/* + * Every time a folder entry is sent to the callback, respond with FTW_SKIP_SUBTREE. + * This should skip that particular folder completely, and continue processing + * with its siblings (or parent, if there are no siblings). + * Return value is expected to be 0, default walking order. + */ +static int test_skip_subtree_on_folder(int with_pseudo){ + int responses[] = {FTW_CONTINUE, FTW_CONTINUE, FTW_SKIP_SUBTREE, FTW_SKIP_SUBTREE, + FTW_SKIP_SUBTREE, LAST_VAL}; + char* fpaths[] = {"walking", + "walking/a1", + "walking/a1/b2", + "walking/a1/b1", + "walking/a1/b3", + LAST_PATH}; + int expected_retval = 0; + int flags = FTW_ACTIONRETVAL; + + return run_test(responses, fpaths, expected_retval, with_pseudo, flags); +} + +/* + * Arguments: + * argv[1]: always the test name + * argv[2], argv[3]: in case the test name refers to a test without using + * pseudo (no_pseudo), then they should be the gid and uid + * of the current user. Otherwise these arguments are ignored. + * + * skip_subtree_pseudo/skip_subtree_no_pseudo: these tests are calling nftw() + * with the FTW_ACTIONRETVAL flag, which reacts based on the return value from the + * callback. These tests check the call's reaction to FTW_SKIP_SUBTREE call, + * upon which nftw() should stop processing the current folder, and continue + * with the next sibling of the folder. + * + * skip_siblings_pseudo/skip_siblings_no_pseudo: very similar to skip_subtree + * tests, but it verified FTW_SKIP_SIBLINGS response, which should stop processing + * the current folder, and continue in its parent. + * + * skip_siblings_chdir_pseudo/skip_siblings_chdir_no_pseudoL same as skip_siblings + * tests, but also pass the FTW_CHDIR flag and verify that the working directory + * is changed as expected between callback calls. + * + * nftw64 call only exists on Linux in case __USE_LARGEFILE64 is defined. + * If this is not the case, just skip this test. + */ +int main(int argc, char* argv[]) +{ +#if !defined(__USE_LARGEFILE64) && NFTW_NAME == nftw64 +return 0; +#endif + if (argc < 2) { + printf("Need a test name as argument\n"); + return 1; + } + + if (argc > 2) { + expected_gid = atoi(argv[2]); + expected_uid = atoi(argv[3]); + } + + if (strcmp(argv[1], "skip_subtree_pseudo") == 0) { + return test_skip_subtree_on_folder(TEST_WITH_PSEUDO); + } else if (strcmp(argv[1], "skip_subtree_no_pseudo") == 0) { + return test_skip_subtree_on_folder(TEST_WITHOUT_PSEUDO); + } else if (strcmp(argv[1], "skip_siblings_pseudo") == 0) { + return test_skip_siblings_file_depth_walking(TEST_WITH_PSEUDO, TEST_NO_CHDIR); + } else if (strcmp(argv[1], "skip_siblings_no_pseudo") == 0) { + return test_skip_siblings_file_depth_walking(TEST_WITHOUT_PSEUDO, TEST_NO_CHDIR); + } else if (strcmp(argv[1], "skip_siblings_chdir_pseudo") == 0) { + return test_skip_siblings_file_depth_walking(TEST_WITH_PSEUDO, TEST_CHDIR); + } else if (strcmp(argv[1], "skip_siblings_chdir_no_pseudo") == 0) { + return test_skip_siblings_file_depth_walking(TEST_WITHOUT_PSEUDO, TEST_CHDIR); + } else { + printf("Unknown test name\n"); + return 1; + } +} diff --git a/test/test-ftw.c b/test/test-ftw.c new file mode 100644 index 0000000..5c47dd9 --- /dev/null +++ b/test/test-ftw.c @@ -0,0 +1,4 @@ +#define FTW_NAME ftw +#define FTW_STAT_STRUCT stat + +#include "ftw-test-impl.c" diff --git a/test/test-ftw64.c b/test/test-ftw64.c new file mode 100644 index 0000000..0b8f906 --- /dev/null +++ b/test/test-ftw64.c @@ -0,0 +1,4 @@ +#define FTW_NAME ftw64 +#define FTW_STAT_STRUCT stat64 + +#include "ftw-test-impl.c" diff --git a/test/test-nftw.c b/test/test-nftw.c new file mode 100644 index 0000000..ecadc1e --- /dev/null +++ b/test/test-nftw.c @@ -0,0 +1,4 @@ +#define NFTW_NAME nftw +#define NFTW_STAT_STRUCT stat + +#include "nftw-test-impl.c" diff --git a/test/test-nftw.sh b/test/test-nftw.sh new file mode 100755 index 0000000..df3890e --- /dev/null +++ b/test/test-nftw.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Test nftw call and its behavior modifying flags +# SPDX-License-Identifier: LGPL-2.1-only +# + +trap "rm -rf ./walking" 0 + +ret=0 + +check_retval_and_fail_if_needed(){ + if [ $1 -ne 0 ]; then + echo test-nftw: $2: Failed + ret=1 + else + echo test-nftw: $2: Passed + fi +} + + +mkdir -p walking/a1/b1/c1 +touch walking/a1/b1/c1/file +mkdir walking/a1/b2 +mkdir walking/a1/b3 +touch walking/a1/b1/c1/file2 +touch walking/a1/b1/c1/file3 +touch walking/a1/b2/file4 +touch walking/a1/b2/file5 + +./test/test-nftw skip_subtree_pseudo +check_retval_and_fail_if_needed $? "nftw subtree skipping with pseudo" + +./test/test-nftw skip_siblings_pseudo +check_retval_and_fail_if_needed $? "nftw sibling skipping with pseudo" + +./test/test-nftw skip_siblings_chdir_pseudo +check_retval_and_fail_if_needed $? "nftw sibling skipping chddir with pseudo" + +./test/test-nftw64 skip_subtree_pseudo +check_retval_and_fail_if_needed $? "nftw64 subtree skipping with pseudo" + +./test/test-nftw64 skip_siblings_pseudo +check_retval_and_fail_if_needed $? "nftw64 sibling skipping with pseudo" + +./test/test-ftw pseudo_no_recursion +check_retval_and_fail_if_needed $? "ftw non-recursive walking with pseudo" + +./test/test-ftw pseudo_recursion +check_retval_and_fail_if_needed $? "ftw recursive walking with pseudo" + +./test/test-ftw64 pseudo_no_recursion +check_retval_and_fail_if_needed $? "ftw64 non-recursive walking with pseudo" + +./test/test-ftw64 pseudo_recursion +check_retval_and_fail_if_needed $? "ftw64 recursive walking with pseudo" + + +export PSEUDO_DISABLED=1 + +uid=`env -i id -u` +gid=`env -i id -g` + +./test/test-nftw skip_subtree_no_pseudo $gid $uid +check_retval_and_fail_if_needed $? "nftw subtree skipping without pseudo" + +./test/test-nftw skip_siblings_no_pseudo $gid $uid +check_retval_and_fail_if_needed $? "nftw sibling skipping without pseudo" + +./test/test-nftw skip_siblings_chdir_no_pseudo $gid $uid +check_retval_and_fail_if_needed $? "nftw sibling skipping chdir without pseudo" + +./test/test-nftw64 skip_subtree_no_pseudo $gid $uid +check_retval_and_fail_if_needed $? "nftw subtree skipping without pseudo" + +./test/test-nftw64 skip_siblings_no_pseudo $gid $uid +check_retval_and_fail_if_needed $? "nftw sibling skipping without pseudo" + +./test/test-ftw no_pseudo_no_recursion $gid $uid +check_retval_and_fail_if_needed $? "ftw non-recursive walking without pseudo" + +./test/test-ftw no_pseudo_recursion $gid $uid +check_retval_and_fail_if_needed $? "ftw recursive walking without pseudo" + +./test/test-ftw64 no_pseudo_no_recursion $gid $uid +check_retval_and_fail_if_needed $? "ftw non-recursive walking without pseudo" + +./test/test-ftw64 no_pseudo_recursion $gid $uid +check_retval_and_fail_if_needed $? "ftw recursive walking without pseudo" + +exit $ret diff --git a/test/test-nftw64.c b/test/test-nftw64.c new file mode 100644 index 0000000..20f25af --- /dev/null +++ b/test/test-nftw64.c @@ -0,0 +1,4 @@ +#define NFTW_NAME nftw64 +#define NFTW_STAT_STRUCT stat64 + +#include "nftw-test-impl.c"