Skip to content

Commit

Permalink
Updating the Emscripten file system to support the new MODULARIZE arg…
Browse files Browse the repository at this point in the history
…ument.

Now, you pass in the FS/PATH/ERRNO_CODES objects of the module you want to use with the file system to the EmscriptenFS constructor. If none specified, it uses the globals.
  • Loading branch information
John Vilk committed Feb 9, 2015
1 parent 6fdd660 commit aa9a840
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 47 deletions.
38 changes: 21 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,36 @@
},
"main": "dist/browserfs.js",
"devDependencies": {
"archiver": "*",
"async": ">= 0.2.7",
"karma-chrome-launcher": "*",
"karma-opera-launcher": "*",
"karma-safari-launcher": "*",
"karma-firefox-launcher": "*",
"karma-jasmine": "~0.1.0",
"karma-requirejs": "*",
"bower": "*",
"coffee-script": ">= 1.6.2",
"connect-gzip-static": "^1.0.0",
"dropbox": "~0.10.1",
"glob": ">= 3.1.21",
"grunt-karma": "*",
"grunt": "*",
"grunt-contrib-connect": "~0.3.0",
"grunt-cli": "*",
"grunt-contrib-connect": "~0.7",
"grunt-contrib-requirejs": "*",
"grunt-contrib-watch": "*",
"grunt-karma": "latest",
"grunt-shell": "~0.5.0",
"grunt-ts": "latest",
"xhr2": "*",
"jasmine-tapreporter": "*",
"karma-chrome-launcher": "*",
"karma-firefox-launcher": "*",
"karma-jasmine": "~0.1.0",
"karma-opera-launcher": "*",
"karma-requirejs": "*",
"karma-safari-launcher": "*",
"open": "*",
"dropbox": "~0.10.1",
"typescript": "~1.4",
"parseurl": "^1.3.0",
"requirejs": "*",
"grunt-shell": "~0.5.0",
"bower": "*",
"grunt-cli": "*",
"jasmine-tapreporter": "*",
"zlibjs": "*",
"archiver": "*"
"send": "^0.11.1",
"serve-static": "^1.8.1",
"typescript": "~1.4",
"xhr2": "*",
"zlibjs": "*"
},
"scripts": {
"//": "Testling doesn't have Bower installed globally.",
Expand Down
94 changes: 64 additions & 30 deletions src/generic/emscripten_fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,32 +81,39 @@ export interface EmscriptenFS {
stream_ops: EmscriptenStreamOps;
}

declare var FS, ERRNO_CODES, PATH;

class BFSEmscriptenStreamOps implements EmscriptenStreamOps {
private FS;
private PATH;
private ERRNO_CODES;

constructor(private fs: BFSEmscriptenFS) {
this.FS = fs.getFS();
this.PATH = fs.getPATH();
this.ERRNO_CODES = fs.getERRNO_CODES();
}

public open(stream: EmscriptenStream): void {
var path = this.fs.realPath(stream.node);
var path = this.fs.realPath(stream.node),
FS = this.FS;
try {
if (FS.isFile(stream.node.mode)) {
stream.nfd = fs.openSync(path, this.fs.flagsToPermissionString(stream.flags));
}
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

public close(stream: EmscriptenStream): void {
var FS = this.FS;
try {
if (FS.isFile(stream.node.mode) && stream.nfd) {
fs.closeSync(stream.nfd);
}
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

Expand All @@ -118,7 +125,7 @@ class BFSEmscriptenStreamOps implements EmscriptenStreamOps {
try {
res = fs.readSync(stream.nfd, nbuffer, 0, length, position);
} catch (e) {
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
// No copying needed, since we wrote directly into UintArray.
return res;
Expand All @@ -132,7 +139,7 @@ class BFSEmscriptenStreamOps implements EmscriptenStreamOps {
try {
res = fs.writeSync(stream.nfd, nbuffer, 0, length, position);
} catch (e) {
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
return res;
}
Expand All @@ -142,18 +149,18 @@ class BFSEmscriptenStreamOps implements EmscriptenStreamOps {
if (whence === 1) { // SEEK_CUR.
position += stream.position;
} else if (whence === 2) { // SEEK_END.
if (FS.isFile(stream.node.mode)) {
if (this.FS.isFile(stream.node.mode)) {
try {
var stat = fs.fstatSync(stream.nfd);
position += stat.size;
} catch (e) {
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}
}

if (position < 0) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
throw new this.FS.ErrnoError(this.ERRNO_CODES.EINVAL);
}

stream.position = position;
Expand All @@ -162,7 +169,15 @@ class BFSEmscriptenStreamOps implements EmscriptenStreamOps {
}

class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
constructor(private fs: BFSEmscriptenFS) {}
private FS;
private PATH;
private ERRNO_CODES;

constructor(private fs: BFSEmscriptenFS) {
this.FS = fs.getFS();
this.PATH = fs.getPATH();
this.ERRNO_CODES = fs.getERRNO_CODES();
}

public getattr(node: EmscriptenFSNode): Stats {
var path = this.fs.realPath(node);
Expand All @@ -171,7 +186,7 @@ class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
stat = fs.lstatSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
return {
dev: stat.dev,
Expand Down Expand Up @@ -212,12 +227,12 @@ class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
// writes files, but never really requires the value to be set.
return;
}
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

public lookup(parent: EmscriptenFSNode, name: string): EmscriptenFSNode {
var path = PATH.join2(this.fs.realPath(parent), name);
var path = this.PATH.join2(this.fs.realPath(parent), name);
var mode = this.fs.getMode(path);
return this.fs.createNode(parent, name, mode);
}
Expand All @@ -227,46 +242,46 @@ class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
// create the backing node for this in the fs root as well
var path = this.fs.realPath(node);
try {
if (FS.isDir(node.mode)) {
if (this.FS.isDir(node.mode)) {
fs.mkdirSync(path, node.mode);
} else {
fs.writeFileSync(path, '', { mode: node.mode });
}
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
return node;
}

public rename(oldNode: EmscriptenFSNode, newDir: EmscriptenFSNode, newName: string): void {
var oldPath = this.fs.realPath(oldNode);
var newPath = PATH.join2(this.fs.realPath(newDir), newName);
var newPath = this.PATH.join2(this.fs.realPath(newDir), newName);
try {
fs.renameSync(oldPath, newPath);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

public unlink(parent: EmscriptenFSNode, name: string): void {
var path = PATH.join2(this.fs.realPath(parent), name);
var path = this.PATH.join2(this.fs.realPath(parent), name);
try {
fs.unlinkSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

public rmdir(parent: EmscriptenFSNode, name: string) {
var path = PATH.join2(this.fs.realPath(parent), name);
var path = this.PATH.join2(this.fs.realPath(parent), name);
try {
fs.rmdirSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

Expand All @@ -276,17 +291,17 @@ class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
return fs.readdirSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

public symlink(parent: EmscriptenFSNode, newName: string, oldPath: string): void {
var newPath = PATH.join2(this.fs.realPath(parent), newName);
var newPath = this.PATH.join2(this.fs.realPath(parent), newName);
try {
fs.symlinkSync(oldPath, newPath);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}

Expand All @@ -296,25 +311,32 @@ class BFSEmscriptenNodeOps implements EmscriptenNodeOps {
return fs.readlinkSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
}
}

export class BFSEmscriptenFS implements EmscriptenFS {
constructor() {
private FS;
private PATH;
private ERRNO_CODES;
constructor(_FS = self['FS'], _PATH = self['PATH'], _ERRNO_CODES = self['ERRNO_CODES']) {
if (typeof BrowserFS === 'undefined') {
throw new Error("BrowserFS is not loaded. Please load it before this library.");
}
this.FS = _FS;
this.PATH = _PATH;
this.ERRNO_CODES = _ERRNO_CODES;
}

public mount(mount: {opts: {root: string}}): EmscriptenFSNode {
return this.createNode(null, '/', this.getMode(mount.opts.root), 0);
}

public createNode(parent: EmscriptenFSNode, name: string, mode: number, dev?: any): EmscriptenFSNode {
var FS = this.FS;
if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
throw new FS.ErrnoError(this.ERRNO_CODES.EINVAL);
}
var node = FS.createNode(parent, name, mode);
node.node_ops = this.node_ops;
Expand All @@ -328,7 +350,7 @@ export class BFSEmscriptenFS implements EmscriptenFS {
stat = fs.lstatSync(path);
} catch (e) {
if (!e.code) throw e;
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
throw new this.FS.ErrnoError(this.ERRNO_CODES[e.code]);
}
return stat.mode;
}
Expand All @@ -341,7 +363,7 @@ export class BFSEmscriptenFS implements EmscriptenFS {
}
parts.push(node.mount.opts.root);
parts.reverse();
return PATH.join.apply(null, parts);
return this.PATH.join.apply(null, parts);
}
// This maps the integer permission modes from http://linux.die.net/man/3/open
// to node.js-specific file open permission strings at http://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback
Expand Down Expand Up @@ -380,6 +402,18 @@ export class BFSEmscriptenFS implements EmscriptenFS {
}
}

public getFS() {
return this.FS;
}

public getPATH() {
return this.PATH;
}

public getERRNO_CODES() {
return this.ERRNO_CODES;
}

public node_ops: EmscriptenNodeOps = new BFSEmscriptenNodeOps(this);
public stream_ops: EmscriptenStreamOps = new BFSEmscriptenStreamOps(this);
}
Expand Down

7 comments on commit aa9a840

@dreamlayers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks stuff. Take a look at this compiled JavaScript:

function BFSEmscriptenNodeOps(fs) {
    this.fs = fs;
    this.FS = fs.getFS();
    this.PATH = fs.getPATH();
    this.ERRNO_CODES = fs.getERRNO_CODES();
}

function BFSEmscriptenFS(_FS, _PATH, _ERRNO_CODES) {
    if (_FS === void 0) { _FS = self['FS']; }
    if (_PATH === void 0) { _PATH = self['PATH']; }
    if (_ERRNO_CODES === void 0) { _ERRNO_CODES = self['ERRNO_CODES']; }
    // This maps the integer permission modes from http://linux.die.net/man/3/open
    // to node.js-specific file open permission strings at http://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback
    this.flagsToPermissionStringMap = {
    // Snipped out here
    };
    this.node_ops = new BFSEmscriptenNodeOps(this);
    this.stream_ops = new BFSEmscriptenStreamOps(this);
    if (typeof BrowserFS === 'undefined') {
        throw new Error("BrowserFS is not loaded. Please load it before this library.");
    }
    this.FS = _FS;
    this.PATH = _PATH;
    this.ERRNO_CODES = _ERRNO_CODES;
}

this.FS is needed at this.node_ops = new BFSEmscriptenNodeOps(this);, but it is only initialized afterwards. As a result node_ops.FS is undefined and attempts to do operations on the file system fail. Reverting this commit makes things work.

@jvilk
Copy link
Owner

@jvilk jvilk commented on aa9a840 Feb 16, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, good catch! Field initializers run before the constructor in TypeScript. I'll fix it now.

@jvilk
Copy link
Owner

@jvilk jvilk commented on aa9a840 Feb 16, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just updated the release:
https://github.com/jvilk/BrowserFS/releases/tag/v0.3.7

I tested it w/ a non-modular Emscripten build, and it works fine!

@dreamlayers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! It works for me now with Em-DOSBox.

@jvilk
Copy link
Owner

@jvilk jvilk commented on aa9a840 Feb 16, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Let me know how it goes w/ a modular build!

@dreamlayers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to access FS, PATH and ERRNO_CODES from a preRun function in a modular build. They are needed by the EmscriptenFS constructor. If I hack things to provide access to those, I can use BrowserFS with Em-DOSBox compiled with -s MODULARIZE=1 -s "EXPORT_NAME='mkDOSBox'".

BTW. The Emscripten instructions in README.md need a change. Emscripten creates /home, and an attempt to create that directory again will cause an error.

@jvilk
Copy link
Owner

@jvilk jvilk commented on aa9a840 Feb 18, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to access FS, PATH and ERRNO_CODES from a preRun function in a modular build

They don't export those somehow on Module? I'm not familiar with the new MODULARIZE command; I made the change because some folks in #jsmess suggested it. Maybe we should ask the Emscripten devs to provide access to them in some official manner?

BTW. The Emscripten instructions in README.md need a change.

I'll update that now; thanks!

Please sign in to comment.