code wiki / (root) / fs.nx

fs.nx source

↩ module page · 201 lines · 7280 B

1// fs.nx -- file I/O stdlib (Phase G1 in the roadmap). 2// 3// Typed wrappers around Linux / NishiOS file syscalls. Every fallible 4// operation returns `*Result<i64, FsError>`. Callers match on the 5// variant -- the `-errno` sentinel convention is gone from the public 6// API. Raw-errno helpers remain as `fs_*_raw` for the rare case 7// where a caller wants the bare syscall return. 8// 9// Syscall numbers (Linux RV64, NishiOS matches): 10// 56 openat 11// 57 close 12// 63 read 13// 64 write 14// 80 fstat 15// 34 mkdirat 16// 82 renameat 17// 18// AT_FDCWD = -100 is the "current working directory" magic fd used by 19// the *at syscalls when the path is relative. 20 21import "syscalls.nx" 22import "stdlib.nx" 23 24// ---- flags --------------------------------------------------------- 25 26const O_RDONLY: i64 = 0 27const O_WRONLY: i64 = 1 28const O_RDWR: i64 = 2 29const O_CREAT: i64 = 0x40 30const O_TRUNC: i64 = 0x200 31const O_APPEND: i64 = 0x400 32 33const SYS_FSTAT: i64 = 80 34const SYS_MKDIRAT: i64 = 34 35const SYS_RENAMEAT: i64 = 82 36 37// ---- FsError ------------------------------------------------------- 38// 39// Named error kinds that the syscall layer maps `-errno` onto. Stays 40// aligned with Linux errno numbers where one kind exists (NotFound = 41// ENOENT, PermDenied = EACCES, ...). Unknown lumps everything else 42// so callers can still pattern-match exhaustively. 43enum FsError { 44 NotFound, // -ENOENT = -2 45 PermDenied, // -EACCES = -13 46 Exists, // -EEXIST = -17 47 NotDir, // -ENOTDIR = -20 48 IsDir, // -EISDIR = -21 49 InvalidArg, // -EINVAL = -22 50 NoSpace, // -ENOSPC = -28 51 Io, // -EIO = -5 52 Unknown, 53} 54 55// Translate a syscall errno (as negative i64) into an FsError 56// discriminant. Callers that want the original numeric errno can 57// still use `*_raw`. 58func fs_errno_to(e: i64) -> i64 { 59 let err: i64 = 0 - e 60 if err == 2 { return FsError::NotFound } 61 if err == 13 { return FsError::PermDenied } 62 if err == 17 { return FsError::Exists } 63 if err == 20 { return FsError::NotDir } 64 if err == 21 { return FsError::IsDir } 65 if err == 22 { return FsError::InvalidArg } 66 if err == 28 { return FsError::NoSpace } 67 if err == 5 { return FsError::Io } 68 return FsError::Unknown 69} 70 71// ---- open / close -------------------------------------------------- 72 73// Raw variants expose the bare syscall rc (fd >= 0 or -errno). Kept 74// public so rare callers can reach under the Result wrapper. 75func fs_open_rd_raw(path: *u8) -> i64 { 76 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY, 0, 0, 0) 77} 78 79func fs_open_wr_raw(path: *u8, mode: i64) -> i64 { 80 let flags: i64 = O_WRONLY | O_CREAT | O_TRUNC 81 return __syscall(SYS_OPENAT, AT_FDCWD, path, flags, mode, 0, 0) 82} 83 84func fs_open_append_raw(path: *u8, mode: i64) -> i64 { 85 let flags: i64 = O_WRONLY | O_CREAT | O_APPEND 86 return __syscall(SYS_OPENAT, AT_FDCWD, path, flags, mode, 0, 0) 87} 88 89// Typed API. Return Result<fd, FsError>. 90func fs_open_rd(path: *u8) -> *Result<i64, FsError> { 91 let rc: i64 = fs_open_rd_raw(path) 92 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 93 return Result::Ok(rc) 94} 95 96func fs_open_wr(path: *u8, mode: i64) -> *Result<i64, FsError> { 97 let rc: i64 = fs_open_wr_raw(path, mode) 98 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 99 return Result::Ok(rc) 100} 101 102func fs_open_append(path: *u8, mode: i64) -> *Result<i64, FsError> { 103 let rc: i64 = fs_open_append_raw(path, mode) 104 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 105 return Result::Ok(rc) 106} 107 108// Close an fd. Returns Result<0, FsError>; the payload on Ok is a 109// conventional placeholder so callers that don't care can ignore it. 110func fs_close(fd: i64) -> *Result<i64, FsError> { 111 let rc: i64 = sys_close(fd) 112 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 113 return Result::Ok(0) 114} 115 116// ---- read / write -------------------------------------------------- 117 118// Read up to `n` bytes into `buf`. Ok payload is bytes read (0 on 119// EOF); Err captures any i/o failure. 120func fs_read(fd: i64, buf: *u8, n: i64) -> *Result<i64, FsError> { 121 let rc: i64 = sys_read(fd, buf, n) 122 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 123 return Result::Ok(rc) 124} 125 126// Write `n` bytes. Ok payload is bytes written. 127func fs_write(fd: i64, buf: *u8, n: i64) -> *Result<i64, FsError> { 128 let rc: i64 = sys_write(fd, buf, n) 129 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 130 return Result::Ok(rc) 131} 132 133// Read ALL of a file into a fresh mmap'd buffer. The buffer pointer 134// is returned in *out_len bundled (buf, len). Null pointer indicates 135// open failure and out_len is untouched; callers can route this into 136// a Result<*u8, FsError> in a future pass when pointer payloads carry 137// their type under match. 138func fs_read_all(path: *u8, out_len: *i64) -> *u8 { 139 return sys_read_file(path, out_len) 140} 141 142// Write a full buffer to a file (creating / truncating). Ok payload 143// is bytes written; closes the fd on its way out. 144func fs_write_all(path: *u8, buf: *u8, n: i64, mode: i64) -> *Result<i64, FsError> { 145 let fd_raw: i64 = fs_open_wr_raw(path, mode) 146 if fd_raw < 0 { return Result::Err(fs_errno_to(fd_raw)) } 147 var total: i64 = 0 148 while total < n { 149 let want: i64 = n - total 150 let tail_addr: i64 = (buf as i64) + total 151 let tail: *u8 = tail_addr as *u8 152 let got: i64 = sys_write(fd_raw, tail, want) 153 if got <= 0 { 154 sys_close(fd_raw) 155 if got < 0 { return Result::Err(fs_errno_to(got)) } 156 return Result::Ok(total) 157 } 158 total = total + got 159 } 160 sys_close(fd_raw) 161 return Result::Ok(total) 162} 163 164// ---- metadata ------------------------------------------------------ 165 166// Size of a file in bytes. Opens, stats, closes. 167func fs_size(path: *u8) -> *Result<i64, FsError> { 168 let fd: i64 = fs_open_rd_raw(path) 169 if fd < 0 { return Result::Err(fs_errno_to(fd)) } 170 let scratch: *u8 = sys_mmap(160) 171 let rc: i64 = __syscall(SYS_FSTAT, fd, scratch as i64, 0, 0, 0, 0) 172 sys_close(fd) 173 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 174 let sb: i64 = scratch as i64 175 let sp: *i64 = (sb + 48) as *i64 176 return Result::Ok(*sp) 177} 178 179// Does `path` exist? Infallible boolean -- stays i64 because "file 180// missing" is the expected success path, not an error condition. 181func fs_exists(path: *u8) -> i64 { 182 let fd: i64 = fs_open_rd_raw(path) 183 if fd < 0 { return 0 } 184 sys_close(fd) 185 return 1 186} 187 188// ---- directory ops ------------------------------------------------- 189 190func fs_mkdir(path: *u8, mode: i64) -> *Result<i64, FsError> { 191 let rc: i64 = __syscall(SYS_MKDIRAT, AT_FDCWD, path, mode, 0, 0, 0) 192 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 193 return Result::Ok(0) 194} 195 196func fs_rename(old_path: *u8, new_path: *u8) -> *Result<i64, FsError> { 197 let rc: i64 = __syscall(SYS_RENAMEAT, AT_FDCWD, old_path, 198 AT_FDCWD, new_path, 0, 0) 199 if rc < 0 { return Result::Err(fs_errno_to(rc)) } 200 return Result::Ok(0) 201}