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}