Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f13f113
initial NODERAWFS implementation
saschanaz Dec 30, 2017
65c0229
fix Ubuntu bug
saschanaz Dec 31, 2017
20b67c4
use permission mapper
saschanaz Dec 31, 2017
e071850
test both file read/write
saschanaz Dec 31, 2017
37ab5cf
shorter postset
saschanaz Jan 4, 2018
3a95bbf
apply append mode fix
saschanaz Jan 4, 2018
99f147c
Merge branch 'incoming' into noderawfs
saschanaz Jan 4, 2018
a1eeaf0
also_with_noderawfs + fs_nodefs_rw
saschanaz Jan 4, 2018
3e074a6
nodefs_cloexec
saschanaz Jan 4, 2018
1fa114b
fix flags mapping
saschanaz Jan 4, 2018
b9cf0c1
fix llseek positioning bug
saschanaz Jan 4, 2018
20bb015
support chdir
saschanaz Jan 4, 2018
29014e9
test_unistd_access
saschanaz Jan 4, 2018
6df292b
short lookupPath, fixes access test
saschanaz Jan 4, 2018
97f2d2d
fix closure error
saschanaz Jan 4, 2018
6c6eec6
add tty property, fix emptyPath test
saschanaz Jan 5, 2018
c537992
move noderawfs test
saschanaz Jan 5, 2018
65230b9
fix fclose
saschanaz Jan 5, 2018
39059ea
fallback to VFS for PIPEFS
saschanaz Jan 5, 2018
ffcd591
return stats
saschanaz Jan 5, 2018
62f3d59
split truncate test
saschanaz Jan 5, 2018
94312f3
unlink test
saschanaz Jan 5, 2018
c0a6c63
skip access tests on root
saschanaz Jan 6, 2018
32ba4e9
read in outside
saschanaz Jan 6, 2018
8368f03
cleanup
saschanaz Jan 6, 2018
e5043ff
return from readlinkSync
saschanaz Jan 6, 2018
b550227
disable file embed/preload
saschanaz Jan 6, 2018
5e26f87
fix compiler args problem
saschanaz Jan 6, 2018
12e4f38
Merge branch 'incoming' into noderawfs
saschanaz Jan 6, 2018
d16bd31
node throws ENOTEMPTY
saschanaz Jan 6, 2018
714fd86
fix closure error
saschanaz Jan 6, 2018
af8ea28
documentation [ci skip]
saschanaz Jan 7, 2018
1e948c7
Merge branch 'incoming' into noderawfs
saschanaz Jan 11, 2018
0869a2a
conditional directories
saschanaz Jan 11, 2018
a7e445c
move to test_other
saschanaz Jan 13, 2018
e473771
throw when not node.js [ci skip]
saschanaz Jan 14, 2018
1574770
revise documentation [ci skip]
saschanaz Jan 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,7 @@ def check(input_file):
shared.Settings.PROXY_TO_WORKER = 1

if options.use_preload_plugins or len(options.preload_files) > 0 or len(options.embed_files) > 0:
assert not shared.Settings.NODERAWFS, '--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem'
# if we include any files, or intend to use preload plugins, then we definitely need filesystem support
shared.Settings.FORCE_FILESYSTEM = 1

Expand Down
3 changes: 3 additions & 0 deletions src/library_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ mergeInto(LibraryManager.library, {
#endif
#if __EMSCRIPTEN_HAS_workerfs_js__
'$WORKERFS',
#endif
#if __EMSCRIPTEN_HAS_noderawfs_js__
'$NODERAWFS',
#endif
'stdin', 'stdout', 'stderr'],
$FS__postset: 'FS.staticInit();' +
Expand Down
53 changes: 23 additions & 30 deletions src/library_nodefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ mergeInto(LibraryManager.library, {
isWindows: false,
staticInit: function() {
NODEFS.isWindows = !!process.platform.match(/^win/);
var flags = process["binding"]("constants")["fs"];
NODEFS.flagsForNodeMap = {
"{{{ cDefine('O_APPEND') }}}": flags["O_APPEND"],
"{{{ cDefine('O_CREAT') }}}": flags["O_CREAT"],
"{{{ cDefine('O_EXCL') }}}": flags["O_EXCL"],
"{{{ cDefine('O_RDONLY') }}}": flags["O_RDONLY"],
"{{{ cDefine('O_RDWR') }}}": flags["O_RDWR"],
"{{{ cDefine('O_DSYNC') }}}": flags["O_SYNC"],
"{{{ cDefine('O_TRUNC') }}}": flags["O_TRUNC"],
"{{{ cDefine('O_WRONLY') }}}": flags["O_WRONLY"]
};
},
mount: function (mount) {
assert(ENVIRONMENT_IS_NODE);
Expand Down Expand Up @@ -46,39 +57,21 @@ mergeInto(LibraryManager.library, {
},
// 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
flagsToPermissionStringMap: {
0/*O_RDONLY*/: 'r',
1/*O_WRONLY*/: 'r+',
2/*O_RDWR*/: 'r+',
64/*O_CREAT*/: 'r',
65/*O_WRONLY|O_CREAT*/: 'r+',
66/*O_RDWR|O_CREAT*/: 'r+',
129/*O_WRONLY|O_EXCL*/: 'rx+',
193/*O_WRONLY|O_CREAT|O_EXCL*/: 'rx+',
514/*O_RDWR|O_TRUNC*/: 'w+',
577/*O_WRONLY|O_CREAT|O_TRUNC*/: 'w',
578/*O_CREAT|O_RDWR|O_TRUNC*/: 'w+',
705/*O_WRONLY|O_CREAT|O_EXCL|O_TRUNC*/: 'wx',
706/*O_RDWR|O_CREAT|O_EXCL|O_TRUNC*/: 'wx+',
1024/*O_APPEND*/: 'a',
1025/*O_WRONLY|O_APPEND*/: 'a',
1026/*O_RDWR|O_APPEND*/: 'a+',
1089/*O_WRONLY|O_CREAT|O_APPEND*/: 'a',
1090/*O_RDWR|O_CREAT|O_APPEND*/: 'a+',
1153/*O_WRONLY|O_EXCL|O_APPEND*/: 'ax',
1154/*O_RDWR|O_EXCL|O_APPEND*/: 'ax+',
1217/*O_WRONLY|O_CREAT|O_EXCL|O_APPEND*/: 'ax',
1218/*O_RDWR|O_CREAT|O_EXCL|O_APPEND*/: 'ax+',
4096/*O_RDONLY|O_DSYNC*/: 'rs',
4098/*O_RDWR|O_DSYNC*/: 'rs+'
},
flagsToPermissionString: function(flags) {
flagsForNode: function(flags) {
flags &= ~0x200000 /*O_PATH*/; // Ignore this flag from musl, otherwise node.js fails to open the file.
flags &= ~0x800 /*O_NONBLOCK*/; // Ignore this flag from musl, otherwise node.js fails to open the file.
flags &= ~0x8000 /*O_LARGEFILE*/; // Ignore this flag from musl, otherwise node.js fails to open the file.
flags &= ~0x80000 /*O_CLOEXEC*/; // Some applications may pass it; it makes no sense for a single process.
if (flags in NODEFS.flagsToPermissionStringMap) {
return NODEFS.flagsToPermissionStringMap[flags];
var newFlags = 0;
for (var k in NODEFS.flagsForNodeMap) {
if (flags & k) {
newFlags |= NODEFS.flagsForNodeMap[k];
flags ^= k;
}
}

if (!flags) {
return newFlags;
} else {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
Expand Down Expand Up @@ -221,7 +214,7 @@ mergeInto(LibraryManager.library, {
var path = NODEFS.realPath(stream.node);
try {
if (FS.isFile(stream.node.mode)) {
stream.nfd = fs.openSync(path, NODEFS.flagsToPermissionString(stream.flags));
stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags));
}
} catch (e) {
if (!e.code) throw e;
Expand Down
126 changes: 126 additions & 0 deletions src/library_noderawfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
mergeInto(LibraryManager.library, {
$NODERAWFS__deps: ['$ERRNO_CODES', '$FS', '$NODEFS'],
$NODERAWFS__postset: 'if (ENVIRONMENT_IS_NODE) {' +
'var _wrapNodeError = function(func) { return function() { try { return func.apply(this, arguments) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(ERRNO_CODES[e.code]); } } };' +
'var VFS = Object.assign({}, FS);' +
'for (var _key in NODERAWFS) FS[_key] = _wrapNodeError(NODERAWFS[_key]);' +
'}' +
'else { throw new Error("NODERAWFS is currently only supported on Node.js environment.") }',
$NODERAWFS: {
lookupPath: function(path) {
return { path: path, node: { mode: NODEFS.getMode(path) } };
},
createStandardStreams: function() {
FS.streams[0] = { fd: 0, nfd: 0, position: 0, path: '', flags: 0, tty: true };
for (var i = 1; i < 3; i++) {
FS.streams[i] = { fd: i, nfd: i, position: 0, path: '', flags: 577, tty: true };
}
},
// generic function for all node creation
cwd: function() { return process.cwd(); },
chdir: function() { process.chdir.apply(void 0, arguments); },
mknod: function(path, mode) {
if (FS.isDir(path)) {
fs.mkdirSync(path, mode);
} else {
fs.writeFileSync(path, '', { mode: mode });
}
},
mkdir: function() { fs.mkdirSync.apply(void 0, arguments); },
symlink: function() { fs.symlinkSync.apply(void 0, arguments); },
rename: function() { fs.renameSync.apply(void 0, arguments); },
rmdir: function() { fs.rmdirSync.apply(void 0, arguments); },
readdir: function() { fs.readdirSync.apply(void 0, arguments); },
unlink: function() { fs.unlinkSync.apply(void 0, arguments); },
readlink: function() { return fs.readlinkSync.apply(void 0, arguments); },
stat: function() { return fs.statSync.apply(void 0, arguments); },
lstat: function() { return fs.lstatSync.apply(void 0, arguments); },
chmod: function() { fs.chmodSync.apply(void 0, arguments); },
fchmod: function() { fs.fchmodSync.apply(void 0, arguments); },
chown: function() { fs.chownSync.apply(void 0, arguments); },
fchown: function() { fs.fchownSync.apply(void 0, arguments); },
truncate: function() { fs.truncateSync.apply(void 0, arguments); },
ftruncate: function() { fs.ftruncateSync.apply(void 0, arguments); },
utime: function() { fs.utimesSync.apply(void 0, arguments); },
open: function(path, flags, mode, suggestFD) {
if (typeof flags === "number") {
flags = NODEFS.flagsForNode(flags)
}
var nfd = fs.openSync(path, flags, mode);
var fd = suggestFD != null ? suggestFD : FS.nextfd(nfd);
var stream = { fd: fd, nfd: nfd, position: 0, path: path, flags: flags };
FS.streams[fd] = stream;
return stream;
},
close: function(stream) {
if (!stream.stream_ops) {
// this stream is created by in-memory filesystem
fs.closeSync(stream.nfd);
}
FS.closeStream(stream.fd);
},
llseek: function(stream, offset, whence) {
if (stream.stream_ops) {
// this stream is created by in-memory filesystem
return VFS.llseek(stream, offset, whence);
}
var position = offset;
if (whence === 1) { // SEEK_CUR.
position += stream.position;
} else if (whence === 2) { // SEEK_END.
position += fs.fstatSync(stream.nfd).size;
}

if (position < 0) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
stream.position = position;
return position;
},
read: function(stream, buffer, offset, length, position) {
if (stream.stream_ops) {
// this stream is created by in-memory filesystem
return VFS.read(stream, buffer, offset, length, position);
}
var seeking = true;
if (typeof position === 'undefined') {
seeking = false;
}
var bytesRead = fs.readSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position);
if (!seeking) stream.position += bytesRead;
return bytesRead;
},
write: function(stream, buffer, offset, length, position) {
if (stream.stream_ops) {
// this stream is created by in-memory filesystem
return VFS.write(stream, buffer, offset, length, position);
}
if (stream.flags & +"{{{ cDefine('O_APPEND') }}}") {
// seek to the end before writing in append mode
position = FS.llseek(stream, 0, +"{{{ cDefine('SEEK_END') }}}");
}
var seeking = true;
if (typeof position === 'undefined') {
seeking = false;
}
var bytesWritten = fs.writeSync(stream.nfd, Buffer.from(buffer.buffer), offset, length, position);
if (!seeking) stream.position += bytesWritten;
return bytesWritten;
},
allocate: function() {
throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP);
},
mmap: function() {
throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
},
msync: function() {
return 0;
},
munmap: function() {
return 0;
},
ioctl: function() {
throw new FS.ErrnoError(ERRNO_CODES.ENOTTY);
}
}
});
4 changes: 4 additions & 0 deletions src/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ var LibraryManager = {
'library_workerfs.js',
'library_lz4.js',
]);

if (NODERAWFS) {
libraries.push('library_noderawfs.js')
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ var NO_FILESYSTEM = 0; // If set, does not build in any filesystem support. Usef
var FORCE_FILESYSTEM = 0; // Makes full filesystem support be included, even if statically it looks like it is not
// used. For example, if your C code uses no files, but you include some JS that does,
// you might need this.
var NODERAWFS = 0; // This mode is intended for use with Node.js (and will throw if the build runs in other engines)
// The File System API will directly use Node.js API without requiring `FS.mount()`.
// The initial working directory will be same as process.cwd() instead of VFS root directory.

var EXPORTED_FUNCTIONS = ['_main'];
// Functions that are explicitly exported. These functions are kept alive
Expand Down
2 changes: 1 addition & 1 deletion src/struct_info.compiled.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/struct_info.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@
"defines": [
"O_CREAT",
"O_SYNC",
"O_DSYNC",
"F_GETFD",
"F_SETFL",
"O_NOFOLLOW",
Expand Down
12 changes: 11 additions & 1 deletion tests/fs/test_nodefs_cloexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@
#include <fcntl.h>
#include <emscripten.h>

#ifdef NODERAWFS
#define CWD ""
#else
#define CWD "/working/"
#endif

int main(void)
{
EM_ASM(
#ifdef NODERAWFS
FS.close(FS.open('test.txt', 'w'));
#else
FS.mkdir('/working');
FS.mount(NODEFS, {root: '.'}, '/working');
FS.close(FS.open('/working/test.txt', 'w'));
#endif
);
assert(open("/working/test.txt", O_RDONLY | O_CLOEXEC) != -1);
assert(open(CWD "test.txt", O_RDONLY | O_CLOEXEC) != -1);
printf("success\n");
return 0;
}
12 changes: 10 additions & 2 deletions tests/fs/test_nodefs_rw.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
#include <string.h>
#include <emscripten.h>

#ifdef NODERAWFS
#define CWD ""
#else
#define CWD "/working/"
#endif

int main() {
FILE *file;
int res;
Expand All @@ -14,15 +20,17 @@ int main() {
fs.writeFileSync('foobar.txt', 'yeehaw');
);

#ifndef NODERAWFS
// mount the current folder as a NODEFS instance
// inside of emscripten
EM_ASM(
FS.mkdir('/working');
FS.mount(NODEFS, { root: '.' }, '/working');
);
#endif

// read and validate the contents of the file
file = fopen("/working/foobar.txt", "r");
file = fopen(CWD "foobar.txt", "r");
assert(file);
res = fread(buffer, sizeof(char), 6, file);
assert(res == 6);
Expand All @@ -31,7 +39,7 @@ int main() {
assert(!strcmp(buffer, "yeehaw"));

// write out something new
file = fopen("/working/foobar.txt", "w");
file = fopen(CWD "foobar.txt", "w");
assert(file);
res = fwrite("cheez", sizeof(char), 5, file);
assert(res == 5);
Expand Down
Loading