commit 6f2c6913373d2c7047d484579d4bd42f4d7d4b55
parent 1e1a83509093a266a0ec296003e9b87d83b36f06
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Fri, 13 Mar 2026 23:36:54 +0100
Nftw bound
Diffstat:
3 files changed, 263 insertions(+), 10 deletions(-)
diff --git a/project.janet b/project.janet
@@ -0,0 +1,5 @@
+(declare-project :name "nftw")
+
+(declare-native
+ :name "nftw"
+ :source ["src/native/nftw.c"])
diff --git a/src/native/nftw.c b/src/native/nftw.c
@@ -1,23 +1,78 @@
+#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64
+
#include <ftw.h>
#include <janet.h>
+#include "os.c"
+
static JanetFunction *janet_callback = NULL;
-int callback(const char *path, const struct stat *st, int flag,
- struct FTW *info) {
- return janet_unwrap_integer(janet_call(
- janet_callback, 2,
- (const Janet[]){janet_wrap_string(path), janet_wrap_integer(flag)}));
+static const char * flagname(int flag) {
+ switch (flag) {
+ case FTW_F: return "f";
+ case FTW_D: return "d";
+ case FTW_DNR: return "dnr";
+ case FTW_DP: return "dp";
+ case FTW_NS: return "ns";
+ case FTW_SL: return "sl";
+ case FTW_SLN: return "sln";
+ default: return "err";
+ }
+}
+
+int callback(const char *path, const struct stat *st, int flag, struct FTW *info) {
+ JanetTable * stattab = janet_table(0);
+
+ if (st) {
+ for (const struct OsStatGetter *sg = os_stat_getters; sg->name != NULL; sg++) {
+ janet_table_put(stattab, janet_ckeywordv(sg->name), sg->fn(st));
+ }
+ }
+
+ JanetTable * ftwtab = janet_table(0);
+ janet_table_put(ftwtab, janet_ckeywordv("base"), janet_wrap_integer(info->base));
+ janet_table_put(ftwtab, janet_ckeywordv("level"), janet_wrap_integer(info->level));
+
+ return janet_unwrap_integer(
+ janet_call(
+ janet_callback, 4,
+ (const Janet[]){
+ janet_cstringv(path),
+ st ? janet_wrap_table(stattab) : janet_wrap_nil(),
+ janet_ckeywordv(flagname(flag)),
+ janet_wrap_table(ftwtab)
+ }));
}
+struct {const char* name; int value;} flagnames[] = {
+ {"chdir", FTW_CHDIR},
+ {"depth", FTW_DEPTH},
+ {"mount", FTW_MOUNT},
+ {"phys", FTW_PHYS},
+};
+
static Janet c_nftw(int32_t argc, Janet *argv) {
- janet_fixarity(argc, 2);
- janet_callback = janet_unwrap_function(argv[2]);
- return janet_wrap_integer(nftw((const char *)janet_unwrap_string(argv[1]),
- callback, 1024, FTW_DEPTH));
+ janet_arity(argc, 3, 7);
+
+ int flags = 0;
+
+ for (int i = 3; i < argc; i++) {
+ for (int j = 0; j < sizeof(flagnames)/sizeof(flagnames[0]); j++) {
+ if (strcmp((const char*)janet_unwrap_keyword(argv[i]), flagnames[j].name) == 0) {
+ flags |= flagnames[j].value;
+ break;
+ }
+ }
+ }
+
+ janet_callback = janet_unwrap_function(argv[1]);
+
+ return janet_wrap_integer(nftw((const char *)janet_unwrap_string(argv[0]),
+ callback, janet_unwrap_integer(argv[2]), flags));
}
static JanetReg cfuns[] = {
- {"nftw", c_nftw, "(nftw path callback)\n\nruns nftw"}, {NULL, NULL, NULL}};
+ {"nftw", c_nftw, "(nftw path callback fd_limit & flags)\n\nruns nftw"}, {NULL, NULL, NULL}};
JANET_MODULE_ENTRY(JanetTable *env) { janet_cfuns(env, "nftw", cfuns); }
diff --git a/src/native/os.c b/src/native/os.c
@@ -0,0 +1,193 @@
+typedef struct stat jstat_t;
+typedef mode_t jmode_t;
+
+static int32_t janet_perm_to_unix(mode_t m) {
+ return (int32_t) m;
+}
+
+static mode_t janet_perm_from_unix(int32_t x) {
+ return (mode_t) x;
+}
+
+static const uint8_t *janet_decode_mode(mode_t m) {
+ const char *str = "other";
+ if (S_ISREG(m)) str = "file";
+ else if (S_ISDIR(m)) str = "directory";
+ else if (S_ISFIFO(m)) str = "fifo";
+ else if (S_ISBLK(m)) str = "block";
+ else if (S_ISSOCK(m)) str = "socket";
+ else if (S_ISLNK(m)) str = "link";
+ else if (S_ISCHR(m)) str = "character";
+ return janet_ckeyword(str);
+}
+
+static int32_t janet_decode_permissions(jmode_t mode) {
+ return (int32_t)(mode & 0777);
+}
+
+static int32_t os_parse_permstring(const uint8_t *perm) {
+ int32_t m = 0;
+ if (perm[0] == 'r') m |= 0400;
+ if (perm[1] == 'w') m |= 0200;
+ if (perm[2] == 'x') m |= 0100;
+ if (perm[3] == 'r') m |= 0040;
+ if (perm[4] == 'w') m |= 0020;
+ if (perm[5] == 'x') m |= 0010;
+ if (perm[6] == 'r') m |= 0004;
+ if (perm[7] == 'w') m |= 0002;
+ if (perm[8] == 'x') m |= 0001;
+ return m;
+}
+
+static Janet os_make_permstring(int32_t permissions) {
+ uint8_t bytes[9] = {0};
+ bytes[0] = (permissions & 0400) ? 'r' : '-';
+ bytes[1] = (permissions & 0200) ? 'w' : '-';
+ bytes[2] = (permissions & 0100) ? 'x' : '-';
+ bytes[3] = (permissions & 0040) ? 'r' : '-';
+ bytes[4] = (permissions & 0020) ? 'w' : '-';
+ bytes[5] = (permissions & 0010) ? 'x' : '-';
+ bytes[6] = (permissions & 0004) ? 'r' : '-';
+ bytes[7] = (permissions & 0002) ? 'w' : '-';
+ bytes[8] = (permissions & 0001) ? 'x' : '-';
+ return janet_stringv(bytes, sizeof(bytes));
+}
+
+static int32_t os_get_unix_mode(const Janet *argv, int32_t n) {
+ int32_t unix_mode;
+ if (janet_checkint(argv[n])) {
+ /* Integer mode */
+ int32_t x = janet_unwrap_integer(argv[n]);
+ if (x < 0 || x > 0777) {
+ janet_panicf("bad slot #%d, expected integer in range [0, 8r777], got %v", n, argv[n]);
+ }
+ unix_mode = x;
+ } else {
+ /* Bytes mode */
+ JanetByteView bytes = janet_getbytes(argv, n);
+ if (bytes.len != 9) {
+ janet_panicf("bad slot #%d: expected byte sequence of length 9, got %v", n, argv[n]);
+ }
+ unix_mode = os_parse_permstring(bytes.bytes);
+ }
+ return unix_mode;
+}
+
+static jmode_t os_getmode(const Janet *argv, int32_t n) {
+ return janet_perm_from_unix(os_get_unix_mode(argv, n));
+}
+
+/* Getters */
+static Janet os_stat_dev(const jstat_t *st) {
+ return janet_wrap_number(st->st_dev);
+}
+static Janet os_stat_inode(const jstat_t *st) {
+ return janet_wrap_number(st->st_ino);
+}
+static Janet os_stat_mode(const jstat_t *st) {
+ return janet_wrap_keyword(janet_decode_mode(st->st_mode));
+}
+static Janet os_stat_int_permissions(const jstat_t *st) {
+ return janet_wrap_integer(janet_perm_to_unix(janet_decode_permissions(st->st_mode)));
+}
+static Janet os_stat_permissions(const jstat_t *st) {
+ return os_make_permstring(janet_perm_to_unix(janet_decode_permissions(st->st_mode)));
+}
+static Janet os_stat_uid(const jstat_t *st) {
+ return janet_wrap_number(st->st_uid);
+}
+static Janet os_stat_gid(const jstat_t *st) {
+ return janet_wrap_number(st->st_gid);
+}
+static Janet os_stat_nlink(const jstat_t *st) {
+ return janet_wrap_number(st->st_nlink);
+}
+static Janet os_stat_rdev(const jstat_t *st) {
+ return janet_wrap_number(st->st_rdev);
+}
+static Janet os_stat_size(const jstat_t *st) {
+ return janet_wrap_number(st->st_size);
+}
+static Janet os_stat_accessed(const jstat_t *st) {
+ return janet_wrap_number((double) st->st_atime);
+}
+static Janet os_stat_modified(const jstat_t *st) {
+ return janet_wrap_number((double) st->st_mtime);
+}
+static Janet os_stat_changed(const jstat_t *st) {
+ return janet_wrap_number((double) st->st_ctime);
+}
+static Janet os_stat_blocks(const jstat_t *st) {
+ return janet_wrap_number(st->st_blocks);
+}
+static Janet os_stat_blocksize(const jstat_t *st) {
+ return janet_wrap_number(st->st_blksize);
+}
+
+struct OsStatGetter {
+ const char *name;
+ Janet(*fn)(const jstat_t *st);
+};
+
+static const struct OsStatGetter os_stat_getters[] = {
+ {"dev", os_stat_dev},
+ {"inode", os_stat_inode},
+ {"mode", os_stat_mode},
+ {"int-permissions", os_stat_int_permissions},
+ {"permissions", os_stat_permissions},
+ {"uid", os_stat_uid},
+ {"gid", os_stat_gid},
+ {"nlink", os_stat_nlink},
+ {"rdev", os_stat_rdev},
+ {"size", os_stat_size},
+ {"blocks", os_stat_blocks},
+ {"blocksize", os_stat_blocksize},
+ {"accessed", os_stat_accessed},
+ {"modified", os_stat_modified},
+ {"changed", os_stat_changed},
+ {NULL, NULL}
+};
+
+static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
+ janet_sandbox_assert(JANET_SANDBOX_FS_READ);
+ janet_arity(argc, 1, 2);
+ const char *path = janet_getcstring(argv, 0);
+ JanetTable *tab = NULL;
+ const uint8_t *key = NULL;
+ if (argc == 2) {
+ if (janet_checktype(argv[1], JANET_KEYWORD)) {
+ key = janet_getkeyword(argv, 1);
+ } else {
+ tab = janet_gettable(argv, 1);
+ }
+ } else {
+ tab = janet_table(0);
+ }
+
+ /* Build result */
+ jstat_t st;
+ int res;
+ if (do_lstat) {
+ res = lstat(path, &st);
+ } else {
+ res = stat(path, &st);
+ }
+ if (-1 == res) {
+ return janet_wrap_nil();
+ }
+
+ if (NULL == key) {
+ /* Put results in table */
+ for (const struct OsStatGetter *sg = os_stat_getters; sg->name != NULL; sg++) {
+ janet_table_put(tab, janet_ckeywordv(sg->name), sg->fn(&st));
+ }
+ return janet_wrap_table(tab);
+ } else {
+ /* Get one result */
+ for (const struct OsStatGetter *sg = os_stat_getters; sg->name != NULL; sg++) {
+ if (janet_cstrcmp(key, sg->name)) continue;
+ return sg->fn(&st);
+ }
+ janet_panicf("unexpected keyword %v", janet_wrap_keyword(key));
+ }
+}