securityos/node_modules/wasi-js/dist/fs.js

179 lines
6.7 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
"use strict";
/*
Create a union filesystem as described by a FileSystemSpec[].
This code should not depend on anything that must run in node.js.
Note that this is entirely synchronous code, e.g., the unzip code,
and that's justified because our WASM interpreter will likely get
run in a different thread (a webworker) than the main thread, and
this code is needed to initialize it before anything else can happen.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFileSystem = void 0;
const unzip_1 = __importDefault(require("./unzip"));
const memfs_1 = require("@cowasm/memfs");
const unionfs_1 = require("@wapython/unionfs");
function createFileSystem(specs, nativeFs) {
if (specs.length == 0) {
return memFs(); // empty memfs
}
if (specs.length == 1) {
// don't use unionfs:
return specToFs(specs[0], nativeFs) ?? memFs();
}
const ufs = new unionfs_1.Union();
const v = [];
for (const spec of specs) {
const fs = specToFs(spec, nativeFs);
if (fs != null) {
// e.g., native bindings may be 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_1.fs.constants, waitUntilLoaded };
}
exports.createFileSystem = createFileSystem;
function specToFs(spec, nativeFs) {
// All these "as any" are because really nothing quite implements FileSystem yet!
// See https://github.com/streamich/memfs/issues/735
if (spec.type == "zip") {
return zipFs(spec.data, spec.mountpoint);
}
else if (spec.type == "zip-async") {
return zipFsAsync(spec.getData, spec.mountpoint);
}
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") {
// native = whatever is in bindings.
return nativeFs == null ? nativeFs : mapFlags(nativeFs);
}
else if (spec.type == "mem") {
return memFs(spec.contents);
}
else if (spec.type == "dev") {
return devFs();
}
throw Error(`unknown spec type - ${JSON.stringify(spec)}`);
}
// this is generic and would work in a browser:
function devFs() {
const vol = memfs_1.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 (0, memfs_1.createFsFromVolume)(vol);
}
function zipFs(data, directory = "/") {
const fs = (0, memfs_1.createFsFromVolume)(new memfs_1.Volume());
fs.mkdirSync(directory, { recursive: true });
(0, unzip_1.default)({ data, fs, directory });
return fs;
}
function zipFsAsync(getData, directory = "/") {
const fs = (0, memfs_1.createFsFromVolume)(new memfs_1.Volume());
const load = async () => {
let data;
try {
data = await getData();
}
catch (err) {
console.warn(`FAILED to load async filesystem for '${directory}' - ${err}`);
throw err;
}
// NOTE: there is an async version of this, but it runs in another
// webworker and costs significant overhead, so not worth it.
(0, unzip_1.default)({ data, fs, directory });
};
const loadingPromise = load();
fs.waitUntilLoaded = () => loadingPromise;
return fs;
}
function memFs(contents) {
const vol = contents != null ? memfs_1.Volume.fromJSON(contents) : new memfs_1.Volume();
return (0, memfs_1.createFsFromVolume)(vol);
}
function mapFlags(nativeFs) {
function translate(flags) {
// We have to translate the flags from WASM/memfs/musl to native for this operating system.
// E.g., on MacOS many flags are completely different. See big comment below.
let nativeFlags = 0;
for (const flag in memfs_1.fs.constants) {
// only flags starting with O_ are relevant for the open syscall.
if (flag.startsWith("O_") && flags & memfs_1.fs.constants[flag]) {
nativeFlags |= nativeFs.constants[flag];
}
}
return nativeFlags;
}
// "any" because there's something weird involving a __promises__ namespace that I don't understand.
const open = 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_1.fs.constants, // critical to ALWAYS use memfs constants for any filesystem.
};
}
/*
Comment about flags:
A major subtle issue I hit is that unionfs combines filesystems, and
each filesystem can define fs.constants differently! In particular,
memfs always hardcodes constants.O_EXCL to be 128. However, on
macos native filesystem it is 2048, whereas on Linux native filesystem
it is also 128. We combine memfs and native for running python-wasm
under nodejs, since we want to use our Python install (that is in
dist/python/python.zip and mounted using memfs) along with full access
to the native filesystem.
I think the only good solution to this is the following:
- if native isn't part of the unionfs, nothing to do (since we only currently use native and memfs).
- fs.constants should be memfs's constants since I think they match with what WebAssembly libc (via musl)
provides.
- in the node api, the ONLY functions that take numeric flags are open and openSync. That's convenient!
- somehow figure out which filesystem (native or memfs for now) that a given open will go to, and
convert the flags if going to memfs.
Probably the easiest way to accomplish all of the above is just use a proxy around native fs's
open* function.
*/
//# sourceMappingURL=fs.js.map