Wrap sys/stat.h: stat, fstat, lstat, fstatat, statvfs, fstatvfs
#9
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Source: TODO.md §4.1 (at
669b2b3)stat,fstat,lstat,fstatatstatvfs,fstatvfsStraightforward errno-reporting wrappers; the interest is in whether the wrapper
hands back the caller's own
struct stat(consistent with thecaller-owns-the-storage rule the hash map already follows) or a subset the library
defines.
Files:
src/,include/akstdlib.hFiled by Tachikoma (Claude Code, Opus 5, 1M context)
@tachikoma let's get a better plan on this
Plan
The open question on the ticket — caller's
struct stator a subset this librarydefines — is answered by a rule the library already follows. The rest is scope.
Steps:
ENOENTmeans heresys/stat.hcomes alongfstatatflagsThe struct question
Hand back the caller's own
struct stat. Three reasons, in blast-radiusorder:
aksl_str*family already follow. A subset type would be the only allocatedoutput in the file-metadata surface.
is a caller that has to call
stat(2)directly and step outside the errorconvention — which is the exact complaint that opened #10.
struct statis POSIX and stable. There is no portability win to buy.So the shape is the one the rest of the library uses — output through a pointer
parameter,
NULLon success:This costs the header a public
#include <sys/stat.h>and<sys/statvfs.h>.akstdlib.halready publishes<sys/types.h>alongside<stdarg.h>,<stddef.h>,<stdint.h>and<stdio.h>, so this is a widening of an existingPOSIX dependency rather than a new one — but it is still a widening, and every
consumer of
akstdlib.hpicks it up whether or not it calls these.ENOENTis a failureWorth writing down, because the library has a rule that looks like it says
otherwise. "Finding nothing is success" governs searching functions —
aksl_strchrwrites NULL and returns NULL.statis not a search. The callernamed one path and asked about it, so
ENOENTpropagates as itself and a callertesting for existence writes a
DETECTonENOENT.aksl_fopenalready behaves this way for a missing file. Anything else would puttwo file-facing families on opposite conventions.
The rest of
sys/stat.hThe header also declares
mkdir,mkdirat,chmod,fchmod,fchmodat,umaskandmknod. The ticket covers only thestatfamily, and I think thatis right, but it should be a decision rather than an omission: those are
mutating calls and they belong with whatever wraps
unlink/rename/rmdir,not with metadata reads.
Recommendation: file a separate issue for the mutating
sys/stat.hsurfaceand say so in
TODO.md, so the next reader does not think the family was missed.umaskis a genuine oddity — it cannot fail and it returns the previous mask, soits wrapper would raise nothing at all. That is the same "cannot fail" scope
question as
rewinddirin #10, and both should be answered the same way.fstatatflagsAT_SYMLINK_NOFOLLOWmakesaksl_fstatatcoveraksl_lstat, andAT_FDCWDmakes it cover
aksl_stat. Wrap all four anyway — the two-argument spellings arewhat callers actually write, and collapsing them would make the common case carry
two constants it does not care about.
Validate
flags: anything outsideAT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNTisAKERR_VALUEraised by the wrapper, before the call.EINVALfrom the kernel says only "bad flags" with no indication which.
Tests
staton a regular filest_sizeandS_ISREGmatch what was writtenstaton a path that does not existENOENTstatthrough a component that is not a directoryENOTDIRstatvslstaton a symlinkS_ISLNKdiffersfstaton an open fdst_inoasstaton its pathfstaton a closed fdEBADFfstatatwithAT_FDCWDaksl_statfstatatwith an unknown flag bitAKERR_VALUE, anderrnountouchedstatvfson.f_frsizenon-zeropathname, NULLdestAKERR_NULLPOINTERon eachThe symlink cases need the test to create the symlink, which means the suite
needs
symlink(2)— not wrapped, and not worth wrapping for a test. Call itdirectly from the test and note why.
ENAMETOOLONGis reachable with a path overPATH_MAXif you want the branch,but it is a libc-boundary check rather than library behavior. I would leave it
out and let the coverage listing show it.
Files
include/akstdlib.h, a newsrc/stat.c(or fold into thesrc/dir.cthat #10creates — they are the same domain and
d_type'sDT_UNKNOWNfallback lands instat), the AGENTS.md file table,tests/test_stat.cand itsAKSL_TESTSentry.
Sequencing
Land this with or before #10. #10's
d_typedocumentation has to tell thecaller to fall back to
stat, and that should name a wrapper that exists.What I would change on the ticket
Scope settles if you take the caller's-struct decision and split the mutating
surface into its own issue. The
umask-and-rewinddir"cannot fail" question isthe one thing that spans both tickets and wants a single answer.
— Tachikoma (Claude Code, Opus 5, 1M context)
@tachikoma
This is incorrect. from
man 2 uname:@tachikoma ^ respond
@andrew I checked the specific contract. The
umaskstatement was correct:umask(2)declaresmode_t umask(mode_t mask), returns the prior mask, and states that the call always succeeds. It has no-1/errnopath.uname(2)is different: it writes throughstruct utsname *bufand can return-1witherrno = EFAULTfor an invalid buffer. I should not have implied that it was comparable toumask.The correction to the proposed classification is that
umaskbelongs with the mutatingsys/stat.hsurface, not the metadata-read wrappers in this ticket. Since it returns the previous mask rather than an error context, wrapping it needs a separate result-API decision; it should not be folded into this plan or treated as an ordinary errno wrapper.