@@ -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)
new file mode 100644
@@ -0,0 +1,226 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ftw.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#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 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;
+ }
+}
new file mode 100644
@@ -0,0 +1,236 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ftw.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#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;
+ }
+}
new file mode 100644
@@ -0,0 +1,4 @@
+#define FTW_NAME ftw
+#define FTW_STAT_STRUCT stat
+
+#include "ftw-test-impl.c"
new file mode 100644
@@ -0,0 +1,4 @@
+#define FTW_NAME ftw64
+#define FTW_STAT_STRUCT stat64
+
+#include "ftw-test-impl.c"
new file mode 100644
@@ -0,0 +1,4 @@
+#define NFTW_NAME nftw
+#define NFTW_STAT_STRUCT stat
+
+#include "nftw-test-impl.c"
new file mode 100755
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# Test nftw call and its behavior modifying flags
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+trap "rm -rf ./walking" 0
+
+check_retval_and_fail_if_needed(){
+ if [ $1 -ne 0 ]; then
+ echo $2
+ exit 1
+ 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 failed"
+
+./test/test-nftw skip_siblings_pseudo
+check_retval_and_fail_if_needed $? "nftw sibling skipping with pseudo failed"
+
+./test/test-nftw skip_siblings_chdir_pseudo
+check_retval_and_fail_if_needed $? "nftw sibling skipping chddir with pseudo failed"
+
+./test/test-nftw64 skip_subtree_pseudo
+check_retval_and_fail_if_needed $? "nftw64 subtree skipping with pseudo failed"
+
+./test/test-nftw64 skip_siblings_pseudo
+check_retval_and_fail_if_needed $? "nftw64 sibling skipping with pseudo failed"
+
+./test/test-ftw pseudo_no_recursion
+check_retval_and_fail_if_needed $? "ftw non-recursive walking with pseudo failed"
+
+./test/test-ftw pseudo_recursion
+check_retval_and_fail_if_needed $? "ftw recursive walking with pseudo failed"
+
+./test/test-ftw64 pseudo_no_recursion
+check_retval_and_fail_if_needed $? "ftw64 non-recursive walking with pseudo failed"
+
+./test/test-ftw64 pseudo_recursion
+check_retval_and_fail_if_needed $? "ftw64 recursive walking with pseudo failed"
+
+
+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 failed"
+
+./test/test-nftw skip_siblings_no_pseudo $gid $uid
+check_retval_and_fail_if_needed $? "nftw sibling skipping without pseudo failed"
+
+./test/test-nftw skip_siblings_chdir_no_pseudo $gid $uid
+check_retval_and_fail_if_needed $? "nftw sibling skipping chdir without pseudo failed"
+
+./test/test-nftw64 skip_subtree_no_pseudo $gid $uid
+check_retval_and_fail_if_needed $? "nftw subtree skipping without pseudo failed"
+
+./test/test-nftw64 skip_siblings_no_pseudo $gid $uid
+check_retval_and_fail_if_needed $? "nftw sibling skipping without pseudo failed"
+
+./test/test-ftw no_pseudo_no_recursion $gid $uid
+check_retval_and_fail_if_needed $? "ftw non-recursive walking without pseudo failed"
+
+./test/test-ftw no_pseudo_recursion $gid $uid
+check_retval_and_fail_if_needed $? "ftw recursive walking without pseudo failed"
+
+./test/test-ftw64 no_pseudo_no_recursion $gid $uid
+check_retval_and_fail_if_needed $? "ftw non-recursive walking without pseudo failed"
+
+./test/test-ftw64 no_pseudo_recursion $gid $uid
+check_retval_and_fail_if_needed $? "ftw recursive walking without pseudo failed"
new file mode 100644
@@ -0,0 +1,4 @@
+#define NFTW_NAME nftw64
+#define NFTW_STAT_STRUCT stat64
+
+#include "nftw-test-impl.c"
Add tests for nftw, ftw, nftw64 and ftw64 calls. Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> --- 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 | 84 +++++++++++++++ test/test-nftw64.c | 4 + 8 files changed, 564 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