From fd63dff65e9f11caa3c5101a6f9c94fb855b6474 Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sun, 10 May 2026 11:35:17 +0000 Subject: [PATCH] ffi/libc: implement chdir, errno, strerror MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Smallest Phase 0 module per CLAUDE.md §4 implementation order. M.chdir(path) returns (true) or (false, errmsg) — errmsg via strerror(__errno_location()[0]). Glibc errno is thread-local behind __errno_location() rather than a plain global, hence the indirect access. Verified against PHASE0.md §7 expectation: a libc.chdir() persists across subsequent io.popen() calls (popen's child inherits the parent's wd), which is the property executor.lua relies on for `cd` interception. Smoke: libc.chdir("/tmp"); io.popen("pwd"):read("*l") --> /tmp Co-Authored-By: Claude Opus 4.7 (1M context) --- ffi/libc.lua | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ffi/libc.lua b/ffi/libc.lua index e22c5c5..d473315 100644 --- a/ffi/libc.lua +++ b/ffi/libc.lua @@ -1,18 +1,33 @@ --- ffi/libc.lua — shared libc bindings: errno, signal, write, read, chdir. +-- ffi/libc.lua — shared libc bindings: chdir, errno, strerror. +-- Phase 0: just enough to make `cd` interception work in executor.lua. +-- See docs/PHASE0.md §7. local ffi = require("ffi") ffi.cdef[[ int chdir(const char *path); -int errno; +int *__errno_location(void); char *strerror(int errnum); ]] +local C = ffi.C + local M = {} -- Apply chdir per PHASE0.md §7 (intercepts `cd` so wd persists across popen). +-- Returns: true on success; false, errmsg on failure. function M.chdir(path) - error("libc.chdir: not implemented (Phase 0 pending)") + local rc = C.chdir(path) + if rc == 0 then return true end + return false, ffi.string(C.strerror(C.__errno_location()[0])) +end + +function M.errno() + return C.__errno_location()[0] +end + +function M.strerror(en) + return ffi.string(C.strerror(en)) end return M