nx_dirent.nx source
↩ module page · 149 lines · 4559 B
1// nx_dirent.nx -- getdents64(2) directory iteration.
2//
3// Used by:
4// - Build tools that walk source directories (find *.nx files)
5// - Test harnesses that enumerate test cases
6// - File-based config loaders
7// - Sovereign `ls` (companion to objdump / readelf / nm)
8//
9// Linux RV64 syscall: 61 (getdents64).
10// Returns a buffer of variable-sized struct linux_dirent64 records:
11// ino_t d_ino u64
12// off_t d_off u64
13// ushort d_reclen u16 (size of THIS record)
14// uchar d_type u8
15// char d_name[] NUL-terminated, padded to alignment
16//
17// Total per record = d_reclen. Caller scans by adding d_reclen.
18
19// nx_safety_envelope:
20// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
21// sil_target: SIL1
22// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
23// verdict: NOT_YET_EVALUATED
24
25import "syscalls.nx"
26import "nx_fcntl.nx"
27const NX_MAGIC_8192: i64 = 8192
28
29// Per-target syscall number. Cross-target via @ifdef TARGET_X86_64
30// (same shape as nx_syscalls.nx + nx_fcntl.nx; bit-level no-proliferation
31// extends to substrate syscall ABI).
32@ifdef TARGET_X86_64
33const NX_SYS_GETDENTS64: i64 = 217
34@endif
35@ifndef TARGET_X86_64
36const NX_SYS_GETDENTS64: i64 = 61
37@endif
38
39// d_type values.
40const NX_DT_UNKNOWN: i64 = 0
41const NX_DT_FIFO: i64 = 1
42const NX_DT_CHR: i64 = 2
43const NX_DT_DIR: i64 = 4
44const NX_DT_BLK: i64 = 6
45const NX_DT_REG: i64 = 8
46const NX_DT_LNK: i64 = 10
47const NX_DT_SOCK: i64 = 12
48
49struct NxDirent {
50 ino: i64,
51 off: i64,
52 reclen: i64, // we store the unsigned u16 as i64 for ergonomics
53 dtype: i64, // u8 widened
54 name: *u8, // points into the calling buffer; NUL-terminated
55}
56
57const NX_DIRENT_BYTES: i64 = 40
58
59// Read a batch of directory entries into a caller buffer. Returns
60// the number of bytes filled (0 = end of directory, negative =
61// errno). Use the buffer with nx_dirent_iter to walk records.
62func nx_dirent_read(fd: i64, buf: *u8, cap: i64) -> i64 {
63 return __syscall(NX_SYS_GETDENTS64, fd, buf as i64, cap, 0, 0, 0)
64}
65
66// Decode the record at buf[off..] into `out` and return the offset
67// of the next record (off + d_reclen). Returns -1 if buf is
68// exhausted (caller should check before reading `out`).
69//
70// The `name` field points into the caller's buffer and remains
71// valid only while that buffer is held. Copy if you need to keep
72// it past the next nx_dirent_read.
73func nx_dirent_iter(buf: *u8, off: i64, end: i64, out: *NxDirent) -> i64 {
74 if off >= end { return -1 }
75 let p: i64 = (buf as i64) + off
76
77 // d_ino at +0 (u64 LE)
78 let ino_p: *i64 = p as *i64
79 out.ino = *ino_p
80
81 // d_off at +8 (u64 LE)
82 let off_p: *i64 = (p + 8) as *i64
83 out.off = *off_p
84
85 // d_reclen at +16 (u16 LE)
86 let rb: *u8 = (p + 16) as *u8
87 out.reclen = (rb[0] as i64) | ((rb[1] as i64) << 8)
88
89 // d_type at +18 (u8)
90 let tb: *u8 = (p + 18) as *u8
91 out.dtype = tb[0]
92
93 // d_name at +19, NUL-terminated.
94 out.name = ((p + 19) as *u8)
95
96 return off + out.reclen
97}
98
99// strlen-on-name helper.
100func nx_dirent_name_len(d: *NxDirent) -> i64 {
101 var n: i64 = 0
102 while d.name[n] != 0 { n = n + 1 }
103 return n
104}
105
106// ---- self-test ---------------------------------------------------
107
108func main() -> i64 {
109 // Open "." and read at least one entry.
110 let dot: *u8 = sys_mmap(8)
111 dot[0] = 0x2E; dot[1] = 0
112 let fd: i64 = nx_openat(NX_AT_FDCWD, dot, NX_O_RDONLY | NX_O_DIRECTORY, 0)
113 if fd < 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
114
115 let buf: *u8 = sys_mmap(NX_MAGIC_8192)
116 let n: i64 = nx_dirent_read(fd, buf, NX_MAGIC_8192)
117 if n <= 0 {
118 sys_close(fd)
119 return __syscall(93, 2, 0, 0, 0, 0, 0)
120 }
121
122 let dr_raw: *u8 = sys_mmap(NX_DIRENT_BYTES)
123 let dr: *NxDirent = dr_raw as *NxDirent
124
125 // First entry should be "." (always present in any directory).
126 let next_off: i64 = nx_dirent_iter(buf, 0, n, dr)
127 if next_off <= 0 {
128 sys_close(fd)
129 return __syscall(93, 3, 0, 0, 0, 0, 0)
130 }
131 if dr.name[0] != 0x2E {
132 sys_close(fd)
133 return __syscall(93, 4, 0, 0, 0, 0, 0)
134 }
135 // Either "." (single-char) or ".." (two-char).
136 if dr.name[1] != 0 {
137 if dr.name[1] != 0x2E {
138 sys_close(fd)
139 return __syscall(93, 5, 0, 0, 0, 0, 0)
140 }
141 }
142 if dr.reclen <= 0 {
143 sys_close(fd)
144 return __syscall(93, 6, 0, 0, 0, 0, 0)
145 }
146
147 sys_close(fd)
148 return 0
149}