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 | |
parent | 710c8868a5789916c0a2f1dcddd55fe6245a7f98 (diff) |
initial VFS work
-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') }}}) |