diff options
author | Anthony Pesch <inolen@gmail.com> | 2013-07-12 20:31:09 -0700 |
---|---|---|
committer | Anthony Pesch <inolen@gmail.com> | 2013-08-05 12:37:19 -0700 |
commit | a9fb60ebd640597ec710217c69aad0e5c87884b3 (patch) | |
tree | 0715350794ae832a7a9e838dddcc035e2ba7b18f /src | |
parent | 710c8868a5789916c0a2f1dcddd55fe6245a7f98 (diff) |
initial VFS work
Diffstat (limited to 'src')
-rw-r--r-- | src/library.js | 2070 | ||||
-rw-r--r-- | src/library_path.js | 133 | ||||
-rw-r--r-- | src/modules.js | 2 | ||||
-rw-r--r-- | src/settings.js | 35 |
4 files changed, 2197 insertions, 43 deletions
diff --git a/src/library.js b/src/library.js index 0d443adb..1c0e0320 100644 --- a/src/library.js +++ b/src/library.js @@ -77,6 +77,7 @@ LibraryManager.library = { } }, +#if USE_OLD_FS $FS__deps: ['$FSCOM', '$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr', '_impure_ptr'], $FS__postset: 'FS.staticInit();' + '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + @@ -688,6 +689,1509 @@ LibraryManager.library = { delete path.parentObject.contents[path.name]; } }, +#else + $FS__deps: ['$FSCOM', '$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 + 'Module["FS_createFolder"] = FS.createFolder;' + + 'Module["FS_createPath"] = FS.createPath;' + + 'Module["FS_createDataFile"] = FS.createDataFile;' + + 'Module["FS_createPreloadedFile"] = FS.createPreloadedFile;' + + 'Module["FS_createLazyFile"] = FS.createLazyFile;' + + 'Module["FS_createLink"] = FS.createLink;' + + 'Module["FS_createDevice"] = FS.createDevice;', + $FS: { + root: null, + nodes: [null], + devices: [null], + streams: [null], + 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, + + 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; + } + return (parentid + hash) % FS.name_table.length; + }, + hashAddNode: function (node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.name_table[hash]; + FS.name_table[hash] = node; + }, + 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; + } + } + }, + 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; + } + } + // if we failed to find it in the cache, call into the VFS + return VFS.lookup(parent, name); + }, + 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 (!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); + } + + // 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); + } + } + } + } + + return { path: current_path, node: current }; + }, + 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 { + 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)); + }, + + // + // 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); + } + }, + 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; + } + } + 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 (Module['stdout']) { + FS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + VFS.symlink('/dev/tty', '/dev/stdout'); + } + if (Module['stderr']) { + FS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + VFS.symlink('/dev/tty1', '/dev/stderr'); + } + + // 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); + } + }, + + // + // 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); + }, + 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; + var current = PATH.join(parent, part); + try { + VFS.mkdir(current, 0777); + } catch (e) { + // ignore EEXIST + } + parent = current; + } + return current; + }, + 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); + }, + 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; + }, + createLazyFile: function (parent, name, url, canRead, canWrite) { + throw new Error('TODO'); + }, + 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); + }, + createPreloadedFile: function () { + FSCOM.createPreloadedFile.apply(this, arguments); + } + }, + + $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; + }, + lookup: function (parent, name) { + return parent.node_ops.lookup(parent, name); + }, + // 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); + }, + // 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 { + node = path; + } + 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() + }); + }, + 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 { + 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; + }, + 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); + } + }, + + $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); + } + } + } + // 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); + } + 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 + 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; + }, + 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; + }, + node_ops: { + }, + stream_ops: { + }, + websocket_sock_ops: { + } + }, + + $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)); + } + } + } + }, +#endif // ========================================================================== @@ -700,12 +2204,13 @@ 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. +#if USE_OLD_FS var path = FS.absolutePath(Pointer_stringify(dirname)); if (path === null) { ___setErrNo(ERRNO_CODES.ENOENT); @@ -744,11 +2249,34 @@ LibraryManager.library = { FS.checkStreams(); #endif return stream.fd; +#else + var path = Pointer_stringify(dirname); + if (!path) { + ___setErrNo(ERRNO_CODES.ENOENT); + return 0; + } + var node; + try { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } catch (e) { + ___setErrNo(e.errno); + return 0; + } + if (!FS.isDir(node.mode)) { + ___setErrNo(ERRNO_CODES.ENOTDIR); + return 0; + } + 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; +#endif }, - 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 USE_OLD_FS var stream = FS.getStream(dirp); if (!stream || !stream.object.isFolder) { ___setErrNo(ERRNO_CODES.EBADF); @@ -757,22 +2285,30 @@ LibraryManager.library = { _free(stream.currentEntry); FS.closeStream(stream); return 0; +#else + return _close(dirp); +#endif }, telldir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], telldir: function(dirp) { // long int telldir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html var stream = FS.getStream(dirp); +#if USE_OLD_FS if (!stream || !stream.object.isFolder) { +#else + if (!stream || !FS.isDir(stream.node.mode)) { +#endif ___setErrNo(ERRNO_CODES.EBADF); return -1; } 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 USE_OLD_FS var stream = FS.getStream(dirp); if (!stream || !stream.object.isFolder) { ___setErrNo(ERRNO_CODES.EBADF); @@ -785,21 +2321,33 @@ LibraryManager.library = { } else { stream.position = loc; } +#else + _lseek(dirp, loc, {{{ cDefine('SEEK_SET') }}}); +#endif }, rewinddir__deps: ['seekdir'], rewinddir: function(dirp) { // void rewinddir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/rewinddir.html +#if USE_OLD_FS _seekdir(dirp, -2); +#else + _seekdir(dirp, 0); +#endif }, 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 var stream = FS.getStream(dirp); +#if USE_OLD_FS if (!stream || !stream.object.isFolder) { +#else + if (!stream) { +#endif return ___setErrNo(ERRNO_CODES.EBADF); } +#if USE_OLD_FS var loc = stream.position; var entries = 0; for (var key in stream.contents) entries++; @@ -838,16 +2386,59 @@ LibraryManager.library = { {{{ makeSetValue('result', '0', 'entry', 'i8*') }}} } return 0; +#else + 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*') }}} + 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; +#endif }, 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 var stream = FS.getStream(dirp); +#if USE_OLD_FS if (!stream || !stream.object.isFolder) { +#else + if (!stream) { +#endif ___setErrNo(ERRNO_CODES.EBADF); return 0; } +#if USE_OLD_FS if (!_readdir.result) _readdir.result = _malloc(4); _readdir_r(dirp, stream.currentEntry, _readdir.result); if ({{{ makeGetValue(0, '_readdir.result', 'i8*') }}} === 0) { @@ -855,6 +2446,17 @@ LibraryManager.library = { } else { return stream.currentEntry; } +#else + // 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*') }}}; +#endif }, __01readdir64_: 'readdir', // TODO: Check if we need to link any other aliases. @@ -879,10 +2481,21 @@ LibraryManager.library = { } else { time = Date.now(); } - var file = FS.findObject(Pointer_stringify(path)); + path = Pointer_stringify(path); +#if USE_OLD_FS + var file = FS.findObject(path); if (file === null) return -1; file.timestamp = time; return 0; +#else + try { + VFS.utime(path, time, time); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif; }, utimes: function() { throw 'utimes not implemented' }, @@ -975,7 +2588,9 @@ 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); + path = typeof path !== 'string' ? Pointer_stringify(path) : path; +#if USE_OLD_FS + var obj = FS.findObject(path, dontResolveLastLink); if (obj === null || !FS.forceLoadFile(obj)) return -1; var offsets = ___stat_struct_layout; @@ -1034,8 +2649,29 @@ LibraryManager.library = { 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; +#else + 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; + } +#endif }, lstat__deps: ['stat'], lstat: function(path, buf) { @@ -1052,14 +2688,14 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var pathArray = intArrayFromString(stream.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); +#if USE_OLD_FS var fmt = (mode & {{{ cDefine('S_IFMT') }}}); if (fmt !== {{{ cDefine('S_IFREG') }}} && fmt !== {{{ cDefine('S_IFCHR') }}} && fmt !== {{{ cDefine('S_IFBLK') }}} && fmt !== {{{ cDefine('S_IFIFO') }}} && @@ -1083,12 +2719,29 @@ LibraryManager.library = { } catch (e) { return -1; } +#else + // 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; + } + try { + VFS.mknod(path, mode, dev); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, mkdir__deps: ['mknod'], mkdir: function(path, mode) { // int mkdir(const char *path, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html path = Pointer_stringify(path); +#if USE_OLD_FS path = FS.analyzePath(path); var properties = { contents: [], isFolder: true }; try { @@ -1098,6 +2751,15 @@ LibraryManager.library = { } catch (e) { return -1; } +#else + try { + VFS.mkdir(path, mode, 0); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, mkfifo__deps: ['__setErrNo', '$ERRNO_CODES'], mkfifo: function(path, mode) { @@ -1110,33 +2772,65 @@ 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; +#if USE_OLD_FS 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; +#else + try { + VFS.chmod(path, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, 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 +#if USE_OLD_FS var stream = FS.getStream(fildes); if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } return _chmod(stream.path, mode); +#else + try { + VFS.fchmod(fildes, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, - lchmod: function(path, mode) { + lchmod__deps: ['chmod'], + lchmod: function (path, mode) { + path = Pointer_stringify(path); +#if USE_OLD_FS return _chmod(path, mode, true); +#else + try { + VFS.lchmod(path, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, umask__deps: ['$FS'], @@ -1220,9 +2914,9 @@ 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') }}}; +#if USE_OLD_FS // Simplify flags. var accessMode = oflag & {{{ cDefine('O_ACCMODE') }}}; var isWrite = accessMode != {{{ cDefine('O_RDONLY') }}}; @@ -1322,6 +3016,16 @@ LibraryManager.library = { FS.checkStreams(); #endif return stream.fd; +#else + path = Pointer_stringify(path); + try { + var stream = VFS.open(path, oflag, mode); + return stream.fd; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, creat__deps: ['open'], creat: function(path, mode) { @@ -1354,20 +3058,31 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } - var newStream = {}; + var newStream; +#if USE_OLD_FS + newStream = {}; for (var member in stream) { newStream[member] = stream[member]; } arg = dup2 ? arg : Math.max(arg, FS.streams.length); // dup2 wants exactly arg; fcntl wants a free descriptor >= arg FS.createStream(newStream, arg); +#else + try { + newStream = VFS.open(stream.path, stream.flags, 0, arg); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endiif #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') }}}: +#if USE_OLD_FS var flags = 0; if (stream.isRead && stream.isWrite) flags = {{{ cDefine('O_RDWR') }}}; else if (!stream.isRead && stream.isWrite) flags = {{{ cDefine('O_WRONLY') }}}; @@ -1375,10 +3090,17 @@ LibraryManager.library = { if (stream.isAppend) flags |= {{{ cDefine('O_APPEND') }}}; // Synchronization and blocking flags are irrelevant to us. return flags; +#else + return stream.flags; +#endif case {{{ cDefine('F_SETFL') }}}: var arg = {{{ makeGetValue('varargs', 0, 'i32') }}}; +#if USE_OLD_FS stream.isAppend = Boolean(arg | {{{ cDefine('O_APPEND') }}}); // Synchronization and blocking flags are irrelevant to us. +#else + stream.flags |= arg; +#endif return 0; case {{{ cDefine('F_GETLK') }}}: case {{{ cDefine('F_GETLK64') }}}: @@ -1417,14 +3139,28 @@ LibraryManager.library = { // int posix_fallocate(int fd, off_t offset, off_t len); // http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html var stream = FS.getStream(fd); +#if USE_OLD_FS if (!stream || !stream.isWrite || stream.link || stream.isFolder || stream.isDevice) { +#else + if (!stream) { +#endif ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS var contents = stream.object.contents; var limit = offset + len; while (limit > contents.length) contents.push(0); return 0; +#else + try { + VFS.allocate(stream, offset, len); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, // ========================================================================== @@ -1479,6 +3215,7 @@ LibraryManager.library = { // int access(const char *path, int amode); // http://pubs.opengroup.org/onlinepubs/000095399/functions/access.html path = Pointer_stringify(path); +#if USE_OLD_FS var target = FS.findObject(path); if (target === null) return -1; if ((amode & 2 && !target.write) || // W_OK. @@ -1488,6 +3225,30 @@ LibraryManager.library = { } else { return 0; } +#else + 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; + } + return 0; +#endif }, chdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], chdir: function(path) { @@ -1495,6 +3256,7 @@ 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); +#if USE_OLD_FS path = FS.analyzePath(path); if (!path.exists) { ___setErrNo(path.error); @@ -1506,6 +3268,26 @@ LibraryManager.library = { FS.currentPath = path.path; return 0; } +#else + var lookup; + try { + lookup = FS.lookupPath(path, { follow: true }); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } + if (!FS.isDir(lookup.node.mode)) { + ___setErrNo(ERRNO_CODES.ENOTDIR); + return -1; + } + var err = FS.nodePermissions(lookup.node, 'x'); + if (err) { + ___setErrNo(err); + return -1; + } + FS.currentPath = lookup.path; + return 0; +#endif }, chown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], chown: function(path, owner, group, dontResolveLastLink) { @@ -1516,10 +3298,20 @@ 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); +#if USE_OLD_FS var target = FS.findObject(path, dontResolveLastLink); if (target === null) return -1; target.timestamp = Date.now(); return 0; +#else + try { + VFS.chown(path, owner, group); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, chroot__deps: ['__setErrNo', '$ERRNO_CODES'], chroot: function(path) { @@ -1537,11 +3329,21 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS if (stream.currentEntry) { _free(stream.currentEntry); } FS.closeStream(stream); return 0; +#else + try { + VFS.close(stream); + return 0; + } catch (e) { + ___setErrNo(e.errno);; + return -1; + } +#endif }, dup__deps: ['fcntl'], dup: function(fildes) { @@ -1561,20 +3363,39 @@ LibraryManager.library = { return fildes; } else { _close(fildes2); +#if USE_OLD_FS return _fcntl(fildes, 0, allocate([fildes2, 0, 0, 0], 'i32', ALLOC_STACK), true); // F_DUPFD. +#else + try { + var stream2 = VFS.open(stream.path, stream.flags, 0, fildes2, fildes2); + return stream2.fd; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif } }, 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 USE_OLD_FS var stream = FS.getStream(fildes); - if (stream) { - return _chown(stream.path, owner, group); - } else { + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + return _chown(stream.path, owner, group); +#else + try { + VFS.fchown(fildes, owner, group); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, fchdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chdir'], fchdir: function(fildes) { @@ -1670,11 +3491,12 @@ 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 (typeof path !== 'string') path = Pointer_stringify(path); +#if USE_OLD_FS if (length < 0) { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } else { - if (typeof path !== 'string') path = Pointer_stringify(path); var target = FS.findObject(path); if (target === null) return -1; if (target.isFolder) { @@ -1693,21 +3515,40 @@ LibraryManager.library = { target.timestamp = Date.now(); return 0; } +#else + try { + VFS.truncate(path, length); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; } +#endif }, 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 USE_OLD_FS var stream = FS.getStream(fildes); if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (!stream.write) { + } + if (!stream.isWrite) { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } return _truncate(stream.path, length); +#else + try { + VFS.ftruncate(fildes, length); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, getcwd__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], getcwd: function(buf, size) { @@ -1742,7 +3583,12 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return 0; } +#if USE_OLD_FS if (!stream.object.isTerminal) { +#else + // HACK - implement tcgetattr + if (!stream.tty) { +#endif ___setErrNo(ERRNO_CODES.ENOTTY); return 0; } @@ -1781,10 +3627,15 @@ LibraryManager.library = { // off_t lseek(int fildes, off_t offset, int whence); // http://pubs.opengroup.org/onlinepubs/000095399/functions/lseek.html var stream = FS.getStream(fildes); +#if USE_OLD_FS if (!stream || stream.object.isDevice) { +#else + if (!stream) { +#endif ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS var position = offset; if (whence === 1) { // SEEK_CUR. position += stream.position; @@ -1799,6 +3650,14 @@ LibraryManager.library = { stream.position = position; return position; } +#else + try { + return VFS.llseek(stream, offset, whence); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, pipe__deps: ['__setErrNo', '$ERRNO_CODES'], pipe: function(fildes) { @@ -1814,7 +3673,12 @@ LibraryManager.library = { // 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.getStream(fildes); - if (!stream || stream.object.isDevice) { + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } +#if USE_OLD_FS + if (stream.object.isDevice) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } else if (!stream.isRead) { @@ -1851,17 +3715,28 @@ LibraryManager.library = { bytesRead += size; return bytesRead; } +#else + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.read(stream, slab, buf, nbyte, offset); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, 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.getStream(fildes); - if (stream && ('socket' in stream)) { - return _recv(fildes, buf, nbyte, 0); - } else if (!stream) { + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; + } +#if USE_OLD_FS + if (stream && ('socket' in stream)) { + return _recv(fildes, buf, nbyte, 0); } else if (!stream.isRead) { ___setErrNo(ERRNO_CODES.EACCES); return -1; @@ -1902,6 +3777,15 @@ LibraryManager.library = { return bytesRead; } } +#else + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.read(stream, slab, buf, nbyte); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, sync: function() { // void sync(void); @@ -1913,6 +3797,7 @@ LibraryManager.library = { // int rmdir(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/rmdir.html path = Pointer_stringify(path); +#if USE_OLD_FS path = FS.analyzePath(path, true); if (!path.parentExists || !path.exists) { ___setErrNo(path.error); @@ -1934,12 +3819,22 @@ LibraryManager.library = { delete path.parentObject.contents[path.name]; return 0; } +#else + try { + VFS.rmdir(path); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, unlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], unlink: function(path) { // int unlink(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/unlink.html path = Pointer_stringify(path); +#if USE_OLD_FS path = FS.analyzePath(path, true); if (!path.parentExists || !path.exists) { ___setErrNo(path.error); @@ -1954,6 +3849,15 @@ LibraryManager.library = { delete path.parentObject.contents[path.name]; return 0; } +#else + try { + VFS.unlink(path); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, ttyname__deps: ['ttyname_r'], ttyname: function(fildes) { @@ -1978,11 +3882,14 @@ 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); + path1 = Pointer_stringify(path1); + path2 = Pointer_stringify(path2); +#if USE_OLD_FS + var path = FS.analyzePath(path2, true); if (!path.parentExists) { ___setErrNo(path.error); return -1; @@ -1991,15 +3898,26 @@ LibraryManager.library = { return -1; } else { FS.createLink(path.parentPath, path.name, - Pointer_stringify(path1), true, true); + path1, true, true); return 0; } +#else + try { + VFS.symlink(path1, path2); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, 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); + path = Pointer_stringify(path); +#if USE_OLD_FS + var target = FS.findObject(path, true); if (target === null) return -1; if (target.link !== undefined) { var length = Math.min(bufsize - 1, target.link.length); @@ -2012,13 +3930,30 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } +#else + 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; +#endif }, 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.getStream(fildes); - if (!stream || stream.object.isDevice) { + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } +#if USE_OLD_FS + if (stream.object.isDevice) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } else if (!stream.isWrite) { @@ -2039,17 +3974,28 @@ LibraryManager.library = { stream.object.timestamp = Date.now(); return i; } +#else + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.write(stream, slab, buf, nbyte, offset); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, 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.getStream(fildes); - if (stream && ('socket' in stream)) { - return _send(fildes, buf, nbyte, 0); - } else if (!stream) { + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; + } +#if USE_OLD_FS + if (stream && ('socket' in stream)) { + return _send(fildes, buf, nbyte, 0); } else if (!stream.isWrite) { ___setErrNo(ERRNO_CODES.EACCES); return -1; @@ -2079,6 +4025,15 @@ LibraryManager.library = { return bytesWritten; } } +#else + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.write(stream, slab, buf, nbyte); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, alarm: function(seconds) { // unsigned alarm(unsigned seconds); @@ -3226,6 +5181,7 @@ LibraryManager.library = { fflush: function(stream) { // int fflush(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fflush.html +#if USE_OLD_FS var flush = function(filedes) { // Right now we write all data directly, except for output devices. var streamObj = FS.getStream(filedes); @@ -3246,6 +5202,8 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EIO); return -1; } +#else +#endif }, fgetc__deps: ['$FS', 'fread'], fgetc__postset: '_fgetc.ret = allocate([0], "i8", ALLOC_STATIC);', @@ -3283,7 +5241,11 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS if (stream.object.isDevice) { +#else + if (FS.isChrdev(stream.node.mode)) { +#endif ___setErrNo(ERRNO_CODES.ESPIPE); return -1; } @@ -3475,7 +5437,11 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS if (stream.object.isDevice) { +#else + if (FS.isChrdev(stream.node.mode)) { +#endif ___setErrNo(ERRNO_CODES.EPIPE); return -1; } @@ -3494,7 +5460,11 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EBADF); return -1; } +#if USE_OLD_FS if (stream.object.isDevice) { +#else + if (FS.isChrdev(stream.node.mode)) { +#endif ___setErrNo(ERRNO_CODES.ESPIPE); return -1; } else { @@ -3556,11 +5526,14 @@ 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_)); + old_path = Pointer_stringify(old_path); + new_path = Pointer_stringify(new_path); +#if USE_OLD_FS + var oldObj = FS.analyzePath(old_path); + var newObj = FS.analyzePath(new_path); if (newObj.path == oldObj.path) { return 0; } else if (!oldObj.exists) { @@ -3581,6 +5554,15 @@ LibraryManager.library = { newObj.parentObject.contents[newObj.name] = oldObj.object; return 0; } +#else + try { + VFS.rename(old_path, new_path); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } +#endif }, rewind__deps: ['$FS', 'fseek'], rewind: function(stream) { @@ -3825,18 +5807,20 @@ 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.getStream(stream); if (!info) return -1; +#if USE_OLD_FS var contents = info.object.contents; // Only make a new copy when MAP_PRIVATE is specified. if (flags & MAP_PRIVATE == 0) { @@ -3858,6 +5842,16 @@ LibraryManager.library = { HEAPU8.set(contents, ptr); allocated = true; } +#else + 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; + } +#endif } _mmap.mappings[ptr] = { malloc: ptr, num: num, allocated: allocated }; @@ -7009,8 +9003,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)); @@ -8038,7 +10030,8 @@ LibraryManager.library = { inQueue: new CircularBuffer(INCOMING_QUEUE_LENGTH), header: new Uint16Array(2), bound: false, - socket: true + socket: true, + stream_ops: {} }); assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 var stream = type == {{{ cDefine('SOCK_STREAM') }}}; @@ -8403,7 +10396,8 @@ LibraryManager.library = { var stream = FS.createStream({ connected: false, stream: stream, - socket: true + socket: true, + stream_ops: {} }); assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 return stream.fd; 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..b35f9b24 100644 --- a/src/settings.js +++ b/src/settings.js @@ -880,9 +880,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 +1297,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' }; |