diff mbox series

[pseudo,v2,1/2] nftw, ftw: add wrapper

Message ID 20250407191414.2992785-2-skandigraun@gmail.com
State New
Headers show
Series nftw, ftw: add wrappers | expand

Commit Message

Gyorgy Sarvari April 7, 2025, 7:14 p.m. UTC
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.

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

Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
 guts/README                            |   6 +-
 ports/linux/guts/ftw64.c               |  16 --
 ports/linux/nftw64/guts/ftw64.c        |  29 ++++
 ports/linux/{ => nftw64}/guts/nftw64.c |   7 +-
 ports/linux/nftw64/pseudo_wrappers.c   |  45 ++++++
 ports/linux/nftw64/wrapfuncs.in        |   2 +
 ports/linux/subports                   |  14 ++
 ports/linux/wrapfuncs.in               |   2 -
 ports/unix/guts/ftw.c                  |  13 +-
 ports/unix/guts/nftw.c                 |   7 +-
 ports/unix/guts/nftw_wrapper_base.c    | 211 +++++++++++++++++++++++++
 ports/unix/pseudo_wrappers.c           |  45 ++++++
 ports/unix/wrapfuncs.in                |   2 +-
 13 files changed, 373 insertions(+), 26 deletions(-)
 delete mode 100644 ports/linux/guts/ftw64.c
 create mode 100644 ports/linux/nftw64/guts/ftw64.c
 rename ports/linux/{ => nftw64}/guts/nftw64.c (57%)
 create mode 100644 ports/linux/nftw64/pseudo_wrappers.c
 create mode 100644 ports/linux/nftw64/wrapfuncs.in
 create mode 100644 ports/unix/guts/nftw_wrapper_base.c
diff mbox series

Patch

diff --git a/guts/README b/guts/README
index 0a1fe5f..5bcc198 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,15 +131,11 @@  calling the underlying routine.
 	eaccess
 	euidaccess
 	fts_open
-	ftw64
-	ftw
 	glob64
 	glob
 	lutimes
 	mkdtemp
 	mktemp
-	nftw64
-	nftw
 	opendir
 	pathconf
 	readlinkat
diff --git a/ports/linux/guts/ftw64.c b/ports/linux/guts/ftw64.c
deleted file mode 100644
index 48adb80..0000000
--- a/ports/linux/guts/ftw64.c
+++ /dev/null
@@ -1,16 +0,0 @@ 
-/* 
- * Copyright (c) 2010 Wind River Systems; see
- * guts/COPYRIGHT for information.
- *
- * SPDX-License-Identifier: LGPL-2.1-only
- *
- * static int
- * wrap_ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd) {
- *	int rc = -1;
- */
-
-	rc = real_ftw64(path, fn, nopenfd);
-
-/*	return rc;
- * }
- */
diff --git a/ports/linux/nftw64/guts/ftw64.c b/ports/linux/nftw64/guts/ftw64.c
new file mode 100644
index 0000000..1fe0868
--- /dev/null
+++ b/ports/linux/nftw64/guts/ftw64.c
@@ -0,0 +1,29 @@ 
+/* 
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * static int
+ * wrap_ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd) {
+ *	int rc = -1;
+ */
+    typedef int (*pseudo_nftw64_func_t) (const char *filename,
+                                        const struct stat64 *status, int flag,
+                                        struct FTW *info);
+
+    // 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.
+
+    // This, of course in turn will be captured by pseudo, and will be passed
+    // to the respective wrapper of nftw64()
+    rc = nftw64(path, (pseudo_nftw64_func_t)fn, nopenfd, 0);
+
+/*	return rc;
+ * }
+ */
diff --git a/ports/linux/guts/nftw64.c b/ports/linux/nftw64/guts/nftw64.c
similarity index 57%
rename from ports/linux/guts/nftw64.c
rename to ports/linux/nftw64/guts/nftw64.c
index 816faba..0dac9d3 100644
--- a/ports/linux/guts/nftw64.c
+++ b/ports/linux/nftw64/guts/nftw64.c
@@ -9,7 +9,12 @@ 
  *	int rc = -1;
  */
 
-	rc = real_nftw64(path, fn, nopenfd, flag);
+/***********************************
+ * This call has a custom wrapper
+ * where all the logic happens just
+ * around this single line.
+ ***********************************/
+    rc = real_nftw64(path, NFTW_CALLBACK_NAME, nopenfd, flag);
 
 /*	return rc;
  * }
diff --git a/ports/linux/nftw64/pseudo_wrappers.c b/ports/linux/nftw64/pseudo_wrappers.c
new file mode 100644
index 0000000..2bd8227
--- /dev/null
+++ b/ports/linux/nftw64/pseudo_wrappers.c
@@ -0,0 +1,45 @@ 
+#undef NFTW_NAME
+#undef NFTW_WRAP_NAME
+#undef NFTW_REAL_NAME
+#undef NFTW_STAT_STRUCT
+#undef NFTW_STAT_NAME
+#undef NFTW_LSTAT_NAME
+#undef NFTW_GUTS_INCLUDE
+#undef NFTW_CALLBACK_NAME
+#undef NFTW_STORAGE_STRUCT_NAME
+#undef NFTW_STORAGE_ARRAY_SIZE
+#undef NFTW_MUTEX_NAME
+#undef NFTW_STORAGE_ARRAY_NAME
+#undef NFTW_APPEND_FN_NAME
+#undef NFTW_DELETE_FN_NAME
+#undef NFTW_FIND_FN_NAME
+
+#define CONCAT_EXPANDED(prefix, value) prefix ## value
+#define CONCAT(prefix, value) CONCAT_EXPANDED(prefix, value)
+
+#define NFTW_NAME nftw64
+#define NFTW_WRAP_NAME CONCAT(wrap_, NFTW_NAME)
+#define NFTW_REAL_NAME CONCAT(real_, NFTW_NAME)
+#define NFTW_STAT_STRUCT stat64
+#define NFTW_STAT_NAME stat64
+#define NFTW_LSTAT_NAME lstat64
+
+// this file is in the same directory as this wrapper, but
+// this path is relative to the nftw_wrapper_base file, where
+// it gets included.
+#define NFTW_GUTS_INCLUDE "../../linux/nftw64/guts/nftw64.c"
+#define NFTW_CALLBACK_NAME CONCAT(wrap_callback_, NFTW_NAME)
+#define NFTW_STORAGE_STRUCT_NAME CONCAT(storage_struct_, NFTW_NAME)
+#define NFTW_STORAGE_ARRAY_SIZE CONCAT(storage_size_, NFTW_NAME)
+#define NFTW_MUTEX_NAME CONCAT(mutex_, NFTW_NAME)
+#define NFTW_STORAGE_ARRAY_NAME CONCAT(storage_array_, NFTW_NAME)
+#define NFTW_APPEND_FN_NAME CONCAT(append_to_array_, NFTW_NAME)
+#define NFTW_DELETE_FN_NAME CONCAT(delete_from_array_, NFTW_NAME)
+#define NFTW_FIND_FN_NAME CONCAT(find_in_array_, NFTW_NAME)
+
+// nftw64() is identical to nftw() in all regards, except
+// that it uses "stat64" structs instead of "stat", so
+// the whole codebase can be reused, accounting for naming
+// and type changes using the macros above.
+
+#include "../../unix/guts/nftw_wrapper_base.c"
diff --git a/ports/linux/nftw64/wrapfuncs.in b/ports/linux/nftw64/wrapfuncs.in
new file mode 100644
index 0000000..3e2ed67
--- /dev/null
+++ b/ports/linux/nftw64/wrapfuncs.in
@@ -0,0 +1,2 @@ 
+int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); /* noignore_path=1, hand_wrapped=1 */
+int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd);
diff --git a/ports/linux/subports b/ports/linux/subports
index 099ea59..b55bea9 100755
--- a/ports/linux/subports
+++ b/ports/linux/subports
@@ -70,3 +70,17 @@  else
 fi
 rm -f dummy.c dummy.o
 
+# nftw64 (and its deprecated pair, ftw64) are only present in case large
+# file support is present.
+cat > dummy.c <<EOF
+#define _GNU_SOURCE
+#include <features.h>
+#ifndef __USE_LARGEFILE64
+#error "no large file support"
+#endif
+int i;
+EOF
+if ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
+        echo linux/nftw64
+fi
+rm -f dummy.c dummy.o
diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in
index 60ce5f5..c4b06ae 100644
--- a/ports/linux/wrapfuncs.in
+++ b/ports/linux/wrapfuncs.in
@@ -34,9 +34,7 @@  int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLI
 int __fxstat64(int ver, int fd, struct stat64 *buf);
 int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags);
 FILE *fopen64(const char *path, const char *mode); /* noignore_path=1 */
-int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); /* noignore_path=1 */
 FILE *freopen64(const char *path, const char *mode, FILE *stream);  /* noignore_path=1 */
-int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd);
 int glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob);
 int scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)());
 int truncate64(const char *path, off64_t length);
diff --git a/ports/unix/guts/ftw.c b/ports/unix/guts/ftw.c
index 58945a1..4b3f49f 100644
--- a/ports/unix/guts/ftw.c
+++ b/ports/unix/guts/ftw.c
@@ -9,7 +9,18 @@ 
  *	int rc = -1;
  */
 
-	rc = real_ftw(path, fn, nopenfd);
+    typedef int (*pseudo_nftw_func_t) (const char *filename,
+                                      const struct stat *status, int flag,
+                                      struct FTW *info);
+
+    // 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 = nftw(path, (pseudo_nftw_func_t)fn, nopenfd, 0);
 
 /*	return rc;
  * }
diff --git a/ports/unix/guts/nftw.c b/ports/unix/guts/nftw.c
index dac3106..d22cd1c 100644
--- a/ports/unix/guts/nftw.c
+++ b/ports/unix/guts/nftw.c
@@ -9,7 +9,12 @@ 
  *	int rc = -1;
  */
 
-	rc = real_nftw(path, fn, nopenfd, flag);
+/***********************************
+ * This call has a custom wrapper
+ * where all the logic happens just
+ * around this single line.
+ ***********************************/
+    rc = real_nftw(path, NFTW_CALLBACK_NAME, 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..7479b80
--- /dev/null
+++ b/ports/unix/guts/nftw_wrapper_base.c
@@ -0,0 +1,211 @@ 
+int
+NFTW_NAME(const char *path, int (*fn)(const char *, const struct NFTW_STAT_STRUCT *, int, struct FTW *), int nopenfd, int flag) {
+        sigset_t saved;
+
+        int rc = -1;
+
+        if (!pseudo_check_wrappers() || !NFTW_REAL_NAME) {
+                /* rc was initialized to the "failure" value */
+                pseudo_enosys("nftw");
+                return rc;
+        }
+
+        if (pseudo_disabled) {
+                rc = (*NFTW_REAL_NAME)(path, fn, nopenfd, flag);
+
+                return rc;
+        }
+
+        pseudo_debug(PDBGF_WRAPPER, "wrapper called: %s\n", __func__);
+        pseudo_sigblock(&saved);
+        pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "%s - signals blocked, obtaining lock\n", __func__);
+        if (pseudo_getlock()) {
+                errno = EBUSY;
+                sigprocmask(SIG_SETMASK, &saved, NULL);
+                pseudo_debug(PDBGF_WRAPPER, "%s failed to get lock, giving EBUSY.\n", __func__);
+                return -1;
+        }
+
+        int save_errno;
+        if (antimagic > 0) {
+                /* call the real syscall */
+                pseudo_debug(PDBGF_SYSCALL, "%s calling real syscall.\n", __func__);
+                rc = (*NFTW_REAL_NAME)(path, fn, nopenfd, flag);
+        } else {
+                path = pseudo_root_path(__func__, __LINE__, AT_FDCWD, path, 0);
+                if (pseudo_client_ignore_path(path)) {
+                        /* call the real syscall */
+                        pseudo_debug(PDBGF_SYSCALL, "%s ignored path, calling real syscall.\n", __func__);
+                        rc = (*NFTW_REAL_NAME)(path, fn, nopenfd, flag);
+                } else {
+                        /* exec*() use this to restore the sig mask */
+                        pseudo_saved_sigmask = saved;
+                        rc = NFTW_WRAP_NAME(path, fn, nopenfd, flag);
+                }
+        }
+
+        save_errno = errno;
+        pseudo_droplock();
+        sigprocmask(SIG_SETMASK, &saved, NULL);
+        pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "%s - yielded lock, restored signals\n", __func__);
+        pseudo_debug(PDBGF_WRAPPER, "wrapper completed: %s returns %d (errno: %d)\n", __func__, rc, save_errno);
+        errno = save_errno;
+        return rc;
+}
+
+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 *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;
+
+    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.
+    // Error checking is not done here, as if real_nftw couldn't perform it,
+    // then it has already returned an error, and this part isn't reached. This
+    // just repeats the successful 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) {
+            chdir(fpath);
+        } else {
+            target_dir = malloc(ftwbuf->base + 1);
+            memset(target_dir, 0, ftwbuf->base + 1);
+            strncpy(target_dir, fpath, ftwbuf->base);
+            chdir(target_dir);
+        }
+    }
+
+    // This is the main point of this call. Instead of the stat that
+    // came from real_nftw, use the stat returned by pseudo.
+    // If the target can't be stat'd (DNR), then just forward whatever
+    // is inside - no information can be retrieved of it anyway.
+    if (typeflag != FTW_DNR) {
+        (saved_details.flags & FTW_PHYS) ? NFTW_LSTAT_NAME(fpath, sb) : NFTW_STAT_NAME(fpath, sb);
+    }
+
+    int ret = saved_details.callback(fpath, sb, typeflag, ftwbuf);
+
+    if (saved_details.flags & FTW_CHDIR) {
+        if (orig_cwd_fd != -1) {
+            fchdir(orig_cwd_fd);
+            close(orig_cwd_fd);
+        } else if (orig_cwd != NULL) {
+            chdir(orig_cwd);
+        }
+        free(target_dir);
+    }
+
+    return ret;
+}
+
+static int
+NFTW_WRAP_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);
+
+#include NFTW_GUTS_INCLUDE
+
+    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..5f15930 100644
--- a/ports/unix/pseudo_wrappers.c
+++ b/ports/unix/pseudo_wrappers.c
@@ -2,6 +2,10 @@ 
  * SPDX-License-Identifier: LGPL-2.1-only
  *
  */
+
+/**********************************************
+ * POPEN
+ **********************************************/
 FILE *
 popen(const char *command, const char *mode) {
 	sigset_t saved;
@@ -52,3 +56,44 @@  wrap_popen(const char *command, const char *mode) {
 	return rc;
 }
 
+
+/**********************************************
+ * NFTW
+ **********************************************/
+
+#define CONCAT_EXPANDED(prefix, value) prefix ## value
+#define CONCAT(prefix, value) CONCAT_EXPANDED(prefix, value)
+
+#undef NFTW_NAME
+#undef NFTW_WRAP_NAME
+#undef NFTW_REAL_NAME
+#undef NFTW_STAT_STRUCT
+#undef NFTW_STAT_NAME
+#undef NFTW_LSTAT_NAME
+#undef NFTW_GUTS_INCLUDE
+#undef NFTW_CALLBACK_NAME
+#undef NFTW_STORAGE_STRUCT_NAME
+#undef NFTW_STORAGE_ARRAY_SIZE
+#undef NFTW_MUTEX_NAME
+#undef NFTW_STORAGE_ARRAY_NAME
+#undef NFTW_APPEND_FN_NAME
+#undef NFTW_DELETE_FN_NAME
+#undef NFTW_FIND_FN_NAME
+
+#define NFTW_NAME nftw
+#define NFTW_WRAP_NAME CONCAT(wrap_, NFTW_NAME)
+#define NFTW_REAL_NAME CONCAT(real_, NFTW_NAME)
+#define NFTW_STAT_NAME stat
+#define NFTW_STAT_STRUCT stat
+#define NFTW_LSTAT_NAME lstat
+#define NFTW_CALLBACK_NAME CONCAT(wrap_callback_, NFTW_NAME)
+#define NFTW_GUTS_INCLUDE "nftw.c"
+#define NFTW_STORAGE_STRUCT_NAME CONCAT(storage_struct_, NFTW_NAME)
+#define NFTW_STORAGE_ARRAY_SIZE CONCAT(storage_size_, NFTW_NAME)
+#define NFTW_MUTEX_NAME CONCAT(mutex_, NFTW_NAME)
+#define NFTW_STORAGE_ARRAY_NAME CONCAT(storage_array_, NFTW_NAME)
+#define NFTW_APPEND_FN_NAME CONCAT(append_to_array_, NFTW_NAME)
+#define NFTW_DELETE_FN_NAME CONCAT(delete_from_array_, NFTW_NAME)
+#define NFTW_FIND_FN_NAME CONCAT(find_in_array_, NFTW_NAME)
+
+#include "guts/nftw_wrapper_base.c"
diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in
index 7724fc7..5b1f557 100644
--- a/ports/unix/wrapfuncs.in
+++ b/ports/unix/wrapfuncs.in
@@ -14,7 +14,7 @@  int faccessat(int dirfd, const char *path, int mode, int flags);
 int faccessat2(int dirfd, const char *path, int mode, int flags);
 FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */
 int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd);
-int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag);
+int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag); /* hand_wrapped=1 */
 int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob);
 int lutimes(const char *path, const struct timeval *tv); /* flags=AT_SYMLINK_NOFOLLOW */
 char *mkdtemp(char *template);