diff mbox series

[pseudo,v3,2/3] nftw, nftw64: add wrapper

Message ID 1746302395-8723-3-git-send-email-mark.hatle@kernel.crashing.org
State New
Headers show
Series nftw, ftw: add wrappers | expand

Commit Message

Mark Hatle May 3, 2025, 7:59 p.m. UTC
From: "Gyorgy Sarvari via lists.yoctoproject.org" <skandigraun=gmail.com@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 <skandigraun@gmail.com>

Rework nftw_wrapper_base to provide a 'pseudo_<name>' 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 <mark.hatle@amd.com>
---
 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

Comments

Gyorgy Sarvari May 4, 2025, 1:48 p.m. UTC | #1
On 5/3/25 21:59, Mark Hatle wrote:
> From: "Gyorgy Sarvari via lists.yoctoproject.org" <skandigraun=gmail.com@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 <skandigraun@gmail.com>
>
> Rework nftw_wrapper_base to provide a 'pseudo_<name>' 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 <mark.hatle@amd.com>
> ---
>  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));
> +    }
I don't think that this is correct. Most likely we could just remove the
"if (typeflag ==...)" condition completely.
I ran a quick test[1], to see what is included in the stat struct when
the typeflag is FTW_DNR, and it has more than I expected.

[1]:
https://gist.github.com/OldManYellsAtCloud/09a34f28d5dbb43722fc3767159462ab

> +
> +    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"
diff mbox series

Patch

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"