import unzip from "./unzip";
import {
Volume,
createFsFromVolume,
fs as memfs,
DirectoryJSON,
} from "@cowasm/memfs";
import { Union } from "@wapython/unionfs";
import { WASIFileSystem } from "./types";
interface NativeFs {
type: "native";
}
interface DevFs {
type: "dev";
}
interface ZipFsFile {
type: "zipfile";
zipfile: string;
mountpoint: string;
async?: boolean;
}
interface ZipFsUrl {
type: "zipurl";
zipurl: string;
mountpoint: string;
async?: boolean;
}
interface ZipFs {
type: "zip";
data: Buffer;
mountpoint: string;
}
interface ZipFsAsync {
type: "zip-async";
getData: () => Promise<Buffer>;
mountpoint: string;
}
interface MemFs {
type: "mem";
contents?: DirectoryJSON;
}
export type FileSystemSpec =
| NativeFs
| ZipFs
| ZipFsAsync
| ZipFsFile
| ZipFsUrl
| MemFs
| DevFs;
export function createFileSystem(
specs: FileSystemSpec[],
nativeFs?: WASIFileSystem
): WASIFileSystem {
if (specs.length == 0) {
return memFs();
}
if (specs.length == 1) {
return specToFs(specs[0], nativeFs) ?? memFs();
}
const ufs = new Union();
const v: Function[] = [];
for (const spec of specs) {
const fs = specToFs(spec, nativeFs);
if (fs != null) {
ufs.use(fs);
if (fs.waitUntilLoaded != null) {
v.push(fs.waitUntilLoaded.bind(fs));
}
}
}
const waitUntilLoaded = async () => {
for (const wait of v) {
await wait();
}
};
return { ...ufs, constants: memfs.constants, waitUntilLoaded };
}
function specToFs(
spec: FileSystemSpec,
nativeFs?: WASIFileSystem
): WASIFileSystem | undefined {
if (spec.type == "zip") {
return zipFs(spec.data, spec.mountpoint) as any;
} else if (spec.type == "zip-async") {
return zipFsAsync(spec.getData, spec.mountpoint) as any;
} else if (spec.type == "zipfile") {
throw Error(`you must convert zipfile -- read ${spec.zipfile} into memory`);
} else if (spec.type == "zipurl") {
throw Error(`you must convert zipurl -- read ${spec.zipurl} into memory`);
} else if (spec.type == "native") {
return nativeFs == null ? nativeFs : mapFlags(nativeFs);
} else if (spec.type == "mem") {
return memFs(spec.contents) as any;
} else if (spec.type == "dev") {
return devFs() as any;
}
throw Error(`unknown spec type - ${JSON.stringify(spec)}`);
}
function devFs(): WASIFileSystem {
const vol = Volume.fromJSON({
"/dev/stdin": "",
"/dev/stdout": "",
"/dev/stderr": "",
});
vol.releasedFds = [0, 1, 2];
const fdErr = vol.openSync("/dev/stderr", "w");
const fdOut = vol.openSync("/dev/stdout", "w");
const fdIn = vol.openSync("/dev/stdin", "r");
if (fdErr != 2) throw Error(`invalid handle for stderr: ${fdErr}`);
if (fdOut != 1) throw Error(`invalid handle for stdout: ${fdOut}`);
if (fdIn != 0) throw Error(`invalid handle for stdin: ${fdIn}`);
return createFsFromVolume(vol) as unknown as WASIFileSystem;
}
function zipFs(data: Buffer, directory: string = "/"): WASIFileSystem {
const fs = createFsFromVolume(new Volume()) as any;
fs.mkdirSync(directory, { recursive: true });
unzip({ data, fs, directory });
return fs;
}
function zipFsAsync(
getData: () => Promise<Buffer>,
directory: string = "/"
): WASIFileSystem {
const fs = createFsFromVolume(new Volume()) as any;
const load = async () => {
let data;
try {
data = await getData();
} catch (err) {
console.warn(
`FAILED to load async filesystem for '${directory}' - ${err}`
);
throw err;
}
unzip({ data, fs, directory });
};
const loadingPromise = load();
fs.waitUntilLoaded = () => loadingPromise;
return fs;
}
function memFs(contents?: DirectoryJSON): WASIFileSystem {
const vol = contents != null ? Volume.fromJSON(contents) : new Volume();
return createFsFromVolume(vol) as unknown as WASIFileSystem;
}
function mapFlags(nativeFs: WASIFileSystem): WASIFileSystem {
function translate(flags: number): number {
let nativeFlags = 0;
for (const flag in memfs.constants) {
if (flag.startsWith("O_") && flags & memfs.constants[flag]) {
nativeFlags |= nativeFs.constants[flag];
}
}
return nativeFlags;
}
const open: any = async (path, flags, mode?) => {
return await nativeFs.open(path, translate(flags), mode);
};
const openSync = (path, flags, mode?) => {
return nativeFs.openSync(path, translate(flags), mode);
};
const promises = {
...nativeFs.promises,
open: async (path, flags, mode?) => {
return await nativeFs.promises.open(path, flags, mode);
},
};
return {
...{ ...nativeFs, promises },
open,
openSync,
constants: memfs.constants,
};
}