diff options
Diffstat (limited to 'src/library_fs.js')
-rw-r--r-- | src/library_fs.js | 1720 |
1 files changed, 1720 insertions, 0 deletions
diff --git a/src/library_fs.js b/src/library_fs.js new file mode 100644 index 00000000..e97ba588 --- /dev/null +++ b/src/library_fs.js @@ -0,0 +1,1720 @@ +mergeInto(LibraryManager.library, { + $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$PATH', '$TTY', '$MEMFS', '$IDBFS', '$NODEFS', '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, + mounts: [], + devices: [null], + streams: [], + nextInode: 1, + nameTable: null, + 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: null, // set during init + genericErrors: {}, + + handleFSError: function(e) { + if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + stackTrace(); + return ___setErrNo(e.errno); + }, + + // + // paths + // + lookupPath: function(path, opts) { + path = PATH.resolve(FS.cwd(), path); + opts = opts || {}; + + var defaults = { + follow_mount: true, + recurse_count: 0 + }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } + + 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.join2(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (FS.isMountpoint(current)) { + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } + } + + // 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 = FS.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)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length-1] !== '/' ? mount + '/' + path : mount + path; + } + path = path ? node.name + '/' + path : node.name; + node = node.parent; + } + }, + + // + // nodes + // + hashName: function(parentid, name) { + var hash = 0; + +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode: function(node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode: function(node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[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); +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; +#if CASE_INSENSITIVE_FS + nodeName = nodeName.toLowerCase(); +#endif + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return FS.lookup(parent, name); + }, + createNode: function(parent, name, mode, rdev) { + if (!FS.FSNode) { + FS.FSNode = function(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.node_ops = {}; + this.stream_ops = {}; + this.rdev = rdev; + }; + + FS.FSNode.prototype = {}; + + // 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(FS.FSNode.prototype, { + read: { + get: function() { return (this.mode & readMode) === readMode; }, + set: function(val) { val ? this.mode |= readMode : this.mode &= ~readMode; } + }, + write: { + get: function() { return (this.mode & writeMode) === writeMode; }, + set: function(val) { val ? this.mode |= writeMode : this.mode &= ~writeMode; } + }, + isFolder: { + get: function() { return FS.isDir(this.mode); }, + }, + isDevice: { + get: function() { return FS.isChrdev(this.mode); }, + }, + }); + } + + var node = new FS.FSNode(parent, name, mode, rdev); + + 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') }}}; + }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, + + // + // 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'); + }, + 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.cwd()) { + 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)); + }, + + // + // streams + // + MAX_OPEN_FDS: 4096, + nextfd: function(fd_start, fd_end) { + fd_start = fd_start || 0; + 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]; + }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. + createStream: function(stream, fd_start, fd_end) { + if (!FS.FSStream) { + FS.FSStream = function(){}; + FS.FSStream.prototype = {}; + // compatibility + Object.defineProperties(FS.FSStream.prototype, { + object: { + get: function() { return this.node; }, + set: function(val) { this.node = val; } + }, + isRead: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; } + }, + isWrite: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; } + }, + isAppend: { + get: function() { return (this.flags & {{{ cDefine('O_APPEND') }}}); } + } + }); + } + if (stream.__proto__) { + // reuse the object + stream.__proto__ = FS.FSStream.prototype; + } else { + var newStream = new FS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; + } + stream = newStream; + } + var fd = FS.nextfd(fd_start, fd_end); + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream: function(fd) { + FS.streams[fd] = null; + }, + + // + // file pointers + // + // instead of maintaining a separate mapping from FILE* to file descriptors, + // we employ a simple trick: the pointer to a stream is its fd plus 1. This + // means that all valid streams have a valid non-zero pointer while allowing + // the fs for stdin to be the standard value of zero. + // + // + getStreamFromPtr: function(ptr) { + return FS.streams[ptr - 1]; + }, + getPtrForStream: function(stream) { + return stream ? stream.fd + 1 : 0; + }, + + // + // 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]; + }, + + // + // core + // + getMounts: function(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push.apply(check, m.mounts); + } + + return mounts; + }, + syncfs: function(populate, callback) { + if (typeof(populate) === 'function') { + callback = populate; + populate = false; + } + + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + + function done(err) { + if (err) { + if (!done.errored) { + done.errored = true; + return callback(err); + } + return; + } + if (++completed >= mounts.length) { + callback(null); + } + }; + + // sync all mounts + mounts.forEach(function (mount) { + if (!mount.type.syncfs) { + return done(null); + } + mount.type.syncfs(mount, populate, done); + }); + }, + mount: function(type, opts, mountpoint) { + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && FS.root) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + + if (!FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + } + + var mount = { + type: type, + opts: opts, + mountpoint: mountpoint, + mounts: [] + }; + + // create a root node for the fs + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + FS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + + return mountRoot; + }, + unmount: function (mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + + Object.keys(FS.nameTable).forEach(function (hash) { + var current = FS.nameTable[hash]; + + while (current) { + var next = current.name_next; + + if (mounts.indexOf(current.mount) !== -1) { + FS.destroyNode(current); + } + + current = next; + } + }); + + // no longer a mountpoint + node.mounted = null; + + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); + assert(idx !== -1); + node.mount.mounts.splice(idx, 1); + }, + 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 = mode !== undefined ? mode : 0666; + mode &= {{{ cDefine('S_IALLUGO') }}}; + mode |= {{{ cDefine('S_IFREG') }}}; + return FS.mknod(path, mode, 0); + }, + mkdir: function(path, mode) { + mode = mode !== undefined ? mode : 0777; + mode &= {{{ cDefine('S_IRWXUGO') }}} | {{{ cDefine('S_ISVTX') }}}; + mode |= {{{ cDefine('S_IFDIR') }}}; + return FS.mknod(path, mode, 0); + }, + mkdev: function(path, mode, dev) { + if (typeof(dev) === 'undefined') { + dev = mode; + mode = 0666; + } + mode |= {{{ cDefine('S_IFCHR') }}}; + return FS.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 already exists + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) { + // not fatal + } + // early out if nothing needs to change + 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_dir.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); + }, + readdir: function(path) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node.node_ops.readdir) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + return node.node_ops.readdir(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); + 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 FS.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) { + FS.chmod(path, mode, true); + }, + fchmod: function(fd, mode) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.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) { + FS.chown(path, uid, gid, true); + }, + fchown: function(fd, uid, gid) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.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); + } + FS.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) { + flags = typeof flags === 'string' ? FS.modeStringToFlags(flags) : flags; + mode = typeof mode === 'undefined' ? 0666 : mode; + if ((flags & {{{ cDefine('O_CREAT') }}})) { + mode = (mode & {{{ cDefine('S_IALLUGO') }}}) | {{{ cDefine('S_IFREG') }}}; + } else { + mode = 0; + } + var node; + if (typeof path === 'object') { + node = path; + } else { + path = PATH.normalize(path); + try { + var lookup = FS.lookupPath(path, { + follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}}) + }); + node = lookup.node; + } 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 = FS.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')}}})) { + FS.truncate(node, 0); + } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~({{{ cDefine('O_EXCL') }}} | {{{ cDefine('O_TRUNC') }}}); + + // register the stream with the filesystem + var stream = FS.createStream({ + node: node, + path: FS.getPath(node), // we want the absolute path to the 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); + } + if (Module['logReadFiles'] && !(flags & {{{ cDefine('O_WRONLY')}}})) { + if (!FS.readFiles) FS.readFiles = {}; + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + Module['printErr']('read file: ' + path); + } + } + 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); + }, + 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, canOwn) { + 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 + FS.llseek(stream, 0, {{{ cDefine('SEEK_END') }}}); + } + var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); + 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); |