diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyzer.js | 2 | ||||
-rw-r--r-- | src/library.js | 3464 | ||||
-rw-r--r-- | src/library_gl.js | 7 | ||||
-rw-r--r-- | src/library_jansson.js | 2 | ||||
-rw-r--r-- | src/library_openal.js | 596 | ||||
-rw-r--r-- | src/library_path.js | 134 | ||||
-rw-r--r-- | src/library_sdl.js | 168 | ||||
-rw-r--r-- | src/modules.js | 2 | ||||
-rw-r--r-- | src/postamble.js | 94 | ||||
-rw-r--r-- | src/preamble.js | 73 | ||||
-rw-r--r-- | src/settings.js | 1684 | ||||
-rw-r--r-- | src/utility.js | 6 |
12 files changed, 3914 insertions, 2318 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index b1f0b585..1a752305 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -338,7 +338,7 @@ function analyzer(data, sidePass) { if (subItem != item && (!(subItem.intertype in UNUNFOLDABLE) || (subItem.intertype == 'value' && isNumber(subItem.ident) && isIllegalType(subItem.type)))) { if (item.intertype == 'phi') { - assert(subItem.intertype == 'value' || subItem.intertype == 'structvalue', 'We can only unfold illegal constants in phis'); + assert(subItem.intertype == 'value' || subItem.intertype == 'structvalue' || subItem.intertype in PARSABLE_LLVM_FUNCTIONS, 'We can only unfold some expressions in phis'); // we must handle this in the phi itself, if we unfold normally it will not be pushed back with the phi } else { var tempIdent = '$$etemp$' + (tempId++); diff --git a/src/library.js b/src/library.js index 61237d07..78222b9a 100644 --- a/src/library.js +++ b/src/library.js @@ -28,8 +28,9 @@ 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__postset: '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + + $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 } });' + '__ATEXIT__.push({ func: function() { FS.quit() } });' + // export some names through closure @@ -41,288 +42,685 @@ 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) { + this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; } } - // 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(); + this.message = ERRNO_MESSAGES[errno]; + }, + + handleFSError: function(e) { + if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + new Error().stack; + return ___setErrNo(e.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; } - 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 + // + cwd: function() { + return FS.currentPath; + }, + 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) { - FS.ensureRoot(); - 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 | |