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}