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