instow

:)
git clone https://git.sr.ht/~ashymad/instow
Log | Files | Refs | LICENSE

commit 6f2c6913373d2c7047d484579d4bd42f4d7d4b55
parent 1e1a83509093a266a0ec296003e9b87d83b36f06
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Fri, 13 Mar 2026 23:36:54 +0100

Nftw bound

Diffstat:
Aproject.janet | 5+++++
Msrc/native/nftw.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Asrc/native/os.c | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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)); + } +}