diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/library.js | 3118 | ||||
-rw-r--r-- | src/library_gl.js | 5 | ||||
-rw-r--r-- | src/library_path.js | 133 | ||||
-rw-r--r-- | src/modules.js | 2 | ||||
-rw-r--r-- | src/settings.js | 37 |
5 files changed, 2103 insertions, 1192 deletions
diff --git a/src/library.js b/src/library.js index b094b568..e8333b32 100644 --- a/src/library.js +++ b/src/library.js @@ -28,7 +28,7 @@ LibraryManager.library = { stderr: 'allocate(1, "i32*", ALLOC_STATIC)', _impure_ptr: 'allocate(1, "i32*", ALLOC_STATIC)', - $FS__deps: ['$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr', '_impure_ptr'], + $FS__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 } });' + @@ -42,287 +42,682 @@ LibraryManager.library = { 'Module["FS_createLink"] = FS.createLink;' + 'Module["FS_createDevice"] = FS.createDevice;', $FS: { - // The path to the current folder. - currentPath: '/', - // The inode to assign to the next created object. - nextInode: 2, - // Currently opened file or directory streams. Padded with null so the zero - // index is unused, as the indices are used as pointers. This is not split - // into separate fileStreams and folderStreams lists because the pointers - // must be interchangeable, e.g. when used in fdopen(). - // streams is kept as a dense array. It may contain |null| to fill in - // holes, however. + root: null, + nodes: [null], + devices: [null], streams: [null], -#if ASSERTIONS - checkStreams: function() { - for (var i in FS.streams) if (FS.streams.hasOwnProperty(i)) assert(i >= 0 && i < FS.streams.length); // no keys not in dense span - for (var i = 0; i < FS.streams.length; i++) assert(typeof FS.streams[i] == 'object'); // no non-null holes in dense span - }, -#endif + nextInode: 1, + name_table: new Array(4096), + currentPath: '/', + initialized: false, // Whether we are currently ignoring permissions. Useful when preparing the // filesystem and creating files inside read-only folders. // This is set to false when the runtime is initialized, allowing you // to modify the filesystem freely before run() is called. ignorePermissions: true, - createFileHandle: function(stream, fd) { - if (typeof stream === 'undefined') { - stream = null; - } - if (!fd) { - if (stream && stream.socket) { - for (var i = 1; i < 64; i++) { - if (!FS.streams[i]) { - fd = i; - break; - } - } - assert(fd, 'ran out of low fds for sockets'); - } else { - fd = Math.max(FS.streams.length, 64); - for (var i = FS.streams.length; i < fd; i++) { - FS.streams[i] = null; // Keep dense + + ErrnoError: function (errno) { + 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; } - // Close WebSocket first if we are about to replace the fd (i.e. dup2) - if (FS.streams[fd] && FS.streams[fd].socket && FS.streams[fd].socket.close) { - FS.streams[fd].socket.close(); - } - FS.streams[fd] = stream; - return fd; + return (parentid + hash) % FS.name_table.length; }, - removeFileHandle: function(fd) { - FS.streams[fd] = null; + hashAddNode: function (node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.name_table[hash]; + FS.name_table[hash] = node; }, - joinPath: function(parts, forceRelative) { - var ret = parts[0]; - for (var i = 1; i < parts.length; i++) { - if (ret[ret.length-1] != '/') ret += '/'; - ret += parts[i]; + hashRemoveNode: function (node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.name_table[hash] === node) { + FS.name_table[hash] = node.name_next; + } else { + var current = FS.name_table[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } } - if (forceRelative && ret[0] == '/') ret = ret.substr(1); - return ret; }, - // Converts any path to an absolute path. Resolves embedded "." and ".." - // parts. - absolutePath: function(relative, base) { - if (typeof relative !== 'string') return null; - if (base === undefined) base = FS.currentPath; - if (relative && relative[0] == '/') base = ''; - var full = base + '/' + relative; - var parts = full.split('/').reverse(); - var absolute = ['']; - while (parts.length) { - var part = parts.pop(); - if (part == '' || part == '.') { - // Nothing. - } else if (part == '..') { - if (absolute.length > 1) absolute.pop(); - } else { - absolute.push(part); + lookupNode: function (parent, name) { + var err = FS.mayLookup(parent); + if (err) { + throw new FS.ErrnoError(err); + } + var hash = FS.hashName(parent.id, name); + for (var node = FS.name_table[hash]; node; node = node.name_next) { + if (node.parent.id === parent.id && node.name === name) { + return node; } } - return absolute.length == 1 ? '/' : absolute.join('/'); + // if we failed to find it in the cache, call into the VFS + return VFS.lookup(parent, name); }, - // Analyzes a relative or absolute path returning a description, containing: - // isRoot: Whether the path points to the root. - // exists: Whether the object at the path exists. - // error: If !exists, this will contain the errno code of the cause. - // name: The base name of the object (null if !parentExists). - // path: The absolute path to the object, with all links resolved. - // object: The filesystem record of the object referenced by the path. - // parentExists: Whether the parent of the object exist and is a folder. - // parentPath: The absolute path to the parent folder. - // parentObject: The filesystem record of the parent folder. - analyzePath: function(path, dontResolveLastLink, linksVisited) { - var ret = { - isRoot: false, - exists: false, - error: 0, - name: null, - path: null, - object: null, - parentExists: false, - parentPath: null, - parentObject: null + createNode: function (parent, name, mode, rdev) { + var node = { + id: FS.nextInode++, + name: name, + mode: mode, + node_ops: {}, + stream_ops: {}, + rdev: rdev, + parent: null, + mount: null }; -#if FS_LOG - var inputPath = path; - function log() { - Module['print']('FS.analyzePath("' + inputPath + '", ' + - dontResolveLastLink + ', ' + - linksVisited + ') => {' + - 'isRoot: ' + ret.isRoot + ', ' + - 'exists: ' + ret.exists + ', ' + - 'error: ' + ret.error + ', ' + - 'name: "' + ret.name + '", ' + - 'path: "' + ret.path + '", ' + - 'object: ' + ret.object + ', ' + - 'parentExists: ' + ret.parentExists + ', ' + - 'parentPath: "' + ret.parentPath + '", ' + - 'parentObject: ' + ret.parentObject + '}'); + if (!parent) { + parent = node; // root node sets parent to itself + } + node.parent = parent; + node.mount = parent.mount; + // compatibility + var readMode = {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; + var writeMode = {{{ cDefine('S_IWUGO') }}}; + Object.defineProperty(node, 'read', { + get: function () { return (node.mode & readMode) === readMode; }, + set: function (val) { val ? node.mode |= readMode : node.mode &= ~readMode; } + }); + Object.defineProperty(node, 'write', { + get: function () { return (node.mode & writeMode) === writeMode; }, + set: function (val) { val ? node.mode |= writeMode : node.mode &= ~writeMode; } + }); + // TODO add: + // isFolder + // isDevice + FS.hashAddNode(node); + return node; + }, + destroyNode: function (node) { + FS.hashRemoveNode(node); + }, + isRoot: function (node) { + return node === node.parent; + }, + isMountpoint: function (node) { + return node.mounted; + }, + isFile: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}}; + }, + isDir: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFDIR') }}}; + }, + isLink: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFLNK') }}}; + }, + isChrdev: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFCHR') }}}; + }, + isBlkdev: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFBLK') }}}; + }, + isFIFO: function (mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; + }, + + // + // paths + // + lookupPath: function (path, opts) { + path = PATH.resolve(FS.currentPath, path); + opts = opts || { recurse_count: 0 }; + + if (opts.recurse_count > 8) { // max recursive lookup of 8 + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); } -#endif - path = FS.absolutePath(path); - if (path == '/') { - ret.isRoot = true; - ret.exists = ret.parentExists = true; - ret.name = '/'; - ret.path = ret.parentPath = '/'; - ret.object = ret.parentObject = FS.root; - } else if (path !== null) { - linksVisited = linksVisited || 0; - path = path.slice(1).split('/'); - var current = FS.root; - var traversed = ['']; - while (path.length) { - if (path.length == 1 && current.isFolder) { - ret.parentExists = true; - ret.parentPath = traversed.length == 1 ? '/' : traversed.join('/'); - ret.parentObject = current; - ret.name = path[0]; - } - var target = path.shift(); - if (!current.isFolder) { - ret.error = ERRNO_CODES.ENOTDIR; - break; - } else if (!current.read) { - ret.error = ERRNO_CODES.EACCES; - break; - } else if (!current.contents.hasOwnProperty(target)) { - ret.error = ERRNO_CODES.ENOENT; - break; - } - current = current.contents[target]; - if (current.link && !(dontResolveLastLink && path.length == 0)) { - if (linksVisited > 40) { // Usual Linux SYMLOOP_MAX. - ret.error = ERRNO_CODES.ELOOP; - break; + + // split the path + var parts = PATH.normalizeArray(path.split('/').filter(function (p) { + return !!p; + }), false); + + // start at the root + var current = FS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = (i === parts.length-1); + if (islast && opts.parent) { + // stop resolving + break; + } + + current = FS.lookupNode(current, parts[i]); + current_path = PATH.join(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (FS.isMountpoint(current)) { + current = current.mount.root; + } + + // follow symlinks + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (!islast || opts.follow) { + var count = 0; + while (FS.isLink(current.mode)) { + var link = VFS.readlink(current_path); + current_path = PATH.resolve(PATH.dirname(current_path), link); + + var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); + current = lookup.node; + + if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); } - var link = FS.absolutePath(current.link, traversed.join('/')); - ret = FS.analyzePath([link].concat(path).join('/'), - dontResolveLastLink, linksVisited + 1); -#if FS_LOG - log(); -#endif - return ret; - } - traversed.push(target); - if (path.length == 0) { - ret.exists = true; - ret.path = traversed.join('/'); - ret.object = current; } } } -#if FS_LOG - log(); -#endif - return ret; + + return { path: current_path, node: current }; }, - // Finds the file system object at a given path. If dontResolveLastLink is - // set to true and the object is a symbolic link, it will be returned as is - // instead of being resolved. Links embedded in the path are still resolved. - findObject: function(path, dontResolveLastLink) { - var ret = FS.analyzePath(path, dontResolveLastLink); - if (ret.exists) { - return ret.object; + getPath: function (node) { + var path; + while (true) { + if (FS.isRoot(node)) { + return path ? PATH.join(node.mount.mountpoint, path) : node.mount.mountpoint; + } + path = path ? PATH.join(node.name, path) : node.name; + node = node.parent; + } + }, + + // + // permissions + // + flagModes: { + '"r"': {{{ cDefine('O_RDONLY') }}}, + '"rs"': {{{ cDefine('O_RDONLY') }}} | {{{ cDefine('O_SYNC') }}}, + '"r+"': {{{ cDefine('O_RDWR') }}}, + '"w"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"wx"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}}, + '"xw"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}}, + '"w+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + '"wx+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}}, + '"xw+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}}, + '"a"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"ax"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}}, + '"xa"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}} | {{{ cDefine('O_EXCL') }}}, + '"a+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + '"ax+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}}, + '"xa+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}} | {{{ cDefine('O_EXCL') }}} + }, + // convert the 'r', 'r+', etc. to it's corresponding set of O_* flags + modeStringToFlags: function (str) { + var flags = FS.flagModes[str]; + if (typeof flags === 'undefined') { + throw new Error('Unknown file open mode: ' + str); + } + return flags; + }, + // convert O_* bitmask to a string for nodePermissions + flagsToPermissionString: function (flag) { + var accmode = flag & {{{ cDefine('O_ACCMODE') }}}; + var perms = ['r', 'w', 'rw'][accmode]; + if ((flag & {{{ cDefine('O_TRUNC') }}})) { + perms += 'w'; + } + return perms; + }, + nodePermissions: function (node, perms) { + if (FS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.indexOf('r') !== -1 && !(node.mode & {{{ cDefine('S_IRUGO') }}})) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf('w') !== -1 && !(node.mode & {{{ cDefine('S_IWUGO') }}})) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf('x') !== -1 && !(node.mode & {{{ cDefine('S_IXUGO') }}})) { + return ERRNO_CODES.EACCES; + } + return 0; + }, + mayLookup: function (dir) { + return FS.nodePermissions(dir, 'x'); + }, + mayMknod: function (mode) { + switch (mode & {{{ cDefine('S_IFMT') }}}) { + case {{{ cDefine('S_IFREG') }}}: + case {{{ cDefine('S_IFCHR') }}}: + case {{{ cDefine('S_IFBLK') }}}: + case {{{ cDefine('S_IFIFO') }}}: + case {{{ cDefine('S_IFSOCK') }}}: + return 0; + default: + return ERRNO_CODES.EINVAL; + } + }, + mayCreate: function (dir, name) { + try { + var node = FS.lookupNode(dir, name); + return ERRNO_CODES.EEXIST; + } catch (e) { + } + return FS.nodePermissions(dir, 'wx'); + }, + mayDelete: function (dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var err = FS.nodePermissions(dir, 'wx'); + if (err) { + return err; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return ERRNO_CODES.ENOTDIR; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.currentPath) { + return ERRNO_CODES.EBUSY; + } } else { - ___setErrNo(ret.error); - return null; + if (FS.isDir(node.mode)) { + return ERRNO_CODES.EISDIR; + } } + return 0; + }, + mayOpen: function (node, flags) { + if (!node) { + return ERRNO_CODES.ENOENT; + } + if (FS.isLink(node.mode)) { + return ERRNO_CODES.ELOOP; + } else if (FS.isDir(node.mode)) { + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY')}}} || // opening for write + (flags & {{{ cDefine('O_TRUNC') }}})) { + return ERRNO_CODES.EISDIR; + } + } + return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); }, - // Creates a file system record: file, link, device or folder. - createObject: function(parent, name, properties, canRead, canWrite) { -#if FS_LOG - Module['print']('FS.createObject("' + parent + '", ' + - '"' + name + '", ' + - JSON.stringify(properties) + ', ' + - canRead + ', ' + - canWrite + ')'); -#endif - if (!parent) parent = '/'; - if (typeof parent === 'string') parent = FS.findObject(parent); - if (!parent) { - ___setErrNo(ERRNO_CODES.EACCES); - throw new Error('Parent path must exist.'); + // + // devices + // + // each character device consists of a device id + stream operations. + // when a character device node is created (e.g. /dev/stdin) it is + // assigned a device id that lets us map back to the actual device. + // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. + // however, once opened, the stream's operations are overridden with + // the operations of the device its underlying node maps back to. + chrdev_stream_ops: { + open: function (stream) { + var device = FS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + }, + llseek: function () { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); } - if (!parent.isFolder) { - ___setErrNo(ERRNO_CODES.ENOTDIR); - throw new Error('Parent must be a folder.'); + }, + major: function (dev) { + return ((dev) >> 8); + }, + minor: function (dev) { + return ((dev) & 0xff); + }, + makedev: function (ma, mi) { + return ((ma) << 8 | (mi)); + }, + registerDevice: function (dev, ops) { + FS.devices[dev] = { stream_ops: ops }; + }, + getDevice: function (dev) { + return FS.devices[dev]; + }, + + // + // streams + // + MAX_OPEN_FDS: 4096, + nextfd: function (fd_start, fd_end) { + fd_start = fd_start || 1; + fd_end = fd_end || FS.MAX_OPEN_FDS; + for (var fd = fd_start; fd <= fd_end; fd++) { + if (!FS.streams[fd]) { + return fd; + } } - if (!parent.write && !FS.ignorePermissions) { - ___setErrNo(ERRNO_CODES.EACCES); - throw new Error('Parent folder must be writeable.'); + throw new FS.ErrnoError(ERRNO_CODES.EMFILE); + }, + getStream: function (fd) { + return FS.streams[fd]; + }, + createStream: function (stream, fd_start, fd_end) { + var fd = FS.nextfd(fd_start, fd_end); + stream.fd = fd; + // compatibility + Object.defineProperty(stream, 'object', { + get: function () { return stream.node; }, + set: function (val) { stream.node = val; } + }); + Object.defineProperty(stream, 'isRead', { + get: function () { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; } + }); + Object.defineProperty(stream, 'isWrite', { + get: function () { return (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; } + }); + Object.defineProperty(stream, 'isAppend', { + get: function () { return (stream.flags & {{{ cDefine('O_APPEND') }}}); } + }); + FS.streams[fd] = stream; + return stream; + }, + closeStream: function (fd) { + FS.streams[fd] = null; + }, + + // + // general + // + createDefaultDirectories: function () { + VFS.mkdir('/tmp', 0777); + }, + createDefaultDevices: function () { + // create /dev + VFS.mkdir('/dev', 0777); + // setup /dev/null + FS.registerDevice(FS.makedev(1, 3), { + read: function () { return 0; }, + write: function () { return 0; } + }); + VFS.mkdev('/dev/null', 0666, FS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using Module['printErr'] + // so we register a second tty just for it. + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + VFS.mkdev('/dev/tty', 0666, FS.makedev(5, 0)); + VFS.mkdev('/dev/tty1', 0666, FS.makedev(6, 0)); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + VFS.mkdir('/dev/shm', 0777); + VFS.mkdir('/dev/shm/tmp', 0777); + }, + createStandardStreams: function () { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes FS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (Module['stdin']) { + FS.createDevice('/dev', 'stdin', Module['stdin']); + } else { + VFS.symlink('/dev/tty', '/dev/stdin'); } - if (!name || name == '.' || name == '..') { - ___setErrNo(ERRNO_CODES.ENOENT); - throw new Error('Name must not be empty.'); + if (Module['stdout']) { + FS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + VFS.symlink('/dev/tty', '/dev/stdout'); } - if (parent.contents.hasOwnProperty(name)) { - ___setErrNo(ERRNO_CODES.EEXIST); - throw new Error("Can't overwrite object."); + if (Module['stderr']) { + FS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + VFS.symlink('/dev/tty1', '/dev/stderr'); } - parent.contents[name] = { - read: canRead === undefined ? true : canRead, - write: canWrite === undefined ? false : canWrite, - timestamp: Date.now(), - inodeNumber: FS.nextInode++ - }; - for (var key in properties) { - if (properties.hasOwnProperty(key)) { - parent.contents[name][key] = properties[key]; + // open default streams for the stdin, stdout and stderr devices + var stdin = VFS.open('/dev/stdin', 'r'); + {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 'stdin.fd', 'void*') }}}; + assert(stdin.fd === 1, 'invalid handle for stdin (' + stdin.fd + ')'); + + var stdout = VFS.open('/dev/stdout', 'w'); + {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 'stdout.fd', 'void*') }}}; + assert(stdout.fd === 2, 'invalid handle for stdout (' + stdout.fd + ')'); + + var stderr = VFS.open('/dev/stderr', 'w'); + {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 'stderr.fd', 'void*') }}}; + assert(stderr.fd === 3, 'invalid handle for stderr (' + stderr.fd + ')'); + }, + staticInit: function () { + FS.root = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); + VFS.mount(MEMFS, {}, '/'); + + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + }, + init: function (input, output, error) { + assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); + FS.init.initialized = true; + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + Module['stdin'] = input || Module['stdin']; + Module['stdout'] = output || Module['stdout']; + Module['stderr'] = error || Module['stderr']; + + FS.createStandardStreams(); + }, + quit: function () { + FS.init.initialized = false; + for (var i = 0; i < FS.streams.length; i++) { + var stream = FS.streams[i]; + if (!stream) { + continue; } + VFS.close(stream); } + }, - return parent.contents[name]; + // + // compatibility + // + getMode: function (canRead, canWrite) { + var mode = 0; + if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; + if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}}; + return mode; + }, + joinPath: function (parts, forceRelative) { + var path = PATH.join.apply(null, parts); + if (forceRelative && path[0] == '/') path = path.substr(1); + return path; }, - // Creates a folder. - createFolder: function(parent, name, canRead, canWrite) { - var properties = {isFolder: true, isDevice: false, contents: {}}; - return FS.createObject(parent, name, properties, canRead, canWrite); + absolutePath: function (relative, base) { + return PATH.resolve(base, relative); }, - // Creates a folder and all its missing parents. - createPath: function(parent, path, canRead, canWrite) { - var current = FS.findObject(parent); - if (current === null) throw new Error('Invalid parent.'); - path = path.split('/').reverse(); - while (path.length) { - var part = path.pop(); + 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; - if (!current.contents.hasOwnProperty(part)) { - FS.createFolder(current, part, canRead, canWrite); + var current = PATH.join(parent, part); + try { + VFS.mkdir(current, 0777); + } catch (e) { + // ignore EEXIST } - current = current.contents[part]; + parent = current; } return current; }, - - // Creates a file record, given specific properties. - createFile: function(parent, name, properties, canRead, canWrite) { - properties.isFolder = false; - return FS.createObject(parent, name, properties, canRead, canWrite); + createFile: function (parent, name, properties, canRead, canWrite) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + return VFS.create(path, mode); }, - // Creates a file record from existing data. - createDataFile: function(parent, name, data, canRead, canWrite) { - if (typeof data === 'string') { - var dataArray = new Array(data.length); - for (var i = 0, len = data.length; i < len; ++i) dataArray[i] = data.charCodeAt(i); - data = dataArray; - } - var properties = { - isDevice: false, - contents: data.subarray ? data.subarray(0) : data // as an optimization, create a new array wrapper (not buffer) here, to help JS engines understand this object - }; - return FS.createFile(parent, name, properties, canRead, canWrite); + createDataFile: function (parent, name, data, canRead, canWrite) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = FS.getMode(canRead, canWrite); + var node = VFS.create(path, mode); + if (data) { + if (typeof data === 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + VFS.chmod(path, mode | {{{ cDefine('S_IWUGO') }}}); + var stream = VFS.open(path, 'w'); + VFS.write(stream, data, 0, data.length, 0); + VFS.close(stream); + VFS.chmod(path, mode); + } + return node; + }, + createDevice: function (parent, name, input, output) { + var path = PATH.join(typeof parent === 'string' ? parent : FS.getPath(parent), name); + var mode = input && output ? 0777 : (input ? 0333 : 0555); + if (!FS.createDevice.major) FS.createDevice.major = 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + FS.registerDevice(dev, { + open: function (stream) { + stream.seekable = false; + }, + close: function (stream) { + // flush any pending line data + if (output.buffer && output.buffer.length) { + output({{{ charCode('\n') }}}); + } + }, + read: function (stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || 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. @@ -341,7 +736,6 @@ LibraryManager.library = { LazyUint8Array.prototype.setDataGetter = function(getter) { this.getter = getter; } - LazyUint8Array.prototype.cacheLength = function() { // Find length var xhr = new XMLHttpRequest(); @@ -423,7 +817,45 @@ LibraryManager.library = { var properties = { isDevice: false, url: url }; } - return FS.createFile(parent, name, properties, canRead, canWrite); + 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. @@ -437,9 +869,10 @@ LibraryManager.library = { // 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__deps: ['$PATH'], createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) { Browser.init(); - var fullname = FS.joinPath([parent, name], true); + var fullname = PATH.join(parent, name).substr(1); function processData(byteArray) { function finish(byteArray) { if (!dontCreateFile) { @@ -469,212 +902,839 @@ LibraryManager.library = { } 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; + // compatibility update FS.root if we mount to / + if (mountpoint === '/') { + FS.root = mount.root; + } + } + return root; }, - // Creates a link to a specific local path. - createLink: function(parent, name, target, canRead, canWrite) { - var properties = {isDevice: false, link: target}; - return FS.createFile(parent, name, properties, canRead, canWrite); + lookup: function (parent, name) { + return parent.node_ops.lookup(parent, name); }, - // Creates a character device with input and output callbacks: - // input: Takes no parameters, returns a byte value or null if no data is - // currently available. - // output: Takes a byte value; doesn't return anything. Can also be passed - // null to perform a flush of any cached data. - createDevice: function(parent, name, input, output) { - if (!(input || output)) { - throw new Error('A device must have at least one callback defined.'); - } - var ops = {isDevice: true, input: input, output: output}; - return FS.createFile(parent, name, ops, Boolean(input), Boolean(output)); + // 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); }, - // 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; + // 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 { - throw new Error('Cannot load without read() or XMLHttpRequest.'); + node = path; } - if (!success) ___setErrNo(ERRNO_CODES.EIO); - return success; + 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() + }); }, - staticInit: function () { - // The main file system tree. All the contents are inside this. - FS.root = { - read: true, - write: true, - isFolder: true, - isDevice: false, - timestamp: Date.now(), - inodeNumber: 1, - contents: {} - }; - // Create the temporary folder, if not already created + 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 { - FS.createFolder('/', 'tmp', true, true); - } catch(e) {} - FS.createFolder('/', 'dev', true, true); + var lookup = FS.lookupPath(path, { + follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}}) + }); + node = lookup.node; + path = lookup.path; + } catch (e) { + // ignore + } + // perhaps we need to create the node + if ((flags & {{{ cDefine('O_CREAT') }}})) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if ((flags & {{{ cDefine('O_EXCL') }}})) { + throw new FS.ErrnoError(ERRNO_CODES.EEXIST); + } + } else { + // node doesn't exist, try to create it + node = VFS.mknod(path, mode, 0); + } + } + if (!node) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + // can't truncate a device + if (FS.isChrdev(node.mode)) { + flags &= ~{{{ cDefine('O_TRUNC') }}}; + } + // check permissions + var err = FS.mayOpen(node, flags); + if (err) { + throw new FS.ErrnoError(err); + } + // do truncation if necessary + if ((flags & {{{ cDefine('O_TRUNC')}}})) { + VFS.truncate(node, 0); + } + // register the stream with the filesystem + var stream = FS.createStream({ + path: path, + node: node, + flags: flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false + }, fd_start, fd_end); + // call the new stream's open function + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + return stream; }, - // Initializes the filesystems with stdin/stdout/stderr devices, given - // optional handlers. - init: function(input, output, error) { - // Make sure we initialize only once. - 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; + close: function (stream) { + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + }, + llseek: function (stream, offset, whence) { + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + return stream.stream_ops.llseek(stream, offset, whence); + }, + readdir: function (stream) { + if (!stream.stream_ops.readdir) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + return stream.stream_ops.readdir(stream); + }, + read: function (stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var seeking = true; + if (typeof position === 'undefined') { + position = stream.position; + seeking = false; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write: function (stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var seeking = true; + if (typeof position === 'undefined') { + position = stream.position; + seeking = false; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + if (stream.flags & {{{ cDefine('O_APPEND') }}}) { + // seek to the end before writing in append mode + VFS.llseek(stream, 0, {{{ cDefine('SEEK_END') }}}); + } + var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesWritten; + return bytesWritten; + }, + allocate: function (stream, offset, length) { + if (offset < 0 || length <= 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (!FS.isFile(stream.node.mode) && !FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + if (!stream.stream_ops.allocate) { + throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP); + } + stream.stream_ops.allocate(stream, offset, length); + }, + mmap: function (stream, buffer, offset, length, position, prot, flags) { + // TODO if PROT is PROT_WRITE, make sure we have write acccess + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new FS.ErrnoError(ERRNO_CODES.EACCES); + } + if (!stream.stream_ops.mmap) { + throw new FS.errnoError(ERRNO_CODES.ENODEV); + } + return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags); + } + }, - // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here - input = input || Module['stdin']; - output = output || Module['stdout']; - error = error || Module['stderr']; - - // Default handlers. - var stdinOverridden = true, stdoutOverridden = true, stderrOverridden = true; - if (!input) { - stdinOverridden = false; - input = function() { - if (!input.cache || !input.cache.length) { - var result; - if (typeof window != 'undefined' && - typeof window.prompt == 'function') { - // Browser. - result = window.prompt('Input: '); - if (result === null) result = String.fromCharCode(0); // cancel ==> EOF - } else if (typeof readline == 'function') { - // Command line. - result = readline(); + $MEMFS__deps: ['$FS'], + $MEMFS: { + mount: function (mount) { + return MEMFS.create_node(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); + }, + create_node: function (parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + // no supported + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + var node = FS.createNode(parent, name, mode, dev); + node.node_ops = MEMFS.node_ops; + if (FS.isDir(node.mode)) { + node.stream_ops = MEMFS.stream_ops; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.stream_ops = MEMFS.stream_ops; + node.contents = []; + } else if (FS.isLink(node.mode)) { + node.stream_ops = MEMFS.stream_ops; + } else if (FS.isChrdev(node.mode)) { + node.stream_ops = FS.chrdev_stream_ops; + } + node.timestamp = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + } + return node; + }, + node_ops: { + getattr: function (node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.contents.length; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr: function (node, attr) { + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + var contents = node.contents; + if (attr.size < contents.length) contents.length = attr.size; + else while (attr.size > contents.length) contents.push(0); + } + }, + lookup: function (parent, name) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + }, + mknod: function (parent, name, mode, dev) { + return MEMFS.create_node(parent, name, mode, dev); + }, + rename: function (old_node, new_dir, new_name) { + // if we're overwriting a directory at new_name, make sure it's empty. + if (FS.isDir(old_node.mode)) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) { + } + if (new_node) { + for (var i in new_node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); } - if (!result) result = ''; - input.cache = intArrayFromString(result + '\n', true); } - return input.cache.shift(); - }; - } - var utf8 = new Runtime.UTF8Processor(); - function createSimpleOutput() { - var fn = function (val) { - if (val === null || val === {{{ charCode('\n') }}}) { - fn.printer(fn.buffer.join('')); - fn.buffer = []; - } else { - fn.buffer.push(utf8.processCChar(val)); + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + old_node.name = new_name; + new_dir.contents[new_name] = old_node; + }, + unlink: function (parent, name) { + delete parent.contents[name]; + }, + rmdir: function (parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + delete parent.contents[name]; + }, + symlink: function (parent, newname, oldpath) { + var node = MEMFS.create_node(parent, newname, 0777 | {{{ cDefine('S_IFLNK') }}}, 0); + node.link = oldpath; + return node; + }, + readlink: function (node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return node.link; + }, + }, + stream_ops: { + open: function (stream) { + if (FS.isDir(stream.node.mode)) { + // cache off the directory entries when open'd + var entries = ['.', '..'] + for (var key in stream.node.contents) { + if (!stream.node.contents.hasOwnProperty(key)) { + continue; + } + entries.push(key); } - }; - return fn; - } - if (!output) { - stdoutOverridden = false; - output = createSimpleOutput(); - } - if (!output.printer) output.printer = Module['print']; - if (!output.buffer) output.buffer = []; - if (!error) { - stderrOverridden = false; - error = createSimpleOutput(); - } - if (!error.printer) error.printer = Module['printErr']; - if (!error.buffer) error.buffer = []; - - // Create the I/O devices. - var stdin = FS.createDevice('/dev', 'stdin', input); - stdin.isTerminal = !stdinOverridden; - var stdout = FS.createDevice('/dev', 'stdout', null, output); - stdout.isTerminal = !stdoutOverridden; - var stderr = FS.createDevice('/dev', 'stderr', null, error); - stderr.isTerminal = !stderrOverridden; - FS.createDevice('/dev', 'tty', input, output); - FS.createDevice('/dev', 'null', function(){}, function(){}); - - // Create default streams. - FS.streams[1] = { - path: '/dev/stdin', - object: stdin, - position: 0, - isRead: true, - isWrite: false, - isAppend: false, - error: false, - eof: false, - ungotten: [] - }; - FS.streams[2] = { - path: '/dev/stdout', - object: stdout, - position: 0, - isRead: false, - isWrite: true, - isAppend: false, - error: false, - eof: false, - ungotten: [] - }; - FS.streams[3] = { - path: '/dev/stderr', - object: stderr, - position: 0, - isRead: false, - isWrite: true, - isAppend: false, - error: false, - eof: false, - ungotten: [] - }; - // TODO: put these low in memory like we used to assert on: assert(Math.max(_stdin, _stdout, _stderr) < 15000); // make sure these are low, we flatten arrays with these - {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 1, 'void*') }}}; - {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 2, 'void*') }}}; - {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 3, 'void*') }}}; - - // Other system paths - FS.createPath('/', 'dev/shm/tmp', true, true); // temp files - - // Newlib initialization - for (var i = FS.streams.length; i < Math.max(_stdin, _stdout, _stderr) + {{{ QUANTUM_SIZE }}}; i++) { - FS.streams[i] = null; // Make sure to keep FS.streams dense - } - FS.streams[_stdin] = FS.streams[1]; - FS.streams[_stdout] = FS.streams[2]; - FS.streams[_stderr] = FS.streams[3]; -#if ASSERTIONS - FS.checkStreams(); - // see previous TODO on stdin etc.: assert(FS.streams.length < 1024); // at this early stage, we should not have a large set of file descriptors - just a few + stream.entries = entries; + } + }, + read: function (stream, buffer, offset, length, position) { + var contents = stream.node.contents; + var size = Math.min(contents.length - position, length); +#if USE_TYPED_ARRAYS == 2 + if (contents.subarray) { // typed array + buffer.set(contents.subarray(position, position + size), offset); + } else #endif - allocate([ allocate( - {{{ Runtime.QUANTUM_SIZE === 4 ? '[0, 0, 0, 0, _stdin, 0, 0, 0, _stdout, 0, 0, 0, _stderr, 0, 0, 0]' : '[0, _stdin, _stdout, _stderr]' }}}, - 'void*', ALLOC_NORMAL) ], 'void*', ALLOC_NONE, {{{ makeGlobalUse('__impure_ptr') }}}); + { + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } + return size; + }, + write: function (stream, buffer, offset, length, position) { + var contents = stream.node.contents; + while (contents.length < position) contents.push(0); + for (var i = 0; i < length; i++) { + contents[position + i] = buffer[offset + i]; + } + stream.node.timestamp = Date.now(); + return length; + }, + llseek: function (stream, offset, whence) { + var position = offset; + if (whence === 1) { // SEEK_CUR. + position += stream.position; + } else if (whence === 2) { // SEEK_END. + if (FS.isFile(stream.node.mode)) { + position += stream.node.contents.length; + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + stream.ungotten = []; + stream.position = position; + return position; + }, + readdir: function (stream) { + return stream.entries; + }, + allocate: function (stream, offset, length) { + var contents = stream.node.contents; + var limit = offset + length; + while (limit > contents.length) contents.push(0); + }, + mmap: function (stream, buffer, offset, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if (!(flags & {{{ cDefine('MAP_PRIVATE') }}})) { + // We can't emulate MAP_SHARED when the file is not backed by the buffer + // we're mapping to (e.g. the HEAP buffer). + assert(contents.buffer === buffer || contents.buffer === buffer.buffer); + allocated = false; + ptr = contents.byteOffset; + } else { + // Try to avoid unnecessary slices. + if (position > 0 || position + length < contents.length) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call(contents, position, position + length); + } + } + allocated = true; + ptr = _malloc(length); + if (!ptr) { + throw new FS.ErrnoError(ERRNO_CODES.ENOMEM); + } + buffer.set(contents, ptr); + } + return { ptr: ptr, allocated: allocated }; + }, + } + }, + + $SOCKFS__postset: '__ATINIT__.push({ func: function() { SOCKFS.root = VFS.mount(SOCKFS, {}, null); } });', + $SOCKFS__deps: ['$FS'], + $SOCKFS: { + mount: function (mount) { + var node = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); + node.node_ops = SOCKFS.node_ops; + node.stream_ops = SOCKFS.stream_ops; + return node; }, - - quit: function() { - if (!FS.init.initialized) return; - // Flush any partially-printed lines in stdout and stderr. Careful, they may have been closed - if (FS.streams[2] && FS.streams[2].object.output.buffer.length > 0) FS.streams[2].object.output({{{ charCode('\n') }}}); - if (FS.streams[3] && FS.streams[3].object.output.buffer.length > 0) FS.streams[3].object.output({{{ charCode('\n') }}}); + node_ops: { }, - - // Standardizes a path. Useful for making comparisons of pathnames work in a consistent manner. - // For example, ./file and file are really the same, so this function will remove ./ - standardizePath: function(path) { - if (path.substr(0, 2) == './') path = path.substr(2); - return path; + stream_ops: { }, + websocket_sock_ops: { + } + }, - deleteFile: function(path) { - path = FS.analyzePath(path); - if (!path.parentExists || !path.exists) { - throw 'Invalid path ' + path; + $TTY__deps: ['$FS'], + $TTY: { + ttys: [], + register: function (dev, ops) { + TTY.ttys[dev] = { input: [], output: [], ops: ops }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open: function (stream) { + // this wouldn't be required if the library wasn't eval'd at first... + if (!TTY.utf8) { + TTY.utf8 = new Runtime.UTF8Processor(); + } + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + stream.tty = tty; + stream.seekable = false; + }, + close: function (stream) { + // flush any pending line data + if (stream.tty.output.length) { + stream.tty.ops.put_char(stream.tty, {{{ charCode('\n') }}}); + } + }, + read: function (stream, buffer, offset, length, pos /* ignored */) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function (stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + for (var i = 0; i < length; i++) { + try { + stream.tty.ops.put_char(stream.tty, buffer[offset+i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }, + // NOTE: This is weird to support stdout and stderr + // overrides in addition to print and printErr orverrides. + default_tty_ops: { + get_char: function (tty) { + if (!tty.input.length) { + var result = null; + if (ENVIRONMENT_IS_NODE) { + if (process.stdin.destroyed) { + return undefined; + } + result = process.stdin.read(); + } else if (typeof window != 'undefined' && + typeof window.prompt == 'function') { + // Browser. + result = window.prompt('Input: '); // returns null on cancel + if (result !== null) { + result += '\n'; + } + } else if (typeof readline == 'function') { + // Command line. + result = readline(); + if (result !== null) { + result += '\n'; + } + } + if (!result) { + return null; + } + tty.input = intArrayFromString(result, true); + } + return tty.input.shift(); + }, + put_char: function (tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + Module['print'](tty.output.join('')); + tty.output = []; + } else { + tty.output.push(TTY.utf8.processCChar(val)); + } + } + }, + default_tty1_ops: { + put_char: function (tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + Module['printErr'](tty.output.join('')); + tty.output = []; + } else { + tty.output.push(TTY.utf8.processCChar(val)); + } } - delete path.parentObject.contents[path.name]; } }, +#endif + // ========================================================================== // dirent.h @@ -686,160 +1746,125 @@ LibraryManager.library = { ['i32', 'd_off'], ['i32', 'd_reclen'], ['i32', 'd_type']]), - opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], + opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout', 'open'], opendir: function(dirname) { // DIR *opendir(const char *dirname); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/opendir.html // NOTE: Calculating absolute path redundantly since we need to associate it // with the opened stream. - var path = FS.absolutePath(Pointer_stringify(dirname)); - if (path === null) { + var path = Pointer_stringify(dirname); + if (!path) { ___setErrNo(ERRNO_CODES.ENOENT); return 0; } - var target = FS.findObject(path); - if (target === null) return 0; - if (!target.isFolder) { - ___setErrNo(ERRNO_CODES.ENOTDIR); + var node; + try { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } catch (e) { + ___setErrNo(e.errno); return 0; - } else if (!target.read) { - ___setErrNo(ERRNO_CODES.EACCES); + } + if (!FS.isDir(node.mode)) { + ___setErrNo(ERRNO_CODES.ENOTDIR); return 0; } - var contents = []; - for (var key in target.contents) contents.push(key); - var id = FS.createFileHandle({ - path: path, - object: target, - // An index into contents. Special values: -2 is ".", -1 is "..". - position: -2, - isRead: true, - isWrite: false, - isAppend: false, - error: false, - eof: false, - ungotten: [], - // Folder-specific properties: - // Remember the contents at the time of opening in an array, so we can - // seek between them relying on a single order. - contents: contents, - // Each stream has its own area for readdir() returns. - currentEntry: _malloc(___dirent_struct_layout.__size__) - }); -#if ASSERTIONS - FS.checkStreams(); -#endif - return id; + var err = _open(dirname, {{{ cDefine('O_RDONLY') }}}, allocate([0, 0, 0, 0], 'i32', ALLOC_STACK)); + // open returns 0 on failure, not -1 + return err === -1 ? 0 : err; }, - closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'close'], closedir: function(dirp) { // int closedir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html - if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - ___setErrNo(ERRNO_CODES.EBADF); - return -1; - } else { - _free(FS.streams[dirp].currentEntry); - FS.streams[dirp] = null; - return 0; - } + return _close(dirp); }, telldir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], telldir: function(dirp) { // long int telldir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html - if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { + var stream = FS.getStream(dirp); + if (!stream || !FS.isDir(stream.node.mode)) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - return FS.streams[dirp].position; } + return stream.position; }, - seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'lseek'], seekdir: function(dirp, loc) { // void seekdir(DIR *dirp, long int loc); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/seekdir.html - if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - ___setErrNo(ERRNO_CODES.EBADF); - } else { - var entries = 0; - for (var key in FS.streams[dirp].contents) entries++; - if (loc >= entries) { - ___setErrNo(ERRNO_CODES.EINVAL); - } else { - FS.streams[dirp].position = loc; - } - } + _lseek(dirp, loc, {{{ cDefine('SEEK_SET') }}}); }, rewinddir__deps: ['seekdir'], rewinddir: function(dirp) { // void rewinddir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/rewinddir.html - _seekdir(dirp, -2); + _seekdir(dirp, 0); }, readdir_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], readdir_r: function(dirp, entry, result) { // int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html - if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { + var stream = FS.getStream(dirp); + if (!stream) { return ___setErrNo(ERRNO_CODES.EBADF); } - var stream = FS.streams[dirp]; - var loc = stream.position; - var entries = 0; - for (var key in stream.contents) entries++; - if (loc < -2 || loc >= entries) { + var entries; + try { + entries = VFS.readdir(stream); + } catch (e) { + return ___setErrNo(e.errno); + } + if (stream.position < 0 || stream.position >= entries.length) { {{{ makeSetValue('result', '0', '0', 'i8*') }}} - } else { - var name, inode, type; - if (loc === -2) { - name = '.'; - inode = 1; // Really undefined. - type = 4; //DT_DIR - } else if (loc === -1) { - name = '..'; - inode = 1; // Really undefined. - type = 4; //DT_DIR - } else { - var object; - name = stream.contents[loc]; - object = stream.object.contents[name]; - inode = object.inodeNumber; - type = object.isDevice ? 2 // DT_CHR, character device. - : object.isFolder ? 4 // DT_DIR, directory. - : object.link !== undefined ? 10 // DT_LNK, symbolic link. - : 8; // DT_REG, regular file. - } - stream.position++; - var offsets = ___dirent_struct_layout; - {{{ makeSetValue('entry', 'offsets.d_ino', 'inode', 'i32') }}} - {{{ makeSetValue('entry', 'offsets.d_off', 'stream.position', 'i32') }}} - {{{ makeSetValue('entry', 'offsets.d_reclen', 'name.length + 1', 'i32') }}} - for (var i = 0; i < name.length; i++) { - {{{ makeSetValue('entry + offsets.d_name', 'i', 'name.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('entry + offsets.d_name', 'i', '0', 'i8') }}} - {{{ makeSetValue('entry', 'offsets.d_type', 'type', 'i8') }}} - {{{ makeSetValue('result', '0', 'entry', 'i8*') }}} + return; } + var id; + var type; + var name = entries[stream.position]; + var offset = stream.position + 1; + if (!name.indexOf('.')) { + id = 1; + type = 4; + } else { + var child = FS.lookupNode(stream.node, name); + id = child.id; + type = FS.isChrdev(child.mode) ? 2 : // DT_CHR, character device. + FS.isDir(child.mode) ? 4 : // DT_DIR, directory. + FS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link. + 8; // DT_REG, regular file. + } + {{{ makeSetValue('entry', '___dirent_struct_layout.d_ino', 'id', 'i32') }}} + {{{ makeSetValue('entry', '___dirent_struct_layout.d_off', 'offset', 'i32') }}} + {{{ makeSetValue('entry', '___dirent_struct_layout.d_reclen', 'name.length + 1', 'i32') }}} + for (var i = 0; i < name.length; i++) { + {{{ makeSetValue('entry + ___dirent_struct_layout.d_name', 'i', 'name.charCodeAt(i)', 'i8') }}} + } + {{{ makeSetValue('entry + ___dirent_struct_layout.d_name', 'i', '0', 'i8') }}} + {{{ makeSetValue('entry', '___dirent_struct_layout.d_type', 'type', 'i8') }}} + {{{ makeSetValue('result', '0', 'entry', 'i8*') }}} + stream.position++; return 0; }, readdir__deps: ['readdir_r', '__setErrNo', '$ERRNO_CODES'], readdir: function(dirp) { // struct dirent *readdir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html - if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { + var stream = FS.getStream(dirp); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return 0; - } else { - if (!_readdir.result) _readdir.result = _malloc(4); - _readdir_r(dirp, FS.streams[dirp].currentEntry, _readdir.result); - if ({{{ makeGetValue(0, '_readdir.result', 'i8*') }}} === 0) { - return 0; - } else { - return FS.streams[dirp].currentEntry; - } } + // TODO Is it supposed to be safe to execute multiple readdirs? + if (!_readdir.entry) _readdir.entry = _malloc(___dirent_struct_layout.__size__); + if (!_readdir.result) _readdir.result = _malloc(4); + var err = _readdir_r(dirp, _readdir.entry, _readdir.result); + if (err) { + ___setErrNo(err); + return 0; + } + return {{{ makeGetValue(0, '_readdir.result', 'i8*') }}}; }, __01readdir64_: 'readdir', // TODO: Check if we need to link any other aliases. @@ -864,10 +1889,14 @@ LibraryManager.library = { } else { time = Date.now(); } - var file = FS.findObject(Pointer_stringify(path)); - if (file === null) return -1; - file.timestamp = time; - return 0; + path = Pointer_stringify(path); + try { + VFS.utime(path, time, time); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, utimes: function() { throw 'utimes not implemented' }, @@ -960,67 +1989,27 @@ LibraryManager.library = { // int stat(const char *path, struct stat *buf); // NOTE: dontResolveLastLink is a shortcut for lstat(). It should never be // used in client code. - var obj = FS.findObject(Pointer_stringify(path), dontResolveLastLink); - if (obj === null || !FS.forceLoadFile(obj)) return -1; - - var offsets = ___stat_struct_layout; - - // Constants. - {{{ makeSetValue('buf', 'offsets.st_nlink', '1', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_uid', '0', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_gid', '0', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_blksize', '4096', 'i32') }}} - - // Variables. - {{{ makeSetValue('buf', 'offsets.st_ino', 'obj.inodeNumber', 'i32') }}} - var time = Math.floor(obj.timestamp / 1000); - if (offsets.st_atime === undefined) { - offsets.st_atime = offsets.st_atim.tv_sec; - offsets.st_mtime = offsets.st_mtim.tv_sec; - offsets.st_ctime = offsets.st_ctim.tv_sec; - var nanosec = (obj.timestamp % 1000) * 1000; - {{{ makeSetValue('buf', 'offsets.st_atim.tv_nsec', 'nanosec', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_mtim.tv_nsec', 'nanosec', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_ctim.tv_nsec', 'nanosec', 'i32') }}} - } - {{{ makeSetValue('buf', 'offsets.st_atime', 'time', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_mtime', 'time', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_ctime', 'time', 'i32') }}} - var mode = 0; - var size = 0; - var blocks = 0; - var dev = 0; - var rdev = 0; - if (obj.isDevice) { - // Device numbers reuse inode numbers. - dev = rdev = obj.inodeNumber; - size = blocks = 0; - mode = 0x2000; // S_IFCHR. - } else { - dev = 1; - rdev = 0; - // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), - // but this is not required by the standard. - if (obj.isFolder) { - size = 4096; - blocks = 1; - mode = 0x4000; // S_IFDIR. - } else { - var data = obj.contents || obj.link; - size = data.length; - blocks = Math.ceil(data.length / 4096); - mode = obj.link === undefined ? 0x8000 : 0xA000; // S_IFREG, S_IFLNK. - } + path = typeof path !== 'string' ? Pointer_stringify(path) : path; + try { + var stat = dontResolveLastLink ? VFS.lstat(path) : VFS.stat(path); + {{{ makeSetValue('buf', '___stat_struct_layout.st_dev', 'stat.dev', 'i32') }}}; + {{{ makeSetValue('buf', '___stat_struct_layout.st_ino', 'stat.ino', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_mode', 'stat.mode', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_nlink', 'stat.nlink', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_uid', 'stat.uid', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_gid', 'stat.gid', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_rdev', 'stat.rdev', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_size', 'stat.size', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_atime', 'Math.floor(stat.atime.getTime() / 1000)', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_mtime', 'Math.floor(stat.mtime.getTime() / 1000)', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_ctime', 'Math.floor(stat.ctime.getTime() / 1000)', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_blksize', '4096', 'i32') }}} + {{{ makeSetValue('buf', '___stat_struct_layout.st_blocks', 'stat.blocks', 'i32') }}} + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; } - {{{ makeSetValue('buf', 'offsets.st_dev', 'dev', 'i32') }}}; - {{{ makeSetValue('buf', 'offsets.st_rdev', 'rdev', 'i32') }}}; - {{{ makeSetValue('buf', 'offsets.st_size', 'size', 'i32') }}} - {{{ makeSetValue('buf', 'offsets.st_blocks', 'blocks', 'i32') }}} - if (obj.read) mode |= 0x16D; // S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH. - if (obj.write) mode |= 0x92; // S_IWUSR | S_IWGRP | S_IWOTH. - {{{ makeSetValue('buf', 'offsets.st_mode', 'mode', 'i32') }}} - - return 0; }, lstat__deps: ['stat'], lstat: function(path, buf) { @@ -1032,40 +2021,30 @@ LibraryManager.library = { fstat: function(fildes, buf) { // int fstat(int fildes, struct stat *buf); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fstat.html - if (!FS.streams[fildes]) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - var pathArray = intArrayFromString(FS.streams[fildes].path); - return _stat(allocate(pathArray, 'i8', ALLOC_STACK), buf); } + return _stat(stream.path, buf); }, mknod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], mknod: function(path, mode, dev) { // int mknod(const char *path, mode_t mode, dev_t dev); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mknod.html path = Pointer_stringify(path); - var fmt = (mode & {{{ cDefine('S_IFMT') }}}); - if (fmt !== {{{ cDefine('S_IFREG') }}} && fmt !== {{{ cDefine('S_IFCHR') }}} && - fmt !== {{{ cDefine('S_IFBLK') }}} && fmt !== {{{ cDefine('S_IFIFO') }}} && - fmt !== {{{ cDefine('S_IFSOCK') }}}) { - // not valid formats for mknod - ___setErrNo(ERRNO_CODES.EINVAL); + // we don't want this in the JS API as the JS API + // uses mknod to create all nodes. + var err = FS.mayMknod(mode); + if (err) { + ___setErrNo(err); return -1; } - if (fmt === {{{ cDefine('S_IFCHR') }}} || fmt === {{{ cDefine('S_IFBLK') }}} || - fmt === {{{ cDefine('S_IFIFO') }}} || fmt === {{{ cDefine('S_IFSOCK') }}}) { - // not supported currently - ___setErrNo(ERRNO_CODES.EPERM); - return -1; - } - path = FS.analyzePath(path); - var properties = { contents: [], isFolder: false }; // S_IFDIR. try { - FS.createObject(path.parentObject, path.name, properties, - mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + VFS.mknod(path, mode, dev); return 0; } catch (e) { + ___setErrNo(e.errno); return -1; } }, @@ -1074,13 +2053,11 @@ LibraryManager.library = { // int mkdir(const char *path, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html path = Pointer_stringify(path); - path = FS.analyzePath(path); - var properties = { contents: [], isFolder: true }; try { - FS.createObject(path.parentObject, path.name, properties, - mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + VFS.mkdir(path, mode, 0); return 0; } catch (e) { + ___setErrNo(e.errno); return -1; } }, @@ -1095,33 +2072,43 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EROFS); return -1; }, - chmod__deps: ['$FS'], + chmod__deps: ['$FS', '__setErrNo'], chmod: function(path, mode, dontResolveLastLink) { // int chmod(const char *path, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/chmod.html // NOTE: dontResolveLastLink is a shortcut for lchmod(). It should never be // used in client code. path = typeof path !== 'string' ? Pointer_stringify(path) : path; - var obj = FS.findObject(path, dontResolveLastLink); - if (obj === null) return -1; - obj.read = mode & 0x100; // S_IRUSR. - obj.write = mode & 0x80; // S_IWUSR. - obj.timestamp = Date.now(); - return 0; + try { + VFS.chmod(path, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, fchmod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chmod'], fchmod: function(fildes, mode) { // int fchmod(int fildes, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fchmod.html - var stream = FS.streams[fildes]; - if (!stream) { - ___setErrNo(ERRNO_CODES.EBADF); + try { + VFS.fchmod(fildes, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); return -1; } - return _chmod(stream.path, mode); }, - lchmod: function(path, mode) { - return _chmod(path, mode, true); + lchmod__deps: ['chmod'], + lchmod: function (path, mode) { + path = Pointer_stringify(path); + try { + VFS.lchmod(path, mode); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, umask__deps: ['$FS'], @@ -1205,108 +2192,15 @@ LibraryManager.library = { // http://pubs.opengroup.org/onlinepubs/009695399/functions/open.html // NOTE: This implementation tries to mimic glibc rather than strictly // following the POSIX standard. - var mode = {{{ makeGetValue('varargs', 0, 'i32') }}}; - - // Simplify flags. - var accessMode = oflag & {{{ cDefine('O_ACCMODE') }}}; - var isWrite = accessMode != {{{ cDefine('O_RDONLY') }}}; - var isRead = accessMode != {{{ cDefine('O_WRONLY') }}}; - var isCreate = Boolean(oflag & {{{ cDefine('O_CREAT') }}}); - var isExistCheck = Boolean(oflag & {{{ cDefine('O_EXCL') }}}); - var isTruncate = Boolean(oflag & {{{ cDefine('O_TRUNC') }}}); - var isAppend = Boolean(oflag & {{{ cDefine('O_APPEND') }}}); - - // Verify path. - var origPath = path; - path = FS.analyzePath(Pointer_stringify(path)); - if (!path.parentExists) { - ___setErrNo(path.error); + path = Pointer_stringify(path); + try { + var stream = VFS.open(path, oflag, mode); + return stream.fd; + } catch (e) { + ___setErrNo(e.errno); return -1; } - var target = path.object || null; - var finalPath; - - // Verify the file exists, create if needed and allowed. - if (target) { - if (isCreate && isExistCheck) { - ___setErrNo(ERRNO_CODES.EEXIST); - return -1; - } - if ((isWrite || isTruncate) && target.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); - return -1; - } - if (isRead && !target.read || isWrite && !target.write) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } - if (isTruncate && !target.isDevice) { - target.contents = []; - } else { - if (!FS.forceLoadFile(target)) { - ___setErrNo(ERRNO_CODES.EIO); - return -1; - } - } - finalPath = path.path; - } else { - if (!isCreate) { - ___setErrNo(ERRNO_CODES.ENOENT); - return -1; - } - if (!path.parentObject.write) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } - target = FS.createDataFile(path.parentObject, path.name, [], - mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. - finalPath = path.parentPath + '/' + path.name; - } - // Actually create an open stream. - var id; - if (target.isFolder) { - var entryBuffer = 0; - if (___dirent_struct_layout) { - entryBuffer = _malloc(___dirent_struct_layout.__size__); - } - var contents = []; - for (var key in target.contents) contents.push(key); - id = FS.createFileHandle({ - path: finalPath, - object: target, - // An index into contents. Special values: -2 is ".", -1 is "..". - position: -2, - isRead: true, - isWrite: false, - isAppend: false, - error: false, - eof: false, - ungotten: [], - // Folder-specific properties: - // Remember the contents at the time of opening in an array, so we can - // seek between them relying on a single order. - contents: contents, - // Each stream has its own area for readdir() returns. - currentEntry: entryBuffer - }); - } else { - id = FS.createFileHandle({ - path: finalPath, - object: target, - position: 0, - isRead: isRead, - isWrite: isWrite, - isAppend: isAppend, - error: false, - eof: false, - ungotten: [] - }); - } -#if ASSERTIONS - FS.checkStreams(); -#endif - return id; }, creat__deps: ['open'], creat: function(path, mode) { @@ -1327,11 +2221,11 @@ LibraryManager.library = { fcntl: function(fildes, cmd, varargs, dup2) { // int fcntl(int fildes, int cmd, ...); // http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html - if (!FS.streams[fildes]) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var stream = FS.streams[fildes]; switch (cmd) { case {{{ cDefine('F_DUPFD') }}}: var arg = {{{ makeGetValue('varargs', 0, 'i32') }}}; @@ -1339,31 +2233,22 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } - var newStream = {}; - for (var member in stream) { - newStream[member] = stream[member]; + var newStream; + try { + newStream = VFS.open(stream.path, stream.flags, 0, arg); + } catch (e) { + ___setErrNo(e.errno); + return -1; } - arg = dup2 ? arg : Math.max(arg, FS.streams.length); // dup2 wants exactly arg; fcntl wants a free descriptor >= arg - FS.createFileHandle(newStream, arg); -#if ASSERTIONS - FS.checkStreams(); -#endif - return arg; + return newStream.fd; case {{{ cDefine('F_GETFD') }}}: case {{{ cDefine('F_SETFD') }}}: return 0; // FD_CLOEXEC makes no sense for a single process. case {{{ cDefine('F_GETFL') }}}: - var flags = 0; - if (stream.isRead && stream.isWrite) flags = {{{ cDefine('O_RDWR') }}}; - else if (!stream.isRead && stream.isWrite) flags = {{{ cDefine('O_WRONLY') }}}; - else if (stream.isRead && !stream.isWrite) flags = {{{ cDefine('O_RDONLY') }}}; - if (stream.isAppend) flags |= {{{ cDefine('O_APPEND') }}}; - // Synchronization and blocking flags are irrelevant to us. - return flags; + return stream.flags; case {{{ cDefine('F_SETFL') }}}: var arg = {{{ makeGetValue('varargs', 0, 'i32') }}}; - stream.isAppend = Boolean(arg | {{{ cDefine('O_APPEND') }}}); - // Synchronization and blocking flags are irrelevant to us. + stream.flags |= arg; return 0; case {{{ cDefine('F_GETLK') }}}: case {{{ cDefine('F_GETLK64') }}}: @@ -1401,15 +2286,18 @@ LibraryManager.library = { posix_fallocate: function(fd, offset, len) { // int posix_fallocate(int fd, off_t offset, off_t len); // http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html - if (!FS.streams[fd] || !FS.streams[fd].isWrite || FS.streams[fd].link || - FS.streams[fd].isFolder || FS.streams[fd].isDevice) { + var stream = FS.getStream(fd); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var contents = FS.streams[fd].object.contents; - var limit = offset + len; - while (limit > contents.length) contents.push(0); - return 0; + try { + VFS.allocate(stream, offset, len); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, // ========================================================================== @@ -1442,8 +2330,8 @@ LibraryManager.library = { var fd = {{{ makeGetValue('pollfd', 'offsets.fd', 'i32') }}}; var events = {{{ makeGetValue('pollfd', 'offsets.events', 'i16') }}}; var revents = 0; - if (FS.streams[fd]) { - var stream = FS.streams[fd]; + var stream = FS.getStream(fd); + if (stream) { if (events & {{{ cDefine('POLLIN') }}}) revents |= {{{ cDefine('POLLIN') }}}; if (events & {{{ cDefine('POLLOUT') }}}) revents |= {{{ cDefine('POLLOUT') }}}; } else { @@ -1464,15 +2352,28 @@ LibraryManager.library = { // int access(const char *path, int amode); // http://pubs.opengroup.org/onlinepubs/000095399/functions/access.html path = Pointer_stringify(path); - var target = FS.findObject(path); - if (target === null) return -1; - if ((amode & 2 && !target.write) || // W_OK. - ((amode & 1 || amode & 4) && !target.read)) { // X_OK, R_OK. + if (amode & ~{{{ cDefine('S_IRWXO') }}}) { + // need a valid mode + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + var node; + try { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } + var perms = ''; + if (amode & {{{ cDefine('R_OK') }}}) perms += 'r'; + if (amode & {{{ cDefine('W_OK') }}}) perms += 'w'; + if (amode & {{{ cDefine('X_OK') }}}) perms += 'x'; + if (perms /* otherwise, they've just passed F_OK */ && FS.nodePermissions(node, perms)) { ___setErrNo(ERRNO_CODES.EACCES); return -1; - } else { - return 0; } + return 0; }, chdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], chdir: function(path) { @@ -1480,17 +2381,24 @@ LibraryManager.library = { // http://pubs.opengroup.org/onlinepubs/000095399/functions/chdir.html // NOTE: The path argument may be a string, to simplify fchdir(). if (typeof path !== 'string') path = Pointer_stringify(path); - path = FS.analyzePath(path); - if (!path.exists) { - ___setErrNo(path.error); + var lookup; + try { + lookup = FS.lookupPath(path, { follow: true }); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else if (!path.object.isFolder) { + } + if (!FS.isDir(lookup.node.mode)) { ___setErrNo(ERRNO_CODES.ENOTDIR); return -1; - } else { - FS.currentPath = path.path; - return 0; } + var err = FS.nodePermissions(lookup.node, 'x'); + if (err) { + ___setErrNo(err); + return -1; + } + FS.currentPath = lookup.path; + return 0; }, chown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], chown: function(path, owner, group, dontResolveLastLink) { @@ -1501,10 +2409,13 @@ LibraryManager.library = { // NOTE: dontResolveLastLink is a shortcut for lchown(). It should never be // used in client code. if (typeof path !== 'string') path = Pointer_stringify(path); - var target = FS.findObject(path, dontResolveLastLink); - if (target === null) return -1; - target.timestamp = Date.now(); - return 0; + try { + VFS.chown(path, owner, group); + return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, chroot__deps: ['__setErrNo', '$ERRNO_CODES'], chroot: function(path) { @@ -1517,16 +2428,18 @@ LibraryManager.library = { close: function(fildes) { // int close(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/close.html - if (FS.streams[fildes]) { - if (FS.streams[fildes].currentEntry) { - _free(FS.streams[fildes].currentEntry); - } - FS.streams[fildes] = null; - return 0; - } else { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + try { + VFS.close(stream); + return 0; + } catch (e) { + ___setErrNo(e.errno);; + return -1; + } }, dup__deps: ['fcntl'], dup: function(fildes) { @@ -1538,24 +2451,32 @@ LibraryManager.library = { dup2: function(fildes, fildes2) { // int dup2(int fildes, int fildes2); // http://pubs.opengroup.org/onlinepubs/000095399/functions/dup.html + var stream = FS.getStream(fildes); if (fildes2 < 0) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (fildes === fildes2 && FS.streams[fildes]) { + } else if (fildes === fildes2 && stream) { return fildes; } else { _close(fildes2); - return _fcntl(fildes, 0, allocate([fildes2, 0, 0, 0], 'i32', ALLOC_STACK), true); // F_DUPFD. + try { + var stream2 = VFS.open(stream.path, stream.flags, 0, fildes2, fildes2); + return stream2.fd; + } catch (e) { + ___setErrNo(e.errno); + return -1; + } } }, fchown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chown'], fchown: function(fildes, owner, group) { // int fchown(int fildes, uid_t owner, gid_t group); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fchown.html - if (FS.streams[fildes]) { - return _chown(FS.streams[fildes].path, owner, group); - } else { - ___setErrNo(ERRNO_CODES.EBADF); + try { + VFS.fchown(fildes, owner, group); + return 0; + } catch (e) { + ___setErrNo(e.errno); return -1; } }, @@ -1563,8 +2484,9 @@ LibraryManager.library = { fchdir: function(fildes) { // int fchdir(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fchdir.html - if (FS.streams[fildes]) { - return _chdir(FS.streams[fildes].path); + var stream = FS.getStream(fildes); + if (stream) { + return _chdir(stream.path); } else { ___setErrNo(ERRNO_CODES.EBADF); return -1; @@ -1637,7 +2559,8 @@ LibraryManager.library = { fsync: function(fildes) { // int fsync(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fsync.html - if (FS.streams[fildes]) { + var stream = FS.getStream(fildes); + if (stream) { // We write directly to the file system, so there's nothing to do here. return 0; } else { @@ -1651,42 +2574,24 @@ LibraryManager.library = { // int truncate(const char *path, off_t length); // http://pubs.opengroup.org/onlinepubs/000095399/functions/truncate.html // NOTE: The path argument may be a string, to simplify ftruncate(). - if (length < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); + if (typeof path !== 'string') path = Pointer_stringify(path); + try { + VFS.truncate(path, length); + return 0; + } catch (e) { + ___setErrNo(e.errno); return -1; - } else { - if (typeof path !== 'string') path = Pointer_stringify(path); - var target = FS.findObject(path); - if (target === null) return -1; - if (target.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); - return -1; - } else if (target.isDevice) { - ___setErrNo(ERRNO_CODES.EINVAL); - return -1; - } else if (!target.write) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else { - var contents = target.contents; - if (length < contents.length) contents.length = length; - else while (length > contents.length) contents.push(0); - target.timestamp = Date.now(); - return 0; - } } }, ftruncate__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'truncate'], ftruncate: function(fildes, length) { // int ftruncate(int fildes, off_t length); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ftruncate.html - if (FS.streams[fildes] && FS.streams[fildes].isWrite) { - return _truncate(FS.streams[fildes].path, length); - } else if (FS.streams[fildes]) { - ___setErrNo(ERRNO_CODES.EINVAL); - return -1; - } else { - ___setErrNo(ERRNO_CODES.EBADF); + try { + VFS.ftruncate(fildes, length); + return 0; + } catch (e) { + ___setErrNo(e.errno); return -1; } }, @@ -1718,12 +2623,13 @@ LibraryManager.library = { isatty: function(fildes) { // int isatty(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/isatty.html - var stream = FS.streams[fildes]; + var stream = FS.getStream(fildes); if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } - if (!stream.object.isTerminal) { + // HACK - implement tcgetattr + if (!stream.tty) { ___setErrNo(ERRNO_CODES.ENOTTY); return 0; } @@ -1747,7 +2653,8 @@ LibraryManager.library = { lockf: function(fildes, func, size) { // int lockf(int fildes, int function, off_t size); // http://pubs.opengroup.org/onlinepubs/000095399/functions/lockf.html - if (FS.streams[fildes]) { + var stream = FS.getStream(fildes); + if (stream) { // Pretend whatever locking or unlocking operation succeeded. Locking does // not make much sense, since we have a single process/thread. return 0; @@ -1760,26 +2667,17 @@ LibraryManager.library = { lseek: function(fildes, offset, whence) { // off_t lseek(int fildes, off_t offset, int whence); // http://pubs.opengroup.org/onlinepubs/000095399/functions/lseek.html - if (FS.streams[fildes] && !FS.streams[fildes].object.isDevice) { - var stream = FS.streams[fildes]; - var position = offset; - if (whence === 1) { // SEEK_CUR. - position += stream.position; - } else if (whence === 2) { // SEEK_END. - position += stream.object.contents.length; - } - if (position < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); - return -1; - } else { - stream.ungotten = []; - stream.position = position; - return position; - } - } else { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + try { + return VFS.llseek(stream, offset, whence); + } catch (e) { + ___setErrNo(e.errno); + return -1; + } }, pipe__deps: ['__setErrNo', '$ERRNO_CODES'], pipe: function(fildes) { @@ -1794,94 +2692,39 @@ LibraryManager.library = { pread: function(fildes, buf, nbyte, offset) { // ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); // http://pubs.opengroup.org/onlinepubs/000095399/functions/read.html - var stream = FS.streams[fildes]; - if (!stream || stream.object.isDevice) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (!stream.isRead) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else if (stream.object.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); - return -1; - } else if (nbyte < 0 || offset < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); + } + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.read(stream, slab, buf, nbyte, offset); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else if (offset >= stream.object.contents.length) { - return 0; - } else { - var bytesRead = 0; - var contents = stream.object.contents; - var size = Math.min(contents.length - offset, nbyte); - assert(size >= 0); - -#if USE_TYPED_ARRAYS == 2 - if (contents.subarray) { // typed array - HEAPU8.set(contents.subarray(offset, offset+size), buf); - } else -#endif - if (contents.slice) { // normal array - for (var i = 0; i < size; i++) { - {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}} - } - } else { - for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR - {{{ makeSetValue('buf', 'i', 'contents.get(offset + i)', 'i8') }}} - } - } - bytesRead += size; - return bytesRead; } }, read__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'recv', 'pread'], read: function(fildes, buf, nbyte) { // ssize_t read(int fildes, void *buf, size_t nbyte); // http://pubs.opengroup.org/onlinepubs/000095399/functions/read.html - var stream = FS.streams[fildes]; - if (stream && ('socket' in stream)) { - return _recv(fildes, buf, nbyte, 0); - } else if (!stream) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (!stream.isRead) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else if (nbyte < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); + } + + if (stream && ('socket' in stream)) { + return _recv(fildes, buf, nbyte, 0); + } + + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.read(stream, slab, buf, nbyte); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else { - var bytesRead; - if (stream.object.isDevice) { - if (stream.object.input) { - bytesRead = 0; - for (var i = 0; i < nbyte; i++) { - try { - var result = stream.object.input(); - } catch (e) { - ___setErrNo(ERRNO_CODES.EIO); - return -1; - } - if (result === undefined && bytesRead === 0) { - ___setErrNo(ERRNO_CODES.EAGAIN); - return -1; - } - if (result === null || result === undefined) break; - bytesRead++; - {{{ makeSetValue('buf', 'i', 'result', 'i8') }}} - } - return bytesRead; - } else { - ___setErrNo(ERRNO_CODES.ENXIO); - return -1; - } - } else { - bytesRead = _pread(fildes, buf, nbyte, stream.position); - assert(bytesRead >= -1); - if (bytesRead != -1) { - stream.position += bytesRead; - } - return bytesRead; - } } }, sync: function() { @@ -1894,26 +2737,12 @@ LibraryManager.library = { // int rmdir(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/rmdir.html path = Pointer_stringify(path); - path = FS.analyzePath(path, true); - if (!path.parentExists || !path.exists) { - ___setErrNo(path.error); - return -1; - } else if (!path.parentObject.write) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else if (!path.object.isFolder) { - ___setErrNo(ERRNO_CODES.ENOTDIR); - return -1; - } else if (path.isRoot || path.path == FS.currentPath) { - ___setErrNo(ERRNO_CODES.EBUSY); - return -1; - } else { - for (var i in path.object.contents) { - ___setErrNo(ERRNO_CODES.ENOTEMPTY); - return -1; - } - delete path.parentObject.contents[path.name]; + try { + VFS.rmdir(path); return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; } }, unlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], @@ -1921,19 +2750,12 @@ LibraryManager.library = { // int unlink(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/unlink.html path = Pointer_stringify(path); - path = FS.analyzePath(path, true); - if (!path.parentExists || !path.exists) { - ___setErrNo(path.error); - return -1; - } else if (path.object.isFolder) { - ___setErrNo(ERRNO_CODES.EPERM); - return -1; - } else if (!path.parentObject.write) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else { - delete path.parentObject.contents[path.name]; + try { + VFS.unlink(path); return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; } }, ttyname__deps: ['ttyname_r'], @@ -1947,7 +2769,7 @@ LibraryManager.library = { ttyname_r: function(fildes, name, namesize) { // int ttyname_r(int fildes, char *name, size_t namesize); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ttyname.html - var stream = FS.streams[fildes]; + var stream = FS.getStream(fildes); var ttyname = '/dev/tty'; if (!stream) { return ___setErrNo(ERRNO_CODES.EBADF); @@ -1959,106 +2781,73 @@ LibraryManager.library = { writeStringToMemory(ttyname, name); return 0; }, - symlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + symlink__deps: ['$FS', '$PATH', '__setErrNo', '$ERRNO_CODES'], symlink: function(path1, path2) { // int symlink(const char *path1, const char *path2); // http://pubs.opengroup.org/onlinepubs/000095399/functions/symlink.html - var path = FS.analyzePath(Pointer_stringify(path2), true); - if (!path.parentExists) { - ___setErrNo(path.error); - return -1; - } else if (path.exists) { - ___setErrNo(ERRNO_CODES.EEXIST); - return -1; - } else { - FS.createLink(path.parentPath, path.name, - Pointer_stringify(path1), true, true); + path1 = Pointer_stringify(path1); + path2 = Pointer_stringify(path2); + try { + VFS.symlink(path1, path2); return 0; + } catch (e) { + ___setErrNo(e.errno); + return -1; } }, readlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], readlink: function(path, buf, bufsize) { // ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize); // http://pubs.opengroup.org/onlinepubs/000095399/functions/readlink.html - var target = FS.findObject(Pointer_stringify(path), true); - if (target === null) return -1; - if (target.link !== undefined) { - var length = Math.min(bufsize - 1, target.link.length); - for (var i = 0; i < length; i++) { - {{{ makeSetValue('buf', 'i', 'target.link.charCodeAt(i)', 'i8') }}} - } - if (bufsize - 1 > length) {{{ makeSetValue('buf', 'i', '0', 'i8') }}} - return i; - } else { - ___setErrNo(ERRNO_CODES.EINVAL); + path = Pointer_stringify(path); + var str; + try { + str = VFS.readlink(path); + } catch (e) { + ___setErrNo(e.errno); return -1; } + str = str.slice(0, Math.max(0, bufsize - 1)); + writeStringToMemory(str, buf, true); + return str.length; }, pwrite__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], pwrite: function(fildes, buf, nbyte, offset) { // ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); // http://pubs.opengroup.org/onlinepubs/000095399/functions/write.html - var stream = FS.streams[fildes]; - if (!stream || stream.object.isDevice) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (!stream.isWrite) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else if (stream.object.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); - return -1; - } else if (nbyte < 0 || offset < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); + } + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.write(stream, slab, buf, nbyte, offset); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else { - var contents = stream.object.contents; - while (contents.length < offset) contents.push(0); - for (var i = 0; i < nbyte; i++) { - contents[offset + i] = {{{ makeGetValue('buf', 'i', 'i8', undefined, 1) }}}; - } - stream.object.timestamp = Date.now(); - return i; } }, write__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'send', 'pwrite'], write: function(fildes, buf, nbyte) { // ssize_t write(int fildes, const void *buf, size_t nbyte); // http://pubs.opengroup.org/onlinepubs/000095399/functions/write.html - var stream = FS.streams[fildes]; - if (stream && ('socket' in stream)) { - return _send(fildes, buf, nbyte, 0); - } else if (!stream) { + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else if (!stream.isWrite) { - ___setErrNo(ERRNO_CODES.EACCES); - return -1; - } else if (nbyte < 0) { - ___setErrNo(ERRNO_CODES.EINVAL); + } + + if (stream && ('socket' in stream)) { + return _send(fildes, buf, nbyte, 0); + } + + try { + var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; + return VFS.write(stream, slab, buf, nbyte); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else { - if (stream.object.isDevice) { - if (stream.object.output) { - for (var i = 0; i < nbyte; i++) { - try { - stream.object.output({{{ makeGetValue('buf', 'i', 'i8') }}}); - } catch (e) { - ___setErrNo(ERRNO_CODES.EIO); - return -1; - } - } - stream.object.timestamp = Date.now(); - return i; - } else { - ___setErrNo(ERRNO_CODES.ENXIO); - return -1; - } - } else { - var bytesWritten = _pwrite(fildes, buf, nbyte, stream.position); - if (bytesWritten != -1) stream.position += bytesWritten; - return bytesWritten; - } } }, alarm: function(seconds) { @@ -3157,7 +3946,8 @@ LibraryManager.library = { clearerr: function(stream) { // void clearerr(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/clearerr.html - if (FS.streams[stream]) FS.streams[stream].error = false; + stream = FS.getStream(stream); + if (stream) stream.error = false; }, fclose__deps: ['close', 'fsync'], fclose: function(stream) { @@ -3170,68 +3960,51 @@ LibraryManager.library = { fdopen: function(fildes, mode) { // FILE *fdopen(int fildes, const char *mode); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fdopen.html - if (FS.streams[fildes]) { - var stream = FS.streams[fildes]; - mode = Pointer_stringify(mode); - if ((mode.indexOf('w') != -1 && !stream.isWrite) || - (mode.indexOf('r') != -1 && !stream.isRead) || - (mode.indexOf('a') != -1 && !stream.isAppend) || - (mode.indexOf('+') != -1 && (!stream.isRead || !stream.isWrite))) { - ___setErrNo(ERRNO_CODES.EINVAL); - return 0; - } else { - stream.error = false; - stream.eof = false; - return fildes; - } - } else { + mode = Pointer_stringify(mode); + var stream = FS.getStream(fildes); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } + if ((mode.indexOf('w') != -1 && !stream.isWrite) || + (mode.indexOf('r') != -1 && !stream.isRead) || + (mode.indexOf('a') != -1 && !stream.isAppend) || + (mode.indexOf('+') != -1 && (!stream.isRead || !stream.isWrite))) { + ___setErrNo(ERRNO_CODES.EINVAL); + return 0; + } else { + stream.error = false; + stream.eof = false; + return fildes; + } }, feof__deps: ['$FS'], feof: function(stream) { // int feof(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/feof.html - return Number(FS.streams[stream] && FS.streams[stream].eof); + stream = FS.getStream(stream); + return Number(stream && stream.eof); }, ferror__deps: ['$FS'], ferror: function(stream) { // int ferror(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ferror.html - return Number(FS.streams[stream] && FS.streams[stream].error); + stream = FS.getStream(stream); + return Number(stream && stream.error); }, fflush__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], fflush: function(stream) { // int fflush(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fflush.html - var flush = function(filedes) { - // Right now we write all data directly, except for output devices. - if (FS.streams[filedes] && FS.streams[filedes].object.output) { - if (!FS.streams[filedes].object.isTerminal) { // don't flush terminals, it would cause a \n to also appear - FS.streams[filedes].object.output(null); - } - } - }; - try { - if (stream === 0) { - for (var i = 0; i < FS.streams.length; i++) if (FS.streams[i]) flush(i); - } else { - flush(stream); - } - return 0; - } catch (e) { - ___setErrNo(ERRNO_CODES.EIO); - return -1; - } + // we don't currently perform any user-space buffering of data }, fgetc__deps: ['$FS', 'fread'], fgetc__postset: '_fgetc.ret = allocate([0], "i8", ALLOC_STATIC);', fgetc: function(stream) { // int fgetc(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetc.html - if (!FS.streams[stream]) return -1; - var streamObj = FS.streams[stream]; + var streamObj = FS.getStream(stream); + if (!streamObj) return -1; if (streamObj.eof || streamObj.error) return -1; var ret = _fread(_fgetc.ret, 1, 1, stream); if (ret == 0) { @@ -3256,28 +4029,26 @@ LibraryManager.library = { fgetpos: function(stream, pos) { // int fgetpos(FILE *restrict stream, fpos_t *restrict pos); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetpos.html - if (FS.streams[stream]) { - stream = FS.streams[stream]; - if (stream.object.isDevice) { - ___setErrNo(ERRNO_CODES.ESPIPE); - return -1; - } else { - {{{ makeSetValue('pos', '0', 'stream.position', 'i32') }}} - var state = (stream.eof ? 1 : 0) + (stream.error ? 2 : 0); - {{{ makeSetValue('pos', Runtime.getNativeTypeSize('i32'), 'state', 'i32') }}} - return 0; - } - } else { + stream = FS.getStream(stream); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + if (FS.isChrdev(stream.node.mode)) { + ___setErrNo(ERRNO_CODES.ESPIPE); + return -1; + } + {{{ makeSetValue('pos', '0', 'stream.position', 'i32') }}} + var state = (stream.eof ? 1 : 0) + (stream.error ? 2 : 0); + {{{ makeSetValue('pos', Runtime.getNativeTypeSize('i32'), 'state', 'i32') }}} + return 0; }, fgets__deps: ['fgetc'], fgets: function(s, n, stream) { // char *fgets(char *restrict s, int n, FILE *restrict stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgets.html - if (!FS.streams[stream]) return 0; - var streamObj = FS.streams[stream]; + var streamObj = FS.getStream(stream); + if (!streamObj) return 0; if (streamObj.error || streamObj.eof) return 0; var byte_; for (var i = 0; i < n - 1 && byte_ != {{{ charCode('\n') }}}; i++) { @@ -3355,7 +4126,8 @@ LibraryManager.library = { {{{ makeSetValue('_fputc.ret', '0', 'chr', 'i8') }}} var ret = _write(stream, _fputc.ret, 1); if (ret == -1) { - if (FS.streams[stream]) FS.streams[stream].error = true; + var streamObj = FS.getStream(stream); + if (streamObj) streamObj.error = true; return -1; } else { return chr; @@ -3399,7 +4171,7 @@ LibraryManager.library = { return 0; } var bytesRead = 0; - var streamObj = FS.streams[stream]; + var streamObj = FS.getStream(stream); while (streamObj.ungotten.length && bytesToRead > 0) { {{{ makeSetValue('ptr++', '0', 'streamObj.ungotten.pop()', 'i8') }}} bytesToRead--; @@ -3419,12 +4191,13 @@ LibraryManager.library = { // FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/freopen.html if (!filename) { - if (!FS.streams[stream]) { + var streamObj = FS.getStream(stream); + if (!streamObj) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } if (_freopen.buffer) _free(_freopen.buffer); - filename = intArrayFromString(FS.streams[stream].path); + filename = intArrayFromString(streamObj.path); filename = allocate(filename, 'i8', ALLOC_NORMAL); } _fclose(stream); @@ -3437,10 +4210,10 @@ LibraryManager.library = { var ret = _lseek(stream, offset, whence); if (ret == -1) { return -1; - } else { - FS.streams[stream].eof = false; - return 0; } + stream = FS.getStream(stream); + stream.eof = false; + return 0; }, fseeko: 'fseek', fseeko64: 'fseek', @@ -3448,38 +4221,36 @@ LibraryManager.library = { fsetpos: function(stream, pos) { // int fsetpos(FILE *stream, const fpos_t *pos); // http://pubs.opengroup.org/onlinepubs/000095399/functions/fsetpos.html - if (FS.streams[stream]) { - if (FS.streams[stream].object.isDevice) { - ___setErrNo(ERRNO_CODES.EPIPE); - return -1; - } else { - FS.streams[stream].position = {{{ makeGetValue('pos', '0', 'i32') }}}; - var state = {{{ makeGetValue('pos', Runtime.getNativeTypeSize('i32'), 'i32') }}}; - FS.streams[stream].eof = Boolean(state & 1); - FS.streams[stream].error = Boolean(state & 2); - return 0; - } - } else { + stream = FS.getStream(stream); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + if (FS.isChrdev(stream.node.mode)) { + ___setErrNo(ERRNO_CODES.EPIPE); + return -1; + } + stream.position = {{{ makeGetValue('pos', '0', 'i32') }}}; + var state = {{{ makeGetValue('pos', Runtime.getNativeTypeSize('i32'), 'i32') }}}; + stream.eof = Boolean(state & 1); + stream.error = Boolean(state & 2); + return 0; }, ftell__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], ftell: function(stream) { // long ftell(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ftell.html - if (FS.streams[stream]) { - stream = FS.streams[stream]; - if (stream.object.isDevice) { - ___setErrNo(ERRNO_CODES.ESPIPE); - return -1; - } else { - return stream.position; - } - } else { + stream = FS.getStream(stream); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } + if (FS.isChrdev(stream.node.mode)) { + ___setErrNo(ERRNO_CODES.ESPIPE); + return -1; + } else { + return stream.position; + } }, ftello: 'ftell', ftello64: 'ftell', @@ -3491,7 +4262,8 @@ LibraryManager.library = { if (bytesToWrite == 0) return 0; var bytesWritten = _write(stream, ptr, bytesToWrite); if (bytesWritten == -1) { - if (FS.streams[stream]) FS.streams[stream].error = true; + var streamObj = FS.getStream(stream); + if (streamObj) streamObj.error = true; return 0; } else { return Math.floor(bytesWritten / size); @@ -3535,30 +4307,17 @@ LibraryManager.library = { return ret; }, rename__deps: ['__setErrNo', '$ERRNO_CODES'], - rename: function(old, new_) { + rename: function(old_path, new_path) { // int rename(const char *old, const char *new); // http://pubs.opengroup.org/onlinepubs/000095399/functions/rename.html - var oldObj = FS.analyzePath(Pointer_stringify(old)); - var newObj = FS.analyzePath(Pointer_stringify(new_)); - if (newObj.path == oldObj.path) { + old_path = Pointer_stringify(old_path); + new_path = Pointer_stringify(new_path); + try { + VFS.rename(old_path, new_path); return 0; - } else if (!oldObj.exists) { - ___setErrNo(oldObj.error); - return -1; - } else if (oldObj.isRoot || oldObj.path == FS.currentPath) { - ___setErrNo(ERRNO_CODES.EBUSY); - return -1; - } else if (newObj.parentPath && - newObj.parentPath.indexOf(oldObj.path) == 0) { - ___setErrNo(ERRNO_CODES.EINVAL); - return -1; - } else if (newObj.exists && newObj.object.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); + } catch (e) { + ___setErrNo(e.errno); return -1; - } else { - delete oldObj.parentObject.contents[oldObj.name]; - newObj.parentObject.contents[newObj.name] = oldObj.object; - return 0; } }, rewind__deps: ['$FS', 'fseek'], @@ -3566,7 +4325,8 @@ LibraryManager.library = { // void rewind(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/rewind.html _fseek(stream, 0, 0); // SEEK_SET. - if (FS.streams[stream]) FS.streams[stream].error = false; + var streamObj = FS.getStream(stream); + if (streamObj) streamObj.error = false; }, setvbuf: function(stream, buf, type, size) { // int setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size); @@ -3625,7 +4385,7 @@ LibraryManager.library = { ungetc: function(c, stream) { // int ungetc(int c, FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ungetc.html - stream = FS.streams[stream]; + stream = FS.getStream(stream); if (!stream) { return -1; } @@ -3650,20 +4410,20 @@ LibraryManager.library = { fscanf: function(stream, format, varargs) { // int fscanf(FILE *restrict stream, const char *restrict format, ... ); // http://pubs.opengroup.org/onlinepubs/000095399/functions/scanf.html - if (FS.streams[stream]) { - var buffer = []; - var get = function() { - var c = _fgetc(stream); - buffer.push(c); - return c; - }; - var unget = function() { - _ungetc(buffer.pop(), stream); - }; - return __scanString(format, get, unget, varargs); - } else { + var streamObj = FS.getStream(stream); + if (!streamObj) { return -1; } + var buffer = []; + var get = function() { + var c = _fgetc(stream); + buffer.push(c); + return c; + }; + var unget = function() { + _ungetc(buffer.pop(), stream); + }; + return __scanString(format, get, unget, varargs); }, scanf__deps: ['fscanf'], scanf: function(format, varargs) { @@ -3803,38 +4563,26 @@ LibraryManager.library = { * mmap. */ var MAP_PRIVATE = 2; + var ptr; var allocated = false; if (!_mmap.mappings) _mmap.mappings = {}; if (stream == -1) { - var ptr = _malloc(num); + ptr = _malloc(num); if (!ptr) return -1; _memset(ptr, 0, num); allocated = true; } else { - var info = FS.streams[stream]; + var info = FS.getStream(stream); if (!info) return -1; - var contents = info.object.contents; - // Only make a new copy when MAP_PRIVATE is specified. - if (flags & MAP_PRIVATE == 0) { - // We can't emulate MAP_SHARED when the file is not backed by HEAP. - assert(contents.buffer === HEAPU8.buffer); - ptr = contents.byteOffset; - allocated = false; - } else { - // Try to avoid unnecessary slices. - if (offset > 0 || offset + num < contents.length) { - if (contents.subarray) { - contents = contents.subarray(offset, offset+num); - } else { - contents = Array.prototype.slice.call(contents, offset, offset+num); - } - } - ptr = _malloc(num); - if (!ptr) return -1; - HEAPU8.set(contents, ptr); - allocated = true; + try { + var res = VFS.mmap(info, HEAPU8, start, num, offset, prot, flags); + ptr = res.ptr; + allocated = res.allocated; + } catch (e) { + ___setErrNo(e.errno); + return -1; } } @@ -6987,8 +7735,6 @@ LibraryManager.library = { // ========================================================================== // sys/types.h // ========================================================================== - - // NOTE: These are fake, since we don't support the C device creation API. // http://www.kernel.org/doc/man-pages/online/pages/man3/minor.3.html makedev: function(maj, min) { return ((maj) << 8 | (min)); @@ -8197,15 +8943,16 @@ LibraryManager.library = { socket__deps: ['$Sockets'], socket: function(family, type, protocol) { var INCOMING_QUEUE_LENGTH = 64; - var fd = FS.createFileHandle({ + var stream = FS.createStream({ addr: null, port: null, inQueue: new CircularBuffer(INCOMING_QUEUE_LENGTH), header: new Uint16Array(2), bound: false, - socket: true + socket: true, + stream_ops: {} }); - assert(fd < 64); // select() assumes socket fd values are in 0..63 + assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 var stream = type == {{{ cDefine('SOCK_STREAM') }}}; if (protocol) { assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if stream, must be tcp @@ -8319,7 +9066,7 @@ LibraryManager.library = { }; }; - return fd; + return stream.fd; }, mkport__deps: ['$Sockets'], @@ -8340,7 +9087,7 @@ LibraryManager.library = { bind__deps: ['$Sockets', '_inet_ntoa_raw', 'ntohs', 'mkport'], bind: function(fd, addr, addrlen) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; if (addr) { info.port = _ntohs(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); @@ -8361,7 +9108,7 @@ LibraryManager.library = { sendmsg__deps: ['$Sockets', 'bind', '_inet_ntoa_raw', 'ntohs'], sendmsg: function(fd, msg, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; // if we are not connected, use the address info in the message if (!info.bound) { @@ -8417,7 +9164,7 @@ LibraryManager.library = { recvmsg__deps: ['$Sockets', 'bind', '__setErrNo', '$ERRNO_CODES', 'htons'], recvmsg: function(fd, msg, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; // if we are not connected, use the address info in the message if (!info.port) { @@ -8468,14 +9215,14 @@ LibraryManager.library = { }, shutdown: function(fd, how) { - var info = FS.streams[fd]; - if (!info) return -1; - info.close(); - FS.removeFileHandle(fd); + var stream = FS.getStream(fd); + if (!stream) return -1; + stream.close(); + FS.closeStream(stream); }, ioctl: function(fd, request, varargs) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; var bytes = 0; if (info.hasData()) { @@ -8495,7 +9242,7 @@ LibraryManager.library = { // TODO: webrtc queued incoming connections, etc. // For now, the model is that bind does a connect, and we "accept" that one connection, // which has host:port the same as ours. We also return the same socket fd. - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; if (addr) { setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); @@ -8536,7 +9283,7 @@ LibraryManager.library = { var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh; if (int_ & mask) { // index is in the set, check if it is ready for read - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (info && can(info)) { // set bit fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); @@ -8565,18 +9312,19 @@ LibraryManager.library = { if (protocol) { assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp } - var fd = FS.createFileHandle({ + var stream = FS.createStream({ connected: false, stream: stream, - socket: true + socket: true, + stream_ops: {} }); - assert(fd < 64); // select() assumes socket fd values are in 0..63 - return fd; + assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 + return stream.fd; }, connect__deps: ['$FS', '$Sockets', '_inet_ntoa_raw', 'ntohs', 'gethostbyname'], connect: function(fd, addr, addrlen) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; info.connected = true; info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); @@ -8694,7 +9442,7 @@ LibraryManager.library = { recv__deps: ['$FS'], recv: function(fd, buf, len, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; if (!info.hasData()) { ___setErrNo(ERRNO_CODES.EAGAIN); // no data, and all sockets are nonblocking, so this is the right behavior @@ -8720,7 +9468,7 @@ LibraryManager.library = { send__deps: ['$FS'], send: function(fd, buf, len, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; info.sender(HEAPU8.subarray(buf, buf+len)); return len; @@ -8728,7 +9476,7 @@ LibraryManager.library = { sendmsg__deps: ['$FS', '$Sockets', 'connect'], sendmsg: function(fd, msg, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; // if we are not connected, use the address info in the message if (!info.connected) { @@ -8763,7 +9511,7 @@ LibraryManager.library = { recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'], recvmsg: function(fd, msg, flags) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; // if we are not connected, use the address info in the message if (!info.connected) { @@ -8821,7 +9569,7 @@ LibraryManager.library = { recvfrom__deps: ['$FS', 'connect', 'recv'], recvfrom: function(fd, buf, len, flags, addr, addrlen) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; // if we are not connected, use the address info in the message if (!info.connected) { @@ -8832,14 +9580,14 @@ LibraryManager.library = { }, shutdown: function(fd, how) { - var info = FS.streams[fd]; - if (!info) return -1; - info.socket.close(); - FS.removeFileHandle(fd); + var stream = FS.getStream(fd); + if (!stream) return -1; + stream.socket.close(); + FS.closeStream(stream); }, ioctl: function(fd, request, varargs) { - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; var bytes = 0; if (info.hasData()) { @@ -8869,7 +9617,7 @@ LibraryManager.library = { // TODO: webrtc queued incoming connections, etc. // For now, the model is that bind does a connect, and we "accept" that one connection, // which has host:port the same as ours. We also return the same socket fd. - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (!info) return -1; if (addr) { setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); @@ -8925,7 +9673,7 @@ LibraryManager.library = { var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh; if (int_ & mask) { // index is in the set, check if it is ready for read - var info = FS.streams[fd]; + var info = FS.getStream(fd); if (info && can(info)) { // set bit fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); diff --git a/src/library_gl.js b/src/library_gl.js index 54b2f956..8c724245 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -636,6 +636,11 @@ var LibraryGL = { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; var buffer = GL.buffers[id]; + + // From spec: "glDeleteBuffers silently ignores 0's and names that do not + // correspond to existing buffer objects." + if (!buffer) continue; + Module.ctx.deleteBuffer(buffer); buffer.name = 0; GL.buffers[id] = null; diff --git a/src/library_path.js b/src/library_path.js new file mode 100644 index 00000000..3df6ca5b --- /dev/null +++ b/src/library_path.js @@ -0,0 +1,133 @@ +mergeInto(LibraryManager.library, { + $PATH: { + // split a filename into [root, dir, basename, ext], unix version + // 'root' is just a slash, or nothing. + splitPath: function (filename) { + var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: function (parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + return parts; + }, + normalize: function (path) { + var isAbsolute = path.charAt(0) === '/', + trailingSlash = path.substr(-1) === '/'; + // Normalize the path + path = PATH.normalizeArray(path.split('/').filter(function(p) { + return !!p; + }), !isAbsolute).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; + }, + dirname: function (path) { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + return root + dir; + }, + basename: function (path, ext) { + // EMSCRIPTEN return '/'' for '/', not an empty string + if (path === '/') return '/'; + var f = PATH.splitPath(path)[2]; + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; + }, + join: function () { + var paths = Array.prototype.slice.call(arguments, 0); + return PATH.normalize(paths.filter(function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); + }, + resolve: function () { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter(function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; + }, + relative: function(from, to) { + from = PATH.resolve(from).substr(1); + to = PATH.resolve(to).substr(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + } + } +});
\ No newline at end of file diff --git a/src/modules.js b/src/modules.js index 9f419234..f85e7d0e 100644 --- a/src/modules.js +++ b/src/modules.js @@ -422,7 +422,7 @@ var LibraryManager = { load: function() { if (this.library) return; - var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries); + var libraries = ['library.js', 'library_path.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries); for (var i = 0; i < libraries.length; i++) { eval(processMacros(preprocess(read(libraries[i])))); } diff --git a/src/settings.js b/src/settings.js index 87ab820d..19108f3b 100644 --- a/src/settings.js +++ b/src/settings.js @@ -249,8 +249,6 @@ var FS_LOG = 0; // Log all FS operations. This is especially helpful when you'r // a new project and want to see a list of file system operations happening // so that you can create a virtual file system with all of the required files. -var USE_OLD_FS = 1; // Switch to toggle the new / old FS code. Currently only used for testing purposes. - var USE_BSS = 1; // https://en.wikipedia.org/wiki/.bss // When enabled, 0-initialized globals are sorted to the end of the globals list, // enabling us to not explicitly store the initialization value for each 0 byte. @@ -880,9 +878,9 @@ var C_DEFINES = { 'S_IRGRP': '0000040', 'S_IROTH': '0000004', 'S_IRUSR': '0000400', - 'S_IRWXG': '0000040', - 'S_IRWXO': '0000004', - 'S_IRWXU': '0000400', + 'S_IRWXG': '0000070', + 'S_IRWXO': '0000007', + 'S_IRWXU': '0000700', 'S_ISGID': '0002000', 'S_ISUID': '0004000', 'S_ISVTX': '0001000', @@ -1297,6 +1295,33 @@ var C_DEFINES = { '___int8_t_defined': '1', '___int_least16_t_defined': '1', '___int_least32_t_defined': '1', - '___int_least8_t_defined': '1' + '___int_least8_t_defined': '1', + 'FMODE_READ': '0x1', + 'FMODE_WRITE': '0x2', + 'FMODE_LSEEK': '0x4', + 'FMODE_PREAD': '0x8', + 'FMODE_PWRITE': '0x10', + 'FMODE_EXEC': '0x20', + 'FMODE_NDELAY': '0x40', + 'FMODE_EXCL': '0x80', + 'FMODE_NOCMTIME': '0x800', + 'FMODE_RANDOM': '0x1000', + 'FMODE_UNSIGNED_OFFSET': '0x2000', + 'FMODE_PATH': '0x4000', + 'FMODE_NONOTIFY': '0x1000000', + 'S_IRWXUGO': '511', + 'S_IALLUGO': '4095', + 'S_IRUGO': '292', + 'S_IWUGO': '146', + 'S_IXUGO': '73', + 'LOOKUP_FOLLOW': '0x0001', + 'LOOKUP_DIRECTORY': '0x0002', + 'LOOKUP_PARENT': '0x0010', + 'MAP_SHARED': '0x01', + 'MAP_PRIVATE': '0x02', + 'MAP_TYPE': '0x0f', + 'MAP_FIXED': '0x100', + 'MAP_ANONYMOUS': '0x10', + 'O_NOFOLLOW': '0200000' }; |