ffi/libc: implement chdir, errno, strerror

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:35:17 +00:00
parent 2704edd57d
commit fd63dff65e
+18 -3
View File
@@ -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