diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-08-10 19:57:19 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-08-10 19:57:19 -0700 |
commit | 6f95f937bb4a805081f7ddb07fa8c8d5da7d1d1c (patch) | |
tree | 65bd70b16c9836cab4b3be64048fe55b99f3a401 /src | |
parent | 3ecb9d7f45e8af5a980940af3ed8bc17efd1a300 (diff) | |
parent | 35d58fd518da39db69adccd47882e2924961a861 (diff) |
Merge pull request #1498 from inolen/lib_reorg
FS-related module reorganization
Diffstat (limited to 'src')
-rw-r--r-- | src/library.js | 1781 | ||||
-rw-r--r-- | src/library_fs.js | 1367 | ||||
-rw-r--r-- | src/library_memfs.js | 217 | ||||
-rw-r--r-- | src/library_sockfs.js | 18 | ||||
-rw-r--r-- | src/library_tty.js | 121 | ||||
-rw-r--r-- | src/modules.js | 2 |
6 files changed, 1752 insertions, 1754 deletions
diff --git a/src/library.js b/src/library.js index 6ce77268..fedb8760 100644 --- a/src/library.js +++ b/src/library.js @@ -18,1737 +18,12 @@ // Memory allocated during startup, in postsets, should only be ALLOC_STATIC LibraryManager.library = { - // ========================================================================== - // File system base. - // ========================================================================== - // keep this low in memory, because we flatten arrays with them in them stdin: 'allocate(1, "i32*", ALLOC_STATIC)', stdout: 'allocate(1, "i32*", ALLOC_STATIC)', stderr: 'allocate(1, "i32*", ALLOC_STATIC)', _impure_ptr: 'allocate(1, "i32*", ALLOC_STATIC)', - $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$VFS', '$PATH', '$TTY', '$MEMFS', 'stdin', 'stdout', 'stderr', 'fflush'], - $FS__postset: 'FS.staticInit();' + - '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + - '__ATMAIN__.push({ func: function() { FS.ignorePermissions = false } });' + - '__ATEXIT__.push({ func: function() { FS.quit() } });' + - // export some names through closure - '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) { - this.errno = errno; - for (var key in ERRNO_CODES) { - if (ERRNO_CODES[key] === errno) { - this.code = key; - break; - } - } - this.message = ERRNO_MESSAGES[errno]; - }, - - handleFSError: function(e) { - if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + new Error().stack; - return ___setErrNo(e.errno); - }, - - // - // nodes - // - hashName: function(parentid, name) { - var hash = 0; - for (var i = 0; i < name.length; i++) { - hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; - } - 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') }}}; - // NOTE we must use Object.defineProperties instead of individual calls to - // Object.defineProperty in order to make closure compiler happy - Object.defineProperties(node, { - read: { - get: function() { return (node.mode & readMode) === readMode; }, - set: function(val) { val ? node.mode |= readMode : node.mode &= ~readMode; } - }, - write: { - get: function() { return (node.mode & writeMode) === writeMode; }, - set: function(val) { val ? node.mode |= writeMode : node.mode &= ~writeMode; } - }, - isFolder: { - get: function() { return FS.isDir(node.mode); }, - }, - isDevice: { - get: function() { return FS.isChrdev(node.mode); }, - }, - }); - FS.hashAddNode(node); - return node; - }, - destroyNode: function(node) { - FS.hashRemoveNode(node); - }, - isRoot: function(node) { - return node === node.parent; - }, - isMountpoint: function(node) { - return node.mounted; - }, - isFile: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}}; - }, - isDir: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFDIR') }}}; - }, - isLink: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFLNK') }}}; - }, - isChrdev: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFCHR') }}}; - }, - isBlkdev: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFBLK') }}}; - }, - isFIFO: function(mode) { - return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; - }, - - // - // paths - // - cwd: function() { - return FS.currentPath; - }, - lookupPath: function(path, opts) { - path = PATH.resolve(FS.currentPath, path); - opts = opts || { recurse_count: 0 }; - - if (opts.recurse_count > 8) { // max recursive lookup of 8 - throw new FS.ErrnoError(ERRNO_CODES.ELOOP); - } - - // 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.defineProperties(stream, { - object: { - get: function() { return stream.node; }, - set: function(val) { stream.node = val; } - }, - isRead: { - get: function() { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; } - }, - isWrite: { - get: function() { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; } - }, - 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); - }, - standardizePath: function(path) { - return PATH.normalize(path); - }, - 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; - }, - 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 && output.buffer && output.buffer.length) { - output({{{ charCode('\n') }}}); - } - }, - read: function(stream, buffer, offset, length, pos /* ignored */) { - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = input(); - } catch (e) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - }, - write: function(stream, buffer, offset, length, pos) { - for (var i = 0; i < length; i++) { - try { - output(buffer[offset+i]); - } catch (e) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - } - }); - return VFS.mkdev(path, mode, dev); - }, - createLink: function(parent, name, target, canRead, canWrite) { - var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); - return VFS.symlink(target, path); - }, - // Makes sure a file's contents are loaded. Returns whether the file has - // been loaded successfully. No-op for files that have been loaded already. - forceLoadFile: function(obj) { - if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; - var success = true; - if (typeof XMLHttpRequest !== 'undefined') { - throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); - } else if (Module['read']) { - // Command-line. - try { - // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as - // read() will try to parse UTF8. - obj.contents = intArrayFromString(Module['read'](obj.url), true); - } catch (e) { - success = false; - } - } else { - throw new Error('Cannot load without read() or XMLHttpRequest.'); - } - if (!success) ___setErrNo(ERRNO_CODES.EIO); - return success; - }, - // Creates a file record for lazy-loading from a URL. XXX This requires a synchronous - // XHR, which is not possible in browsers except in a web worker! Use preloading, - // either --preload-file in emcc or FS.createPreloadedFile - createLazyFile: function(parent, name, url, canRead, canWrite) { - if (typeof XMLHttpRequest !== 'undefined') { - if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; - // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. - var LazyUint8Array = function() { - this.lengthKnown = false; - this.chunks = []; // Loaded chunks. Index is the chunk number - } - LazyUint8Array.prototype.get = function(idx) { - if (idx > this.length-1 || idx < 0) { - return undefined; - } - var chunkOffset = idx % this.chunkSize; - var chunkNum = Math.floor(idx / this.chunkSize); - return this.getter(chunkNum)[chunkOffset]; - } - LazyUint8Array.prototype.setDataGetter = function(getter) { - this.getter = getter; - } - LazyUint8Array.prototype.cacheLength = function() { - // Find length - var xhr = new XMLHttpRequest(); - xhr.open('HEAD', url, false); - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); - var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; -#if SMALL_XHR_CHUNKS - var chunkSize = 1024; // Chunk size in bytes -#else - var chunkSize = 1024*1024; // Chunk size in bytes -#endif - - if (!hasByteServing) chunkSize = datalength; - - // Function to get a range from the remote URL. - var doXHR = (function(from, to) { - if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); - - // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); - - // Some hints to the browser that we want binary data. - if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=x-user-defined'); - } - - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - if (xhr.response !== undefined) { - return new Uint8Array(xhr.response || []); - } else { - return intArrayFromString(xhr.responseText || '', true); - } - }); - var lazyArray = this; - lazyArray.setDataGetter(function(chunkNum) { - var start = chunkNum * chunkSize; - var end = (chunkNum+1) * chunkSize - 1; // including this byte - end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") { - lazyArray.chunks[chunkNum] = doXHR(start, end); - } - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") throw new Error("doXHR failed!"); - return lazyArray.chunks[chunkNum]; - }); - - this._length = datalength; - this._chunkSize = chunkSize; - this.lengthKnown = true; - } - - var lazyArray = new LazyUint8Array(); - Object.defineProperty(lazyArray, "length", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._length; - } - }); - Object.defineProperty(lazyArray, "chunkSize", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._chunkSize; - } - }); - - var properties = { isDevice: false, contents: lazyArray }; - } else { - var properties = { isDevice: false, url: url }; - } - - var node = FS.createFile(parent, name, properties, canRead, canWrite); - // This is a total hack, but I want to get this lazy file code out of the - // core of MEMFS. If we want to keep this lazy file concept I feel it should - // be its own thin LAZYFS proxying calls to MEMFS. - if (properties.contents) { - node.contents = properties.contents; - } else if (properties.url) { - node.contents = null; - node.url = properties.url; - } - // override each stream op with one that tries to force load the lazy file first - var stream_ops = {}; - var keys = Object.keys(node.stream_ops); - keys.forEach(function(key) { - var fn = node.stream_ops[key]; - stream_ops[key] = function() { - if (!FS.forceLoadFile(node)) { - throw new FS.ErrnoError(ERRNO_CODES.EIO); - } - return fn.apply(null, arguments); - }; - }); - // use a custom read function - stream_ops.read = function(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - var size = Math.min(contents.length - position, length); - if (contents.slice) { // normal array - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } - } else { - for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR - buffer[offset + i] = contents.get(position + i); - } - } - return size; - }; - node.stream_ops = stream_ops; - return node; - }, - // Preloads a file asynchronously. You can call this before run, for example in - // preRun. run will be delayed until this file arrives and is set up. - // If you call it after run(), you may want to pause the main loop until it - // completes, if so, you can use the onload parameter to be notified when - // that happens. - // In addition to normally creating the file, we also asynchronously preload - // the browser-friendly versions of it: For an image, we preload an Image - // element and for an audio, and Audio. These are necessary for SDL_Image - // and _Mixer to find the files in preloadedImages/Audios. - // You can also call this with a typed array instead of a url. It will then - // do preloading for the Image/Audio part, as if the typed array were the - // result of an XHR that you did manually. - createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) { - Browser.init(); - // TODO we should allow people to just pass in a complete filename instead - // of parent and name being that we just join them anyways - var fullname = PATH.resolve(PATH.join(parent, name)); - function processData(byteArray) { - function finish(byteArray) { - if (!dontCreateFile) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite); - } - if (onload) onload(); - removeRunDependency('cp ' + fullname); - } - var handled = false; - Module['preloadPlugins'].forEach(function(plugin) { - if (handled) return; - if (plugin['canHandle'](fullname)) { - plugin['handle'](byteArray, fullname, finish, function() { - if (onerror) onerror(); - removeRunDependency('cp ' + fullname); - }); - handled = true; - } - }); - if (!handled) finish(byteArray); - } - addRunDependency('cp ' + fullname); - if (typeof url == 'string') { - Browser.asyncLoad(url, function(byteArray) { - processData(byteArray); - }, onerror); - } else { - processData(url); - } - } - }, - - $VFS__deps: ['$FS'], - $VFS: { - mount: function(type, opts, mountpoint) { - var mount = { - type: type, - opts: opts, - mountpoint: mountpoint, - root: null - }; - var lookup; - if (mountpoint) { - lookup = FS.lookupPath(mountpoint, { follow: false }); - } - // create a root node for the fs - var root = type.mount(mount); - root.mount = mount; - mount.root = root; - // assign the mount info to the mountpoint's node - if (lookup) { - lookup.node.mount = mount; - lookup.node.mounted = true; - // compati |