GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: MIT
4
5import {
6 dircookie,
7 errno,
8 fd,
9 fd_readdir,
10 fdflags,
11 filestat,
12 lookupflags,
13 oflags,
14 path_filestat_get,
15 path_open,
16 rights,
17} from "@assemblyscript/wasi-shim/assembly/bindings/wasi_snapshot_preview1";
18import { Descriptor } from "as-wasi/assembly";
19
20export class FS {
21 private fs: FileSystem = new FileSystem(3);
22 private base: string;
23
24 constructor(base: string) {
25 if (base.startsWith("/")) {
26 base = base.substring(1);
27 }
28 if (base.endsWith("/")) {
29 base = base.substring(0, base.length - 1);
30 }
31 this.base = base;
32 }
33
34 readDir(path: string): string[] | null {
35 const dirs = this.fs.readdir(this.base + "/" + path);
36 if (dirs == null) {
37 return [];
38 }
39 return dirs;
40 }
41
42 readAll(path: string): string | null {
43 const fd = this.fs.open(this.base + "/" + path);
44 if (fd == null) {
45 return null;
46 }
47 const data = fd.readString();
48 if (data == null) {
49 return null;
50 }
51 fd.close();
52 return data;
53 }
54
55 readLine(path: string): File | null {
56 const fd = this.fs.open(this.base + "/" + path);
57 if (fd == null) {
58 return null;
59 }
60 return new File(fd);
61 }
62
63 exists(path: string): bool {
64 return this.fs.exists(this.base + "/" + path);
65 }
66}
67
68class File {
69 constructor(private fd: Descriptor) {}
70
71 readLine(): string | null {
72 return this.fd.readLine();
73 }
74
75 close(): void {
76 this.fd.close();
77 }
78}
79
80/**
81 * ######################################
82 * COPIED FROM AS-WASI
83 * ######################################
84 */
85
86/**
87 * A class to access a filesystem
88 */
89class FileSystem {
90 constructor(private dirfd: u32) {}
91
92 /**
93 * Open a path
94 * @path path
95 * @flags r, r+, w, wx, w+ or xw+
96 * @returns a descriptor
97 */
98 open(path: string, flags: string = "r"): Descriptor | null {
99 let dirfd = this.dirfdForPath(path);
100 let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW;
101 let fd_oflags: u16 = 0;
102 let fd_rights: u64 = 0;
103 if (flags == "r") {
104 fd_rights =
105 rights.FD_READ |
106 rights.FD_SEEK |
107 rights.FD_TELL |
108 rights.FD_FILESTAT_GET |
109 rights.FD_READDIR;
110 } else if (flags == "r+") {
111 fd_rights =
112 rights.FD_WRITE |
113 rights.FD_READ |
114 rights.FD_SEEK |
115 rights.FD_TELL |
116 rights.FD_FILESTAT_GET |
117 rights.PATH_CREATE_FILE;
118 } else if (flags == "w") {
119 fd_oflags = oflags.CREAT | oflags.TRUNC;
120 fd_rights =
121 rights.FD_WRITE |
122 rights.FD_SEEK |
123 rights.FD_TELL |
124 rights.FD_FILESTAT_GET |
125 rights.PATH_CREATE_FILE;
126 } else if (flags == "wx") {
127 fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL;
128 fd_rights =
129 rights.FD_WRITE |
130 rights.FD_SEEK |
131 rights.FD_TELL |
132 rights.FD_FILESTAT_GET |
133 rights.PATH_CREATE_FILE;
134 } else if (flags == "w+") {
135 fd_oflags = oflags.CREAT | oflags.TRUNC;
136 fd_rights =
137 rights.FD_WRITE |
138 rights.FD_READ |
139 rights.FD_SEEK |
140 rights.FD_TELL |
141 rights.FD_FILESTAT_GET |
142 rights.PATH_CREATE_FILE;
143 } else if (flags == "xw+") {
144 fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL;
145 fd_rights =
146 rights.FD_WRITE |
147 rights.FD_READ |
148 rights.FD_SEEK |
149 rights.FD_TELL |
150 rights.FD_FILESTAT_GET |
151 rights.PATH_CREATE_FILE;
152 } else {
153 return null;
154 }
155 let fd_rights_inherited = fd_rights;
156 let fd_flags: fdflags = 0;
157 let path_utf8_buf = String.UTF8.encode(path);
158 let path_utf8_len: usize = path_utf8_buf.byteLength;
159 let path_utf8 = changetype<usize>(path_utf8_buf);
160 let fd_buf = memory.data(8);
161 let res = path_open(
162 dirfd as fd,
163 fd_lookup_flags,
164 path_utf8,
165 path_utf8_len,
166 fd_oflags,
167 fd_rights,
168 fd_rights_inherited,
169 fd_flags,
170 fd_buf
171 );
172 if (res !== errno.SUCCESS) {
173 return null;
174 }
175 let fd = load<u32>(fd_buf);
176 return new Descriptor(fd);
177 }
178
179 /**
180 * Check if a file exists at a given path
181 * @path path
182 * @returns `true` on success, `false` on failure
183 */
184 exists(path: string): bool {
185 let dirfd = this.dirfdForPath(path);
186 let path_utf8_buf = String.UTF8.encode(path);
187 let path_utf8_len: usize = path_utf8_buf.byteLength;
188 let path_utf8 = changetype<usize>(path_utf8_buf);
189 let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW;
190 let st_buf = changetype<usize>(new ArrayBuffer(64));
191 let res = path_filestat_get(
192 dirfd,
193 fd_lookup_flags,
194 path_utf8,
195 path_utf8_len,
196 changetype<filestat>(st_buf)
197 );
198 return res === errno.SUCCESS;
199 }
200
201 /**
202 * Get the content of a directory
203 * @param path the directory path
204 * @returns An array of file names
205 */
206 readdir(path: string): Array<string> | null {
207 let fd = this.open(path, "r");
208 if (fd === null) {
209 return null;
210 }
211 let out = new Array<string>();
212 let buf_size = 4096;
213 // @ts-ignore
214 let buf = heap.alloc(buf_size);
215 // @ts-ignore
216 let buf_used_p = memory.data(8);
217 let buf_used = 0;
218 for (;;) {
219 if (
220 fd_readdir(fd.rawfd, buf, buf_size, 0 as dircookie, buf_used_p) !==
221 errno.SUCCESS
222 ) {
223 fd.close();
224 }
225 buf_used = load<u32>(buf_used_p);
226 if (buf_used < buf_size) {
227 break;
228 }
229 buf_size <<= 1;
230 // @ts-ignore
231 buf = heap.realloc(buf, buf_size);
232 }
233 let offset = 0;
234 while (offset < buf_used) {
235 offset += 16;
236 let name_len = load<u32>(buf + offset);
237 offset += 8;
238 if (offset + name_len > buf_used) {
239 return null;
240 }
241 let name = String.UTF8.decodeUnsafe(buf + offset, name_len);
242 out.push(name);
243 offset += name_len;
244 }
245 // @ts-ignore
246 heap.free(buf);
247 fd.close();
248
249 return out;
250 }
251
252 dirfdForPath(path: string): fd {
253 return this.dirfd;
254 }
255}