summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2013-08-06 10:32:57 -0700
committerAlon Zakai <alonzakai@gmail.com>2013-08-06 10:32:57 -0700
commit8dfcd06d78a82786a04fe74afa707dc0c0cfd1a6 (patch)
tree74113a365c645407d92a12c29ab2913f4170f9dc
parentca19fbd11b665e14cc0759666380ac85c71c70dc (diff)
parentf739fc81c0d7ad4ed4c82598a21852dbad7ca357 (diff)
Merge pull request #1458 from inolen/vfs
initial vfs work
-rw-r--r--LICENSE26
-rw-r--r--src/library.js3118
-rw-r--r--src/library_path.js133
-rw-r--r--src/modules.js2
-rw-r--r--src/settings.js37
-rw-r--r--tests/filesystem/src.js1
-rwxr-xr-xtests/runner.py32
-rw-r--r--tests/stat/test_chmod.c24
-rw-r--r--tests/stat/test_mknod.c5
-rw-r--r--tests/stat/test_stat.c3
-rw-r--r--tests/stdio/test_rename.c107
11 files changed, 2234 insertions, 1254 deletions
diff --git a/LICENSE b/LICENSE
index 6d090777..f742692e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -66,3 +66,29 @@ IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
+
+==============================================================================
+
+This program uses portions of Node.js source code located in src/library_path.js,
+in accordance with the terms of the MIT license. Node's license follows:
+
+ """
+ Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+ """ \ No newline at end of file
diff --git a/src/library.js b/src/library.js
index 85d1d13f..0e4ac0b2 100644
--- a/src/library.js
+++ b/src/library.js
@@ -28,7 +28,7 @@ LibraryManager.library = {
stderr: 'allocate(1, "i32*", ALLOC_STATIC)',
_impure_ptr: 'allocate(1, "i32*", ALLOC_STATIC)',
- $FS__deps: ['$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr', '_impure_ptr'],
+ $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$VFS', '$PATH', '$TTY', '$MEMFS', 'stdin', 'stdout', 'stderr', 'fflush'],
$FS__postset: 'FS.staticInit();' +
'__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' +
'__ATMAIN__.push({ func: function() { FS.ignorePermissions = false } });' +
@@ -42,287 +42,682 @@ LibraryManager.library = {
'Module["FS_createLink"] = FS.createLink;' +
'Module["FS_createDevice"] = FS.createDevice;',
$FS: {
- // The path to the current folder.
- currentPath: '/',
- // The inode to assign to the next created object.
- nextInode: 2,
- // Currently opened file or directory streams. Padded with null so the zero
- // index is unused, as the indices are used as pointers. This is not split
- // into separate fileStreams and folderStreams lists because the pointers
- // must be interchangeable, e.g. when used in fdopen().
- // streams is kept as a dense array. It may contain |null| to fill in
- // holes, however.
+ root: null,
+ nodes: [null],
+ devices: [null],
streams: [null],
-#if ASSERTIONS
- checkStreams: function() {
- for (var i in FS.streams) if (FS.streams.hasOwnProperty(i)) assert(i >= 0 && i < FS.streams.length); // no keys not in dense span
- for (var i = 0; i < FS.streams.length; i++) assert(typeof FS.streams[i] == 'object'); // no non-null holes in dense span
- },
-#endif
+ nextInode: 1,
+ name_table: new Array(4096),
+ currentPath: '/',
+ initialized: false,
// Whether we are currently ignoring permissions. Useful when preparing the
// filesystem and creating files inside read-only folders.
// This is set to false when the runtime is initialized, allowing you
// to modify the filesystem freely before run() is called.
ignorePermissions: true,
- createFileHandle: function(stream, fd) {
- if (typeof stream === 'undefined') {
- stream = null;
- }
- if (!fd) {
- if (stream && stream.socket) {
- for (var i = 1; i < 64; i++) {
- if (!FS.streams[i]) {
- fd = i;
- break;
- }
- }
- assert(fd, 'ran out of low fds for sockets');
- } else {
- fd = Math.max(FS.streams.length, 64);
- for (var i = FS.streams.length; i < fd; i++) {
- FS.streams[i] = null; // Keep dense
+
+ ErrnoError: function (errno) {
+ function ErrnoError(errno) {
+ this.errno = errno;
+ for (var key in ERRNO_CODES) {
+ if (ERRNO_CODES[key] === errno) {
+ this.code = key;
+ break;
}
}
+ this.message = ERRNO_MESSAGES[errno];
+ };
+ ErrnoError.prototype = Object.create(Error.prototype);
+ ErrnoError.prototype.contructor = ErrnoError;
+ return new ErrnoError(errno);
+ },
+
+ //
+ // nodes
+ //
+ hashName: function (parentid, name) {
+ var hash = 0;
+ for (var i = 0; i < name.length; i++) {
+ hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
}
- // Close WebSocket first if we are about to replace the fd (i.e. dup2)
- if (FS.streams[fd] && FS.streams[fd].socket && FS.streams[fd].socket.close) {
- FS.streams[fd].socket.close();
- }
- FS.streams[fd] = stream;
- return fd;
+ return (parentid + hash) % FS.name_table.length;
},
- removeFileHandle: function(fd) {
- FS.streams[fd] = null;
+ hashAddNode: function (node) {
+ var hash = FS.hashName(node.parent.id, node.name);
+ node.name_next = FS.name_table[hash];
+ FS.name_table[hash] = node;
},
- joinPath: function(parts, forceRelative) {
- var ret = parts[0];
- for (var i = 1; i < parts.length; i++) {
- if (ret[ret.length-1] != '/') ret += '/';
- ret += parts[i];
+ hashRemoveNode: function (node) {
+ var hash = FS.hashName(node.parent.id, node.name);
+ if (FS.name_table[hash] === node) {
+ FS.name_table[hash] = node.name_next;
+ } else {
+ var current = FS.name_table[hash];
+ while (current) {
+ if (current.name_next === node) {
+ current.name_next = node.name_next;
+ break;
+ }
+ current = current.name_next;
+ }
}
- if (forceRelative && ret[0] == '/') ret = ret.substr(1);
- return ret;
},
- // Converts any path to an absolute path. Resolves embedded "." and ".."
- // parts.
- absolutePath: function(relative, base) {
- if (typeof relative !== 'string') return null;
- if (base === undefined) base = FS.currentPath;
- if (relative && relative[0] == '/') base = '';
- var full = base + '/' + relative;
- var parts = full.split('/').reverse();
- var absolute = [''];
- while (parts.length) {
- var part = parts.pop();
- if (part == '' || part == '.') {
- // Nothing.
- } else if (part == '..') {
- if (absolute.length > 1) absolute.pop();
- } else {
- absolute.push(part);
+ lookupNode: function (parent, name) {
+ var err = FS.mayLookup(parent);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ var hash = FS.hashName(parent.id, name);
+ for (var node = FS.name_table[hash]; node; node = node.name_next) {
+ if (node.parent.id === parent.id && node.name === name) {
+ return node;
}
}
- return absolute.length == 1 ? '/' : absolute.join('/');
+ // if we failed to find it in the cache, call into the VFS
+ return VFS.lookup(parent, name);
},
- // Analyzes a relative or absolute path returning a description, containing:
- // isRoot: Whether the path points to the root.
- // exists: Whether the object at the path exists.
- // error: If !exists, this will contain the errno code of the cause.
- // name: The base name of the object (null if !parentExists).
- // path: The absolute path to the object, with all links resolved.
- // object: The filesystem record of the object referenced by the path.
- // parentExists: Whether the parent of the object exist and is a folder.
- // parentPath: The absolute path to the parent folder.
- // parentObject: The filesystem record of the parent folder.
- analyzePath: function(path, dontResolveLastLink, linksVisited) {
- var ret = {
- isRoot: false,
- exists: false,
- error: 0,
- name: null,
- path: null,
- object: null,
- parentExists: false,
- parentPath: null,
- parentObject: null
+ createNode: function (parent, name, mode, rdev) {
+ var node = {
+ id: FS.nextInode++,
+ name: name,
+ mode: mode,
+ node_ops: {},
+ stream_ops: {},
+ rdev: rdev,
+ parent: null,
+ mount: null
};
-#if FS_LOG
- var inputPath = path;
- function log() {
- Module['print']('FS.analyzePath("' + inputPath + '", ' +
- dontResolveLastLink + ', ' +
- linksVisited + ') => {' +
- 'isRoot: ' + ret.isRoot + ', ' +
- 'exists: ' + ret.exists + ', ' +
- 'error: ' + ret.error + ', ' +
- 'name: "' + ret.name + '", ' +
- 'path: "' + ret.path + '", ' +
- 'object: ' + ret.object + ', ' +
- 'parentExists: ' + ret.parentExists + ', ' +
- 'parentPath: "' + ret.parentPath + '", ' +
- 'parentObject: ' + ret.parentObject + '}');
+ if (!parent) {
+ parent = node; // root node sets parent to itself
+ }
+ node.parent = parent;
+ node.mount = parent.mount;
+ // compatibility
+ var readMode = {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}};
+ var writeMode = {{{ cDefine('S_IWUGO') }}};
+ Object.defineProperty(node, 'read', {
+ get: function () { return (node.mode & readMode) === readMode; },
+ set: function (val) { val ? node.mode |= readMode : node.mode &= ~readMode; }
+ });
+ Object.defineProperty(node, 'write', {
+ get: function () { return (node.mode & writeMode) === writeMode; },
+ set: function (val) { val ? node.mode |= writeMode : node.mode &= ~writeMode; }
+ });
+ // TODO add:
+ // isFolder
+ // isDevice
+ FS.hashAddNode(node);
+ return node;
+ },
+ destroyNode: function (node) {
+ FS.hashRemoveNode(node);
+ },
+ isRoot: function (node) {
+ return node === node.parent;
+ },
+ isMountpoint: function (node) {
+ return node.mounted;
+ },
+ isFile: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}};
+ },
+ isDir: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFDIR') }}};
+ },
+ isLink: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFLNK') }}};
+ },
+ isChrdev: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFCHR') }}};
+ },
+ isBlkdev: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFBLK') }}};
+ },
+ isFIFO: function (mode) {
+ return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}};
+ },
+
+ //
+ // paths
+ //
+ lookupPath: function (path, opts) {
+ path = PATH.resolve(FS.currentPath, path);
+ opts = opts || { recurse_count: 0 };
+
+ if (opts.recurse_count > 8) { // max recursive lookup of 8
+ throw new FS.ErrnoError(ERRNO_CODES.ELOOP);
}
-#endif
- path = FS.absolutePath(path);
- if (path == '/') {
- ret.isRoot = true;
- ret.exists = ret.parentExists = true;
- ret.name = '/';
- ret.path = ret.parentPath = '/';
- ret.object = ret.parentObject = FS.root;
- } else if (path !== null) {
- linksVisited = linksVisited || 0;
- path = path.slice(1).split('/');
- var current = FS.root;
- var traversed = [''];
- while (path.length) {
- if (path.length == 1 && current.isFolder) {
- ret.parentExists = true;
- ret.parentPath = traversed.length == 1 ? '/' : traversed.join('/');
- ret.parentObject = current;
- ret.name = path[0];
- }
- var target = path.shift();
- if (!current.isFolder) {
- ret.error = ERRNO_CODES.ENOTDIR;
- break;
- } else if (!current.read) {
- ret.error = ERRNO_CODES.EACCES;
- break;
- } else if (!current.contents.hasOwnProperty(target)) {
- ret.error = ERRNO_CODES.ENOENT;
- break;
- }
- current = current.contents[target];
- if (current.link && !(dontResolveLastLink && path.length == 0)) {
- if (linksVisited > 40) { // Usual Linux SYMLOOP_MAX.
- ret.error = ERRNO_CODES.ELOOP;
- break;
+
+ // split the path
+ var parts = PATH.normalizeArray(path.split('/').filter(function (p) {
+ return !!p;
+ }), false);
+
+ // start at the root
+ var current = FS.root;
+ var current_path = '/';
+
+ for (var i = 0; i < parts.length; i++) {
+ var islast = (i === parts.length-1);
+ if (islast && opts.parent) {
+ // stop resolving
+ break;
+ }
+
+ current = FS.lookupNode(current, parts[i]);
+ current_path = PATH.join(current_path, parts[i]);
+
+ // jump to the mount's root node if this is a mountpoint
+ if (FS.isMountpoint(current)) {
+ current = current.mount.root;
+ }
+
+ // follow symlinks
+ // by default, lookupPath will not follow a symlink if it is the final path component.
+ // setting opts.follow = true will override this behavior.
+ if (!islast || opts.follow) {
+ var count = 0;
+ while (FS.isLink(current.mode)) {
+ var link = VFS.readlink(current_path);
+ current_path = PATH.resolve(PATH.dirname(current_path), link);
+
+ var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count });
+ current = lookup.node;
+
+ if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX).
+ throw new FS.ErrnoError(ERRNO_CODES.ELOOP);
}
- var link = FS.absolutePath(current.link, traversed.join('/'));
- ret = FS.analyzePath([link].concat(path).join('/'),
- dontResolveLastLink, linksVisited + 1);
-#if FS_LOG
- log();
-#endif
- return ret;
- }
- traversed.push(target);
- if (path.length == 0) {
- ret.exists = true;
- ret.path = traversed.join('/');
- ret.object = current;
}
}
}
-#if FS_LOG
- log();
-#endif
- return ret;
+
+ return { path: current_path, node: current };
},
- // Finds the file system object at a given path. If dontResolveLastLink is
- // set to true and the object is a symbolic link, it will be returned as is
- // instead of being resolved. Links embedded in the path are still resolved.
- findObject: function(path, dontResolveLastLink) {
- var ret = FS.analyzePath(path, dontResolveLastLink);
- if (ret.exists) {
- return ret.object;
+ getPath: function (node) {
+ var path;
+ while (true) {
+ if (FS.isRoot(node)) {
+ return path ? PATH.join(node.mount.mountpoint, path) : node.mount.mountpoint;
+ }
+ path = path ? PATH.join(node.name, path) : node.name;
+ node = node.parent;
+ }
+ },
+
+ //
+ // permissions
+ //
+ flagModes: {
+ '"r"': {{{ cDefine('O_RDONLY') }}},
+ '"rs"': {{{ cDefine('O_RDONLY') }}} | {{{ cDefine('O_SYNC') }}},
+ '"r+"': {{{ cDefine('O_RDWR') }}},
+ '"w"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}},
+ '"wx"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}},
+ '"xw"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}},
+ '"w+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}},
+ '"wx+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}},
+ '"xw+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}},
+ '"a"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}},
+ '"ax"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}},
+ '"xa"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}},
+ '"a+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}},
+ '"ax+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}},
+ '"xa+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}}
+ },
+ // convert the 'r', 'r+', etc. to it's corresponding set of O_* flags
+ modeStringToFlags: function (str) {
+ var flags = FS.flagModes[str];
+ if (typeof flags === 'undefined') {
+ throw new Error('Unknown file open mode: ' + str);
+ }
+ return flags;
+ },
+ // convert O_* bitmask to a string for nodePermissions
+ flagsToPermissionString: function (flag) {
+ var accmode = flag & {{{ cDefine('O_ACCMODE') }}};
+ var perms = ['r', 'w', 'rw'][accmode];
+ if ((flag & {{{ cDefine('O_TRUNC') }}})) {
+ perms += 'w';
+ }
+ return perms;
+ },
+ nodePermissions: function (node, perms) {
+ if (FS.ignorePermissions) {
+ return 0;
+ }
+ // return 0 if any user, group or owner bits are set.
+ if (perms.indexOf('r') !== -1 && !(node.mode & {{{ cDefine('S_IRUGO') }}})) {
+ return ERRNO_CODES.EACCES;
+ } else if (perms.indexOf('w') !== -1 && !(node.mode & {{{ cDefine('S_IWUGO') }}})) {
+ return ERRNO_CODES.EACCES;
+ } else if (perms.indexOf('x') !== -1 && !(node.mode & {{{ cDefine('S_IXUGO') }}})) {
+ return ERRNO_CODES.EACCES;
+ }
+ return 0;
+ },
+ mayLookup: function (dir) {
+ return FS.nodePermissions(dir, 'x');
+ },
+ mayMknod: function (mode) {
+ switch (mode & {{{ cDefine('S_IFMT') }}}) {
+ case {{{ cDefine('S_IFREG') }}}:
+ case {{{ cDefine('S_IFCHR') }}}:
+ case {{{ cDefine('S_IFBLK') }}}:
+ case {{{ cDefine('S_IFIFO') }}}:
+ case {{{ cDefine('S_IFSOCK') }}}:
+ return 0;
+ default:
+ return ERRNO_CODES.EINVAL;
+ }
+ },
+ mayCreate: function (dir, name) {
+ try {
+ var node = FS.lookupNode(dir, name);
+ return ERRNO_CODES.EEXIST;
+ } catch (e) {
+ }
+ return FS.nodePermissions(dir, 'wx');
+ },
+ mayDelete: function (dir, name, isdir) {
+ var node;
+ try {
+ node = FS.lookupNode(dir, name);
+ } catch (e) {
+ return e.errno;
+ }
+ var err = FS.nodePermissions(dir, 'wx');
+ if (err) {
+ return err;
+ }
+ if (isdir) {
+ if (!FS.isDir(node.mode)) {
+ return ERRNO_CODES.ENOTDIR;
+ }
+ if (FS.isRoot(node) || FS.getPath(node) === FS.currentPath) {
+ return ERRNO_CODES.EBUSY;
+ }
} else {
- ___setErrNo(ret.error);
- return null;
+ if (FS.isDir(node.mode)) {
+ return ERRNO_CODES.EISDIR;
+ }
}
+ return 0;
+ },
+ mayOpen: function (node, flags) {
+ if (!node) {
+ return ERRNO_CODES.ENOENT;
+ }
+ if (FS.isLink(node.mode)) {
+ return ERRNO_CODES.ELOOP;
+ } else if (FS.isDir(node.mode)) {
+ if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY')}}} || // opening for write
+ (flags & {{{ cDefine('O_TRUNC') }}})) {
+ return ERRNO_CODES.EISDIR;
+ }
+ }
+ return FS.nodePermissions(node, FS.flagsToPermissionString(flags));
},
- // Creates a file system record: file, link, device or folder.
- createObject: function(parent, name, properties, canRead, canWrite) {
-#if FS_LOG
- Module['print']('FS.createObject("' + parent + '", ' +
- '"' + name + '", ' +
- JSON.stringify(properties) + ', ' +
- canRead + ', ' +
- canWrite + ')');
-#endif
- if (!parent) parent = '/';
- if (typeof parent === 'string') parent = FS.findObject(parent);
- if (!parent) {
- ___setErrNo(ERRNO_CODES.EACCES);
- throw new Error('Parent path must exist.');
+ //
+ // devices
+ //
+ // each character device consists of a device id + stream operations.
+ // when a character device node is created (e.g. /dev/stdin) it is
+ // assigned a device id that lets us map back to the actual device.
+ // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops.
+ // however, once opened, the stream's operations are overridden with
+ // the operations of the device its underlying node maps back to.
+ chrdev_stream_ops: {
+ open: function (stream) {
+ var device = FS.getDevice(stream.node.rdev);
+ // override node's stream ops with the device's
+ stream.stream_ops = device.stream_ops;
+ // forward the open call
+ if (stream.stream_ops.open) {
+ stream.stream_ops.open(stream);
+ }
+ },
+ llseek: function () {
+ throw new FS.ErrnoError(ERRNO_CODES.ESPIPE);
}
- if (!parent.isFolder) {
- ___setErrNo(ERRNO_CODES.ENOTDIR);
- throw new Error('Parent must be a folder.');
+ },
+ major: function (dev) {
+ return ((dev) >> 8);
+ },
+ minor: function (dev) {
+ return ((dev) & 0xff);
+ },
+ makedev: function (ma, mi) {
+ return ((ma) << 8 | (mi));
+ },
+ registerDevice: function (dev, ops) {
+ FS.devices[dev] = { stream_ops: ops };
+ },
+ getDevice: function (dev) {
+ return FS.devices[dev];
+ },
+
+ //
+ // streams
+ //
+ MAX_OPEN_FDS: 4096,
+ nextfd: function (fd_start, fd_end) {
+ fd_start = fd_start || 1;
+ fd_end = fd_end || FS.MAX_OPEN_FDS;
+ for (var fd = fd_start; fd <= fd_end; fd++) {
+ if (!FS.streams[fd]) {
+ return fd;
+ }
}
- if (!parent.write && !FS.ignorePermissions) {
- ___setErrNo(ERRNO_CODES.EACCES);
- throw new Error('Parent folder must be writeable.');
+ throw new FS.ErrnoError(ERRNO_CODES.EMFILE);
+ },
+ getStream: function (fd) {
+ return FS.streams[fd];
+ },
+ createStream: function (stream, fd_start, fd_end) {
+ var fd = FS.nextfd(fd_start, fd_end);
+ stream.fd = fd;
+ // compatibility
+ Object.defineProperty(stream, 'object', {
+ get: function () { return stream.node; },
+ set: function (val) { stream.node = val; }
+ });
+ Object.defineProperty(stream, 'isRead', {
+ get: function () { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; }
+ });
+ Object.defineProperty(stream, 'isWrite', {
+ get: function () { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; }
+ });
+ Object.defineProperty(stream, 'isAppend', {
+ get: function () { return (stream.flags & {{{ cDefine('O_APPEND') }}}); }
+ });
+ FS.streams[fd] = stream;
+ return stream;
+ },
+ closeStream: function (fd) {
+ FS.streams[fd] = null;
+ },
+
+ //
+ // general
+ //
+ createDefaultDirectories: function () {
+ VFS.mkdir('/tmp', 0777);
+ },
+ createDefaultDevices: function () {
+ // create /dev
+ VFS.mkdir('/dev', 0777);
+ // setup /dev/null
+ FS.registerDevice(FS.makedev(1, 3), {
+ read: function () { return 0; },
+ write: function () { return 0; }
+ });
+ VFS.mkdev('/dev/null', 0666, FS.makedev(1, 3));
+ // setup /dev/tty and /dev/tty1
+ // stderr needs to print output using Module['printErr']
+ // so we register a second tty just for it.
+ TTY.register(FS.makedev(5, 0), TTY.default_tty_ops);
+ TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops);
+ VFS.mkdev('/dev/tty', 0666, FS.makedev(5, 0));
+ VFS.mkdev('/dev/tty1', 0666, FS.makedev(6, 0));
+ // we're not going to emulate the actual shm device,
+ // just create the tmp dirs that reside in it commonly
+ VFS.mkdir('/dev/shm', 0777);
+ VFS.mkdir('/dev/shm/tmp', 0777);
+ },
+ createStandardStreams: function () {
+ // TODO deprecate the old functionality of a single
+ // input / output callback and that utilizes FS.createDevice
+ // and instead require a unique set of stream ops
+
+ // by default, we symlink the standard streams to the
+ // default tty devices. however, if the standard streams
+ // have been overwritten we create a unique device for
+ // them instead.
+ if (Module['stdin']) {
+ FS.createDevice('/dev', 'stdin', Module['stdin']);
+ } else {
+ VFS.symlink('/dev/tty', '/dev/stdin');
}
- if (!name || name == '.' || name == '..') {
- ___setErrNo(ERRNO_CODES.ENOENT);
- throw new Error('Name must not be empty.');
+ if (Module['stdout']) {
+ FS.createDevice('/dev', 'stdout', null, Module['stdout']);
+ } else {
+ VFS.symlink('/dev/tty', '/dev/stdout');
}
- if (parent.contents.hasOwnProperty(name)) {
- ___setErrNo(ERRNO_CODES.EEXIST);
- throw new Error("Can't overwrite object.");
+ if (Module['stderr']) {
+ FS.createDevice('/dev', 'stderr', null, Module['stderr']);
+ } else {
+ VFS.symlink('/dev/tty1', '/dev/stderr');
}
- parent.contents[name] = {
- read: canRead === undefined ? true : canRead,
- write: canWrite === undefined ? false : canWrite,
- timestamp: Date.now(),
- inodeNumber: FS.nextInode++
- };
- for (var key in properties) {
- if (properties.hasOwnProperty(key)) {
- parent.contents[name][key] = properties[key];
+ // open default streams for the stdin, stdout and stderr devices
+ var stdin = VFS.open('/dev/stdin', 'r');
+ {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 'stdin.fd', 'void*') }}};
+ assert(stdin.fd === 1, 'invalid handle for stdin (' + stdin.fd + ')');
+
+ var stdout = VFS.open('/dev/stdout', 'w');
+ {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 'stdout.fd', 'void*') }}};
+ assert(stdout.fd === 2, 'invalid handle for stdout (' + stdout.fd + ')');
+
+ var stderr = VFS.open('/dev/stderr', 'w');
+ {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 'stderr.fd', 'void*') }}};
+ assert(stderr.fd === 3, 'invalid handle for stderr (' + stderr.fd + ')');
+ },
+ staticInit: function () {
+ FS.root = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0);
+ VFS.mount(MEMFS, {}, '/');
+
+ FS.createDefaultDirectories();
+ FS.createDefaultDevices();
+ },
+ init: function (input, output, error) {
+ assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)');
+ FS.init.initialized = true;
+
+ // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here
+ Module['stdin'] = input || Module['stdin'];
+ Module['stdout'] = output || Module['stdout'];
+ Module['stderr'] = error || Module['stderr'];
+
+ FS.createStandardStreams();
+ },
+ quit: function () {
+ FS.init.initialized = false;
+ for (var i = 0; i < FS.streams.length; i++) {
+ var stream = FS.streams[i];
+ if (!stream) {
+ continue;
}
+ VFS.close(stream);
}
+ },
- return parent.contents[name];
+ //
+ // compatibility
+ //
+ getMode: function (canRead, canWrite) {
+ var mode = 0;
+ if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}};
+ if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}};
+ return mode;
+ },
+ joinPath: function (parts, forceRelative) {
+ var path = PATH.join.apply(null, parts);
+ if (forceRelative && path[0] == '/') path = path.substr(1);
+ return path;
+ },
+ absolutePath: function (relative, base) {
+ return PATH.resolve(base, relative);
},
- // Creates a folder.
- createFolder: function(parent, name, canRead, canWrite) {
- var properties = {isFolder: true, isDevice: false, contents: {}};
- return FS.createObject(parent, name, properties, canRead, canWrite);
+ standardizePath: function (path) {
+ return PATH.normalize(path);
},
- // Creates a folder and all its missing parents.
- createPath: function(parent, path, canRead, canWrite) {
- var current = FS.findObject(parent);
- if (current === null) throw new Error('Invalid parent.');
- path = path.split('/').reverse();
- while (path.length) {
- var part = path.pop();
+ findObject: function (path, dontResolveLastLink) {
+ var ret = FS.analyzePath(path, dontResolveLastLink);
+ if (ret.exists) {
+ return ret.object;
+ } else {
+ ___setErrNo(ret.error);
+ return null;
+ }
+ },
+ analyzePath: function (path, dontResolveLastLink) {
+ // operate from within the context of the symlink's target
+ try {
+ var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });
+ path = lookup.path;
+ } catch (e) {
+ }
+ var ret = {
+ isRoot: false, exists: false, error: 0, name: null, path: null, object: null,
+ parentExists: false, parentPath: null, parentObject: null
+ };
+ try {
+ var lookup = FS.lookupPath(path, { parent: true });
+ ret.parentExists = true;
+ ret.parentPath = lookup.path;
+ ret.parentObject = lookup.node;
+ ret.name = PATH.basename(path);
+ lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });
+ ret.exists = true;
+ ret.path = lookup.path;
+ ret.object = lookup.node;
+ ret.name = lookup.node.name;
+ ret.isRoot = lookup.path === '/';
+ } catch (e) {
+ ret.error = e.errno;
+ };
+ return ret;
+ },
+ createFolder: function (parent, name, canRead, canWrite) {
+ var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name);
+ var mode = FS.getMode(canRead, canWrite);
+ return VFS.mkdir(path, mode);
+ },
+ createPath: function (parent, path, canRead, canWrite) {
+ parent = typeof parent === 'string' ? parent : FS.getPath(parent);
+ var parts = path.split('/').reverse();
+ while (parts.length) {
+ var part = parts.pop();
if (!part) continue;
- if (!current.contents.hasOwnProperty(part)) {
- FS.createFolder(current, part, canRead, canWrite);
+ var current = PATH.join(parent, part);
+ try {
+ VFS.mkdir(current, 0777);
+ } catch (e) {
+ // ignore EEXIST
}
- current = current.contents[part];
+ parent = current;
}
return current;
},
-
- // Creates a file record, given specific properties.
- createFile: function(parent, name, properties, canRead, canWrite) {
- properties.isFolder = false;
- return FS.createObject(parent, name, properties, canRead, canWrite);
+ createFile: function (parent, name, properties, canRead, canWrite) {
+ var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name);
+ var mode = FS.getMode(canRead, canWrite);
+ return VFS.create(path, mode);
},
- // Creates a file record from existing data.
- createDataFile: function(parent, name, data, canRead, canWrite) {
- if (typeof data === 'string') {
- var dataArray = new Array(data.length);
- for (var i = 0, len = data.length; i < len; ++i) dataArray[i] = data.charCodeAt(i);
- data = dataArray;
- }
- var properties = {
- isDevice: false,
- contents: data.subarray ? data.subarray(0) : data // as an optimization, create a new array wrapper (not buffer) here, to help JS engines understand this object
- };
- return FS.createFile(parent, name, properties, canRead, canWrite);
+ createDataFile: function (parent, name, data, canRead, canWrite) {
+ var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name);
+ var mode = FS.getMode(canRead, canWrite);
+ var node = VFS.create(path, mode);
+ if (data) {
+ if (typeof data === 'string') {
+ var arr = new Array(data.length);
+ for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i);
+ data = arr;
+ }
+ // make sure we can write to the file
+ VFS.chmod(path, mode | {{{ cDefine('S_IWUGO') }}});
+ var stream = VFS.open(path, 'w');
+ VFS.write(stream, data, 0, data.length, 0);
+ VFS.close(stream);
+ VFS.chmod(path, mode);
+ }
+ return node;
+ },
+ createDevice: function (parent, name, input, output) {
+ var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name);
+ var mode = input && output ? 0777 : (input ? 0333 : 0555);
+ if (!FS.createDevice.major) FS.createDevice.major = 64;
+ var dev = FS.makedev(FS.createDevice.major++, 0);
+ // Create a fake device that a set of stream ops to emulate
+ // the old behavior.
+ FS.registerDevice(dev, {
+ open: function (stream) {
+ stream.seekable = false;
+ },
+ close: function (stream) {
+ // flush any pending line data
+ if (output.buffer && output.buffer.length) {
+ output({{{ charCode('\n') }}});
+ }
+ },
+ read: function (stream, buffer, offset, length, pos /* ignored */) {
+ var bytesRead = 0;
+ for (var i = 0; i < length; i++) {
+ var result;
+ try {
+ result = input();
+ } catch (e) {
+ throw new FS.ErrnoError(ERRNO_CODES.EIO);
+ }
+ if (result === undefined && bytesRead === 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EAGAIN);
+ }
+ if (result === null || result === undefined) break;
+ bytesRead++;
+ buffer[offset+i] = result;
+ }
+ if (bytesRead) {
+ stream.node.timestamp = Date.now();
+ }
+ return bytesRead;
+ },
+ write: function (stream, buffer, offset, length, pos) {
+ for (var i = 0; i < length; i++) {
+ try {
+ output(buffer[offset+i]);
+ } catch (e) {
+ throw new FS.ErrnoError(ERRNO_CODES.EIO);
+ }
+ }
+ if (length) {
+ stream.node.timestamp = Date.now();
+ }
+ return i;
+ }
+ });
+ return VFS.mkdev(path, mode, dev);
+ },
+ createLink: function (parent, name, target, canRead, canWrite) {
+ var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name);
+ return VFS.symlink(target, path);
+ },
+ // Makes sure a file's contents are loaded. Returns whether the file has
+ // been loaded successfully. No-op for files that have been loaded already.
+ forceLoadFile: function(obj) {
+ if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true;
+ var success = true;
+ if (typeof XMLHttpRequest !== 'undefined') {
+ throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");
+ } else if (Module['read']) {
+ // Command-line.
+ try {
+ // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as
+ // read() will try to parse UTF8.
+ obj.contents = intArrayFromString(Module['read'](obj.url), true);
+ } catch (e) {
+ success = false;
+ }
+ } else {
+ throw new Error('Cannot load without read() or XMLHttpRequest.');
+ }
+ if (!success) ___setErrNo(ERRNO_CODES.EIO);
+ return success;
},
// Creates a file record for lazy-loading from a URL. XXX This requires a synchronous
// XHR, which is not possible in browsers except in a web worker! Use preloading,
// either --preload-file in emcc or FS.createPreloadedFile
createLazyFile: function(parent, name, url, canRead, canWrite) {
-
if (typeof XMLHttpRequest !== 'undefined') {
if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc';
// Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse.
@@ -341,7 +736,6 @@ LibraryManager.library = {
LazyUint8Array.prototype.setDataGetter = function(getter) {
this.getter = getter;
}
-
LazyUint8Array.prototype.cacheLength = function() {
// Find length
var xhr = new XMLHttpRequest();
@@ -423,7 +817,45 @@ LibraryManager.library = {
var properties = { isDevice: false, url: url };
}
- return FS.createFile(parent, name, properties, canRead, canWrite);
+ var node = FS.createFile(parent, name, properties, canRead, canWrite);
+ // This is a total hack, but I want to get this lazy file code out of the
+ // core of MEMFS. If we want to keep this lazy file concept I feel it should
+ // be its own thin LAZYFS proxying calls to MEMFS.
+ if (properties.contents) {
+ node.contents = properties.contents;
+ } else if (properties.url) {
+ node.contents = null;
+ node.url = properties.url;
+ }
+ // override each stream op with one that tries to force load the lazy file first
+ var stream_ops = {};
+ var keys = Object.keys(node.stream_ops);
+ keys.forEach(function (key) {
+ var fn = node.stream_ops[key];
+ stream_ops[key] = function () {
+ if (!FS.forceLoadFile(node)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EIO);
+ }
+ return fn.apply(null, arguments);
+ };
+ });
+ // use a custom read function
+ stream_ops.read = function (stream, buffer, offset, length, position) {
+ var contents = stream.node.contents;
+ var size = Math.min(contents.length - position, length);
+ if (contents.slice) { // normal array
+ for (var i = 0; i < size; i++) {
+ buffer[offset + i] = contents[position + i];
+ }
+ } else {
+ for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR
+ buffer[offset + i] = contents.get(position + i);
+ }
+ }
+ return size;
+ };
+ node.stream_ops = stream_ops;
+ return node;
},
// Preloads a file asynchronously. You can call this before run, for example in
// preRun. run will be delayed until this file arrives and is set up.
@@ -437,9 +869,10 @@ LibraryManager.library = {
// You can also call this with a typed array instead of a url. It will then
// do preloading for the Image/Audio part, as if the typed array were the
// result of an XHR that you did manually.
+ createPreloadedFile__deps: ['$PATH'],
createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) {
Browser.init();
- var fullname = FS.joinPath([parent, name], true);
+ var fullname = PATH.join(parent, name).substr(1);
function processData(byteArray) {
function finish(byteArray) {
if (!dontCreateFile) {
@@ -469,212 +902,839 @@ LibraryManager.library = {
} else {
processData(url);
}
+ }
+ },
+
+ $VFS__deps: ['$FS'],
+ $VFS: {
+ mount: function (type, opts, mountpoint) {
+ var mount = {
+ type: type,
+ opts: opts,
+ mountpoint: mountpoint,
+ root: null
+ };
+ var lookup;
+ if (mountpoint) {
+ lookup = FS.lookupPath(mountpoint, { follow: false });
+ }
+ // create a root node for the fs
+ var root = type.mount(mount);
+ root.mount = mount;
+ mount.root = root;
+ // assign the mount info to the mountpoint's node
+ if (lookup) {
+ lookup.node.mount = mount;
+ lookup.node.mounted = true;
+ // compatibility update FS.root if we mount to /
+ if (mountpoint === '/') {
+ FS.root = mount.root;
+ }
+ }
+ return root;
},
- // Creates a link to a specific local path.
- createLink: function(parent, name, target, canRead, canWrite) {
- var properties = {isDevice: false, link: target};
- return FS.createFile(parent, name, properties, canRead, canWrite);
+ lookup: function (parent, name) {
+ return parent.node_ops.lookup(parent, name);
},
- // Creates a character device with input and output callbacks:
- // input: Takes no parameters, returns a byte value or null if no data is
- // currently available.
- // output: Takes a byte value; doesn't return anything. Can also be passed
- // null to perform a flush of any cached data.
- createDevice: function(parent, name, input, output) {
- if (!(input || output)) {
- throw new Error('A device must have at least one callback defined.');
- }
- var ops = {isDevice: true, input: input, output: output};
- return FS.createFile(parent, name, ops, Boolean(input), Boolean(output));
+ // generic function for all node creation
+ mknod: function (path, mode, dev) {
+ var lookup = FS.lookupPath(path, { parent: true });
+ var parent = lookup.node;
+ var name = PATH.basename(path);
+ var err = FS.mayCreate(parent, name);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ if (!parent.node_ops.mknod) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ return parent.node_ops.mknod(parent, name, mode, dev);
},
- // Makes sure a file's contents are loaded. Returns whether the file has
- // been loaded successfully. No-op for files that have been loaded already.
- forceLoadFile: function(obj) {
- if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true;
- var success = true;
- if (typeof XMLHttpRequest !== 'undefined') {
- throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");
- } else if (Module['read']) {
- // Command-line.
- try {
- // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as
- // read() will try to parse UTF8.
- obj.contents = intArrayFromString(Module['read'](obj.url), true);
- } catch (e) {
- success = false;
+ // helpers to create specific types of nodes
+ create: function (path, mode) {
+ mode &= {{{ cDefine('S_IALLUGO') }}};
+ mode |= {{{ cDefine('S_IFREG') }}};
+ return VFS.mknod(path, mode, 0);
+ },
+ mkdir: function (path, mode) {
+ mode &= {{{ cDefine('S_IRWXUGO') }}} | {{{ cDefine('S_ISVTX') }}};
+ mode |= {{{ cDefine('S_IFDIR') }}};
+ return VFS.mknod(path, mode, 0);
+ },
+ mkdev: function (path, mode, dev) {
+ mode |= {{{ cDefine('S_IFCHR') }}};
+ return VFS.mknod(path, mode, dev);
+ },
+ symlink: function (oldpath, newpath) {
+ var lookup = FS.lookupPath(newpath, { parent: true });
+ var parent = lookup.node;
+ var newname = PATH.basename(newpath);
+ var err = FS.mayCreate(parent, newname);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ if (!parent.node_ops.symlink) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ return parent.node_ops.symlink(parent, newname, oldpath);
+ },
+ rename: function (old_path, new_path) {
+ var old_dirname = PATH.dirname(old_path);
+ var new_dirname = PATH.dirname(new_path);
+ var old_name = PATH.basename(old_path);
+ var new_name = PATH.basename(new_path);
+ // parents must exist
+ var lookup, old_dir, new_dir;
+ try {
+ lookup = FS.lookupPath(old_path, { parent: true });
+ old_dir = lookup.node;
+ lookup = FS.lookupPath(new_path, { parent: true });
+ new_dir = lookup.node;
+ } catch (e) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBUSY);
+ }
+ // need to be part of the same mount
+ if (old_dir.mount !== new_dir.mount) {
+ throw new FS.ErrnoError(ERRNO_CODES.EXDEV);
+ }
+ // source must exist
+ var old_node = FS.lookupNode(old_dir, old_name);
+ // old path should not be an ancestor of the new path
+ var relative = PATH.relative(old_path, new_dirname);
+ if (relative.charAt(0) !== '.') {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ // new path should not be an ancestor of the old path
+ relative = PATH.relative(new_path, old_dirname);
+ if (relative.charAt(0) !== '.') {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY);
+ }
+ // see if the new path alreay exists
+ var new_node;
+ try {
+ new_node = FS.lookupNode(new_dir, new_name);
+ } catch (e) {
+ // not fatal
+ }
+ // early out if nothing needs to changews
+ if (old_node === new_node) {
+ return;
+ }
+ // we'll need to delete the old entry
+ var isdir = FS.isDir(old_node.mode);
+ var err = FS.mayDelete(old_dir, old_name, isdir);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ // need delete permissions if we'll be overwriting.
+ // need create permissions if new doesn't already exist.
+ err = new_node ?
+ FS.mayDelete(new_dir, new_name, isdir) :
+ FS.mayCreate(new_dir, new_name);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ if (!old_dir.node_ops.rename) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBUSY);
+ }
+ // if we are going to change the parent, check write permissions
+ if (new_dir !== old_dir) {
+ err = FS.nodePermissions(old_dir, 'w');
+ if (err) {
+ throw new FS.ErrnoError(err);
}
+ }
+ // remove the node from the lookup hash
+ FS.hashRemoveNode(old_node);
+ // do the underlying fs rename
+ try {
+ old_node.node_ops.rename(old_node, new_dir, new_name);
+ } catch (e) {
+ throw e;
+ } finally {
+ // add the node back to the hash (in case node_ops.rename
+ // changed its name)
+ FS.hashAddNode(old_node);
+ }
+ },
+ rmdir: function (path) {
+ var lookup = FS.lookupPath(path, { parent: true });
+ var parent = lookup.node;
+ var name = PATH.basename(path);
+ var node = FS.lookupNode(parent, name);
+ var err = FS.mayDelete(parent, name, true);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ if (!parent.node_ops.rmdir) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ if (FS.isMountpoint(node)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBUSY);
+ }
+ parent.node_ops.rmdir(parent, name);
+ FS.destroyNode(node);
+ },
+ unlink: function (path) {
+ var lookup = FS.lookupPath(path, { parent: true });
+ var parent = lookup.node;
+ var name = PATH.basename(path);
+ var node = FS.lookupNode(parent, name);
+ var err = FS.mayDelete(parent, name, false);
+ if (err) {
+ // POSIX says unlink should set EPERM, not EISDIR
+ if (err === ERRNO_CODES.EISDIR) err = ERRNO_CODES.EPERM;
+ throw new FS.ErrnoError(err);
+ }
+ if (!parent.node_ops.unlink) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ if (FS.isMountpoint(node)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBUSY);
+ }
+ parent.node_ops.unlink(parent, name);
+ FS.destroyNode(node);
+ },
+ readlink: function (path) {
+ var lookup = FS.lookupPath(path, { follow: false });
+ var link = lookup.node;
+ if (!link.node_ops.readlink) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ return link.node_ops.readlink(link);
+ },
+ stat: function (path, dontFollow) {
+ var lookup = FS.lookupPath(path, { follow: !dontFollow });
+ var node = lookup.node;
+ if (!node.node_ops.getattr) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ return node.node_ops.getattr(node);
+ },
+ lstat: function (path) {
+ return VFS.stat(path, true);
+ },
+ chmod: function (path, mode, dontFollow) {
+ var node;
+ if (typeof path === 'string') {
+ var lookup = FS.lookupPath(path, { follow: !dontFollow });
+ node = lookup.node;
} else {
- throw new Error('Cannot load without read() or XMLHttpRequest.');
+ node = path;
}
- if (!success) ___setErrNo(ERRNO_CODES.EIO);
- return success;
+ if (!node.node_ops.setattr) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ node.node_ops.setattr(node, {
+ mode: (mode & {{{ cDefine('S_IALLUGO') }}}) | (node.mode & ~{{{ cDefine('S_IALLUGO') }}}),
+ timestamp: Date.now()
+ });
},
- staticInit: function () {
- // The main file system tree. All the contents are inside this.
- FS.root = {
- read: true,
- write: true,
- isFolder: true,
- isDevice: false,
- timestamp: Date.now(),
- inodeNumber: 1,
- contents: {}
- };
- // Create the temporary folder, if not already created
+ lchmod: function (path, mode) {
+ VFS.chmod(path, mode, true);
+ },
+ fchmod: function (fd, mode) {
+ var stream = FS.getStream(fd);
+ if (!stream) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ VFS.chmod(stream.node, mode);
+ },
+ chown: function (path, uid, gid, dontFollow) {
+ var node;
+ if (typeof path === 'string') {
+ var lookup = FS.lookupPath(path, { follow: !dontFollow });
+ node = lookup.node;
+ } else {
+ node = path;
+ }
+ if (!node.node_ops.setattr) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ node.node_ops.setattr(node, {
+ timestamp: Date.now()
+ // we ignore the uid / gid for now
+ });
+ },
+ lchown: function (path, uid, gid) {
+ VFS.chown(path, uid, gid, true);
+ },
+ fchown: function (fd, uid, gid) {
+ var stream = FS.getStream(fd);
+ if (!stream) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ VFS.chown(stream.node, uid, gid);
+ },
+ truncate: function (path, len) {
+ if (len < 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ var node;
+ if (typeof path === 'string') {
+ var lookup = FS.lookupPath(path, { follow: true });
+ node = lookup.node;
+ } else {
+ node = path;
+ }
+ if (!node.node_ops.setattr) {
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ if (FS.isDir(node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EISDIR);
+ }
+ if (!FS.isFile(node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ var err = FS.nodePermissions(node, 'w');
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ node.node_ops.setattr(node, {
+ size: len,
+ timestamp: Date.now()
+ });
+ },
+ ftruncate: function (fd, len) {
+ var stream = FS.getStream(fd);
+ if (!stream) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ VFS.truncate(stream.node, len);
+ },
+ utime: function (path, atime, mtime) {
+ var lookup = FS.lookupPath(path, { follow: true });
+ var node = lookup.node;
+ node.node_ops.setattr(node, {
+ timestamp: Math.max(atime, mtime)
+ });
+ },
+ open: function (path, flags, mode, fd_start, fd_end) {
+ path = PATH.normalize(path);
+ flags = typeof flags === 'string' ? FS.modeStringToFlags(flags) : flags;
+ if ((flags & {{{ cDefine('O_CREAT') }}})) {
+ mode = (mode & {{{ cDefine('S_IALLUGO') }}}) | {{{ cDefine('S_IFREG') }}};
+ } else {
+ mode = 0;
+ }
+ var node;
try {
- FS.createFolder('/', 'tmp', true, true);
- } catch(e) {}
- FS.createFolder('/', 'dev', true, true);
+ var lookup = FS.lookupPath(path, {
+ follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}})
+ });
+ node = lookup.node;
+ path = lookup.path;
+ } catch (e) {
+ // ignore
+ }
+ // perhaps we need to create the node
+ if ((flags & {{{ cDefine('O_CREAT') }}})) {
+ if (node) {
+ // if O_CREAT and O_EXCL are set, error out if the node already exists
+ if ((flags & {{{ cDefine('O_EXCL') }}})) {
+ throw new FS.ErrnoError(ERRNO_CODES.EEXIST);
+ }
+ } else {
+ // node doesn't exist, try to create it
+ node = VFS.mknod(path, mode, 0);
+ }
+ }
+ if (!node) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOENT);
+ }
+ // can't truncate a device
+ if (FS.isChrdev(node.mode)) {
+ flags &= ~{{{ cDefine('O_TRUNC') }}};
+ }
+ // check permissions
+ var err = FS.mayOpen(node, flags);
+ if (err) {
+ throw new FS.ErrnoError(err);
+ }
+ // do truncation if necessary
+ if ((flags & {{{ cDefine('O_TRUNC')}}})) {
+ VFS.truncate(node, 0);
+ }
+ // register the stream with the filesystem
+ var stream = FS.createStream({
+ path: path,
+ node: node,
+ flags: flags,
+ seekable: true,
+ position: 0,
+ stream_ops: node.stream_ops,
+ // used by the file family libc calls (fopen, fwrite, ferror, etc.)
+ ungotten: [],
+ error: false
+ }, fd_start, fd_end);
+ // call the new stream's open function
+ if (stream.stream_ops.open) {
+ stream.stream_ops.open(stream);
+ }
+ return stream;
},
- // Initializes the filesystems with stdin/stdout/stderr devices, given
- // optional handlers.
- init: function(input, output, error) {
- // Make sure we initialize only once.
- assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)');
- FS.init.initialized = true;
+ close: function (stream) {
+ try {
+ if (stream.stream_ops.close) {
+ stream.stream_ops.close(stream);
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ FS.closeStream(stream.fd);
+ }
+ },
+ llseek: function (stream, offset, whence) {
+ if (!stream.seekable || !stream.stream_ops.llseek) {
+ throw new FS.ErrnoError(ERRNO_CODES.ESPIPE);
+ }
+ return stream.stream_ops.llseek(stream, offset, whence);
+ },
+ readdir: function (stream) {
+ if (!stream.stream_ops.readdir) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR);
+ }
+ return stream.stream_ops.readdir(stream);
+ },
+ read: function (stream, buffer, offset, length, position) {
+ if (length < 0 || position < 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ if (FS.isDir(stream.node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EISDIR);
+ }
+ if (!stream.stream_ops.read) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ var seeking = true;
+ if (typeof position === 'undefined') {
+ position = stream.position;
+ seeking = false;
+ } else if (!stream.seekable) {
+ throw new FS.ErrnoError(ERRNO_CODES.ESPIPE);
+ }
+ var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position);
+ if (!seeking) stream.position += bytesRead;
+ return bytesRead;
+ },
+ write: function (stream, buffer, offset, length, position) {
+ if (length < 0 || position < 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ if (FS.isDir(stream.node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EISDIR);
+ }
+ if (!stream.stream_ops.write) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ var seeking = true;
+ if (typeof position === 'undefined') {
+ position = stream.position;
+ seeking = false;
+ } else if (!stream.seekable) {
+ throw new FS.ErrnoError(ERRNO_CODES.ESPIPE);
+ }
+ if (stream.flags & {{{ cDefine('O_APPEND') }}}) {
+ // seek to the end before writing in append mode
+ VFS.llseek(stream, 0, {{{ cDefine('SEEK_END') }}});
+ }
+ var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position);
+ if (!seeking) stream.position += bytesWritten;
+ return bytesWritten;
+ },
+ allocate: function (stream, offset, length) {
+ if (offset < 0 || length <= 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) {
+ throw new FS.ErrnoError(ERRNO_CODES.EBADF);
+ }
+ if (!FS.isFile(stream.node.mode) && !FS.isDir(node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
+ }
+ if (!stream.stream_ops.allocate) {
+ throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP);
+ }
+ stream.stream_ops.allocate(stream, offset, length);
+ },
+ mmap: function (stream, buffer, offset, length, position, prot, flags) {
+ // TODO if PROT is PROT_WRITE, make sure we have write acccess
+ if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) {
+ throw new FS.ErrnoError(ERRNO_CODES.EACCES);
+ }
+ if (!stream.stream_ops.mmap) {
+ throw new FS.errnoError(ERRNO_CODES.ENODEV);
+ }
+ return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags);
+ }
+ },
- // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here
- input = input || Module['stdin'];
- output = output || Module['stdout'];
- error = error || Module['stderr'];
-
- // Default handlers.
- var stdinOverridden = true, stdoutOverridden = true, stderrOverridden = true;
- if (!input) {
- stdinOverridden = false;
- input = function() {
- if (!input.cache || !input.cache.length) {
- var result;
- if (typeof window != 'undefined' &&
- typeof window.prompt == 'function') {
- // Browser.
- result = window.prompt('Input: ');
- if (result === null) result = String.fromCharCode(0); // cancel ==> EOF
- } else if (typeof readline == 'function') {
- // Command line.
- result = readline();
+ $MEMFS__deps: ['$FS'],
+ $MEMFS: {
+ mount: function (mount) {
+ return MEMFS.create_node(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0);
+ },
+ create_node: function (parent, name, mode, dev) {
+ if (FS.isBlkdev(mode) || FS.isFIFO(mode)) {
+ // no supported
+ throw new FS.ErrnoError(ERRNO_CODES.EPERM);
+ }
+ var node = FS.createNode(parent, name, mode, dev);
+ node.node_ops = MEMFS.node_ops;
+ if (FS.isDir(node.mode)) {
+ node.stream_ops = MEMFS.stream_ops;
+ node.contents = {};
+ } else if (FS.isFile(node.mode)) {
+ node.stream_ops = MEMFS.stream_ops;
+ node.contents = [];
+ } else if (FS.isLink(node.mode)) {
+ node.stream_ops = MEMFS.stream_ops;
+ } else if (FS.isChrdev(node.mode)) {
+ node.stream_ops = FS.chrdev_stream_ops;
+ }
+ node.timestamp = Date.now();
+ // add the new node to the parent
+ if (parent) {
+ parent.contents[name] = node;
+ }
+ return node;
+ },
+ node_ops: {
+ getattr: function (node) {
+ var attr = {};
+ // device numbers reuse inode numbers.
+ attr.dev = FS.isChrdev(node.mode) ? node.id : 1;
+ attr.ino = node.id;
+ attr.mode = node.mode;
+ attr.nlink = 1;
+ attr.uid = 0;
+ attr.gid = 0;
+ attr.rdev = node.rdev;
+ if (FS.isDir(node.mode)) {
+ attr.size = 4096;
+ } else if (FS.isFile(node.mode)) {
+ attr.size = node.contents.length;
+ } else if (FS.isLink(node.mode)) {
+ attr.size = node.link.length;
+ } else {
+ attr.size = 0;
+ }
+ attr.atime = new Date(node.timestamp);
+ attr.mtime = new Date(node.timestamp);
+ attr.ctime = new Date(node.timestamp);
+ // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),
+ // but this is not required by the standard.
+ attr.blksize = 4096;
+ attr.blocks = Math.ceil(attr.size / attr.blksize);
+ return attr;
+ },
+ setattr: function (node, attr) {
+ if (attr.mode !== undefined) {
+ node.mode = attr.mode;
+ }
+ if (attr.timestamp !== undefined) {
+ node.timestamp = attr.timestamp;
+ }
+ if (attr.size !== undefined) {
+ var contents = node.contents;
+ if (attr.size < contents.length) contents.length = attr.size;
+ else while (attr.size > contents.length) contents.push(0);
+ }
+ },
+ lookup: function (parent, name) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOENT);
+ },
+ mknod: function (parent, name, mode, dev) {
+ return MEMFS.create_node(parent, name, mode, dev);
+ },
+ rename: function (old_node, new_dir, new_name) {
+ // if we're overwriting a directory at new_name, make sure it's empty.
+ if (FS.isDir(old_node.mode)) {
+ var new_node;
+ try {
+ new_node = FS.lookupNode(new_dir, new_name);
+ } catch (e) {
+ }
+ if (new_node) {
+ for (var i in new_node.contents) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY);
}
- if (!result) result = '';
- input.cache = intArrayFromString(result + '\n', true);
}
- return input.cache.shift();
- };
- }
- var utf8 = new Runtime.UTF8Processor();
- function createSimpleOutput() {
- var fn = function (val) {
- if (val === null || val === {{{ charCode('\n') }}}) {
- fn.printer(fn.buffer.join(''));
- fn.buffer = [];
- } else {
- fn.buffer.push(utf8.processCChar(val));
+ }
+ // do the internal rewiring
+ delete old_node.parent.contents[old_node.name];
+ old_node.name = new_name;
+ new_dir.contents[new_name] = old_node;
+ },
+ unlink: function (parent, name) {
+ delete parent.contents[name];
+ },
+ rmdir: function (parent, name) {
+ var node = FS.lookupNode(parent, name);
+ for (var i in node.contents) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY);
+ }
+ delete parent.contents[name];
+ },
+ symlink: function (parent, newname, oldpath) {
+ var node = MEMFS.create_node(parent, newname, 0777 | {{{ cDefine('S_IFLNK') }}}, 0);
+ node.link = oldpath;
+ return node;
+ },
+ readlink: function (node) {
+ if (!FS.isLink(node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ return node.link;
+ },
+ },
+ stream_ops: {
+ open: function (stream) {
+ if (FS.isDir(stream.node.mode)) {
+ // cache off the directory entries when open'd
+ var entries = ['.', '..']
+ for (var key in stream.node.contents) {
+ if (!stream.node.contents.hasOwnProperty(key)) {
+ continue;
+ }
+ entries.push(key);
}
- };
- return fn;
- }
- if (!output) {
- stdoutOverridden = false;
- output = createSimpleOutput();
- }
- if (!output.printer) output.printer = Module['print'];
- if (!output.buffer) output.buffer = [];
- if (!error) {
- stderrOverridden = false;
- error = createSimpleOutput();
- }
- if (!error.printer) error.printer = Module['printErr'];
- if (!error.buffer) error.buffer = [];
-
- // Create the I/O devices.
- var stdin = FS.createDevice('/dev', 'stdin', input);
- stdin.isTerminal = !stdinOverridden;
- var stdout = FS.createDevice('/dev', 'stdout', null, output);
- stdout.isTerminal = !stdoutOverridden;
- var stderr = FS.createDevice('/dev', 'stderr', null, error);
- stderr.isTerminal = !stderrOverridden;
- FS.createDevice('/dev', 'tty', input, output);
- FS.createDevice('/dev', 'null', function(){}, function(){});
-
- // Create default streams.
- FS.streams[1] = {
- path: '/dev/stdin',
- object: stdin,
- position: 0,
- isRead: true,
- isWrite: false,
- isAppend: false,
- error: false,
- eof: false,
- ungotten: []
- };
- FS.streams[2] = {
- path: '/dev/stdout',
- object: stdout,
- position: 0,
- isRead: false,
- isWrite: true,
- isAppend: false,
- error: false,
- eof: false,
- ungotten: []
- };
- FS.streams[3] = {
- path: '/dev/stderr',
- object: stderr,
- position: 0,
- isRead: false,
- isWrite: true,
- isAppend: false,
- error: false,
- eof: false,
- ungotten: []
- };
- // TODO: put these low in memory like we used to assert on: assert(Math.max(_stdin, _stdout, _stderr) < 15000); // make sure these are low, we flatten arrays with these
- {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 1, 'void*') }}};
- {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 2, 'void*') }}};
- {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 3, 'void*') }}};
-
- // Other system paths
- FS.createPath('/', 'dev/shm/tmp', true, true); // temp files
-
- // Newlib initialization
- for (var i = FS.streams.length; i < Math.max(_stdin, _stdout, _stderr) + {{{ QUANTUM_SIZE }}}; i++) {
- FS.streams[i] = null; // Make sure to keep FS.streams dense
- }
- FS.streams[_stdin] = FS.streams[1];
- FS.streams[_stdout] = FS.streams[2];
- FS.streams[_stderr] = FS.streams[3];
-#if ASSERTIONS
- FS.checkStreams();
- // see previous TODO on stdin etc.: assert(FS.streams.length < 1024); // at this early stage, we should not have a large set of file descriptors - just a few
+ stream.entries = entries;
+ }
+ },
+ read: function (stream, buffer, offset, length, position) {
+ var contents = stream.node.contents;
+ var size = Math.min(contents.length - position, length);
+#if USE_TYPED_ARRAYS == 2
+ if (contents.subarray) { // typed array
+ buffer.set(contents.subarray(position, position + size), offset);
+ } else
#endif
- allocate([ allocate(
- {{{ Runtime.QUANTUM_SIZE === 4 ? '[0, 0, 0, 0, _stdin, 0, 0, 0, _stdout, 0, 0, 0, _stderr, 0, 0, 0]' : '[0, _stdin, _stdout, _stderr]' }}},
- 'void*', ALLOC_NORMAL) ], 'void*', ALLOC_NONE, {{{ makeGlobalUse('__impure_ptr') }}});
+ {
+ for (var i = 0; i < size; i++) {
+ buffer[offset + i] = contents[position + i];
+ }
+ }
+ return size;
+ },
+ write: function (stream, buffer, offset, length, position) {
+ var contents = stream.node.contents;
+ while (contents.length < position) contents.push(0);
+ for (var i = 0; i < length; i++) {
+ contents[position + i] = buffer[offset + i];
+ }
+ stream.node.timestamp = Date.now();
+ return length;
+ },
+ llseek: function (stream, offset, whence) {
+ var position = offset;
+ if (whence === 1) { // SEEK_CUR.
+ position += stream.position;
+ } else if (whence === 2) { // SEEK_END.
+ if (FS.isFile(stream.node.mode)) {
+ position += stream.node.contents.length;
+ }
+ }
+ if (position < 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
+ }
+ stream.ungotten = [];
+ stream.position = position;
+ return position;
+ },
+ readdir: function (stream) {
+ return stream.entries;
+ },
+ allocate: function (stream, offset, length) {
+ var contents = stream.node.contents;
+ var limit = offset + length;
+ while (limit > contents.length) contents.push(0);
+ },
+ mmap: function (stream, buffer, offset, length, position, prot, flags) {
+ if (!FS.isFile(stream.node.mode)) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
+ }
+ var ptr;
+ var allocated;
+ var contents = stream.node.contents;
+ // Only make a new copy when MAP_PRIVATE is specified.
+ if (!(flags & {{{ cDefine('MAP_PRIVATE') }}})) {
+ // We can't emulate MAP_SHARED when the file is not backed by the buffer
+ // we're mapping to (e.g. the HEAP buffer).
+ assert(contents.buffer === buffer || contents.buffer === buffer.buffer);
+ allocated = false;
+ ptr = contents.byteOffset;
+ } else {
+ // Try to avoid unnecessary slices.
+ if (position > 0 || position + length < contents.length) {
+ if (contents.subarray) {
+ contents = contents.subarray(position, position + length);
+ } else {
+ contents = Array.prototype.slice.call(contents, position, position + length);
+ }
+ }
+ allocated = true;
+ ptr = _malloc(length);
+ if (!ptr) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENOMEM);
+ }
+ buffer.set(contents, ptr);
+ }
+ return { ptr: ptr, allocated: allocated };
+ },
+ }
+ },
+
+ $SOCKFS__postset: '__ATINIT__.push({ func: function() { SOCKFS.root = VFS.mount(SOCKFS, {}, null); } });',
+ $SOCKFS__deps: ['$FS'],
+ $SOCKFS: {
+ mount: function (mount) {
+ var node = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0);
+ node.node_ops = SOCKFS.node_ops;
+ node.stream_ops = SOCKFS.stream_ops;
+ return node;
},
-
- quit: function() {
- if (!FS.init.initialized) return;
- // Flush any partially-printed lines in stdout and stderr. Careful, they may have been closed
- if (FS.streams[2] && FS.streams[2].object.output.buffer.length > 0) FS.streams[2].object.output({{{ charCode('\n') }}});
- if (FS.streams[3] && FS.streams[3].object.output.buffer.length > 0) FS.streams[3].object.output({{{ charCode('\n') }}});
+ node_ops: {
},
-
- // Standardizes a path. Useful for making comparisons of pathnames work in a consistent manner.
- // For example, ./file and file are really the same, so this function will remove ./
- standardizePath: function(path) {
- if (path.substr(0, 2) == './') path = path.substr(2);
- return path;
+ stream_ops: {
},
+ websocket_sock_ops: {
+ }
+ },
- deleteFile: function(path) {
- path = FS.analyzePath(path);
- if (!path.parentExists || !path.exists) {
- throw 'Invalid path ' + path;
+ $TTY__deps: ['$FS'],
+ $TTY: {
+ ttys: [],
+ register: function (dev, ops) {
+ TTY.ttys[dev] = { input: [], output: [], ops: ops };
+ FS.registerDevice(dev, TTY.stream_ops);
+ },
+ stream_ops: {
+ open: function (stream) {
+ // this wouldn't be required if the library wasn't eval'd at first...
+ if (!TTY.utf8) {
+ TTY.utf8 = new Runtime.UTF8Processor();
+ }
+ var tty = TTY.ttys[stream.node.rdev];
+ if (!tty) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
+ }
+ stream.tty = tty;
+ stream.seekable = false;
+ },
+ close: function (stream) {
+ // flush any pending line data
+ if (stream.tty.output.length) {
+ stream.tty.ops.put_char(stream.tty, {{{ charCode('\n') }}});
+ }
+ },
+ read: function (stream, buffer, offset, length, pos /* ignored */) {
+ if (!stream.tty || !stream.tty.ops.get_char) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENXIO);
+ }
+ var bytesRead = 0;
+ for (var i = 0; i < length; i++) {
+ var result;
+ try {
+ result = stream.tty.ops.get_char(stream.tty);
+ } catch (e) {
+ throw new FS.ErrnoError(ERRNO_CODES.EIO);
+ }
+ if (result === undefined && bytesRead === 0) {
+ throw new FS.ErrnoError(ERRNO_CODES.EAGAIN);
+ }
+ if (result === null || result === undefined) break;
+ bytesRead++;
+ buffer[offset+i] = result;
+ }
+ if (bytesRead) {
+ stream.node.timestamp = Date.now();
+ }
+ return bytesRead;
+ },
+ write: function (stream, buffer, offset, length, pos) {
+ if (!stream.tty || !stream.tty.ops.put_char) {
+ throw new FS.ErrnoError(ERRNO_CODES.ENXIO);
+ }
+ for (var i = 0; i < length; i++) {
+ try {
+ stream.tty.ops.put_char(stream.tty, buffer[offset+i]);
+ } catch (e) {
+ throw new FS.ErrnoError(ERRNO_CODES.EIO);
+ }
+ }
+ if (length) {
+ stream.node.timestamp = Date.now();
+ }
+ return i;
+ }
+ },
+ // NOTE: This is weird to support stdout and stderr
+ // overrides in addition to print and printErr orverrides.
+ default_tty_ops: {
+ get_char: function (tty) {
+ if (!tty.input.length) {
+ var result = null;
+ if (ENVIRONMENT_IS_NODE) {
+ if (process.stdin.destroyed) {
+ return undefined;
+ }
+ result = process.stdin.read();
+ } else if (typeof window != 'undefined' &&
+ typeof window.prompt == 'function') {
+ // Browser.
+ result = window.prompt('Input: '); // returns null on cancel
+ if (result !== null) {
+ result += '\n';
+ }
+ } else if (typeof readline == 'function') {
+ // Command line.
+ result = readline();
+ if (result !== null) {
+ result += '\n';
+ }
+ }
+ if (!result) {
+ return null;
+ }
+ tty.input = intArrayFromString(result, true);
+ }
+ return tty.input.shift();
+ },
+ put_char: function (tty, val) {
+ if (val === null || val === {{{ charCode('\n') }}}) {
+ Module['print'](tty.output.join(''));
+ tty.output = [];
+ } else {
+ tty.output.push(TTY.utf8.processCChar(val));
+ }
+ }
+ },
+ default_tty1_ops: {
+ put_char: function (tty, val) {
+ if (val === null || val === {{{ charCode('\n') }}}) {
+ Module['printErr'](tty.output.join(''));
+ tty.output = [];
+ } else {
+ tty.output.push(TTY.utf8.processCChar(val));
+ }
}
- delete path.parentObject.contents[path.name];
}
},
+#endif
+
// ==========================================================================
// dirent.h
@@ -686,160 +1746,125 @@ LibraryManager.library = {
['i32', 'd_off'],
['i32', 'd_reclen'],
['i32', 'd_type']]),
- opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'],
+ opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout', 'open'],
opendir: function(dirname) {
// DIR *opendir(const char *dirname);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/opendir.html
// NOTE: Calculating absolute path redundantly since we need to associate it
// with the opened stream.
- var path = FS.absolutePath(Pointer_stringify(dirname));
- if (path === null) {
+ var path = Pointer_stringify(dirname);
+ if (!path) {
___setErrNo(ERRNO_CODES.ENOENT);
return 0;
}
- var target = FS.findObject(path);
- if (target === null) return 0;
- if (!target.isFolder) {
- ___setErrNo(ERRNO_CODES.ENOTDIR);
+ var node;
+ try {
+ var lookup = FS.lookupPath(path, { follow: true });
+ node = lookup.node;
+ } catch (e) {
+ ___setErrNo(e.errno);
return 0;
- } else if (!target.read) {
- ___setErrNo(ERRNO_CODES.EACCES);
+ }
+ if (!FS.isDir(node.mode)) {
+ ___setErrNo(ERRNO_CODES.ENOTDIR);
return 0;
}
- var contents = [];
- for (var key in target.contents) contents.push(key);
- var id = FS.createFileHandle({
- path: path,
- object: target,
- // An index into contents. Special values: -2 is ".", -1 is "..".
- position: -2,
- isRead: true,
- isWrite: false,
- isAppend: false,
- error: false,
- eof: false,
- ungotten: [],
- // Folder-specific properties:
- // Remember the contents at the time of opening in an array, so we can
- // seek between them relying on a single order.
- contents: contents,
- // Each stream has its own area for readdir() returns.
- currentEntry: _malloc(___dirent_struct_layout.__size__)
- });
-#if ASSERTIONS
- FS.checkStreams();
-#endif
- return id;
+ var err = _open(dirname, {{{ cDefine('O_RDONLY') }}}, allocate([0, 0, 0, 0], 'i32', ALLOC_STACK));
+ // open returns 0 on failure, not -1
+ return err === -1 ? 0 : err;
},
- closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
+ closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'close'],
closedir: function(dirp) {
// int closedir(DIR *dirp);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html
- if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) {
- ___setErrNo(ERRNO_CODES.EBADF);
- return -1;
- } else {
- _free(FS.streams[dirp].currentEntry);
- FS.streams[dirp] = null;
- return 0;
- }
+ return _close(dirp);
},
telldir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
telldir: function(dirp) {
// long int telldir(DIR *dirp);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html
- if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) {
+ var stream = FS.getStream(dirp);
+ if (!stream || !FS.isDir(stream.node.mode)) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else {
- return FS.streams[dirp].position;
}
+ return stream.position;
},
- seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
+ seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'lseek'],
seekdir: function(dirp, loc) {
// void seekdir(DIR *dirp, long int loc);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/seekdir.html
- if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) {
- ___setErrNo(ERRNO_CODES.EBADF);
- } else {
- var entries = 0;
- for (var key in FS.streams[dirp].contents) entries++;
- if (loc >= entries) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- } else {
- FS.streams[dirp].position = loc;
- }
- }
+ _lseek(dirp, loc, {{{ cDefine('SEEK_SET') }}});
},
rewinddir__deps: ['seekdir'],
rewinddir: function(dirp) {
// void rewinddir(DIR *dirp);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/rewinddir.html
- _seekdir(dirp, -2);
+ _seekdir(dirp, 0);
},
readdir_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'],
readdir_r: function(dirp, entry, result) {
// int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html
- if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) {
+ var stream = FS.getStream(dirp);
+ if (!stream) {
return ___setErrNo(ERRNO_CODES.EBADF);
}
- var stream = FS.streams[dirp];
- var loc = stream.position;
- var entries = 0;
- for (var key in stream.contents) entries++;
- if (loc < -2 || loc >= entries) {
+ var entries;
+ try {
+ entries = VFS.readdir(stream);
+ } catch (e) {
+ return ___setErrNo(e.errno);
+ }
+ if (stream.position < 0 || stream.position >= entries.length) {
{{{ makeSetValue('result', '0', '0', 'i8*') }}}
- } else {
- var name, inode, type;
- if (loc === -2) {
- name = '.';
- inode = 1; // Really undefined.
- type = 4; //DT_DIR
- } else if (loc === -1) {
- name = '..';
- inode = 1; // Really undefined.
- type = 4; //DT_DIR
- } else {
- var object;
- name = stream.contents[loc];
- object = stream.object.contents[name];
- inode = object.inodeNumber;
- type = object.isDevice ? 2 // DT_CHR, character device.
- : object.isFolder ? 4 // DT_DIR, directory.
- : object.link !== undefined ? 10 // DT_LNK, symbolic link.
- : 8; // DT_REG, regular file.
- }
- stream.position++;
- var offsets = ___dirent_struct_layout;
- {{{ makeSetValue('entry', 'offsets.d_ino', 'inode', 'i32') }}}
- {{{ makeSetValue('entry', 'offsets.d_off', 'stream.position', 'i32') }}}
- {{{ makeSetValue('entry', 'offsets.d_reclen', 'name.length + 1', 'i32') }}}
- for (var i = 0; i < name.length; i++) {
- {{{ makeSetValue('entry + offsets.d_name', 'i', 'name.charCodeAt(i)', 'i8') }}}
- }
- {{{ makeSetValue('entry + offsets.d_name', 'i', '0', 'i8') }}}
- {{{ makeSetValue('entry', 'offsets.d_type', 'type', 'i8') }}}
- {{{ makeSetValue('result', '0', 'entry', 'i8*') }}}
+ return;
}
+ var id;
+ var type;
+ var name = entries[stream.position];
+ var offset = stream.position + 1;
+ if (!name.indexOf('.')) {
+ id = 1;
+ type = 4;
+ } else {
+ var child = FS.lookupNode(stream.node, name);
+ id = child.id;
+ type = FS.isChrdev(child.mode) ? 2 : // DT_CHR, character device.
+ FS.isDir(child.mode) ? 4 : // DT_DIR, directory.
+ FS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link.
+ 8; // DT_REG, regular file.
+ }
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_ino', 'id', 'i32') }}}
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_off', 'offset', 'i32') }}}
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_reclen', 'name.length + 1', 'i32') }}}
+ for (var i = 0; i < name.length; i++) {
+ {{{ makeSetValue('entry + ___dirent_struct_layout.d_name', 'i', 'name.charCodeAt(i)', 'i8') }}}
+ }
+ {{{ makeSetValue('entry + ___dirent_struct_layout.d_name', 'i', '0', 'i8') }}}
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_type', 'type', 'i8') }}}
+ {{{ makeSetValue('result', '0', 'entry', 'i8*') }}}
+ stream.position++;
return 0;
},
readdir__deps: ['readdir_r', '__setErrNo', '$ERRNO_CODES'],
readdir: function(dirp) {
// struct dirent *readdir(DIR *dirp);
// http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html
- if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) {
+ var stream = FS.getStream(dirp);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return 0;
- } else {
- if (!_readdir.result) _readdir.result = _malloc(4);
- _readdir_r(dirp, FS.streams[dirp].currentEntry, _readdir.result);
- if ({{{ makeGetValue(0, '_readdir.result', 'i8*') }}} === 0) {
- return 0;
- } else {
- return FS.streams[dirp].currentEntry;
- }
}
+ // TODO Is it supposed to be safe to execute multiple readdirs?
+ if (!_readdir.entry) _readdir.entry = _malloc(___dirent_struct_layout.__size__);
+ if (!_readdir.result) _readdir.result = _malloc(4);
+ var err = _readdir_r(dirp, _readdir.entry, _readdir.result);
+ if (err) {
+ ___setErrNo(err);
+ return 0;
+ }
+ return {{{ makeGetValue(0, '_readdir.result', 'i8*') }}};
},
__01readdir64_: 'readdir',
// TODO: Check if we need to link any other aliases.
@@ -864,10 +1889,14 @@ LibraryManager.library = {
} else {
time = Date.now();
}
- var file = FS.findObject(Pointer_stringify(path));
- if (file === null) return -1;
- file.timestamp = time;
- return 0;
+ path = Pointer_stringify(path);
+ try {
+ VFS.utime(path, time, time);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
utimes: function() { throw 'utimes not implemented' },
@@ -960,67 +1989,27 @@ LibraryManager.library = {
// int stat(const char *path, struct stat *buf);
// NOTE: dontResolveLastLink is a shortcut for lstat(). It should never be
// used in client code.
- var obj = FS.findObject(Pointer_stringify(path), dontResolveLastLink);
- if (obj === null || !FS.forceLoadFile(obj)) return -1;
-
- var offsets = ___stat_struct_layout;
-
- // Constants.
- {{{ makeSetValue('buf', 'offsets.st_nlink', '1', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_uid', '0', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_gid', '0', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_blksize', '4096', 'i32') }}}
-
- // Variables.
- {{{ makeSetValue('buf', 'offsets.st_ino', 'obj.inodeNumber', 'i32') }}}
- var time = Math.floor(obj.timestamp / 1000);
- if (offsets.st_atime === undefined) {
- offsets.st_atime = offsets.st_atim.tv_sec;
- offsets.st_mtime = offsets.st_mtim.tv_sec;
- offsets.st_ctime = offsets.st_ctim.tv_sec;
- var nanosec = (obj.timestamp % 1000) * 1000;
- {{{ makeSetValue('buf', 'offsets.st_atim.tv_nsec', 'nanosec', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_mtim.tv_nsec', 'nanosec', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_ctim.tv_nsec', 'nanosec', 'i32') }}}
- }
- {{{ makeSetValue('buf', 'offsets.st_atime', 'time', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_mtime', 'time', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_ctime', 'time', 'i32') }}}
- var mode = 0;
- var size = 0;
- var blocks = 0;
- var dev = 0;
- var rdev = 0;
- if (obj.isDevice) {
- // Device numbers reuse inode numbers.
- dev = rdev = obj.inodeNumber;
- size = blocks = 0;
- mode = 0x2000; // S_IFCHR.
- } else {
- dev = 1;
- rdev = 0;
- // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),
- // but this is not required by the standard.
- if (obj.isFolder) {
- size = 4096;
- blocks = 1;
- mode = 0x4000; // S_IFDIR.
- } else {
- var data = obj.contents || obj.link;
- size = data.length;
- blocks = Math.ceil(data.length / 4096);
- mode = obj.link === undefined ? 0x8000 : 0xA000; // S_IFREG, S_IFLNK.
- }
+ path = typeof path !== 'string' ? Pointer_stringify(path) : path;
+ try {
+ var stat = dontResolveLastLink ? VFS.lstat(path) : VFS.stat(path);
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_dev', 'stat.dev', 'i32') }}};
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_ino', 'stat.ino', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_mode', 'stat.mode', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_nlink', 'stat.nlink', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_uid', 'stat.uid', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_gid', 'stat.gid', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_rdev', 'stat.rdev', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_size', 'stat.size', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_atime', 'Math.floor(stat.atime.getTime() / 1000)', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_mtime', 'Math.floor(stat.mtime.getTime() / 1000)', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_ctime', 'Math.floor(stat.ctime.getTime() / 1000)', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blksize', '4096', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blocks', 'stat.blocks', 'i32') }}}
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
- {{{ makeSetValue('buf', 'offsets.st_dev', 'dev', 'i32') }}};
- {{{ makeSetValue('buf', 'offsets.st_rdev', 'rdev', 'i32') }}};
- {{{ makeSetValue('buf', 'offsets.st_size', 'size', 'i32') }}}
- {{{ makeSetValue('buf', 'offsets.st_blocks', 'blocks', 'i32') }}}
- if (obj.read) mode |= 0x16D; // S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH.
- if (obj.write) mode |= 0x92; // S_IWUSR | S_IWGRP | S_IWOTH.
- {{{ makeSetValue('buf', 'offsets.st_mode', 'mode', 'i32') }}}
-
- return 0;
},
lstat__deps: ['stat'],
lstat: function(path, buf) {
@@ -1032,40 +2021,30 @@ LibraryManager.library = {
fstat: function(fildes, buf) {
// int fstat(int fildes, struct stat *buf);
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/fstat.html
- if (!FS.streams[fildes]) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else {
- var pathArray = intArrayFromString(FS.streams[fildes].path);
- return _stat(allocate(pathArray, 'i8', ALLOC_STACK), buf);
}
+ return _stat(stream.path, buf);
},
mknod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
mknod: function(path, mode, dev) {
// int mknod(const char *path, mode_t mode, dev_t dev);
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/mknod.html
path = Pointer_stringify(path);
- var fmt = (mode & {{{ cDefine('S_IFMT') }}});
- if (fmt !== {{{ cDefine('S_IFREG') }}} && fmt !== {{{ cDefine('S_IFCHR') }}} &&
- fmt !== {{{ cDefine('S_IFBLK') }}} && fmt !== {{{ cDefine('S_IFIFO') }}} &&
- fmt !== {{{ cDefine('S_IFSOCK') }}}) {
- // not valid formats for mknod
- ___setErrNo(ERRNO_CODES.EINVAL);
+ // we don't want this in the JS API as the JS API
+ // uses mknod to create all nodes.
+ var err = FS.mayMknod(mode);
+ if (err) {
+ ___setErrNo(err);
return -1;
}
- if (fmt === {{{ cDefine('S_IFCHR') }}} || fmt === {{{ cDefine('S_IFBLK') }}} ||
- fmt === {{{ cDefine('S_IFIFO') }}} || fmt === {{{ cDefine('S_IFSOCK') }}}) {
- // not supported currently
- ___setErrNo(ERRNO_CODES.EPERM);
- return -1;
- }
- path = FS.analyzePath(path);
- var properties = { contents: [], isFolder: false }; // S_IFDIR.
try {
- FS.createObject(path.parentObject, path.name, properties,
- mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR.
+ VFS.mknod(path, mode, dev);
return 0;
} catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
},
@@ -1074,13 +2053,11 @@ LibraryManager.library = {
// int mkdir(const char *path, mode_t mode);
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html
path = Pointer_stringify(path);
- path = FS.analyzePath(path);
- var properties = { contents: [], isFolder: true };
try {
- FS.createObject(path.parentObject, path.name, properties,
- mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR.
+ VFS.mkdir(path, mode, 0);
return 0;
} catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
},
@@ -1095,33 +2072,43 @@ LibraryManager.library = {
___setErrNo(ERRNO_CODES.EROFS);
return -1;
},
- chmod__deps: ['$FS'],
+ chmod__deps: ['$FS', '__setErrNo'],
chmod: function(path, mode, dontResolveLastLink) {
// int chmod(const char *path, mode_t mode);
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/chmod.html
// NOTE: dontResolveLastLink is a shortcut for lchmod(). It should never be
// used in client code.
path = typeof path !== 'string' ? Pointer_stringify(path) : path;
- var obj = FS.findObject(path, dontResolveLastLink);
- if (obj === null) return -1;
- obj.read = mode & 0x100; // S_IRUSR.
- obj.write = mode & 0x80; // S_IWUSR.
- obj.timestamp = Date.now();
- return 0;
+ try {
+ VFS.chmod(path, mode);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
fchmod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chmod'],
fchmod: function(fildes, mode) {
// int fchmod(int fildes, mode_t mode);
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/fchmod.html
- var stream = FS.streams[fildes];
- if (!stream) {
- ___setErrNo(ERRNO_CODES.EBADF);
+ try {
+ VFS.fchmod(fildes, mode);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
- return _chmod(stream.path, mode);
},
- lchmod: function(path, mode) {
- return _chmod(path, mode, true);
+ lchmod__deps: ['chmod'],
+ lchmod: function (path, mode) {
+ path = Pointer_stringify(path);
+ try {
+ VFS.lchmod(path, mode);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
umask__deps: ['$FS'],
@@ -1205,108 +2192,15 @@ LibraryManager.library = {
// http://pubs.opengroup.org/onlinepubs/009695399/functions/open.html
// NOTE: This implementation tries to mimic glibc rather than strictly
// following the POSIX standard.
-
var mode = {{{ makeGetValue('varargs', 0, 'i32') }}};
-
- // Simplify flags.
- var accessMode = oflag & {{{ cDefine('O_ACCMODE') }}};
- var isWrite = accessMode != {{{ cDefine('O_RDONLY') }}};
- var isRead = accessMode != {{{ cDefine('O_WRONLY') }}};
- var isCreate = Boolean(oflag & {{{ cDefine('O_CREAT') }}});
- var isExistCheck = Boolean(oflag & {{{ cDefine('O_EXCL') }}});
- var isTruncate = Boolean(oflag & {{{ cDefine('O_TRUNC') }}});
- var isAppend = Boolean(oflag & {{{ cDefine('O_APPEND') }}});
-
- // Verify path.
- var origPath = path;
- path = FS.analyzePath(Pointer_stringify(path));
- if (!path.parentExists) {
- ___setErrNo(path.error);
+ path = Pointer_stringify(path);
+ try {
+ var stream = VFS.open(path, oflag, mode);
+ return stream.fd;
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
- var target = path.object || null;
- var finalPath;
-
- // Verify the file exists, create if needed and allowed.
- if (target) {
- if (isCreate && isExistCheck) {
- ___setErrNo(ERRNO_CODES.EEXIST);
- return -1;
- }
- if ((isWrite || isTruncate) && target.isFolder) {
- ___setErrNo(ERRNO_CODES.EISDIR);
- return -1;
- }
- if (isRead && !target.read || isWrite && !target.write) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- }
- if (isTruncate && !target.isDevice) {
- target.contents = [];
- } else {
- if (!FS.forceLoadFile(target)) {
- ___setErrNo(ERRNO_CODES.EIO);
- return -1;
- }
- }
- finalPath = path.path;
- } else {
- if (!isCreate) {
- ___setErrNo(ERRNO_CODES.ENOENT);
- return -1;
- }
- if (!path.parentObject.write) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- }
- target = FS.createDataFile(path.parentObject, path.name, [],
- mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR.
- finalPath = path.parentPath + '/' + path.name;
- }
- // Actually create an open stream.
- var id;
- if (target.isFolder) {
- var entryBuffer = 0;
- if (___dirent_struct_layout) {
- entryBuffer = _malloc(___dirent_struct_layout.__size__);
- }
- var contents = [];
- for (var key in target.contents) contents.push(key);
- id = FS.createFileHandle({
- path: finalPath,
- object: target,
- // An index into contents. Special values: -2 is ".", -1 is "..".
- position: -2,
- isRead: true,
- isWrite: false,
- isAppend: false,
- error: false,
- eof: false,
- ungotten: [],
- // Folder-specific properties:
- // Remember the contents at the time of opening in an array, so we can
- // seek between them relying on a single order.
- contents: contents,
- // Each stream has its own area for readdir() returns.
- currentEntry: entryBuffer
- });
- } else {
- id = FS.createFileHandle({
- path: finalPath,
- object: target,
- position: 0,
- isRead: isRead,
- isWrite: isWrite,
- isAppend: isAppend,
- error: false,
- eof: false,
- ungotten: []
- });
- }
-#if ASSERTIONS
- FS.checkStreams();
-#endif
- return id;
},
creat__deps: ['open'],
creat: function(path, mode) {
@@ -1327,11 +2221,11 @@ LibraryManager.library = {
fcntl: function(fildes, cmd, varargs, dup2) {
// int fcntl(int fildes, int cmd, ...);
// http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
- if (!FS.streams[fildes]) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
- var stream = FS.streams[fildes];
switch (cmd) {
case {{{ cDefine('F_DUPFD') }}}:
var arg = {{{ makeGetValue('varargs', 0, 'i32') }}};
@@ -1339,31 +2233,22 @@ LibraryManager.library = {
___setErrNo(ERRNO_CODES.EINVAL);
return -1;
}
- var newStream = {};
- for (var member in stream) {
- newStream[member] = stream[member];
+ var newStream;
+ try {
+ newStream = VFS.open(stream.path, stream.flags, 0, arg);
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
- arg = dup2 ? arg : Math.max(arg, FS.streams.length); // dup2 wants exactly arg; fcntl wants a free descriptor >= arg
- FS.createFileHandle(newStream, arg);
-#if ASSERTIONS
- FS.checkStreams();
-#endif
- return arg;
+ return newStream.fd;
case {{{ cDefine('F_GETFD') }}}:
case {{{ cDefine('F_SETFD') }}}:
return 0; // FD_CLOEXEC makes no sense for a single process.
case {{{ cDefine('F_GETFL') }}}:
- var flags = 0;
- if (stream.isRead && stream.isWrite) flags = {{{ cDefine('O_RDWR') }}};
- else if (!stream.isRead && stream.isWrite) flags = {{{ cDefine('O_WRONLY') }}};
- else if (stream.isRead && !stream.isWrite) flags = {{{ cDefine('O_RDONLY') }}};
- if (stream.isAppend) flags |= {{{ cDefine('O_APPEND') }}};
- // Synchronization and blocking flags are irrelevant to us.
- return flags;
+ return stream.flags;
case {{{ cDefine('F_SETFL') }}}:
var arg = {{{ makeGetValue('varargs', 0, 'i32') }}};
- stream.isAppend = Boolean(arg | {{{ cDefine('O_APPEND') }}});
- // Synchronization and blocking flags are irrelevant to us.
+ stream.flags |= arg;
return 0;
case {{{ cDefine('F_GETLK') }}}:
case {{{ cDefine('F_GETLK64') }}}:
@@ -1401,15 +2286,18 @@ LibraryManager.library = {
posix_fallocate: function(fd, offset, len) {
// int posix_fallocate(int fd, off_t offset, off_t len);
// http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html
- if (!FS.streams[fd] || !FS.streams[fd].isWrite || FS.streams[fd].link ||
- FS.streams[fd].isFolder || FS.streams[fd].isDevice) {
+ var stream = FS.getStream(fd);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
- var contents = FS.streams[fd].object.contents;
- var limit = offset + len;
- while (limit > contents.length) contents.push(0);
- return 0;
+ try {
+ VFS.allocate(stream, offset, len);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
// ==========================================================================
@@ -1442,8 +2330,8 @@ LibraryManager.library = {
var fd = {{{ makeGetValue('pollfd', 'offsets.fd', 'i32') }}};
var events = {{{ makeGetValue('pollfd', 'offsets.events', 'i16') }}};
var revents = 0;
- if (FS.streams[fd]) {
- var stream = FS.streams[fd];
+ var stream = FS.getStream(fd);
+ if (stream) {
if (events & {{{ cDefine('POLLIN') }}}) revents |= {{{ cDefine('POLLIN') }}};
if (events & {{{ cDefine('POLLOUT') }}}) revents |= {{{ cDefine('POLLOUT') }}};
} else {
@@ -1464,15 +2352,28 @@ LibraryManager.library = {
// int access(const char *path, int amode);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/access.html
path = Pointer_stringify(path);
- var target = FS.findObject(path);
- if (target === null) return -1;
- if ((amode & 2 && !target.write) || // W_OK.
- ((amode & 1 || amode & 4) && !target.read)) { // X_OK, R_OK.
+ if (amode & ~{{{ cDefine('S_IRWXO') }}}) {
+ // need a valid mode
+ ___setErrNo(ERRNO_CODES.EINVAL);
+ return -1;
+ }
+ var node;
+ try {
+ var lookup = FS.lookupPath(path, { follow: true });
+ node = lookup.node;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
+ var perms = '';
+ if (amode & {{{ cDefine('R_OK') }}}) perms += 'r';
+ if (amode & {{{ cDefine('W_OK') }}}) perms += 'w';
+ if (amode & {{{ cDefine('X_OK') }}}) perms += 'x';
+ if (perms /* otherwise, they've just passed F_OK */ && FS.nodePermissions(node, perms)) {
___setErrNo(ERRNO_CODES.EACCES);
return -1;
- } else {
- return 0;
}
+ return 0;
},
chdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
chdir: function(path) {
@@ -1480,17 +2381,24 @@ LibraryManager.library = {
// http://pubs.opengroup.org/onlinepubs/000095399/functions/chdir.html
// NOTE: The path argument may be a string, to simplify fchdir().
if (typeof path !== 'string') path = Pointer_stringify(path);
- path = FS.analyzePath(path);
- if (!path.exists) {
- ___setErrNo(path.error);
+ var lookup;
+ try {
+ lookup = FS.lookupPath(path, { follow: true });
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else if (!path.object.isFolder) {
+ }
+ if (!FS.isDir(lookup.node.mode)) {
___setErrNo(ERRNO_CODES.ENOTDIR);
return -1;
- } else {
- FS.currentPath = path.path;
- return 0;
}
+ var err = FS.nodePermissions(lookup.node, 'x');
+ if (err) {
+ ___setErrNo(err);
+ return -1;
+ }
+ FS.currentPath = lookup.path;
+ return 0;
},
chown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
chown: function(path, owner, group, dontResolveLastLink) {
@@ -1501,10 +2409,13 @@ LibraryManager.library = {
// NOTE: dontResolveLastLink is a shortcut for lchown(). It should never be
// used in client code.
if (typeof path !== 'string') path = Pointer_stringify(path);
- var target = FS.findObject(path, dontResolveLastLink);
- if (target === null) return -1;
- target.timestamp = Date.now();
- return 0;
+ try {
+ VFS.chown(path, owner, group);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
chroot__deps: ['__setErrNo', '$ERRNO_CODES'],
chroot: function(path) {
@@ -1517,16 +2428,18 @@ LibraryManager.library = {
close: function(fildes) {
// int close(int fildes);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/close.html
- if (FS.streams[fildes]) {
- if (FS.streams[fildes].currentEntry) {
- _free(FS.streams[fildes].currentEntry);
- }
- FS.streams[fildes] = null;
- return 0;
- } else {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
+ try {
+ VFS.close(stream);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);;
+ return -1;
+ }
},
dup__deps: ['fcntl'],
dup: function(fildes) {
@@ -1538,24 +2451,32 @@ LibraryManager.library = {
dup2: function(fildes, fildes2) {
// int dup2(int fildes, int fildes2);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/dup.html
+ var stream = FS.getStream(fildes);
if (fildes2 < 0) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else if (fildes === fildes2 && FS.streams[fildes]) {
+ } else if (fildes === fildes2 && stream) {
return fildes;
} else {
_close(fildes2);
- return _fcntl(fildes, 0, allocate([fildes2, 0, 0, 0], 'i32', ALLOC_STACK), true); // F_DUPFD.
+ try {
+ var stream2 = VFS.open(stream.path, stream.flags, 0, fildes2, fildes2);
+ return stream2.fd;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
}
},
fchown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chown'],
fchown: function(fildes, owner, group) {
// int fchown(int fildes, uid_t owner, gid_t group);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fchown.html
- if (FS.streams[fildes]) {
- return _chown(FS.streams[fildes].path, owner, group);
- } else {
- ___setErrNo(ERRNO_CODES.EBADF);
+ try {
+ VFS.fchown(fildes, owner, group);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
},
@@ -1563,8 +2484,9 @@ LibraryManager.library = {
fchdir: function(fildes) {
// int fchdir(int fildes);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fchdir.html
- if (FS.streams[fildes]) {
- return _chdir(FS.streams[fildes].path);
+ var stream = FS.getStream(fildes);
+ if (stream) {
+ return _chdir(stream.path);
} else {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
@@ -1637,7 +2559,8 @@ LibraryManager.library = {
fsync: function(fildes) {
// int fsync(int fildes);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fsync.html
- if (FS.streams[fildes]) {
+ var stream = FS.getStream(fildes);
+ if (stream) {
// We write directly to the file system, so there's nothing to do here.
return 0;
} else {
@@ -1651,42 +2574,24 @@ LibraryManager.library = {
// int truncate(const char *path, off_t length);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/truncate.html
// NOTE: The path argument may be a string, to simplify ftruncate().
- if (length < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ if (typeof path !== 'string') path = Pointer_stringify(path);
+ try {
+ VFS.truncate(path, length);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else {
- if (typeof path !== 'string') path = Pointer_stringify(path);
- var target = FS.findObject(path);
- if (target === null) return -1;
- if (target.isFolder) {
- ___setErrNo(ERRNO_CODES.EISDIR);
- return -1;
- } else if (target.isDevice) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- return -1;
- } else if (!target.write) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else {
- var contents = target.contents;
- if (length < contents.length) contents.length = length;
- else while (length > contents.length) contents.push(0);
- target.timestamp = Date.now();
- return 0;
- }
}
},
ftruncate__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'truncate'],
ftruncate: function(fildes, length) {
// int ftruncate(int fildes, off_t length);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/ftruncate.html
- if (FS.streams[fildes] && FS.streams[fildes].isWrite) {
- return _truncate(FS.streams[fildes].path, length);
- } else if (FS.streams[fildes]) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- return -1;
- } else {
- ___setErrNo(ERRNO_CODES.EBADF);
+ try {
+ VFS.ftruncate(fildes, length);
+ return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
},
@@ -1718,12 +2623,13 @@ LibraryManager.library = {
isatty: function(fildes) {
// int isatty(int fildes);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/isatty.html
- var stream = FS.streams[fildes];
+ var stream = FS.getStream(fildes);
if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return 0;
}
- if (!stream.object.isTerminal) {
+ // HACK - implement tcgetattr
+ if (!stream.tty) {
___setErrNo(ERRNO_CODES.ENOTTY);
return 0;
}
@@ -1747,7 +2653,8 @@ LibraryManager.library = {
lockf: function(fildes, func, size) {
// int lockf(int fildes, int function, off_t size);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/lockf.html
- if (FS.streams[fildes]) {
+ var stream = FS.getStream(fildes);
+ if (stream) {
// Pretend whatever locking or unlocking operation succeeded. Locking does
// not make much sense, since we have a single process/thread.
return 0;
@@ -1760,26 +2667,17 @@ LibraryManager.library = {
lseek: function(fildes, offset, whence) {
// off_t lseek(int fildes, off_t offset, int whence);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/lseek.html
- if (FS.streams[fildes] && !FS.streams[fildes].object.isDevice) {
- var stream = FS.streams[fildes];
- var position = offset;
- if (whence === 1) { // SEEK_CUR.
- position += stream.position;
- } else if (whence === 2) { // SEEK_END.
- position += stream.object.contents.length;
- }
- if (position < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- return -1;
- } else {
- stream.ungotten = [];
- stream.position = position;
- return position;
- }
- } else {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
+ try {
+ return VFS.llseek(stream, offset, whence);
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
+ }
},
pipe__deps: ['__setErrNo', '$ERRNO_CODES'],
pipe: function(fildes) {
@@ -1794,94 +2692,39 @@ LibraryManager.library = {
pread: function(fildes, buf, nbyte, offset) {
// ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/read.html
- var stream = FS.streams[fildes];
- if (!stream || stream.object.isDevice) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else if (!stream.isRead) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else if (stream.object.isFolder) {
- ___setErrNo(ERRNO_CODES.EISDIR);
- return -1;
- } else if (nbyte < 0 || offset < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ }
+ try {
+ var slab = {{{ makeGetSlabs('buf', 'i8', true) }}};
+ return VFS.read(stream, slab, buf, nbyte, offset);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else if (offset >= stream.object.contents.length) {
- return 0;
- } else {
- var bytesRead = 0;
- var contents = stream.object.contents;
- var size = Math.min(contents.length - offset, nbyte);
- assert(size >= 0);
-
-#if USE_TYPED_ARRAYS == 2
- if (contents.subarray) { // typed array
- HEAPU8.set(contents.subarray(offset, offset+size), buf);
- } else
-#endif
- if (contents.slice) { // normal array
- for (var i = 0; i < size; i++) {
- {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}}
- }
- } else {
- for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR
- {{{ makeSetValue('buf', 'i', 'contents.get(offset + i)', 'i8') }}}
- }
- }
- bytesRead += size;
- return bytesRead;
}
},
read__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'recv', 'pread'],
read: function(fildes, buf, nbyte) {
// ssize_t read(int fildes, void *buf, size_t nbyte);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/read.html
- var stream = FS.streams[fildes];
- if (stream && ('socket' in stream)) {
- return _recv(fildes, buf, nbyte, 0);
- } else if (!stream) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else if (!stream.isRead) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else if (nbyte < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ }
+
+ if (stream && ('socket' in stream)) {
+ return _recv(fildes, buf, nbyte, 0);
+ }
+
+ try {
+ var slab = {{{ makeGetSlabs('buf', 'i8', true) }}};
+ return VFS.read(stream, slab, buf, nbyte);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else {
- var bytesRead;
- if (stream.object.isDevice) {
- if (stream.object.input) {
- bytesRead = 0;
- for (var i = 0; i < nbyte; i++) {
- try {
- var result = stream.object.input();
- } catch (e) {
- ___setErrNo(ERRNO_CODES.EIO);
- return -1;
- }
- if (result === undefined && bytesRead === 0) {
- ___setErrNo(ERRNO_CODES.EAGAIN);
- return -1;
- }
- if (result === null || result === undefined) break;
- bytesRead++;
- {{{ makeSetValue('buf', 'i', 'result', 'i8') }}}
- }
- return bytesRead;
- } else {
- ___setErrNo(ERRNO_CODES.ENXIO);
- return -1;
- }
- } else {
- bytesRead = _pread(fildes, buf, nbyte, stream.position);
- assert(bytesRead >= -1);
- if (bytesRead != -1) {
- stream.position += bytesRead;
- }
- return bytesRead;
- }
}
},
sync: function() {
@@ -1894,26 +2737,12 @@ LibraryManager.library = {
// int rmdir(const char *path);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/rmdir.html
path = Pointer_stringify(path);
- path = FS.analyzePath(path, true);
- if (!path.parentExists || !path.exists) {
- ___setErrNo(path.error);
- return -1;
- } else if (!path.parentObject.write) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else if (!path.object.isFolder) {
- ___setErrNo(ERRNO_CODES.ENOTDIR);
- return -1;
- } else if (path.isRoot || path.path == FS.currentPath) {
- ___setErrNo(ERRNO_CODES.EBUSY);
- return -1;
- } else {
- for (var i in path.object.contents) {
- ___setErrNo(ERRNO_CODES.ENOTEMPTY);
- return -1;
- }
- delete path.parentObject.contents[path.name];
+ try {
+ VFS.rmdir(path);
return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
},
unlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
@@ -1921,19 +2750,12 @@ LibraryManager.library = {
// int unlink(const char *path);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/unlink.html
path = Pointer_stringify(path);
- path = FS.analyzePath(path, true);
- if (!path.parentExists || !path.exists) {
- ___setErrNo(path.error);
- return -1;
- } else if (path.object.isFolder) {
- ___setErrNo(ERRNO_CODES.EPERM);
- return -1;
- } else if (!path.parentObject.write) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else {
- delete path.parentObject.contents[path.name];
+ try {
+ VFS.unlink(path);
return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
},
ttyname__deps: ['ttyname_r'],
@@ -1947,7 +2769,7 @@ LibraryManager.library = {
ttyname_r: function(fildes, name, namesize) {
// int ttyname_r(int fildes, char *name, size_t namesize);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/ttyname.html
- var stream = FS.streams[fildes];
+ var stream = FS.getStream(fildes);
var ttyname = '/dev/tty';
if (!stream) {
return ___setErrNo(ERRNO_CODES.EBADF);
@@ -1959,106 +2781,73 @@ LibraryManager.library = {
writeStringToMemory(ttyname, name);
return 0;
},
- symlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
+ symlink__deps: ['$FS', '$PATH', '__setErrNo', '$ERRNO_CODES'],
symlink: function(path1, path2) {
// int symlink(const char *path1, const char *path2);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/symlink.html
- var path = FS.analyzePath(Pointer_stringify(path2), true);
- if (!path.parentExists) {
- ___setErrNo(path.error);
- return -1;
- } else if (path.exists) {
- ___setErrNo(ERRNO_CODES.EEXIST);
- return -1;
- } else {
- FS.createLink(path.parentPath, path.name,
- Pointer_stringify(path1), true, true);
+ path1 = Pointer_stringify(path1);
+ path2 = Pointer_stringify(path2);
+ try {
+ VFS.symlink(path1, path2);
return 0;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
},
readlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
readlink: function(path, buf, bufsize) {
// ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/readlink.html
- var target = FS.findObject(Pointer_stringify(path), true);
- if (target === null) return -1;
- if (target.link !== undefined) {
- var length = Math.min(bufsize - 1, target.link.length);
- for (var i = 0; i < length; i++) {
- {{{ makeSetValue('buf', 'i', 'target.link.charCodeAt(i)', 'i8') }}}
- }
- if (bufsize - 1 > length) {{{ makeSetValue('buf', 'i', '0', 'i8') }}}
- return i;
- } else {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ path = Pointer_stringify(path);
+ var str;
+ try {
+ str = VFS.readlink(path);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
}
+ str = str.slice(0, Math.max(0, bufsize - 1));
+ writeStringToMemory(str, buf, true);
+ return str.length;
},
pwrite__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
pwrite: function(fildes, buf, nbyte, offset) {
// ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/write.html
- var stream = FS.streams[fildes];
- if (!stream || stream.object.isDevice) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else if (!stream.isWrite) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else if (stream.object.isFolder) {
- ___setErrNo(ERRNO_CODES.EISDIR);
- return -1;
- } else if (nbyte < 0 || offset < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ }
+ try {
+ var slab = {{{ makeGetSlabs('buf', 'i8', true) }}};
+ return VFS.write(stream, slab, buf, nbyte, offset);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else {
- var contents = stream.object.contents;
- while (contents.length < offset) contents.push(0);
- for (var i = 0; i < nbyte; i++) {
- contents[offset + i] = {{{ makeGetValue('buf', 'i', 'i8', undefined, 1) }}};
- }
- stream.object.timestamp = Date.now();
- return i;
}
},
write__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'send', 'pwrite'],
write: function(fildes, buf, nbyte) {
// ssize_t write(int fildes, const void *buf, size_t nbyte);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/write.html
- var stream = FS.streams[fildes];
- if (stream && ('socket' in stream)) {
- return _send(fildes, buf, nbyte, 0);
- } else if (!stream) {
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
- } else if (!stream.isWrite) {
- ___setErrNo(ERRNO_CODES.EACCES);
- return -1;
- } else if (nbyte < 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
+ }
+
+ if (stream && ('socket' in stream)) {
+ return _send(fildes, buf, nbyte, 0);
+ }
+
+ try {
+ var slab = {{{ makeGetSlabs('buf', 'i8', true) }}};
+ return VFS.write(stream, slab, buf, nbyte);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else {
- if (stream.object.isDevice) {
- if (stream.object.output) {
- for (var i = 0; i < nbyte; i++) {
- try {
- stream.object.output({{{ makeGetValue('buf', 'i', 'i8') }}});
- } catch (e) {
- ___setErrNo(ERRNO_CODES.EIO);
- return -1;
- }
- }
- stream.object.timestamp = Date.now();
- return i;
- } else {
- ___setErrNo(ERRNO_CODES.ENXIO);
- return -1;
- }
- } else {
- var bytesWritten = _pwrite(fildes, buf, nbyte, stream.position);
- if (bytesWritten != -1) stream.position += bytesWritten;
- return bytesWritten;
- }
}
},
alarm: function(seconds) {
@@ -3157,7 +3946,8 @@ LibraryManager.library = {
clearerr: function(stream) {
// void clearerr(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/clearerr.html
- if (FS.streams[stream]) FS.streams[stream].error = false;
+ stream = FS.getStream(stream);
+ if (stream) stream.error = false;
},
fclose__deps: ['close', 'fsync'],
fclose: function(stream) {
@@ -3170,68 +3960,51 @@ LibraryManager.library = {
fdopen: function(fildes, mode) {
// FILE *fdopen(int fildes, const char *mode);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fdopen.html
- if (FS.streams[fildes]) {
- var stream = FS.streams[fildes];
- mode = Pointer_stringify(mode);
- if ((mode.indexOf('w') != -1 && !stream.isWrite) ||
- (mode.indexOf('r') != -1 && !stream.isRead) ||
- (mode.indexOf('a') != -1 && !stream.isAppend) ||
- (mode.indexOf('+') != -1 && (!stream.isRead || !stream.isWrite))) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- return 0;
- } else {
- stream.error = false;
- stream.eof = false;
- return fildes;
- }
- } else {
+ mode = Pointer_stringify(mode);
+ var stream = FS.getStream(fildes);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return 0;
}
+ if ((mode.indexOf('w') != -1 && !stream.isWrite) ||
+ (mode.indexOf('r') != -1 && !stream.isRead) ||
+ (mode.indexOf('a') != -1 && !stream.isAppend) ||
+ (mode.indexOf('+') != -1 && (!stream.isRead || !stream.isWrite))) {
+ ___setErrNo(ERRNO_CODES.EINVAL);
+ return 0;
+ } else {
+ stream.error = false;
+ stream.eof = false;
+ return fildes;
+ }
},
feof__deps: ['$FS'],
feof: function(stream) {
// int feof(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/feof.html
- return Number(FS.streams[stream] && FS.streams[stream].eof);
+ stream = FS.getStream(stream);
+ return Number(stream && stream.eof);
},
ferror__deps: ['$FS'],
ferror: function(stream) {
// int ferror(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/ferror.html
- return Number(FS.streams[stream] && FS.streams[stream].error);
+ stream = FS.getStream(stream);
+ return Number(stream && stream.error);
},
fflush__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
fflush: function(stream) {
// int fflush(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fflush.html
- var flush = function(filedes) {
- // Right now we write all data directly, except for output devices.
- if (FS.streams[filedes] && FS.streams[filedes].object.output) {
- if (!FS.streams[filedes].object.isTerminal) { // don't flush terminals, it would cause a \n to also appear
- FS.streams[filedes].object.output(null);
- }
- }
- };
- try {
- if (stream === 0) {
- for (var i = 0; i < FS.streams.length; i++) if (FS.streams[i]) flush(i);
- } else {
- flush(stream);
- }
- return 0;
- } catch (e) {
- ___setErrNo(ERRNO_CODES.EIO);
- return -1;
- }
+ // we don't currently perform any user-space buffering of data
},
fgetc__deps: ['$FS', 'fread'],
fgetc__postset: '_fgetc.ret = allocate([0], "i8", ALLOC_STATIC);',
fgetc: function(stream) {
// int fgetc(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetc.html
- if (!FS.streams[stream]) return -1;
- var streamObj = FS.streams[stream];
+ var streamObj = FS.getStream(stream);
+ if (!streamObj) return -1;
if (streamObj.eof || streamObj.error) return -1;
var ret = _fread(_fgetc.ret, 1, 1, stream);
if (ret == 0) {
@@ -3256,28 +4029,26 @@ LibraryManager.library = {
fgetpos: function(stream, pos) {
// int fgetpos(FILE *restrict stream, fpos_t *restrict pos);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetpos.html
- if (FS.streams[stream]) {
- stream = FS.streams[stream];
- if (stream.object.isDevice) {
- ___setErrNo(ERRNO_CODES.ESPIPE);
- return -1;
- } else {
- {{{ makeSetValue('pos', '0', 'stream.position', 'i32') }}}
- var state = (stream.eof ? 1 : 0) + (stream.error ? 2 : 0);
- {{{ makeSetValue('pos', Runtime.getNativeTypeSize('i32'), 'state', 'i32') }}}
- return 0;
- }
- } else {
+ stream = FS.getStream(stream);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
+ if (FS.isChrdev(stream.node.mode)) {
+ ___setErrNo(ERRNO_CODES.ESPIPE);
+ return -1;
+ }
+ {{{ makeSetValue('pos', '0', 'stream.position', 'i32') }}}
+ var state = (stream.eof ? 1 : 0) + (stream.error ? 2 : 0);
+ {{{ makeSetValue('pos', Runtime.getNativeTypeSize('i32'), 'state', 'i32') }}}
+ return 0;
},
fgets__deps: ['fgetc'],
fgets: function(s, n, stream) {
// char *fgets(char *restrict s, int n, FILE *restrict stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fgets.html
- if (!FS.streams[stream]) return 0;
- var streamObj = FS.streams[stream];
+ var streamObj = FS.getStream(stream);
+ if (!streamObj) return 0;
if (streamObj.error || streamObj.eof) return 0;
var byte_;
for (var i = 0; i < n - 1 && byte_ != {{{ charCode('\n') }}}; i++) {
@@ -3355,7 +4126,8 @@ LibraryManager.library = {
{{{ makeSetValue('_fputc.ret', '0', 'chr', 'i8') }}}
var ret = _write(stream, _fputc.ret, 1);
if (ret == -1) {
- if (FS.streams[stream]) FS.streams[stream].error = true;
+ var streamObj = FS.getStream(stream);
+ if (streamObj) streamObj.error = true;
return -1;
} else {
return chr;
@@ -3399,7 +4171,7 @@ LibraryManager.library = {
return 0;
}
var bytesRead = 0;
- var streamObj = FS.streams[stream];
+ var streamObj = FS.getStream(stream);
while (streamObj.ungotten.length && bytesToRead > 0) {
{{{ makeSetValue('ptr++', '0', 'streamObj.ungotten.pop()', 'i8') }}}
bytesToRead--;
@@ -3419,12 +4191,13 @@ LibraryManager.library = {
// FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/freopen.html
if (!filename) {
- if (!FS.streams[stream]) {
+ var streamObj = FS.getStream(stream);
+ if (!streamObj) {
___setErrNo(ERRNO_CODES.EBADF);
return 0;
}
if (_freopen.buffer) _free(_freopen.buffer);
- filename = intArrayFromString(FS.streams[stream].path);
+ filename = intArrayFromString(streamObj.path);
filename = allocate(filename, 'i8', ALLOC_NORMAL);
}
_fclose(stream);
@@ -3437,10 +4210,10 @@ LibraryManager.library = {
var ret = _lseek(stream, offset, whence);
if (ret == -1) {
return -1;
- } else {
- FS.streams[stream].eof = false;
- return 0;
}
+ stream = FS.getStream(stream);
+ stream.eof = false;
+ return 0;
},
fseeko: 'fseek',
fseeko64: 'fseek',
@@ -3448,38 +4221,36 @@ LibraryManager.library = {
fsetpos: function(stream, pos) {
// int fsetpos(FILE *stream, const fpos_t *pos);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fsetpos.html
- if (FS.streams[stream]) {
- if (FS.streams[stream].object.isDevice) {
- ___setErrNo(ERRNO_CODES.EPIPE);
- return -1;
- } else {
- FS.streams[stream].position = {{{ makeGetValue('pos', '0', 'i32') }}};
- var state = {{{ makeGetValue('pos', Runtime.getNativeTypeSize('i32'), 'i32') }}};
- FS.streams[stream].eof = Boolean(state & 1);
- FS.streams[stream].error = Boolean(state & 2);
- return 0;
- }
- } else {
+ stream = FS.getStream(stream);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
+ if (FS.isChrdev(stream.node.mode)) {
+ ___setErrNo(ERRNO_CODES.EPIPE);
+ return -1;
+ }
+ stream.position = {{{ makeGetValue('pos', '0', 'i32') }}};
+ var state = {{{ makeGetValue('pos', Runtime.getNativeTypeSize('i32'), 'i32') }}};
+ stream.eof = Boolean(state & 1);
+ stream.error = Boolean(state & 2);
+ return 0;
},
ftell__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'],
ftell: function(stream) {
// long ftell(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/ftell.html
- if (FS.streams[stream]) {
- stream = FS.streams[stream];
- if (stream.object.isDevice) {
- ___setErrNo(ERRNO_CODES.ESPIPE);
- return -1;
- } else {
- return stream.position;
- }
- } else {
+ stream = FS.getStream(stream);
+ if (!stream) {
___setErrNo(ERRNO_CODES.EBADF);
return -1;
}
+ if (FS.isChrdev(stream.node.mode)) {
+ ___setErrNo(ERRNO_CODES.ESPIPE);
+ return -1;
+ } else {
+ return stream.position;
+ }
},
ftello: 'ftell',
ftello64: 'ftell',
@@ -3491,7 +4262,8 @@ LibraryManager.library = {
if (bytesToWrite == 0) return 0;
var bytesWritten = _write(stream, ptr, bytesToWrite);
if (bytesWritten == -1) {
- if (FS.streams[stream]) FS.streams[stream].error = true;
+ var streamObj = FS.getStream(stream);
+ if (streamObj) streamObj.error = true;
return 0;
} else {
return Math.floor(bytesWritten / size);
@@ -3535,30 +4307,17 @@ LibraryManager.library = {
return ret;
},
rename__deps: ['__setErrNo', '$ERRNO_CODES'],
- rename: function(old, new_) {
+ rename: function(old_path, new_path) {
// int rename(const char *old, const char *new);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/rename.html
- var oldObj = FS.analyzePath(Pointer_stringify(old));
- var newObj = FS.analyzePath(Pointer_stringify(new_));
- if (newObj.path == oldObj.path) {
+ old_path = Pointer_stringify(old_path);
+ new_path = Pointer_stringify(new_path);
+ try {
+ VFS.rename(old_path, new_path);
return 0;
- } else if (!oldObj.exists) {
- ___setErrNo(oldObj.error);
- return -1;
- } else if (oldObj.isRoot || oldObj.path == FS.currentPath) {
- ___setErrNo(ERRNO_CODES.EBUSY);
- return -1;
- } else if (newObj.parentPath &&
- newObj.parentPath.indexOf(oldObj.path) == 0) {
- ___setErrNo(ERRNO_CODES.EINVAL);
- return -1;
- } else if (newObj.exists && newObj.object.isFolder) {
- ___setErrNo(ERRNO_CODES.EISDIR);
+ } catch (e) {
+ ___setErrNo(e.errno);
return -1;
- } else {
- delete oldObj.parentObject.contents[oldObj.name];
- newObj.parentObject.contents[newObj.name] = oldObj.object;
- return 0;
}
},
rewind__deps: ['$FS', 'fseek'],
@@ -3566,7 +4325,8 @@ LibraryManager.library = {
// void rewind(FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/rewind.html
_fseek(stream, 0, 0); // SEEK_SET.
- if (FS.streams[stream]) FS.streams[stream].error = false;
+ var streamObj = FS.getStream(stream);
+ if (streamObj) streamObj.error = false;
},
setvbuf: function(stream, buf, type, size) {
// int setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size);
@@ -3625,7 +4385,7 @@ LibraryManager.library = {
ungetc: function(c, stream) {
// int ungetc(int c, FILE *stream);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/ungetc.html
- stream = FS.streams[stream];
+ stream = FS.getStream(stream);
if (!stream) {
return -1;
}
@@ -3650,20 +4410,20 @@ LibraryManager.library = {
fscanf: function(stream, format, varargs) {
// int fscanf(FILE *restrict stream, const char *restrict format, ... );
// http://pubs.opengroup.org/onlinepubs/000095399/functions/scanf.html
- if (FS.streams[stream]) {
- var buffer = [];
- var get = function() {
- var c = _fgetc(stream);
- buffer.push(c);
- return c;
- };
- var unget = function() {
- _ungetc(buffer.pop(), stream);
- };
- return __scanString(format, get, unget, varargs);
- } else {
+ var streamObj = FS.getStream(stream);
+ if (!streamObj) {
return -1;
}
+ var buffer = [];
+ var get = function() {
+ var c = _fgetc(stream);
+ buffer.push(c);
+ return c;
+ };
+ var unget = function() {
+ _ungetc(buffer.pop(), stream);
+ };
+ return __scanString(format, get, unget, varargs);
},
scanf__deps: ['fscanf'],
scanf: function(format, varargs) {
@@ -3803,38 +4563,26 @@ LibraryManager.library = {
* mmap.
*/
var MAP_PRIVATE = 2;
+ var ptr;
var allocated = false;
if (!_mmap.mappings) _mmap.mappings = {};
if (stream == -1) {
- var ptr = _malloc(num);
+ ptr = _malloc(num);
if (!ptr) return -1;
_memset(ptr, 0, num);
allocated = true;
} else {
- var info = FS.streams[stream];
+ var info = FS.getStream(stream);
if (!info) return -1;
- var contents = info.object.contents;
- // Only make a new copy when MAP_PRIVATE is specified.
- if (flags & MAP_PRIVATE == 0) {
- // We can't emulate MAP_SHARED when the file is not backed by HEAP.
- assert(contents.buffer === HEAPU8.buffer);
- ptr = contents.byteOffset;
- allocated = false;
- } else {
- // Try to avoid unnecessary slices.
- if (offset > 0 || offset + num < contents.length) {
- if (contents.subarray) {
- contents = contents.subarray(offset, offset+num);
- } else {
- contents = Array.prototype.slice.call(contents, offset, offset+num);
- }
- }
- ptr = _malloc(num);
- if (!ptr) return -1;
- HEAPU8.set(contents, ptr);
- allocated = true;
+ try {
+ var res = VFS.mmap(info, HEAPU8, start, num, offset, prot, flags);
+ ptr = res.ptr;
+ allocated = res.allocated;
+ } catch (e) {
+ ___setErrNo(e.errno);
+ return -1;
}
}
@@ -6987,8 +7735,6 @@ LibraryManager.library = {
// ==========================================================================
// sys/types.h
// ==========================================================================
-
- // NOTE: These are fake, since we don't support the C device creation API.
// http://www.kernel.org/doc/man-pages/online/pages/man3/minor.3.html
makedev: function(maj, min) {
return ((maj) << 8 | (min));
@@ -8010,15 +8756,16 @@ LibraryManager.library = {
socket__deps: ['$Sockets'],
socket: function(family, type, protocol) {
var INCOMING_QUEUE_LENGTH = 64;
- var fd = FS.createFileHandle({
+ var stream = FS.createStream({
addr: null,
port: null,
inQueue: new CircularBuffer(INCOMING_QUEUE_LENGTH),
header: new Uint16Array(2),
bound: false,
- socket: true
+ socket: true,
+ stream_ops: {}
});
- assert(fd < 64); // select() assumes socket fd values are in 0..63
+ assert(stream.fd < 64); // select() assumes socket fd values are in 0..63
var stream = type == {{{ cDefine('SOCK_STREAM') }}};
if (protocol) {
assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if stream, must be tcp
@@ -8132,7 +8879,7 @@ LibraryManager.library = {
};
};
- return fd;
+ return stream.fd;
},
mkport__deps: ['$Sockets'],
@@ -8153,7 +8900,7 @@ LibraryManager.library = {
bind__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs', 'mkport'],
bind: function(fd, addr, addrlen) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
if (addr) {
info.port = _ntohs(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16'));
@@ -8174,7 +8921,7 @@ LibraryManager.library = {
sendmsg__deps: ['$Sockets', 'bind', '_inet_ntop_raw', 'ntohs'],
sendmsg: function(fd, msg, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
// if we are not connected, use the address info in the message
if (!info.bound) {
@@ -8230,7 +8977,7 @@ LibraryManager.library = {
recvmsg__deps: ['$Sockets', 'bind', '__setErrNo', '$ERRNO_CODES', 'htons'],
recvmsg: function(fd, msg, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
// if we are not connected, use the address info in the message
if (!info.port) {
@@ -8281,14 +9028,14 @@ LibraryManager.library = {
},
shutdown: function(fd, how) {
- var info = FS.streams[fd];
- if (!info) return -1;
- info.close();
- FS.removeFileHandle(fd);
+ var stream = FS.getStream(fd);
+ if (!stream) return -1;
+ stream.close();
+ FS.closeStream(stream);
},
ioctl: function(fd, request, varargs) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
var bytes = 0;
if (info.hasData()) {
@@ -8308,7 +9055,7 @@ LibraryManager.library = {
// TODO: webrtc queued incoming connections, etc.
// For now, the model is that bind does a connect, and we "accept" that one connection,
// which has host:port the same as ours. We also return the same socket fd.
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
if (addr) {
setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32');
@@ -8349,7 +9096,7 @@ LibraryManager.library = {
var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh;
if (int_ & mask) {
// index is in the set, check if it is ready for read
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (info && can(info)) {
// set bit
fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask);
@@ -8378,18 +9125,19 @@ LibraryManager.library = {
if (protocol) {
assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp
}
- var fd = FS.createFileHandle({
+ var stream = FS.createStream({
connected: false,
stream: stream,
- socket: true
+ socket: true,
+ stream_ops: {}
});
- assert(fd < 64); // select() assumes socket fd values are in 0..63
- return fd;
+ assert(stream.fd < 64); // select() assumes socket fd values are in 0..63
+ return stream.fd;
},
connect__deps: ['$FS', '$Sockets', '_inet_ntop_raw', 'ntohs', 'gethostbyname'],
connect: function(fd, addr, addrlen) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
info.connected = true;
info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32');
@@ -8507,7 +9255,7 @@ LibraryManager.library = {
recv__deps: ['$FS'],
recv: function(fd, buf, len, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
if (!info.hasData()) {
___setErrNo(ERRNO_CODES.EAGAIN); // no data, and all sockets are nonblocking, so this is the right behavior
@@ -8533,7 +9281,7 @@ LibraryManager.library = {
send__deps: ['$FS'],
send: function(fd, buf, len, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
info.sender(HEAPU8.subarray(buf, buf+len));
return len;
@@ -8541,7 +9289,7 @@ LibraryManager.library = {
sendmsg__deps: ['$FS', '$Sockets', 'connect'],
sendmsg: function(fd, msg, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
// if we are not connected, use the address info in the message
if (!info.connected) {
@@ -8576,7 +9324,7 @@ LibraryManager.library = {
recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'],
recvmsg: function(fd, msg, flags) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
// if we are not connected, use the address info in the message
if (!info.connected) {
@@ -8634,7 +9382,7 @@ LibraryManager.library = {
recvfrom__deps: ['$FS', 'connect', 'recv'],
recvfrom: function(fd, buf, len, flags, addr, addrlen) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
// if we are not connected, use the address info in the message
if (!info.connected) {
@@ -8645,14 +9393,14 @@ LibraryManager.library = {
},
shutdown: function(fd, how) {
- var info = FS.streams[fd];
- if (!info) return -1;
- info.socket.close();
- FS.removeFileHandle(fd);
+ var stream = FS.getStream(fd);
+ if (!stream) return -1;
+ stream.socket.close();
+ FS.closeStream(stream);
},
ioctl: function(fd, request, varargs) {
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
var bytes = 0;
if (info.hasData()) {
@@ -8682,7 +9430,7 @@ LibraryManager.library = {
// TODO: webrtc queued incoming connections, etc.
// For now, the model is that bind does a connect, and we "accept" that one connection,
// which has host:port the same as ours. We also return the same socket fd.
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (!info) return -1;
if (addr) {
setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32');
@@ -8738,7 +9486,7 @@ LibraryManager.library = {
var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh;
if (int_ & mask) {
// index is in the set, check if it is ready for read
- var info = FS.streams[fd];
+ var info = FS.getStream(fd);
if (info && can(info)) {
// set bit
fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask);
diff --git a/src/library_path.js b/src/library_path.js
new file mode 100644
index 00000000..3df6ca5b
--- /dev/null
+++ b/src/library_path.js
@@ -0,0 +1,133 @@
+mergeInto(LibraryManager.library, {
+ $PATH: {
+ // split a filename into [root, dir, basename, ext], unix version
+ // 'root' is just a slash, or nothing.
+ splitPath: function (filename) {
+ var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+ return splitPathRe.exec(filename).slice(1);
+ },
+ normalizeArray: function (parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+ return parts;
+ },
+ normalize: function (path) {
+ var isAbsolute = path.charAt(0) === '/',
+ trailingSlash = path.substr(-1) === '/';
+ // Normalize the path
+ path = PATH.normalizeArray(path.split('/').filter(function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+ return (isAbsolute ? '/' : '') + path;
+ },
+ dirname: function (path) {
+ var result = PATH.splitPath(path),
+ root = result[0],
+ dir = result[1];
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+ return root + dir;
+ },
+ basename: function (path, ext) {
+ // EMSCRIPTEN return '/'' for '/', not an empty string
+ if (path === '/') return '/';
+ var f = PATH.splitPath(path)[2];
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+ },
+ join: function () {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return PATH.normalize(paths.filter(function(p, index) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }).join('/'));
+ },
+ resolve: function () {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0) ? arguments[i] : process.cwd();
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+ resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter(function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+ },
+ relative: function(from, to) {
+ from = PATH.resolve(from).substr(1);
+ to = PATH.resolve(to).substr(1);
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+ return outputParts.join('/');
+ }
+ }
+}); \ No newline at end of file
diff --git a/src/modules.js b/src/modules.js
index 9f419234..f85e7d0e 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -422,7 +422,7 @@ var LibraryManager = {
load: function() {
if (this.library) return;
- var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries);
+ var libraries = ['library.js', 'library_path.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries);
for (var i = 0; i < libraries.length; i++) {
eval(processMacros(preprocess(read(libraries[i]))));
}
diff --git a/src/settings.js b/src/settings.js
index 87ab820d..19108f3b 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -249,8 +249,6 @@ var FS_LOG = 0; // Log all FS operations. This is especially helpful when you'r
// a new project and want to see a list of file system operations happening
// so that you can create a virtual file system with all of the required files.
-var USE_OLD_FS = 1; // Switch to toggle the new / old FS code. Currently only used for testing purposes.
-
var USE_BSS = 1; // https://en.wikipedia.org/wiki/.bss
// When enabled, 0-initialized globals are sorted to the end of the globals list,
// enabling us to not explicitly store the initialization value for each 0 byte.
@@ -880,9 +878,9 @@ var C_DEFINES = {
'S_IRGRP': '0000040',
'S_IROTH': '0000004',
'S_IRUSR': '0000400',
- 'S_IRWXG': '0000040',
- 'S_IRWXO': '0000004',
- 'S_IRWXU': '0000400',
+ 'S_IRWXG': '0000070',
+ 'S_IRWXO': '0000007',
+ 'S_IRWXU': '0000700',
'S_ISGID': '0002000',
'S_ISUID': '0004000',
'S_ISVTX': '0001000',
@@ -1297,6 +1295,33 @@ var C_DEFINES = {
'___int8_t_defined': '1',
'___int_least16_t_defined': '1',
'___int_least32_t_defined': '1',
- '___int_least8_t_defined': '1'
+ '___int_least8_t_defined': '1',
+ 'FMODE_READ': '0x1',
+ 'FMODE_WRITE': '0x2',
+ 'FMODE_LSEEK': '0x4',
+ 'FMODE_PREAD': '0x8',
+ 'FMODE_PWRITE': '0x10',
+ 'FMODE_EXEC': '0x20',
+ 'FMODE_NDELAY': '0x40',
+ 'FMODE_EXCL': '0x80',
+ 'FMODE_NOCMTIME': '0x800',
+ 'FMODE_RANDOM': '0x1000',
+ 'FMODE_UNSIGNED_OFFSET': '0x2000',
+ 'FMODE_PATH': '0x4000',
+ 'FMODE_NONOTIFY': '0x1000000',
+ 'S_IRWXUGO': '511',
+ 'S_IALLUGO': '4095',
+ 'S_IRUGO': '292',
+ 'S_IWUGO': '146',
+ 'S_IXUGO': '73',
+ 'LOOKUP_FOLLOW': '0x0001',
+ 'LOOKUP_DIRECTORY': '0x0002',
+ 'LOOKUP_PARENT': '0x0010',
+ 'MAP_SHARED': '0x01',
+ 'MAP_PRIVATE': '0x02',
+ 'MAP_TYPE': '0x0f',
+ 'MAP_FIXED': '0x100',
+ 'MAP_ANONYMOUS': '0x10',
+ 'O_NOFOLLOW': '0200000'
};
diff --git a/tests/filesystem/src.js b/tests/filesystem/src.js
index 801b45d6..ceea348d 100644
--- a/tests/filesystem/src.js
+++ b/tests/filesystem/src.js
@@ -8,6 +8,7 @@ FS.createDevice('/def', 'deviceB', function() {}, function() {});
FS.createLink('/abc', 'localLink', '123', true, true);
FS.createLink('/abc', 'rootLink', '/', true, true);
FS.createLink('/abc', 'relativeLink', '../def', true, true);
+FS.ignorePermissions = false;
function explore(path) {
Module.print(path);
diff --git a/tests/runner.py b/tests/runner.py
index a0cef588..fa81f899 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -3591,33 +3591,8 @@ Exiting setjmp function, level: 0, prev_jmp: -1
self.do_run(src, 'z:1*', force_c=True)
def test_rename(self):
- src = '''
- #include <stdio.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <assert.h>
-
- int main() {
- int err;
- FILE* fid;
-
- err = mkdir("/foo", 0777);
- err = mkdir("/bar", 0777);
- fid = fopen("/foo/bar", "w+");
- fclose(fid);
-
- err = rename("/foo/bar", "/foo/bar2");
- printf("%d\\n", err);
-
- err = rename("/foo", "/foo/foo");
- printf("%d\\n", err);
-
- err = rename("/foo", "/bar/foo");
- printf("%d\\n", err);
- return 0;
- }
- '''
- self.do_run(src, '0\n-1\n0\n', force_c=True)
+ src = open(path_from_root('tests', 'stdio', 'test_rename.c'), 'r').read()
+ self.do_run(src, 'success', force_c=True)
def test_alloca_stack(self):
if self.emcc_args is None: return # too slow in other modes
@@ -7244,17 +7219,14 @@ def process(filename):
self.do_run(src, 'success', force_c=True)
def test_stat(self):
- Building.COMPILER_TEST_OPTS += ['-DUSE_OLD_FS='+str(Settings.USE_OLD_FS)]
src = open(path_from_root('tests', 'stat', 'test_stat.c'), 'r').read()
self.do_run(src, 'success', force_c=True)
def test_stat_chmod(self):
- Building.COMPILER_TEST_OPTS += ['-DUSE_OLD_FS='+str(Settings.USE_OLD_FS)]
src = open(path_from_root('tests', 'stat', 'test_chmod.c'), 'r').read()
self.do_run(src, 'success', force_c=True)
def test_stat_mknod(self):
- Building.COMPILER_TEST_OPTS += ['-DUSE_OLD_FS='+str(Settings.USE_OLD_FS)]
src = open(path_from_root('tests', 'stat', 'test_mknod.c'), 'r').read()
self.do_run(src, 'success', force_c=True)
diff --git a/tests/stat/test_chmod.c b/tests/stat/test_chmod.c
index bf0e2cbe..3574760b 100644
--- a/tests/stat/test_chmod.c
+++ b/tests/stat/test_chmod.c
@@ -56,11 +56,7 @@ void test() {
memset(&s, 0, sizeof s);
stat("file", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0222 | S_IFREG));
-#else
assert(s.st_mode == (0200 | S_IFREG));
-#endif
assert(s.st_ctime != lastctime);
//
@@ -74,11 +70,7 @@ void test() {
memset(&s, 0, sizeof s);
stat("file", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0000 | S_IFREG));
-#else
assert(s.st_mode == (0100 | S_IFREG));
-#endif
assert(s.st_ctime != lastctime);
//
@@ -95,11 +87,7 @@ void test() {
assert(!err);
memset(&s, 0, sizeof s);
stat("folder", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0222 | S_IFDIR));
-#else
assert(s.st_mode == (0300 | S_IFDIR));
-#endif
assert(s.st_ctime != lastctime);
//
@@ -110,11 +98,7 @@ void test() {
// make sure the file it references changed
stat("file-link", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0555 | S_IFREG));
-#else
assert(s.st_mode == (0400 | S_IFREG));
-#endif
// but the link didn't
lstat("file-link", &s);
@@ -128,19 +112,11 @@ void test() {
// make sure the file it references didn't change
stat("file-link", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0555 | S_IFREG));
-#else
assert(s.st_mode == (0400 | S_IFREG));
-#endif
// but the link did
lstat("file-link", &s);
-#if USE_OLD_FS
- assert(s.st_mode == (0555 | S_IFLNK));
-#else
assert(s.st_mode == (0500 | S_IFLNK));
-#endif
puts("success");
}
diff --git a/tests/stat/test_mknod.c b/tests/stat/test_mknod.c
index 2b8f650d..361b2315 100644
--- a/tests/stat/test_mknod.c
+++ b/tests/stat/test_mknod.c
@@ -53,15 +53,10 @@ void test() {
// mknod a character device
err = mknod("mknod-device", S_IFCHR | 0777, 123);
-#if USE_OLD_FS
- assert(err);
- assert(errno == EPERM);
-#else
assert(!err);
memset(&s, 0, sizeof s);
stat("mknod-device", &s);
assert(S_ISCHR(s.st_mode));
-#endif
#endif
diff --git a/tests/stat/test_stat.c b/tests/stat/test_stat.c
index 2c2bf53d..f59fb3c3 100644
--- a/tests/stat/test_stat.c
+++ b/tests/stat/test_stat.c
@@ -104,13 +104,10 @@ void test() {
assert(s.st_ino);
assert(S_ISCHR(s.st_mode));
assert(s.st_nlink);
-#if !USE_OLD_FS
- // old FS doesn't store proper device ids
#ifndef __APPLE__
// mac uses makedev(3, 2) for /dev/null
assert(s.st_rdev == makedev(1, 3));
#endif
-#endif
assert(!s.st_size);
assert(s.st_atime);
assert(s.st_mtime);
diff --git a/tests/stdio/test_rename.c b/tests/stdio/test_rename.c
new file mode 100644
index 00000000..20ffb513
--- /dev/null
+++ b/tests/stdio/test_rename.c
@@ -0,0 +1,107 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+void create_file(const char *path, const char *buffer, int mode) {
+ int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
+ assert(fd >= 0);
+
+ int err = write(fd, buffer, sizeof(char) * strlen(buffer));
+ assert(err == (sizeof(char) * strlen(buffer)));
+
+ close(fd);
+}
+
+void setup() {
+ create_file("file", "abcdef", 0777);
+ mkdir("dir", 0777);
+ create_file("dir/file", "abcdef", 0777);
+ mkdir("dir/subdir", 0777);
+ mkdir("dir-readonly", 0555);
+ mkdir("dir-nonempty", 0777);
+ create_file("dir-nonempty/file", "abcdef", 0777);
+}
+
+void cleanup() {
+ // we're hulk-smashing and removing original + renamed files to
+ // make sure we get it all regardless of anything failing
+ unlink("file");
+ unlink("dir/file");
+ unlink("dir/file1");
+ unlink("dir/file2");
+ rmdir("dir/subdir");
+ rmdir("dir/subdir1");
+ rmdir("dir/subdir2");
+ rmdir("dir");
+ rmdir("dir-readonly");
+ unlink("dir-nonempty/file");
+ rmdir("dir-nonempty");
+}
+
+void test() {
+ int err;
+
+ // can't rename something that doesn't exist
+ err = rename("noexist", "dir");
+ assert(err == -1);
+ assert(errno == ENOENT);
+
+ // can't overwrite a folder with a file
+ err = rename("file", "dir");
+ assert(err == -1);
+ assert(errno == EISDIR);
+
+ // can't overwrite a file with a folder
+ err = rename("dir", "file");
+ assert(err == -1);
+ assert(errno == ENOTDIR);
+
+ // can't overwrite a non-empty folder
+ err = rename("dir", "dir-nonempty");
+ assert(err == -1);
+ assert(errno == ENOTEMPTY);
+
+ // can't create anything in a read-only directory
+ err = rename("dir", "dir-readonly/dir");
+ assert(err == -1);
+ assert(errno == EACCES);
+
+ // source should not be ancestor of target
+ err = rename("dir", "dir/somename");
+ assert(err == -1);
+ assert(errno == EINVAL);
+
+ // target should not be an ancestor of source
+ err = rename("dir/subdir", "dir");
+ assert(err == -1);
+ assert(errno == ENOTEMPTY);
+
+ // do some valid renaming
+ err = rename("dir/file", "dir/file1");
+ assert(!err);
+ err = rename("dir/file1", "dir/file2");
+ assert(!err);
+ err = access("dir/file2", F_OK);
+ assert(!err);
+ err = rename("dir/subdir", "dir/subdir1");
+ assert(!err);
+ err = rename("dir/subdir1", "dir/subdir2");
+ assert(!err);
+ err = access("dir/subdir2", F_OK);
+ assert(!err);
+
+ puts("success");
+}
+
+int main() {
+ atexit(cleanup);
+ signal(SIGABRT, cleanup);
+ setup();
+ test();
+ return EXIT_SUCCESS;
+} \ No newline at end of file