diff options
Diffstat (limited to 'src/library.js')
-rw-r--r-- | src/library.js | 6827 |
1 files changed, 4172 insertions, 2655 deletions
diff --git a/src/library.js b/src/library.js index e65754ba..f69b52e5 100644 --- a/src/library.js +++ b/src/library.js @@ -18,828 +18,164 @@ // Memory allocated during startup, in postsets, should only be ALLOC_STATIC LibraryManager.library = { - // ========================================================================== - // File system base. - // ========================================================================== - // keep this low in memory, because we flatten arrays with them in them stdin: 'allocate(1, "i32*", ALLOC_STATIC)', stdout: 'allocate(1, "i32*", ALLOC_STATIC)', stderr: 'allocate(1, "i32*", ALLOC_STATIC)', _impure_ptr: 'allocate(1, "i32*", ALLOC_STATIC)', - - $FS__deps: ['$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr', '_impure_ptr'], - $FS__postset: '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + - '__ATMAIN__.push({ func: function() { FS.ignorePermissions = false } });' + - '__ATEXIT__.push({ func: function() { FS.quit() } });' + - // export some names through closure - 'Module["FS_createFolder"] = FS.createFolder;' + - 'Module["FS_createPath"] = FS.createPath;' + - 'Module["FS_createDataFile"] = FS.createDataFile;' + - 'Module["FS_createPreloadedFile"] = FS.createPreloadedFile;' + - 'Module["FS_createLazyFile"] = FS.createLazyFile;' + - 'Module["FS_createLink"] = FS.createLink;' + - 'Module["FS_createDevice"] = FS.createDevice;', - $FS: { - // 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. - 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 - // 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, - 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]; - } - 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); - } - } - return absolute.length == 1 ? '/' : absolute.join('/'); - }, - // 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 - }; -#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 + '}'); - } -#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; - } - 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; - }, - // 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) { - FS.ensureRoot(); - var ret = FS.analyzePath(path, dontResolveLastLink); - if (ret.exists) { - return ret.object; - } else { - ___setErrNo(ret.error); - return null; - } - }, - // 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.'); - } - if (!parent.isFolder) { - ___setErrNo(ERRNO_CODES.ENOTDIR); - throw new Error('Parent must be a folder.'); - } - if (!parent.write && !FS.ignorePermissions) { - ___setErrNo(ERRNO_CODES.EACCES); - throw new Error('Parent folder must be writeable.'); - } - if (!name || name == '.' || name == '..') { - ___setErrNo(ERRNO_CODES.ENOENT); - throw new Error('Name must not be empty.'); - } - if (parent.contents.hasOwnProperty(name)) { - ___setErrNo(ERRNO_CODES.EEXIST); - throw new Error("Can't overwrite object."); - } - - 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]; - } - } - - return parent.contents[name]; - }, - // Creates a folder. - createFolder: function(parent, name, canRead, canWrite) { - var properties = {isFolder: true, isDevice: false, contents: {}}; - return FS.createObject(parent, name, properties, canRead, canWrite); - }, - // 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(); - if (!part) continue; - if (!current.contents.hasOwnProperty(part)) { - FS.createFolder(current, part, canRead, canWrite); - } - current = current.contents[part]; - } - 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); - }, - // 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); - }, - // Creates a file record for lazy-loading from a URL. XXX This requires a synchronous - // XHR, which is not possible in browsers except in a web worker! Use preloading, - // either --preload-file in emcc or FS.createPreloadedFile - createLazyFile: function(parent, name, url, canRead, canWrite) { - - if (typeof XMLHttpRequest !== 'undefined') { - if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; - // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. - var LazyUint8Array = function() { - this.lengthKnown = false; - this.chunks = []; // Loaded chunks. Index is the chunk number - } - LazyUint8Array.prototype.get = function(idx) { - if (idx > this.length-1 || idx < 0) { - return undefined; - } - var chunkOffset = idx % this.chunkSize; - var chunkNum = Math.floor(idx / this.chunkSize); - return this.getter(chunkNum)[chunkOffset]; - } - LazyUint8Array.prototype.setDataGetter = function(getter) { - this.getter = getter; - } - - LazyUint8Array.prototype.cacheLength = function() { - // Find length - var xhr = new XMLHttpRequest(); - xhr.open('HEAD', url, false); - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); - var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; -#if SMALL_XHR_CHUNKS - var chunkSize = 1024; // Chunk size in bytes -#else - var chunkSize = 1024*1024; // Chunk size in bytes -#endif - if (!hasByteServing) chunkSize = datalength; - - // Function to get a range from the remote URL. - var doXHR = (function(from, to) { - if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); - - // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); - - // Some hints to the browser that we want binary data. - if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=x-user-defined'); - } - - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - if (xhr.response !== undefined) { - return new Uint8Array(xhr.response || []); - } else { - return intArrayFromString(xhr.responseText || '', true); - } - }); - var lazyArray = this; - lazyArray.setDataGetter(function(chunkNum) { - var start = chunkNum * chunkSize; - var end = (chunkNum+1) * chunkSize - 1; // including this byte - end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") { - lazyArray.chunks[chunkNum] = doXHR(start, end); - } - if (typeof(lazyArray.chunks[chunkNum]) === "undefined") throw new Error("doXHR failed!"); - return lazyArray.chunks[chunkNum]; - }); - - this._length = datalength; - this._chunkSize = chunkSize; - this.lengthKnown = true; - } - - var lazyArray = new LazyUint8Array(); - Object.defineProperty(lazyArray, "length", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._length; - } - }); - Object.defineProperty(lazyArray, "chunkSize", { - get: function() { - if(!this.lengthKnown) { - this.cacheLength(); - } - return this._chunkSize; - } - }); - - var properties = { isDevice: false, contents: lazyArray }; - } else { - var properties = { isDevice: false, url: url }; - } - - return FS.createFile(parent, name, properties, canRead, canWrite); - }, - // Preloads a file asynchronously. You can call this before run, for example in - // preRun. run will be delayed until this file arrives and is set up. - // If you call it after run(), you may want to pause the main loop until it - // completes, if so, you can use the onload parameter to be notified when - // that happens. - // In addition to normally creating the file, we also asynchronously preload - // the browser-friendly versions of it: For an image, we preload an Image - // element and for an audio, and Audio. These are necessary for SDL_Image - // and _Mixer to find the files in preloadedImages/Audios. - // You can also call this with a typed array instead of a url. It will then - // do preloading for the Image/Audio part, as if the typed array were the - // result of an XHR that you did manually. - createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) { - Browser.init(); - var fullname = FS.joinPath([parent, name], true); - function processData(byteArray) { - function finish(byteArray) { - if (!dontCreateFile) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite); - } - if (onload) onload(); - removeRunDependency('cp ' + fullname); - } - var handled = false; - Module['preloadPlugins'].forEach(function(plugin) { - if (handled) return; - if (plugin['canHandle'](fullname)) { - plugin['handle'](byteArray, fullname, finish, function() { - if (onerror) onerror(); - removeRunDependency('cp ' + fullname); - }); - handled = true; - } - }); - if (!handled) finish(byteArray); - } - addRunDependency('cp ' + fullname); - if (typeof url == 'string') { - Browser.asyncLoad(url, function(byteArray) { - processData(byteArray); - }, onerror); - } else { - processData(url); - } - }, - // 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); - }, - // 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)); - }, - // 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; - }, - ensureRoot: function() { - if (FS.root) return; - // 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: {} - }; - }, - // 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; - - FS.ensureRoot(); - - // 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(); - } - if (!result) result = ''; - input.cache = intArrayFromString(result + '\n', true); - } - return input.cache.shift(); - }; - } - var utf8 = new Runtime.UTF8Processor(); - function simpleOutput(val) { - if (val === null || val === {{{ charCode('\n') }}}) { - output.printer(output.buffer.join('')); - output.buffer = []; - } else { - output.buffer.push(utf8.processCChar(val)); - } - } - if (!output) { - stdoutOverridden = false; - output = simpleOutput; - } - if (!output.printer) output.printer = Module['print']; - if (!output.buffer) output.buffer = []; - if (!error) { - stderrOverridden = false; - error = simpleOutput; - } - if (!error.printer) error.printer = Module['print']; - if (!error.buffer) error.buffer = []; - - // Create the temporary folder, if not already created - try { - FS.createFolder('/', 'tmp', true, true); - } catch(e) {} - - // Create the I/O devices. - var devFolder = FS.createFolder('/', 'dev', true, true); - var stdin = FS.createDevice(devFolder, 'stdin', input); - var stdout = FS.createDevice(devFolder, 'stdout', null, output); - var stderr = FS.createDevice(devFolder, 'stderr', null, error); - FS.createDevice(devFolder, 'tty', input, output); - - // Create default streams. - FS.streams[1] = { - path: '/dev/stdin', - object: stdin, - position: 0, - isRead: true, - isWrite: false, - isAppend: false, - isTerminal: !stdinOverridden, - error: false, - eof: false, - ungotten: [] - }; - FS.streams[2] = { - path: '/dev/stdout', - object: stdout, - position: 0, - isRead: false, - isWrite: true, - isAppend: false, - isTerminal: !stdoutOverridden, - error: false, - eof: false, - ungotten: [] - }; - FS.streams[3] = { - path: '/dev/stderr', - object: stderr, - position: 0, - isRead: false, - isWrite: true, - isAppend: false, - isTerminal: !stderrOverridden, - 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 -#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') }}}); - }, - - 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') }}}); - }, - - // 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; - }, - - deleteFile: function(path) { - path = FS.analyzePath(path); - if (!path.parentExists || !path.exists) { - throw 'Invalid path ' + path; - } - delete path.parentObject.contents[path.name]; - } - }, + __dso_handle: 'allocate(1, "i32*", ALLOC_STATIC)', // ========================================================================== // dirent.h // ========================================================================== - __dirent_struct_layout: Runtime.generateStructInfo([ - ['i32', 'd_ino'], - ['b1024', 'd_name'], - ['i32', 'd_off'], - ['i32', 'd_reclen'], - ['i32', 'd_type']]), - opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], + opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '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) { + FS.handleFSError(e); return 0; - } else if (!target.read) { - ___setErrNo(ERRNO_CODES.EACCES); + } + if (!FS.isDir(node.mode)) { + ___setErrNo(ERRNO_CODES.ENOTDIR); return 0; } - var id = FS.streams.length; // Keep dense - var contents = []; - for (var key in target.contents) contents.push(key); - FS.streams[id] = { - 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 fd = _open(dirname, {{{ cDefine('O_RDONLY') }}}, allocate([0, 0, 0, 0], 'i32', ALLOC_STACK)); + return fd === -1 ? 0 : FS.getPtrForStream(FS.getStream(fd)); }, - closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'close', 'fileno'], 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) { - return ___setErrNo(ERRNO_CODES.EBADF); - } else { - _free(FS.streams[dirp].currentEntry); - FS.streams[dirp] = null; - return 0; - } + var fd = _fileno(dirp); + return _close(fd); }, 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) { - return ___setErrNo(ERRNO_CODES.EBADF); - } else { - return FS.streams[dirp].position; + var stream = FS.getStreamFromPtr(dirp); + if (!stream || !FS.isDir(stream.node.mode)) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } + return stream.position; }, - seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'lseek', 'fileno'], 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; - } - } + var fd = _fileno(dirp); + _lseek(fd, 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__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], 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.getStreamFromPtr(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) { - {{{ 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*') }}} + var entries; + try { + entries = FS.readdir(stream.path); + } catch (e) { + return FS.handleFSError(e); + } + if (stream.position < 0 || stream.position >= entries.length) { + {{{ makeSetValue('result', '0', '0', 'i8*') }}}; + return 0; } + 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', C_STRUCTS.dirent.d_ino, 'id', 'i32') }}}; + {{{ makeSetValue('entry', C_STRUCTS.dirent.d_off, 'offset', 'i32') }}}; + {{{ makeSetValue('entry', C_STRUCTS.dirent.d_reclen, 'name.length + 1', 'i32') }}}; + for (var i = 0; i < name.length; i++) { + {{{ makeSetValue('entry + ' + C_STRUCTS.dirent.d_name, 'i', 'name.charCodeAt(i)', 'i8') }}}; + } + {{{ makeSetValue('entry + ' + C_STRUCTS.dirent.d_name, 'i', '0', 'i8') }}}; + {{{ makeSetValue('entry', C_STRUCTS.dirent.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.getStreamFromPtr(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({{{ C_STRUCTS.dirent.__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. // ========================================================================== // utime.h // ========================================================================== - __utimbuf_struct_layout: Runtime.generateStructInfo([ - ['i32', 'actime'], - ['i32', 'modtime']]), - utime__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__utimbuf_struct_layout'], + utime__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], utime: function(path, times) { // int utime(const char *path, const struct utimbuf *times); // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/utime.h.html var time; if (times) { // NOTE: We don't keep track of access timestamps. - var offset = ___utimbuf_struct_layout.modtime; - time = {{{ makeGetValue('times', 'offset', 'i32') }}} + var offset = {{{ C_STRUCTS.utimbuf.modtime }}}; + time = {{{ makeGetValue('times', 'offset', 'i32') }}}; time *= 1000; } else { time = Date.now(); } - var file = FS.findObject(Pointer_stringify(path)); - if (file === null) return -1; - if (!file.write) { - ___setErrNo(ERRNO_CODES.EPERM); + path = Pointer_stringify(path); + try { + FS.utime(path, time, time); + return 0; + } catch (e) { + FS.handleFSError(e); return -1; } - file.timestamp = time; - return 0; }, utimes: function() { throw 'utimes not implemented' }, @@ -870,13 +206,13 @@ LibraryManager.library = { var length = i; if (allSlashes) { // All slashes result in a single slash. - {{{ makeSetValue('path', '1', '0', 'i8') }}} + {{{ makeSetValue('path', '1', '0', 'i8') }}}; return [path, -1]; } else { // Strip trailing slashes. while (slashPositions.length && slashPositions[slashPositions.length - 1] == length - 1) { - {{{ makeSetValue('path', 'slashPositions.pop(i)', '0', 'i8') }}} + {{{ makeSetValue('path', 'slashPositions.pop(i)', '0', 'i8') }}}; length--; } return [path, slashPositions.pop()]; @@ -890,16 +226,15 @@ LibraryManager.library = { var result = ___libgenSplitName(path); return result[0] + result[1] + 1; < |