| var PATH = { | |
| isAbs: path => path.charAt(0) === "/", | |
| splitPath: filename => { | |
| var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; | |
| return splitPathRe.exec(filename).slice(1) | |
| }, | |
| normalizeArray: (parts, allowAboveRoot) => { | |
| 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 (allowAboveRoot) { | |
| for (; up; up--) { | |
| parts.unshift("..") | |
| } | |
| } | |
| return parts | |
| }, | |
| normalize: path => { | |
| var isAbsolute = PATH.isAbs(path), | |
| trailingSlash = path.slice(-1) === "/"; | |
| path = PATH.normalizeArray(path.split("/").filter(p => !!p), !isAbsolute).join("/"); | |
| if (!path && !isAbsolute) { | |
| path = "." | |
| } | |
| if (path && trailingSlash) { | |
| path += "/" | |
| } | |
| return (isAbsolute ? "/" : "") + path | |
| }, | |
| dirname: path => { | |
| var result = PATH.splitPath(path), | |
| root = result[0], | |
| dir = result[1]; | |
| if (!root && !dir) { | |
| return "." | |
| } | |
| if (dir) { | |
| dir = dir.slice(0, -1) | |
| } | |
| return root + dir | |
| }, | |
| basename: path => path && path.match(/([^\/]+|\/)\/*$/)[1], | |
| join: (...paths) => PATH.normalize(paths.join("/")), | |
| join2: (l, r) => PATH.normalize(l + "/" + r) | |
| }; | |
| var initRandomFill = () => { | |
| if (ENVIRONMENT_IS_NODE) { | |
| var nodeCrypto = require("crypto"); | |
| return view => nodeCrypto.randomFillSync(view) | |
| } | |
| return view => crypto.getRandomValues(view) | |
| }; | |
| var randomFill = view => { | |
| (randomFill = initRandomFill())(view) | |
| }; | |
| var PATH_FS = { | |
| resolve: (...args) => { | |
| var resolvedPath = "", | |
| resolvedAbsolute = false; | |
| for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { | |
| var path = i >= 0 ? args[i] : FS.cwd(); | |
| if (typeof path != "string") { | |
| throw new TypeError("Arguments to path.resolve must be strings") | |
| } else if (!path) { | |
| return "" | |
| } | |
| resolvedPath = path + "/" + resolvedPath; | |
| resolvedAbsolute = PATH.isAbs(path) | |
| } | |
| resolvedPath = PATH.normalizeArray(resolvedPath.split("/").filter(p => !!p), !resolvedAbsolute).join("/"); | |
| return (resolvedAbsolute ? "/" : "") + resolvedPath || "." | |
| }, | |
| relative: (from, to) => { | |
| from = PATH_FS.resolve(from).slice(1); | |
| to = PATH_FS.resolve(to).slice(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("/") | |
| } | |
| }; | |
| var UTF8Decoder = new TextDecoder; | |
| var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { | |
| var maxIdx = idx + maxBytesToRead; | |
| if (ignoreNul) return maxIdx; | |
| while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; | |
| return idx | |
| }; | |
| var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { | |
| var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); | |
| return UTF8Decoder.decode(heapOrArray.buffer ? heapOrArray.subarray(idx, endPtr) : new Uint8Array(heapOrArray.slice(idx, endPtr))) | |
| }; | |
| var FS_stdin_getChar_buffer = []; | |
| var lengthBytesUTF8 = str => { | |
| var len = 0; | |
| for (var i = 0; i < str.length; ++i) { | |
| var c = str.charCodeAt(i); | |
| if (c <= 127) { | |
| len++ | |
| } else if (c <= 2047) { | |
| len += 2 | |
| } else if (c >= 55296 && c <= 57343) { | |
| len += 4; | |
| ++i | |
| } else { | |
| len += 3 | |
| } | |
| } | |
| return len | |
| }; | |
| var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { | |
| if (!(maxBytesToWrite > 0)) return 0; | |
| var startIdx = outIdx; | |
| var endIdx = outIdx + maxBytesToWrite - 1; | |
| for (var i = 0; i < str.length; ++i) { | |
| var u = str.codePointAt(i); | |
| if (u <= 127) { | |
| if (outIdx >= endIdx) break; | |
| heap[outIdx++] = u | |
| } else if (u <= 2047) { | |
| if (outIdx + 1 >= endIdx) break; | |
| heap[outIdx++] = 192 | u >> 6; | |
| heap[outIdx++] = 128 | u & 63 | |
| } else if (u <= 65535) { | |
| if (outIdx + 2 >= endIdx) break; | |
| heap[outIdx++] = 224 | u >> 12; | |
| heap[outIdx++] = 128 | u >> 6 & 63; | |
| heap[outIdx++] = 128 | u & 63 | |
| } else { | |
| if (outIdx + 3 >= endIdx) break; | |
| heap[outIdx++] = 240 | u >> 18; | |
| heap[outIdx++] = 128 | u >> 12 & 63; | |
| heap[outIdx++] = 128 | u >> 6 & 63; | |
| heap[outIdx++] = 128 | u & 63; | |
| i++ | |
| } | |
| } | |
| heap[outIdx] = 0; | |
| return outIdx - startIdx | |
| }; | |
| var intArrayFromString = (stringy, dontAddNull, length) => { | |
| var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; | |
| var u8array = new Array(len); | |
| var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); | |
| if (dontAddNull) u8array.length = numBytesWritten; | |
| return u8array | |
| }; | |
| var FS_stdin_getChar = () => { | |
| if (!FS_stdin_getChar_buffer.length) { | |
| var result = null; | |
| if (ENVIRONMENT_IS_NODE) { | |
| var BUFSIZE = 256; | |
| var buf = Buffer.alloc(BUFSIZE); | |
| var bytesRead = 0; | |
| var fd = process.stdin.fd; | |
| try { | |
| bytesRead = fs.readSync(fd, buf, 0, BUFSIZE) | |
| } catch (e) { | |
| if (e.toString().includes("EOF")) bytesRead = 0; | |
| else throw e | |
| } | |
| if (bytesRead > 0) { | |
| result = buf.slice(0, bytesRead).toString("utf-8") | |
| } | |
| } else if (globalThis.window?.prompt) { | |
| result = window.prompt("Input: "); | |
| if (result !== null) { | |
| result += "\n" | |
| } | |
| } else {} | |
| if (!result) { | |
| return null | |
| } | |
| FS_stdin_getChar_buffer = intArrayFromString(result, true) | |
| } | |
| return FS_stdin_getChar_buffer.shift() | |
| }; | |
| var TTY = { | |
| ttys: [], | |
| init() {}, | |
| shutdown() {}, | |
| register(dev, ops) { | |
| TTY.ttys[dev] = { | |
| input: [], | |
| output: [], | |
| ops | |
| }; | |
| FS.registerDevice(dev, TTY.stream_ops) | |
| }, | |
| stream_ops: { | |
| open(stream) { | |
| var tty = TTY.ttys[stream.node.rdev]; | |
| if (!tty) { | |
| throw new FS.ErrnoError(43) | |
| } | |
| stream.tty = tty; | |
| stream.seekable = false | |
| }, | |
| close(stream) { | |
| stream.tty.ops.fsync(stream.tty) | |
| }, | |
| fsync(stream) { | |
| stream.tty.ops.fsync(stream.tty) | |
| }, | |
| read(stream, buffer, offset, length, pos) { | |
| if (!stream.tty || !stream.tty.ops.get_char) { | |
| throw new FS.ErrnoError(60) | |
| } | |
| 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(29) | |
| } | |
| if (result === undefined && bytesRead === 0) { | |
| throw new FS.ErrnoError(6) | |
| } | |
| if (result === null || result === undefined) break; | |
| bytesRead++; | |
| buffer[offset + i] = result | |
| } | |
| if (bytesRead) { | |
| stream.node.atime = Date.now() | |
| } | |
| return bytesRead | |
| }, | |
| write(stream, buffer, offset, length, pos) { | |
| if (!stream.tty || !stream.tty.ops.put_char) { | |
| throw new FS.ErrnoError(60) | |
| } | |
| try { | |
| for (var i = 0; i < length; i++) { | |
| stream.tty.ops.put_char(stream.tty, buffer[offset + i]) | |
| } | |
| } catch (e) { | |
| throw new FS.ErrnoError(29) | |
| } | |
| if (length) { | |
| stream.node.mtime = stream.node.ctime = Date.now() | |
| } | |
| return i | |
| } | |
| }, | |
| default_tty_ops: { | |
| get_char(tty) { | |
| return FS_stdin_getChar() | |
| }, | |
| put_char(tty, val) { | |
| if (val === null || val === 10) { | |
| out(UTF8ArrayToString(tty.output)); | |
| tty.output = [] | |
| } else { | |
| if (val != 0) tty.output.push(val) | |
| } | |
| }, | |
| fsync(tty) { | |
| if (tty.output?.length > 0) { | |
| out(UTF8ArrayToString(tty.output)); | |
| tty.output = [] | |
| } | |
| }, | |
| ioctl_tcgets(tty) { | |
| return { | |
| c_iflag: 25856, | |
| c_oflag: 5, | |
| c_cflag: 191, | |
| c_lflag: 35387, | |
| c_cc: [3, 28, 127, 21, 4, 0, 1, 0, 17, 19, 26, 0, 18, 15, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
| } | |
| }, | |
| ioctl_tcsets(tty, optional_actions, data) { | |
| return 0 | |
| }, | |
| ioctl_tiocgwinsz(tty) { | |
| return [24, 80] | |
| } | |
| }, | |
| default_tty1_ops: { | |
| put_char(tty, val) { | |
| if (val === null || val === 10) { | |
| err(UTF8ArrayToString(tty.output)); | |
| tty.output = [] | |
| } else { | |
| if (val != 0) tty.output.push(val) | |
| } | |
| }, | |
| fsync(tty) { | |
| if (tty.output?.length > 0) { | |
| err(UTF8ArrayToString(tty.output)); | |
| tty.output = [] | |
| } | |
| } | |
| } | |
| }; | |
| var mmapAlloc = size => { | |
| abort() | |
| }; | |
| var MEMFS = { | |
| ops_table: null, | |
| mount(mount) { | |
| return MEMFS.createNode(null, "/", 16895, 0) | |
| }, | |
| createNode(parent, name, mode, dev) { | |
| if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| MEMFS.ops_table ||= { | |
| dir: { | |
| node: { | |
| getattr: MEMFS.node_ops.getattr, | |
| setattr: MEMFS.node_ops.setattr, | |
| lookup: MEMFS.node_ops.lookup, | |
| mknod: MEMFS.node_ops.mknod, | |
| rename: MEMFS.node_ops.rename, | |
| unlink: MEMFS.node_ops.unlink, | |
| rmdir: MEMFS.node_ops.rmdir, | |
| readdir: MEMFS.node_ops.readdir, | |
| symlink: MEMFS.node_ops.symlink | |
| }, | |
| stream: { | |
| llseek: MEMFS.stream_ops.llseek | |
| } | |
| }, | |
| file: { | |
| node: { | |
| getattr: MEMFS.node_ops.getattr, | |
| setattr: MEMFS.node_ops.setattr | |
| }, | |
| stream: { | |
| llseek: MEMFS.stream_ops.llseek, | |
| read: MEMFS.stream_ops.read, | |
| write: MEMFS.stream_ops.write, | |
| mmap: MEMFS.stream_ops.mmap, | |
| msync: MEMFS.stream_ops.msync | |
| } | |
| }, | |
| link: { | |
| node: { | |
| getattr: MEMFS.node_ops.getattr, | |
| setattr: MEMFS.node_ops.setattr, | |
| readlink: MEMFS.node_ops.readlink | |
| }, | |
| stream: {} | |
| }, | |
| chrdev: { | |
| node: { | |
| getattr: MEMFS.node_ops.getattr, | |
| setattr: MEMFS.node_ops.setattr | |
| }, | |
| stream: FS.chrdev_stream_ops | |
| } | |
| }; | |
| var node = FS.createNode(parent, name, mode, dev); | |
| if (FS.isDir(node.mode)) { | |
| node.node_ops = MEMFS.ops_table.dir.node; | |
| node.stream_ops = MEMFS.ops_table.dir.stream; | |
| node.contents = {} | |
| } else if (FS.isFile(node.mode)) { | |
| node.node_ops = MEMFS.ops_table.file.node; | |
| node.stream_ops = MEMFS.ops_table.file.stream; | |
| node.usedBytes = 0; | |
| node.contents = null | |
| } else if (FS.isLink(node.mode)) { | |
| node.node_ops = MEMFS.ops_table.link.node; | |
| node.stream_ops = MEMFS.ops_table.link.stream | |
| } else if (FS.isChrdev(node.mode)) { | |
| node.node_ops = MEMFS.ops_table.chrdev.node; | |
| node.stream_ops = MEMFS.ops_table.chrdev.stream | |
| } | |
| node.atime = node.mtime = node.ctime = Date.now(); | |
| if (parent) { | |
| parent.contents[name] = node; | |
| parent.atime = parent.mtime = parent.ctime = node.atime | |
| } | |
| return node | |
| }, | |
| getFileDataAsTypedArray(node) { | |
| if (!node.contents) return new Uint8Array(0); | |
| if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); | |
| return new Uint8Array(node.contents) | |
| }, | |
| expandFileStorage(node, newCapacity) { | |
| var prevCapacity = node.contents ? node.contents.length : 0; | |
| if (prevCapacity >= newCapacity) return; | |
| var CAPACITY_DOUBLING_MAX = 1024 * 1024; | |
| newCapacity = Math.max(newCapacity, prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125) >>> 0); | |
| if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); | |
| var oldContents = node.contents; | |
| node.contents = new Uint8Array(newCapacity); | |
| if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0) | |
| }, | |
| resizeFileStorage(node, newSize) { | |
| if (node.usedBytes == newSize) return; | |
| if (newSize == 0) { | |
| node.contents = null; | |
| node.usedBytes = 0 | |
| } else { | |
| var oldContents = node.contents; | |
| node.contents = new Uint8Array(newSize); | |
| if (oldContents) { | |
| node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))) | |
| } | |
| node.usedBytes = newSize | |
| } | |
| }, | |
| node_ops: { | |
| getattr(node) { | |
| var attr = {}; | |
| 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.usedBytes | |
| } else if (FS.isLink(node.mode)) { | |
| attr.size = node.link.length | |
| } else { | |
| attr.size = 0 | |
| } | |
| attr.atime = new Date(node.atime); | |
| attr.mtime = new Date(node.mtime); | |
| attr.ctime = new Date(node.ctime); | |
| attr.blksize = 4096; | |
| attr.blocks = Math.ceil(attr.size / attr.blksize); | |
| return attr | |
| }, | |
| setattr(node, attr) { | |
| for (const key of ["mode", "atime", "mtime", "ctime"]) { | |
| if (attr[key] != null) { | |
| node[key] = attr[key] | |
| } | |
| } | |
| if (attr.size !== undefined) { | |
| MEMFS.resizeFileStorage(node, attr.size) | |
| } | |
| }, | |
| lookup(parent, name) { | |
| if (!MEMFS.doesNotExistError) { | |
| MEMFS.doesNotExistError = new FS.ErrnoError(44); | |
| MEMFS.doesNotExistError.stack = "<generic error, no stack>" | |
| } | |
| throw MEMFS.doesNotExistError | |
| }, | |
| mknod(parent, name, mode, dev) { | |
| return MEMFS.createNode(parent, name, mode, dev) | |
| }, | |
| rename(old_node, new_dir, new_name) { | |
| var new_node; | |
| try { | |
| new_node = FS.lookupNode(new_dir, new_name) | |
| } catch (e) {} | |
| if (new_node) { | |
| if (FS.isDir(old_node.mode)) { | |
| for (var i in new_node.contents) { | |
| throw new FS.ErrnoError(55) | |
| } | |
| } | |
| FS.hashRemoveNode(new_node) | |
| } | |
| delete old_node.parent.contents[old_node.name]; | |
| new_dir.contents[new_name] = old_node; | |
| old_node.name = new_name; | |
| new_dir.ctime = new_dir.mtime = old_node.parent.ctime = old_node.parent.mtime = Date.now() | |
| }, | |
| unlink(parent, name) { | |
| delete parent.contents[name]; | |
| parent.ctime = parent.mtime = Date.now() | |
| }, | |
| rmdir(parent, name) { | |
| var node = FS.lookupNode(parent, name); | |
| for (var i in node.contents) { | |
| throw new FS.ErrnoError(55) | |
| } | |
| delete parent.contents[name]; | |
| parent.ctime = parent.mtime = Date.now() | |
| }, | |
| readdir(node) { | |
| return [".", "..", ...Object.keys(node.contents)] | |
| }, | |
| symlink(parent, newname, oldpath) { | |
| var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); | |
| node.link = oldpath; | |
| return node | |
| }, | |
| readlink(node) { | |
| if (!FS.isLink(node.mode)) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| return node.link | |
| } | |
| }, | |
| stream_ops: { | |
| read(stream, buffer, offset, length, position) { | |
| var contents = stream.node.contents; | |
| if (position >= stream.node.usedBytes) return 0; | |
| var size = Math.min(stream.node.usedBytes - position, length); | |
| if (size > 8 && contents.subarray) { | |
| buffer.set(contents.subarray(position, position + size), offset) | |
| } else { | |
| for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i] | |
| } | |
| return size | |
| }, | |
| write(stream, buffer, offset, length, position, canOwn) { | |
| if (buffer.buffer === HEAP8.buffer) { | |
| canOwn = false | |
| } | |
| if (!length) return 0; | |
| var node = stream.node; | |
| node.mtime = node.ctime = Date.now(); | |
| if (buffer.subarray && (!node.contents || node.contents.subarray)) { | |
| if (canOwn) { | |
| node.contents = buffer.subarray(offset, offset + length); | |
| node.usedBytes = length; | |
| return length | |
| } else if (node.usedBytes === 0 && position === 0) { | |
| node.contents = buffer.slice(offset, offset + length); | |
| node.usedBytes = length; | |
| return length | |
| } else if (position + length <= node.usedBytes) { | |
| node.contents.set(buffer.subarray(offset, offset + length), position); | |
| return length | |
| } | |
| } | |
| MEMFS.expandFileStorage(node, position + length); | |
| if (node.contents.subarray && buffer.subarray) { | |
| node.contents.set(buffer.subarray(offset, offset + length), position) | |
| } else { | |
| for (var i = 0; i < length; i++) { | |
| node.contents[position + i] = buffer[offset + i] | |
| } | |
| } | |
| node.usedBytes = Math.max(node.usedBytes, position + length); | |
| return length | |
| }, | |
| llseek(stream, offset, whence) { | |
| var position = offset; | |
| if (whence === 1) { | |
| position += stream.position | |
| } else if (whence === 2) { | |
| if (FS.isFile(stream.node.mode)) { | |
| position += stream.node.usedBytes | |
| } | |
| } | |
| if (position < 0) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| return position | |
| }, | |
| mmap(stream, length, position, prot, flags) { | |
| if (!FS.isFile(stream.node.mode)) { | |
| throw new FS.ErrnoError(43) | |
| } | |
| var ptr; | |
| var allocated; | |
| var contents = stream.node.contents; | |
| if (!(flags & 2) && contents && contents.buffer === HEAP8.buffer) { | |
| allocated = false; | |
| ptr = contents.byteOffset | |
| } else { | |
| allocated = true; | |
| ptr = mmapAlloc(length); | |
| if (!ptr) { | |
| throw new FS.ErrnoError(48) | |
| } | |
| if (contents) { | |
| 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) | |
| } | |
| } | |
| HEAP8.set(contents, ptr) | |
| } | |
| } | |
| return { | |
| ptr, | |
| allocated | |
| } | |
| }, | |
| msync(stream, buffer, offset, length, mmapFlags) { | |
| MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); | |
| return 0 | |
| } | |
| } | |
| }; | |
| var FS_modeStringToFlags = str => { | |
| var flagModes = { | |
| r: 0, | |
| "r+": 2, | |
| w: 512 | 64 | 1, | |
| "w+": 512 | 64 | 2, | |
| a: 1024 | 64 | 1, | |
| "a+": 1024 | 64 | 2 | |
| }; | |
| var flags = flagModes[str]; | |
| if (typeof flags == "undefined") { | |
| throw new Error(`Unknown file open mode: ${str}`) | |
| } | |
| return flags | |
| }; | |
| var FS_getMode = (canRead, canWrite) => { | |
| var mode = 0; | |
| if (canRead) mode |= 292 | 73; | |
| if (canWrite) mode |= 146; | |
| return mode | |
| }; | |
| var IDBFS = { | |
| dbs: {}, | |
| indexedDB: () => indexedDB, | |
| DB_VERSION: 21, | |
| DB_STORE_NAME: "FILE_DATA", | |
| queuePersist: mount => { | |
| function onPersistComplete() { | |
| if (mount.idbPersistState === "again") startPersist(); | |
| else mount.idbPersistState = 0 | |
| } | |
| function startPersist() { | |
| mount.idbPersistState = "idb"; | |
| IDBFS.syncfs(mount, false, onPersistComplete) | |
| } | |
| if (!mount.idbPersistState) { | |
| mount.idbPersistState = setTimeout(startPersist, 0) | |
| } else if (mount.idbPersistState === "idb") { | |
| mount.idbPersistState = "again" | |
| } | |
| }, | |
| mount: mount => { | |
| var mnt = MEMFS.mount(mount); | |
| if (mount?.opts?.autoPersist) { | |
| mount.idbPersistState = 0; | |
| var memfs_node_ops = mnt.node_ops; | |
| mnt.node_ops = { | |
| ...mnt.node_ops | |
| }; | |
| mnt.node_ops.mknod = (parent, name, mode, dev) => { | |
| var node = memfs_node_ops.mknod(parent, name, mode, dev); | |
| node.node_ops = mnt.node_ops; | |
| node.idbfs_mount = mnt.mount; | |
| node.memfs_stream_ops = node.stream_ops; | |
| node.stream_ops = { | |
| ...node.stream_ops | |
| }; | |
| node.stream_ops.write = (stream, buffer, offset, length, position, canOwn) => { | |
| stream.node.isModified = true; | |
| return node.memfs_stream_ops.write(stream, buffer, offset, length, position, canOwn) | |
| }; | |
| node.stream_ops.close = stream => { | |
| var n = stream.node; | |
| if (n.isModified) { | |
| IDBFS.queuePersist(n.idbfs_mount); | |
| n.isModified = false | |
| } | |
| if (n.memfs_stream_ops.close) return n.memfs_stream_ops.close(stream) | |
| }; | |
| IDBFS.queuePersist(mnt.mount); | |
| return node | |
| }; | |
| mnt.node_ops.rmdir = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.rmdir(...args)); | |
| mnt.node_ops.symlink = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.symlink(...args)); | |
| mnt.node_ops.unlink = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.unlink(...args)); | |
| mnt.node_ops.rename = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.rename(...args)) | |
| } | |
| return mnt | |
| }, | |
| syncfs: (mount, populate, callback) => { | |
| const fn = callback => { | |
| IDBFS.getLocalSet(mount, (err, local) => { | |
| if (err) return callback(err); | |
| IDBFS.getRemoteSet(mount, (err, remote) => { | |
| if (err) return callback(err); | |
| var src = populate ? remote : local; | |
| var dst = populate ? local : remote; | |
| IDBFS.reconcile(src, dst, callback) | |
| }) | |
| }) | |
| }; | |
| if (window.syncfs !== undefined) | |
| window.syncfs(IDBFS, mount, populate, callback, fn); | |
| else | |
| fn(callback); | |
| }, | |
| quit: () => { | |
| for (var value of Object.values(IDBFS.dbs)) { | |
| value.close() | |
| } | |
| IDBFS.dbs = {} | |
| }, | |
| getDB: (name, callback) => { | |
| var db = IDBFS.dbs[name]; | |
| if (db) { | |
| return callback(null, db) | |
| } | |
| var req; | |
| try { | |
| req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION) | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| if (!req) { | |
| return callback("Unable to connect to IndexedDB") | |
| } | |
| req.onupgradeneeded = e => { | |
| var db = e.target.result; | |
| var transaction = e.target.transaction; | |
| var fileStore; | |
| if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { | |
| fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME) | |
| } else { | |
| fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME) | |
| } | |
| if (!fileStore.indexNames.contains("timestamp")) { | |
| fileStore.createIndex("timestamp", "timestamp", { | |
| unique: false | |
| }) | |
| } | |
| }; | |
| req.onsuccess = () => { | |
| db = req.result; | |
| IDBFS.dbs[name] = db; | |
| callback(null, db) | |
| }; | |
| req.onerror = e => { | |
| callback(e.target.error); | |
| e.preventDefault() | |
| } | |
| }, | |
| getLocalSet: (mount, callback) => { | |
| var entries = {}; | |
| function isRealDir(p) { | |
| return p !== "." && p !== ".." | |
| } | |
| function toAbsolute(root) { | |
| return p => PATH.join2(root, p) | |
| } | |
| var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); | |
| while (check.length) { | |
| var path = check.pop(); | |
| var stat; | |
| try { | |
| stat = FS.stat(path) | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| if (FS.isDir(stat.mode)) { | |
| check.push(...FS.readdir(path).filter(isRealDir).map(toAbsolute(path))) | |
| } | |
| entries[path] = { | |
| timestamp: stat.mtime | |
| } | |
| } | |
| return callback(null, { | |
| type: "local", | |
| entries | |
| }) | |
| }, | |
| getRemoteSet: (mount, callback) => { | |
| var entries = {}; | |
| IDBFS.getDB(mount.mountpoint, (err, db) => { | |
| if (err) return callback(err); | |
| try { | |
| var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readonly"); | |
| transaction.onerror = e => { | |
| callback(e.target.error); | |
| e.preventDefault() | |
| }; | |
| var store = transaction.objectStore(IDBFS.DB_STORE_NAME); | |
| var index = store.index("timestamp"); | |
| index.openKeyCursor().onsuccess = event => { | |
| var cursor = event.target.result; | |
| if (!cursor) { | |
| return callback(null, { | |
| type: "remote", | |
| db, | |
| entries | |
| }) | |
| } | |
| entries[cursor.primaryKey] = { | |
| timestamp: cursor.key | |
| }; | |
| cursor.continue() | |
| } | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| }) | |
| }, | |
| loadLocalEntry: (path, callback) => { | |
| var stat, node; | |
| try { | |
| var lookup = FS.lookupPath(path); | |
| node = lookup.node; | |
| stat = FS.stat(path) | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| if (FS.isDir(stat.mode)) { | |
| return callback(null, { | |
| timestamp: stat.mtime, | |
| mode: stat.mode | |
| }) | |
| } else if (FS.isFile(stat.mode)) { | |
| node.contents = MEMFS.getFileDataAsTypedArray(node); | |
| return callback(null, { | |
| timestamp: stat.mtime, | |
| mode: stat.mode, | |
| contents: node.contents | |
| }) | |
| } else { | |
| return callback(new Error("node type not supported")) | |
| } | |
| }, | |
| storeLocalEntry: (path, entry, callback) => { | |
| try { | |
| if (FS.isDir(entry["mode"])) { | |
| FS.mkdirTree(path, entry["mode"]) | |
| } else if (FS.isFile(entry["mode"])) { | |
| FS.writeFile(path, entry["contents"], { | |
| canOwn: true | |
| }) | |
| } else { | |
| return callback(new Error("node type not supported")) | |
| } | |
| FS.chmod(path, entry["mode"]); | |
| FS.utime(path, entry["timestamp"], entry["timestamp"]) | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| callback(null) | |
| }, | |
| removeLocalEntry: (path, callback) => { | |
| try { | |
| var stat = FS.stat(path); | |
| if (FS.isDir(stat.mode)) { | |
| FS.rmdir(path) | |
| } else if (FS.isFile(stat.mode)) { | |
| FS.unlink(path) | |
| } | |
| } catch (e) { | |
| return callback(e) | |
| } | |
| callback(null) | |
| }, | |
| loadRemoteEntry: (store, path, callback) => { | |
| var req = store.get(path); | |
| req.onsuccess = event => callback(null, event.target.result); | |
| req.onerror = e => { | |
| callback(e.target.error); | |
| e.preventDefault() | |
| } | |
| }, | |
| storeRemoteEntry: (store, path, entry, callback) => { | |
| try { | |
| var req = store.put(entry, path) | |
| } catch (e) { | |
| callback(e); | |
| return | |
| } | |
| req.onsuccess = event => callback(); | |
| req.onerror = e => { | |
| callback(e.target.error); | |
| e.preventDefault() | |
| } | |
| }, | |
| removeRemoteEntry: (store, path, callback) => { | |
| var req = store.delete(path); | |
| req.onsuccess = event => callback(); | |
| req.onerror = e => { | |
| callback(e.target.error); | |
| e.preventDefault() | |
| } | |
| }, | |
| reconcile: (src, dst, callback) => { | |
| var total = 0; | |
| var create = []; | |
| for (var [key, e] of Object.entries(src.entries)) { | |
| var e2 = dst.entries[key]; | |
| if (!e2 || e["timestamp"].getTime() != e2["timestamp"].getTime()) { | |
| create.push(key); | |
| total++ | |
| } | |
| } | |
| var remove = []; | |
| for (var key of Object.keys(dst.entries)) { | |
| if (!src.entries[key]) { | |
| remove.push(key); | |
| total++ | |
| } | |
| } | |
| if (!total) { | |
| return callback(null) | |
| } | |
| var errored = false; | |
| var db = src.type === "remote" ? src.db : dst.db; | |
| var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readwrite"); | |
| var store = transaction.objectStore(IDBFS.DB_STORE_NAME); | |
| function done(err) { | |
| if (err && !errored) { | |
| errored = true; | |
| return callback(err) | |
| } | |
| } | |
| transaction.onerror = transaction.onabort = e => { | |
| done(e.target.error); | |
| e.preventDefault() | |
| }; | |
| transaction.oncomplete = e => { | |
| if (!errored) { | |
| callback(null) | |
| } | |
| }; | |
| for (const path of create.sort()) { | |
| if (dst.type === "local") { | |
| IDBFS.loadRemoteEntry(store, path, (err, entry) => { | |
| if (err) return done(err); | |
| IDBFS.storeLocalEntry(path, entry, done) | |
| }) | |
| } else { | |
| IDBFS.loadLocalEntry(path, (err, entry) => { | |
| if (err) return done(err); | |
| IDBFS.storeRemoteEntry(store, path, entry, done) | |
| }) | |
| } | |
| } | |
| for (var path of remove.sort().reverse()) { | |
| if (dst.type === "local") { | |
| IDBFS.removeLocalEntry(path, done) | |
| } else { | |
| IDBFS.removeRemoteEntry(store, path, done) | |
| } | |
| } | |
| } | |
| }; | |
| var asyncLoad = async url => { | |
| var arrayBuffer = await readAsync(url); | |
| return new Uint8Array(arrayBuffer) | |
| }; | |
| var FS_createDataFile = (...args) => FS.createDataFile(...args); | |
| var getUniqueRunDependency = id => id; | |
| var preloadPlugins = []; | |
| var FS_handledByPreloadPlugin = async (byteArray, fullname) => { | |
| if (typeof Browser != "undefined") Browser.init(); | |
| for (var plugin of preloadPlugins) { | |
| if (plugin["canHandle"](fullname)) { | |
| return plugin["handle"](byteArray, fullname) | |
| } | |
| } | |
| return byteArray | |
| }; | |
| var FS_preloadFile = async (parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish) => { | |
| var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; | |
| var dep = getUniqueRunDependency(`cp ${fullname}`); | |
| addRunDependency(dep); | |
| try { | |
| var byteArray = url; | |
| if (typeof url == "string") { | |
| byteArray = await asyncLoad(url) | |
| } | |
| byteArray = await FS_handledByPreloadPlugin(byteArray, fullname); | |
| preFinish?.(); | |
| if (!dontCreateFile) { | |
| FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn) | |
| } | |
| } finally { | |
| removeRunDependency(dep) | |
| } | |
| }; | |
| var FS_createPreloadedFile = (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => { | |
| FS_preloadFile(parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish).then(onload).catch(onerror) | |
| }; | |
| var FS = { | |
| root: null, | |
| mounts: [], | |
| devices: {}, | |
| streams: [], | |
| nextInode: 1, | |
| nameTable: null, | |
| currentPath: "/", | |
| initialized: false, | |
| ignorePermissions: true, | |
| filesystems: null, | |
| syncFSRequests: 0, | |
| readFiles: {}, | |
| ErrnoError: class { | |
| name = "ErrnoError"; | |
| constructor(errno) { | |
| this.errno = errno | |
| } | |
| }, | |
| FSStream: class { | |
| shared = {}; | |
| get object() { | |
| return this.node | |
| } | |
| set object(val) { | |
| this.node = val | |
| } | |
| get isRead() { | |
| return (this.flags & 2097155) !== 1 | |
| } | |
| get isWrite() { | |
| return (this.flags & 2097155) !== 0 | |
| } | |
| get isAppend() { | |
| return this.flags & 1024 | |
| } | |
| get flags() { | |
| return this.shared.flags | |
| } | |
| set flags(val) { | |
| this.shared.flags = val | |
| } | |
| get position() { | |
| return this.shared.position | |
| } | |
| set position(val) { | |
| this.shared.position = val | |
| } | |
| }, | |
| FSNode: class { | |
| node_ops = {}; | |
| stream_ops = {}; | |
| readMode = 292 | 73; | |
| writeMode = 146; | |
| mounted = null; | |
| constructor(parent, name, mode, rdev) { | |
| if (!parent) { | |
| parent = this | |
| } | |
| this.parent = parent; | |
| this.mount = parent.mount; | |
| this.id = FS.nextInode++; | |
| this.name = name; | |
| this.mode = mode; | |
| this.rdev = rdev; | |
| this.atime = this.mtime = this.ctime = Date.now() | |
| } | |
| get read() { | |
| return (this.mode & this.readMode) === this.readMode | |
| } | |
| set read(val) { | |
| val ? this.mode |= this.readMode : this.mode &= ~this.readMode | |
| } | |
| get write() { | |
| return (this.mode & this.writeMode) === this.writeMode | |
| } | |
| set write(val) { | |
| val ? this.mode |= this.writeMode : this.mode &= ~this.writeMode | |
| } | |
| get isFolder() { | |
| return FS.isDir(this.mode) | |
| } | |
| get isDevice() { | |
| return FS.isChrdev(this.mode) | |
| } | |
| }, | |
| lookupPath(path, opts = {}) { | |
| if (!path) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| opts.follow_mount ??= true; | |
| if (!PATH.isAbs(path)) { | |
| path = FS.cwd() + "/" + path | |
| } | |
| linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { | |
| var parts = path.split("/").filter(p => !!p); | |
| 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) { | |
| break | |
| } | |
| if (parts[i] === ".") { | |
| continue | |
| } | |
| if (parts[i] === "..") { | |
| current_path = PATH.dirname(current_path); | |
| if (FS.isRoot(current)) { | |
| path = current_path + "/" + parts.slice(i + 1).join("/"); | |
| nlinks--; | |
| continue linkloop | |
| } else { | |
| current = current.parent | |
| } | |
| continue | |
| } | |
| current_path = PATH.join2(current_path, parts[i]); | |
| try { | |
| current = FS.lookupNode(current, parts[i]) | |
| } catch (e) { | |
| if (e?.errno === 44 && islast && opts.noent_okay) { | |
| return { | |
| path: current_path | |
| } | |
| } | |
| throw e | |
| } | |
| if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) { | |
| current = current.mounted.root | |
| } | |
| if (FS.isLink(current.mode) && (!islast || opts.follow)) { | |
| if (!current.node_ops.readlink) { | |
| throw new FS.ErrnoError(52) | |
| } | |
| var link = current.node_ops.readlink(current); | |
| if (!PATH.isAbs(link)) { | |
| link = PATH.dirname(current_path) + "/" + link | |
| } | |
| path = link + "/" + parts.slice(i + 1).join("/"); | |
| continue linkloop | |
| } | |
| } | |
| return { | |
| path: current_path, | |
| node: current | |
| } | |
| } | |
| throw new FS.ErrnoError(32) | |
| }, | |
| getPath(node) { | |
| var path; | |
| while (true) { | |
| if (FS.isRoot(node)) { | |
| var mount = node.mount.mountpoint; | |
| if (!path) return mount; | |
| return mount[mount.length - 1] !== "/" ? `${mount}/${path}` : mount + path | |
| } | |
| path = path ? `${node.name}/${path}` : node.name; | |
| node = node.parent | |
| } | |
| }, | |
| hashName(parentid, name) { | |
| var hash = 0; | |
| name = name.toLowerCase(); | |
| for (var i = 0; i < name.length; i++) { | |
| hash = (hash << 5) - hash + name.charCodeAt(i) | 0 | |
| } | |
| return (parentid + hash >>> 0) % FS.nameTable.length | |
| }, | |
| hashAddNode(node) { | |
| var hash = FS.hashName(node.parent.id, node.name); | |
| node.name_next = FS.nameTable[hash]; | |
| FS.nameTable[hash] = node | |
| }, | |
| hashRemoveNode(node) { | |
| var hash = FS.hashName(node.parent.id, node.name); | |
| if (FS.nameTable[hash] === node) { | |
| FS.nameTable[hash] = node.name_next | |
| } else { | |
| var current = FS.nameTable[hash]; | |
| while (current) { | |
| if (current.name_next === node) { | |
| current.name_next = node.name_next; | |
| break | |
| } | |
| current = current.name_next | |
| } | |
| } | |
| }, | |
| lookupNode(parent, name) { | |
| var errCode = FS.mayLookup(parent); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| var hash = FS.hashName(parent.id, name); | |
| name = name.toLowerCase(); | |
| for (var node = FS.nameTable[hash]; node; node = node.name_next) { | |
| var nodeName = node.name; | |
| nodeName = nodeName.toLowerCase(); | |
| if (node.parent.id === parent.id && nodeName === name) { | |
| return node | |
| } | |
| } | |
| return FS.lookup(parent, name) | |
| }, | |
| createNode(parent, name, mode, rdev) { | |
| var node = new FS.FSNode(parent, name, mode, rdev); | |
| FS.hashAddNode(node); | |
| return node | |
| }, | |
| destroyNode(node) { | |
| FS.hashRemoveNode(node) | |
| }, | |
| isRoot(node) { | |
| return node === node.parent | |
| }, | |
| isMountpoint(node) { | |
| return !!node.mounted | |
| }, | |
| isFile(mode) { | |
| return (mode & 61440) === 32768 | |
| }, | |
| isDir(mode) { | |
| return (mode & 61440) === 16384 | |
| }, | |
| isLink(mode) { | |
| return (mode & 61440) === 40960 | |
| }, | |
| isChrdev(mode) { | |
| return (mode & 61440) === 8192 | |
| }, | |
| isBlkdev(mode) { | |
| return (mode & 61440) === 24576 | |
| }, | |
| isFIFO(mode) { | |
| return (mode & 61440) === 4096 | |
| }, | |
| isSocket(mode) { | |
| return (mode & 49152) === 49152 | |
| }, | |
| flagsToPermissionString(flag) { | |
| var perms = ["r", "w", "rw"][flag & 3]; | |
| if (flag & 512) { | |
| perms += "w" | |
| } | |
| return perms | |
| }, | |
| nodePermissions(node, perms) { | |
| if (FS.ignorePermissions) { | |
| return 0 | |
| } | |
| if (perms.includes("r") && !(node.mode & 292)) { | |
| return 2 | |
| } else if (perms.includes("w") && !(node.mode & 146)) { | |
| return 2 | |
| } else if (perms.includes("x") && !(node.mode & 73)) { | |
| return 2 | |
| } | |
| return 0 | |
| }, | |
| mayLookup(dir) { | |
| if (!FS.isDir(dir.mode)) return 54; | |
| var errCode = FS.nodePermissions(dir, "x"); | |
| if (errCode) return errCode; | |
| if (!dir.node_ops.lookup) return 2; | |
| return 0 | |
| }, | |
| mayCreate(dir, name) { | |
| if (!FS.isDir(dir.mode)) { | |
| return 54 | |
| } | |
| try { | |
| var node = FS.lookupNode(dir, name); | |
| return 20 | |
| } catch (e) {} | |
| return FS.nodePermissions(dir, "wx") | |
| }, | |
| mayDelete(dir, name, isdir) { | |
| var node; | |
| try { | |
| node = FS.lookupNode(dir, name) | |
| } catch (e) { | |
| return e.errno | |
| } | |
| var errCode = FS.nodePermissions(dir, "wx"); | |
| if (errCode) { | |
| return errCode | |
| } | |
| if (isdir) { | |
| if (!FS.isDir(node.mode)) { | |
| return 54 | |
| } | |
| if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { | |
| return 10 | |
| } | |
| } else { | |
| if (FS.isDir(node.mode)) { | |
| return 31 | |
| } | |
| } | |
| return 0 | |
| }, | |
| mayOpen(node, flags) { | |
| if (!node) { | |
| return 44 | |
| } | |
| if (FS.isLink(node.mode)) { | |
| return 32 | |
| } else if (FS.isDir(node.mode)) { | |
| if (FS.flagsToPermissionString(flags) !== "r" || flags & (512 | 64)) { | |
| return 31 | |
| } | |
| } | |
| return FS.nodePermissions(node, FS.flagsToPermissionString(flags)) | |
| }, | |
| checkOpExists(op, err) { | |
| if (!op) { | |
| throw new FS.ErrnoError(err) | |
| } | |
| return op | |
| }, | |
| MAX_OPEN_FDS: 4096, | |
| nextfd() { | |
| for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) { | |
| if (!FS.streams[fd]) { | |
| return fd | |
| } | |
| } | |
| throw new FS.ErrnoError(33) | |
| }, | |
| getStreamChecked(fd) { | |
| var stream = FS.getStream(fd); | |
| if (!stream) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| return stream | |
| }, | |
| getStream: fd => FS.streams[fd], | |
| createStream(stream, fd = -1) { | |
| stream = Object.assign(new FS.FSStream, stream); | |
| if (fd == -1) { | |
| fd = FS.nextfd() | |
| } | |
| stream.fd = fd; | |
| FS.streams[fd] = stream; | |
| return stream | |
| }, | |
| closeStream(fd) { | |
| FS.streams[fd] = null | |
| }, | |
| dupStream(origStream, fd = -1) { | |
| var stream = FS.createStream(origStream, fd); | |
| stream.stream_ops?.dup?.(stream); | |
| return stream | |
| }, | |
| doSetAttr(stream, node, attr) { | |
| var setattr = stream?.stream_ops.setattr; | |
| var arg = setattr ? stream : node; | |
| setattr ??= node.node_ops.setattr; | |
| FS.checkOpExists(setattr, 63); | |
| setattr(arg, attr) | |
| }, | |
| chrdev_stream_ops: { | |
| open(stream) { | |
| var device = FS.getDevice(stream.node.rdev); | |
| stream.stream_ops = device.stream_ops; | |
| stream.stream_ops.open?.(stream) | |
| }, | |
| llseek() { | |
| throw new FS.ErrnoError(70) | |
| } | |
| }, | |
| major: dev => dev >> 8, | |
| minor: dev => dev & 255, | |
| makedev: (ma, mi) => ma << 8 | mi, | |
| registerDevice(dev, ops) { | |
| FS.devices[dev] = { | |
| stream_ops: ops | |
| } | |
| }, | |
| getDevice: dev => FS.devices[dev], | |
| getMounts(mount) { | |
| var mounts = []; | |
| var check = [mount]; | |
| while (check.length) { | |
| var m = check.pop(); | |
| mounts.push(m); | |
| check.push(...m.mounts) | |
| } | |
| return mounts | |
| }, | |
| syncfs(populate, callback) { | |
| if (typeof populate == "function") { | |
| callback = populate; | |
| populate = false | |
| } | |
| FS.syncFSRequests++; | |
| if (FS.syncFSRequests > 1) { | |
| err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`) | |
| } | |
| var mounts = FS.getMounts(FS.root.mount); | |
| var completed = 0; | |
| function doCallback(errCode) { | |
| FS.syncFSRequests--; | |
| return callback(errCode) | |
| } | |
| function done(errCode) { | |
| if (errCode) { | |
| if (!done.errored) { | |
| done.errored = true; | |
| return doCallback(errCode) | |
| } | |
| return | |
| } | |
| if (++completed >= mounts.length) { | |
| doCallback(null) | |
| } | |
| } | |
| for (var mount of mounts) { | |
| if (mount.type.syncfs) { | |
| mount.type.syncfs(mount, populate, done) | |
| } else { | |
| done(null) | |
| } | |
| } | |
| }, | |
| mount(type, opts, mountpoint) { | |
| var root = mountpoint === "/"; | |
| var pseudo = !mountpoint; | |
| var node; | |
| if (root && FS.root) { | |
| throw new FS.ErrnoError(10) | |
| } else if (!root && !pseudo) { | |
| var lookup = FS.lookupPath(mountpoint, { | |
| follow_mount: false | |
| }); | |
| mountpoint = lookup.path; | |
| node = lookup.node; | |
| if (FS.isMountpoint(node)) { | |
| throw new FS.ErrnoError(10) | |
| } | |
| if (!FS.isDir(node.mode)) { | |
| throw new FS.ErrnoError(54) | |
| } | |
| } | |
| var mount = { | |
| type, | |
| opts, | |
| mountpoint, | |
| mounts: [] | |
| }; | |
| var mountRoot = type.mount(mount); | |
| mountRoot.mount = mount; | |
| mount.root = mountRoot; | |
| if (root) { | |
| FS.root = mountRoot | |
| } else if (node) { | |
| node.mounted = mount; | |
| if (node.mount) { | |
| node.mount.mounts.push(mount) | |
| } | |
| } | |
| return mountRoot | |
| }, | |
| unmount(mountpoint) { | |
| var lookup = FS.lookupPath(mountpoint, { | |
| follow_mount: false | |
| }); | |
| if (!FS.isMountpoint(lookup.node)) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| var node = lookup.node; | |
| var mount = node.mounted; | |
| var mounts = FS.getMounts(mount); | |
| for (var [hash, current] of Object.entries(FS.nameTable)) { | |
| while (current) { | |
| var next = current.name_next; | |
| if (mounts.includes(current.mount)) { | |
| FS.destroyNode(current) | |
| } | |
| current = next | |
| } | |
| } | |
| node.mounted = null; | |
| var idx = node.mount.mounts.indexOf(mount); | |
| node.mount.mounts.splice(idx, 1) | |
| }, | |
| lookup(parent, name) { | |
| return parent.node_ops.lookup(parent, name) | |
| }, | |
| mknod(path, mode, dev) { | |
| var lookup = FS.lookupPath(path, { | |
| parent: true | |
| }); | |
| var parent = lookup.node; | |
| var name = PATH.basename(path); | |
| if (!name) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| if (name === "." || name === "..") { | |
| throw new FS.ErrnoError(20) | |
| } | |
| var errCode = FS.mayCreate(parent, name); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| if (!parent.node_ops.mknod) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| return parent.node_ops.mknod(parent, name, mode, dev) | |
| }, | |
| statfs(path) { | |
| return FS.statfsNode(FS.lookupPath(path, { | |
| follow: true | |
| }).node) | |
| }, | |
| statfsStream(stream) { | |
| return FS.statfsNode(stream.node) | |
| }, | |
| statfsNode(node) { | |
| var rtn = { | |
| bsize: 4096, | |
| frsize: 4096, | |
| blocks: 1e6, | |
| bfree: 5e5, | |
| bavail: 5e5, | |
| files: FS.nextInode, | |
| ffree: FS.nextInode - 1, | |
| fsid: 42, | |
| flags: 2, | |
| namelen: 255 | |
| }; | |
| if (node.node_ops.statfs) { | |
| Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)) | |
| } | |
| return rtn | |
| }, | |
| create(path, mode = 438) { | |
| mode &= 4095; | |
| mode |= 32768; | |
| return FS.mknod(path, mode, 0) | |
| }, | |
| mkdir(path, mode = 511) { | |
| mode &= 511 | 512; | |
| mode |= 16384; | |
| return FS.mknod(path, mode, 0) | |
| }, | |
| mkdirTree(path, mode) { | |
| var dirs = path.split("/"); | |
| var d = ""; | |
| for (var dir of dirs) { | |
| if (!dir) continue; | |
| if (d || PATH.isAbs(path)) d += "/"; | |
| d += dir; | |
| try { | |
| FS.mkdir(d, mode) | |
| } catch (e) { | |
| if (e.errno != 20) throw e | |
| } | |
| } | |
| }, | |
| mkdev(path, mode, dev) { | |
| if (typeof dev == "undefined") { | |
| dev = mode; | |
| mode = 438 | |
| } | |
| mode |= 8192; | |
| return FS.mknod(path, mode, dev) | |
| }, | |
| symlink(oldpath, newpath) { | |
| if (!PATH_FS.resolve(oldpath)) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| var lookup = FS.lookupPath(newpath, { | |
| parent: true | |
| }); | |
| var parent = lookup.node; | |
| if (!parent) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| var newname = PATH.basename(newpath); | |
| var errCode = FS.mayCreate(parent, newname); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| if (!parent.node_ops.symlink) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| return parent.node_ops.symlink(parent, newname, oldpath) | |
| }, | |
| rename(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); | |
| var lookup, old_dir, new_dir; | |
| lookup = FS.lookupPath(old_path, { | |
| parent: true | |
| }); | |
| old_dir = lookup.node; | |
| lookup = FS.lookupPath(new_path, { | |
| parent: true | |
| }); | |
| new_dir = lookup.node; | |
| if (!old_dir || !new_dir) throw new FS.ErrnoError(44); | |
| if (old_dir.mount !== new_dir.mount) { | |
| throw new FS.ErrnoError(75) | |
| } | |
| var old_node = FS.lookupNode(old_dir, old_name); | |
| var relative = PATH_FS.relative(old_path, new_dirname); | |
| if (relative.charAt(0) !== ".") { | |
| throw new FS.ErrnoError(28) | |
| } | |
| relative = PATH_FS.relative(new_path, old_dirname); | |
| if (relative.charAt(0) !== ".") { | |
| throw new FS.ErrnoError(55) | |
| } | |
| var new_node; | |
| try { | |
| new_node = FS.lookupNode(new_dir, new_name) | |
| } catch (e) {} | |
| if (old_node === new_node) { | |
| return | |
| } | |
| var isdir = FS.isDir(old_node.mode); | |
| var errCode = FS.mayDelete(old_dir, old_name, isdir); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| errCode = new_node ? FS.mayDelete(new_dir, new_name, isdir) : FS.mayCreate(new_dir, new_name); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| if (!old_dir.node_ops.rename) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| if (FS.isMountpoint(old_node) || new_node && FS.isMountpoint(new_node)) { | |
| throw new FS.ErrnoError(10) | |
| } | |
| if (new_dir !== old_dir) { | |
| errCode = FS.nodePermissions(old_dir, "w"); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| } | |
| FS.hashRemoveNode(old_node); | |
| try { | |
| old_dir.node_ops.rename(old_node, new_dir, new_name); | |
| old_node.parent = new_dir | |
| } catch (e) { | |
| throw e | |
| } finally { | |
| FS.hashAddNode(old_node) | |
| } | |
| }, | |
| rmdir(path) { | |
| var lookup = FS.lookupPath(path, { | |
| parent: true | |
| }); | |
| var parent = lookup.node; | |
| var name = PATH.basename(path); | |
| var node = FS.lookupNode(parent, name); | |
| var errCode = FS.mayDelete(parent, name, true); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| if (!parent.node_ops.rmdir) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| if (FS.isMountpoint(node)) { | |
| throw new FS.ErrnoError(10) | |
| } | |
| parent.node_ops.rmdir(parent, name); | |
| FS.destroyNode(node) | |
| }, | |
| readdir(path) { | |
| var lookup = FS.lookupPath(path, { | |
| follow: true | |
| }); | |
| var node = lookup.node; | |
| var readdir = FS.checkOpExists(node.node_ops.readdir, 54); | |
| return readdir(node) | |
| }, | |
| unlink(path) { | |
| var lookup = FS.lookupPath(path, { | |
| parent: true | |
| }); | |
| var parent = lookup.node; | |
| if (!parent) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| var name = PATH.basename(path); | |
| var node = FS.lookupNode(parent, name); | |
| var errCode = FS.mayDelete(parent, name, false); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| if (!parent.node_ops.unlink) { | |
| throw new FS.ErrnoError(63) | |
| } | |
| if (FS.isMountpoint(node)) { | |
| throw new FS.ErrnoError(10) | |
| } | |
| parent.node_ops.unlink(parent, name); | |
| FS.destroyNode(node) | |
| }, | |
| readlink(path) { | |
| var lookup = FS.lookupPath(path); | |
| var link = lookup.node; | |
| if (!link) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| if (!link.node_ops.readlink) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| return link.node_ops.readlink(link) | |
| }, | |
| stat(path, dontFollow) { | |
| var lookup = FS.lookupPath(path, { | |
| follow: !dontFollow | |
| }); | |
| var node = lookup.node; | |
| var getattr = FS.checkOpExists(node.node_ops.getattr, 63); | |
| return getattr(node) | |
| }, | |
| fstat(fd) { | |
| var stream = FS.getStreamChecked(fd); | |
| var node = stream.node; | |
| var getattr = stream.stream_ops.getattr; | |
| var arg = getattr ? stream : node; | |
| getattr ??= node.node_ops.getattr; | |
| FS.checkOpExists(getattr, 63); | |
| return getattr(arg) | |
| }, | |
| lstat(path) { | |
| return FS.stat(path, true) | |
| }, | |
| doChmod(stream, node, mode, dontFollow) { | |
| FS.doSetAttr(stream, node, { | |
| mode: mode & 4095 | node.mode & ~4095, | |
| ctime: Date.now(), | |
| dontFollow | |
| }) | |
| }, | |
| chmod(path, mode, dontFollow) { | |
| var node; | |
| if (typeof path == "string") { | |
| var lookup = FS.lookupPath(path, { | |
| follow: !dontFollow | |
| }); | |
| node = lookup.node | |
| } else { | |
| node = path | |
| } | |
| FS.doChmod(null, node, mode, dontFollow) | |
| }, | |
| lchmod(path, mode) { | |
| FS.chmod(path, mode, true) | |
| }, | |
| fchmod(fd, mode) { | |
| var stream = FS.getStreamChecked(fd); | |
| FS.doChmod(stream, stream.node, mode, false) | |
| }, | |
| doChown(stream, node, dontFollow) { | |
| FS.doSetAttr(stream, node, { | |
| timestamp: Date.now(), | |
| dontFollow | |
| }) | |
| }, | |
| chown(path, uid, gid, dontFollow) { | |
| var node; | |
| if (typeof path == "string") { | |
| var lookup = FS.lookupPath(path, { | |
| follow: !dontFollow | |
| }); | |
| node = lookup.node | |
| } else { | |
| node = path | |
| } | |
| FS.doChown(null, node, dontFollow) | |
| }, | |
| lchown(path, uid, gid) { | |
| FS.chown(path, uid, gid, true) | |
| }, | |
| fchown(fd, uid, gid) { | |
| var stream = FS.getStreamChecked(fd); | |
| FS.doChown(stream, stream.node, false) | |
| }, | |
| doTruncate(stream, node, len) { | |
| if (FS.isDir(node.mode)) { | |
| throw new FS.ErrnoError(31) | |
| } | |
| if (!FS.isFile(node.mode)) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| var errCode = FS.nodePermissions(node, "w"); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| FS.doSetAttr(stream, node, { | |
| size: len, | |
| timestamp: Date.now() | |
| }) | |
| }, | |
| truncate(path, len) { | |
| if (len < 0) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| var node; | |
| if (typeof path == "string") { | |
| var lookup = FS.lookupPath(path, { | |
| follow: true | |
| }); | |
| node = lookup.node | |
| } else { | |
| node = path | |
| } | |
| FS.doTruncate(null, node, len) | |
| }, | |
| ftruncate(fd, len) { | |
| var stream = FS.getStreamChecked(fd); | |
| if (len < 0 || (stream.flags & 2097155) === 0) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| FS.doTruncate(stream, stream.node, len) | |
| }, | |
| utime(path, atime, mtime) { | |
| var lookup = FS.lookupPath(path, { | |
| follow: true | |
| }); | |
| var node = lookup.node; | |
| var setattr = FS.checkOpExists(node.node_ops.setattr, 63); | |
| setattr(node, { | |
| atime, | |
| mtime | |
| }) | |
| }, | |
| open(path, flags, mode = 438) { | |
| if (path === "") { | |
| throw new FS.ErrnoError(44) | |
| } | |
| flags = typeof flags == "string" ? FS_modeStringToFlags(flags) : flags; | |
| if (flags & 64) { | |
| mode = mode & 4095 | 32768 | |
| } else { | |
| mode = 0 | |
| } | |
| var node; | |
| var isDirPath; | |
| if (typeof path == "object") { | |
| node = path | |
| } else { | |
| isDirPath = path.endsWith("/"); | |
| var lookup = FS.lookupPath(path, { | |
| follow: !(flags & 131072), | |
| noent_okay: true | |
| }); | |
| node = lookup.node; | |
| path = lookup.path | |
| } | |
| var created = false; | |
| if (flags & 64) { | |
| if (node) { | |
| if (flags & 128) { | |
| throw new FS.ErrnoError(20) | |
| } | |
| } else if (isDirPath) { | |
| throw new FS.ErrnoError(31) | |
| } else { | |
| node = FS.mknod(path, mode | 511, 0); | |
| created = true | |
| } | |
| } | |
| if (!node) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| if (FS.isChrdev(node.mode)) { | |
| flags &= ~512 | |
| } | |
| if (flags & 65536 && !FS.isDir(node.mode)) { | |
| throw new FS.ErrnoError(54) | |
| } | |
| if (!created) { | |
| var errCode = FS.mayOpen(node, flags); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| } | |
| if (flags & 512 && !created) { | |
| FS.truncate(node, 0) | |
| } | |
| flags &= ~(128 | 512 | 131072); | |
| var stream = FS.createStream({ | |
| node, | |
| path: FS.getPath(node), | |
| flags, | |
| seekable: true, | |
| position: 0, | |
| stream_ops: node.stream_ops, | |
| ungotten: [], | |
| error: false | |
| }); | |
| if (stream.stream_ops.open) { | |
| stream.stream_ops.open(stream) | |
| } | |
| if (created) { | |
| FS.chmod(node, mode & 511) | |
| } | |
| if (Module["logReadFiles"] && !(flags & 1)) { | |
| if (!(path in FS.readFiles)) { | |
| FS.readFiles[path] = 1 | |
| } | |
| } | |
| return stream | |
| }, | |
| close(stream) { | |
| if (FS.isClosed(stream)) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if (stream.getdents) stream.getdents = null; | |
| try { | |
| if (stream.stream_ops.close) { | |
| stream.stream_ops.close(stream) | |
| } | |
| } catch (e) { | |
| throw e | |
| } finally { | |
| FS.closeStream(stream.fd) | |
| } | |
| stream.fd = null | |
| }, | |
| isClosed(stream) { | |
| return stream.fd === null | |
| }, | |
| llseek(stream, offset, whence) { | |
| if (FS.isClosed(stream)) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if (!stream.seekable || !stream.stream_ops.llseek) { | |
| throw new FS.ErrnoError(70) | |
| } | |
| if (whence != 0 && whence != 1 && whence != 2) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| stream.position = stream.stream_ops.llseek(stream, offset, whence); | |
| stream.ungotten = []; | |
| return stream.position | |
| }, | |
| read(stream, buffer, offset, length, position) { | |
| if (length < 0 || position < 0) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| if (FS.isClosed(stream)) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if ((stream.flags & 2097155) === 1) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if (FS.isDir(stream.node.mode)) { | |
| throw new FS.ErrnoError(31) | |
| } | |
| if (!stream.stream_ops.read) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| var seeking = typeof position != "undefined"; | |
| if (!seeking) { | |
| position = stream.position | |
| } else if (!stream.seekable) { | |
| throw new FS.ErrnoError(70) | |
| } | |
| var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); | |
| if (!seeking) stream.position += bytesRead; | |
| return bytesRead | |
| }, | |
| write(stream, buffer, offset, length, position, canOwn) { | |
| if (length < 0 || position < 0) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| if (FS.isClosed(stream)) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if ((stream.flags & 2097155) === 0) { | |
| throw new FS.ErrnoError(8) | |
| } | |
| if (FS.isDir(stream.node.mode)) { | |
| throw new FS.ErrnoError(31) | |
| } | |
| if (!stream.stream_ops.write) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| if (stream.seekable && stream.flags & 1024) { | |
| FS.llseek(stream, 0, 2) | |
| } | |
| var seeking = typeof position != "undefined"; | |
| if (!seeking) { | |
| position = stream.position | |
| } else if (!stream.seekable) { | |
| throw new FS.ErrnoError(70) | |
| } | |
| var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); | |
| if (!seeking) stream.position += bytesWritten; | |
| return bytesWritten | |
| }, | |
| mmap(stream, length, position, prot, flags) { | |
| if ((prot & 2) !== 0 && (flags & 2) === 0 && (stream.flags & 2097155) !== 2) { | |
| throw new FS.ErrnoError(2) | |
| } | |
| if ((stream.flags & 2097155) === 1) { | |
| throw new FS.ErrnoError(2) | |
| } | |
| if (!stream.stream_ops.mmap) { | |
| throw new FS.ErrnoError(43) | |
| } | |
| if (!length) { | |
| throw new FS.ErrnoError(28) | |
| } | |
| return stream.stream_ops.mmap(stream, length, position, prot, flags) | |
| }, | |
| msync(stream, buffer, offset, length, mmapFlags) { | |
| if (!stream.stream_ops.msync) { | |
| return 0 | |
| } | |
| return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags) | |
| }, | |
| ioctl(stream, cmd, arg) { | |
| if (!stream.stream_ops.ioctl) { | |
| throw new FS.ErrnoError(59) | |
| } | |
| return stream.stream_ops.ioctl(stream, cmd, arg) | |
| }, | |
| readFile(path, opts = {}) { | |
| opts.flags = opts.flags || 0; | |
| opts.encoding = opts.encoding || "binary"; | |
| if (opts.encoding !== "utf8" && opts.encoding !== "binary") { | |
| abort(`Invalid encoding type "${opts.encoding}"`) | |
| } | |
| var stream = FS.open(path, opts.flags); | |
| var stat = FS.stat(path); | |
| var length = stat.size; | |
| var buf = new Uint8Array(length); | |
| FS.read(stream, buf, 0, length, 0); | |
| if (opts.encoding === "utf8") { | |
| buf = UTF8ArrayToString(buf) | |
| } | |
| FS.close(stream); | |
| return buf | |
| }, | |
| writeFile(path, data, opts = {}) { | |
| opts.flags = opts.flags || 577; | |
| var stream = FS.open(path, opts.flags, opts.mode); | |
| if (typeof data == "string") { | |
| data = new Uint8Array(intArrayFromString(data, true)) | |
| } | |
| if (ArrayBuffer.isView(data)) { | |
| FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn) | |
| } else { | |
| abort("Unsupported data type") | |
| } | |
| FS.close(stream) | |
| }, | |
| cwd: () => FS.currentPath, | |
| chdir(path) { | |
| var lookup = FS.lookupPath(path, { | |
| follow: true | |
| }); | |
| if (lookup.node === null) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| if (!FS.isDir(lookup.node.mode)) { | |
| throw new FS.ErrnoError(54) | |
| } | |
| var errCode = FS.nodePermissions(lookup.node, "x"); | |
| if (errCode) { | |
| throw new FS.ErrnoError(errCode) | |
| } | |
| FS.currentPath = lookup.path | |
| }, | |
| createDefaultDirectories() { | |
| FS.mkdir("/tmp"); | |
| FS.mkdir("/home"); | |
| FS.mkdir("/home/web_user") | |
| }, | |
| createDefaultDevices() { | |
| FS.mkdir("/dev"); | |
| FS.registerDevice(FS.makedev(1, 3), { | |
| read: () => 0, | |
| write: (stream, buffer, offset, length, pos) => length, | |
| llseek: () => 0 | |
| }); | |
| FS.mkdev("/dev/null", FS.makedev(1, 3)); | |
| TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); | |
| TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); | |
| FS.mkdev("/dev/tty", FS.makedev(5, 0)); | |
| FS.mkdev("/dev/tty1", FS.makedev(6, 0)); | |
| var randomBuffer = new Uint8Array(1024), | |
| randomLeft = 0; | |
| var randomByte = () => { | |
| if (randomLeft === 0) { | |
| randomFill(randomBuffer); | |
| randomLeft = randomBuffer.byteLength | |
| } | |
| return randomBuffer[--randomLeft] | |
| }; | |
| FS.createDevice("/dev", "random", randomByte); | |
| FS.createDevice("/dev", "urandom", randomByte); | |
| FS.mkdir("/dev/shm"); | |
| FS.mkdir("/dev/shm/tmp") | |
| }, | |
| createSpecialDirectories() { | |
| FS.mkdir("/proc"); | |
| var proc_self = FS.mkdir("/proc/self"); | |
| FS.mkdir("/proc/self/fd"); | |
| FS.mount({ | |
| mount() { | |
| var node = FS.createNode(proc_self, "fd", 16895, 73); | |
| node.stream_ops = { | |
| llseek: MEMFS.stream_ops.llseek | |
| }; | |
| node.node_ops = { | |
| lookup(parent, name) { | |
| var fd = +name; | |
| var stream = FS.getStreamChecked(fd); | |
| var ret = { | |
| parent: null, | |
| mount: { | |
| mountpoint: "fake" | |
| }, | |
| node_ops: { | |
| readlink: () => stream.path | |
| }, | |
| id: fd + 1 | |
| }; | |
| ret.parent = ret; | |
| return ret | |
| }, | |
| readdir() { | |
| return Array.from(FS.streams.entries()).filter(([k, v]) => v).map(([k, v]) => k.toString()) | |
| } | |
| }; | |
| return node | |
| } | |
| }, {}, "/proc/self/fd") | |
| }, | |
| createStandardStreams(input, output, error) { | |
| if (input) { | |
| FS.createDevice("/dev", "stdin", input) | |
| } else { | |
| FS.symlink("/dev/tty", "/dev/stdin") | |
| } | |
| if (output) { | |
| FS.createDevice("/dev", "stdout", null, output) | |
| } else { | |
| FS.symlink("/dev/tty", "/dev/stdout") | |
| } | |
| if (error) { | |
| FS.createDevice("/dev", "stderr", null, error) | |
| } else { | |
| FS.symlink("/dev/tty1", "/dev/stderr") | |
| } | |
| var stdin = FS.open("/dev/stdin", 0); | |
| var stdout = FS.open("/dev/stdout", 1); | |
| var stderr = FS.open("/dev/stderr", 1) | |
| }, | |
| staticInit() { | |
| FS.nameTable = new Array(4096); | |
| FS.mount(MEMFS, {}, "/"); | |
| FS.createDefaultDirectories(); | |
| FS.createDefaultDevices(); | |
| FS.createSpecialDirectories(); | |
| FS.filesystems = { | |
| MEMFS, | |
| IDBFS | |
| } | |
| }, | |
| init(input, output, error) { | |
| FS.initialized = true; | |
| input ??= Module["stdin"]; | |
| output ??= Module["stdout"]; | |
| error ??= Module["stderr"]; | |
| FS.createStandardStreams(input, output, error) | |
| }, | |
| quit() { | |
| FS.initialized = false; | |
| for (var stream of FS.streams) { | |
| if (stream) { | |
| FS.close(stream) | |
| } | |
| } | |
| }, | |
| findObject(path, dontResolveLastLink) { | |
| var ret = FS.analyzePath(path, dontResolveLastLink); | |
| if (!ret.exists) { | |
| return null | |
| } | |
| return ret.object | |
| }, | |
| analyzePath(path, dontResolveLastLink) { | |
| 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 | |
| }, | |
| createPath(parent, path, canRead, canWrite) { | |
| parent = typeof parent == "string" ? parent : FS.getPath(parent); | |
| var parts = path.split("/").reverse(); | |
| while (parts.length) { | |
| var part = parts.pop(); | |
| if (!part) continue; | |
| var current = PATH.join2(parent, part); | |
| try { | |
| FS.mkdir(current) | |
| } catch (e) { | |
| if (e.errno != 20) throw e | |
| } | |
| parent = current | |
| } | |
| return current | |
| }, | |
| createFile(parent, name, properties, canRead, canWrite) { | |
| var path = PATH.join2(typeof parent == "string" ? parent : FS.getPath(parent), name); | |
| var mode = FS_getMode(canRead, canWrite); | |
| return FS.create(path, mode) | |
| }, | |
| createDataFile(parent, name, data, canRead, canWrite, canOwn) { | |
| var path = name; | |
| if (parent) { | |
| parent = typeof parent == "string" ? parent : FS.getPath(parent); | |
| path = name ? PATH.join2(parent, name) : parent | |
| } | |
| var mode = FS_getMode(canRead, canWrite); | |
| var node = FS.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 | |
| } | |
| FS.chmod(node, mode | 146); | |
| var stream = FS.open(node, 577); | |
| FS.write(stream, data, 0, data.length, 0, canOwn); | |
| FS.close(stream); | |
| FS.chmod(node, mode) | |
| } | |
| }, | |
| createDevice(parent, name, input, output) { | |
| var path = PATH.join2(typeof parent == "string" ? parent : FS.getPath(parent), name); | |
| var mode = FS_getMode(!!input, !!output); | |
| FS.createDevice.major ??= 64; | |
| var dev = FS.makedev(FS.createDevice.major++, 0); | |
| FS.registerDevice(dev, { | |
| open(stream) { | |
| stream.seekable = false | |
| }, | |
| close(stream) { | |
| if (output?.buffer?.length) { | |
| output(10) | |
| } | |
| }, | |
| read(stream, buffer, offset, length, pos) { | |
| var bytesRead = 0; | |
| for (var i = 0; i < length; i++) { | |
| var result; | |
| try { | |
| result = input() | |
| } catch (e) { | |
| throw new FS.ErrnoError(29) | |
| } | |
| if (result === undefined && bytesRead === 0) { | |
| throw new FS.ErrnoError(6) | |
| } | |
| if (result === null || result === undefined) break; | |
| bytesRead++; | |
| buffer[offset + i] = result | |
| } | |
| if (bytesRead) { | |
| stream.node.atime = Date.now() | |
| } | |
| return bytesRead | |
| }, | |
| write(stream, buffer, offset, length, pos) { | |
| for (var i = 0; i < length; i++) { | |
| try { | |
| output(buffer[offset + i]) | |
| } catch (e) { | |
| throw new FS.ErrnoError(29) | |
| } | |
| } | |
| if (length) { | |
| stream.node.mtime = stream.node.ctime = Date.now() | |
| } | |
| return i | |
| } | |
| }); | |
| return FS.mkdev(path, mode, dev) | |
| }, | |
| forceLoadFile(obj) { | |
| if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; | |
| if (globalThis.XMLHttpRequest) { | |
| abort("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 { | |
| try { | |
| obj.contents = readBinary(obj.url) | |
| } catch (e) { | |
| throw new FS.ErrnoError(29) | |
| } | |
| } | |
| }, | |
| createLazyFile(parent, name, url, canRead, canWrite) { | |
| class LazyUint8Array { | |
| lengthKnown = false; | |
| chunks = []; | |
| get(idx) { | |
| if (idx > this.length - 1 || idx < 0) { | |
| return undefined | |
| } | |
| var chunkOffset = idx % this.chunkSize; | |
| var chunkNum = idx / this.chunkSize | 0; | |
| return this.getter(chunkNum)[chunkOffset] | |
| } | |
| setDataGetter(getter) { | |
| this.getter = getter | |
| } | |
| cacheLength() { | |
| var xhr = new XMLHttpRequest; | |
| xhr.open("HEAD", url, false); | |
| xhr.send(null); | |
| if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("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"; | |
| var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; | |
| var chunkSize = 1024 * 1024; | |
| if (!hasByteServing) chunkSize = datalength; | |
| var doXHR = (from, to) => { | |
| if (from > to) abort("invalid range (" + from + ", " + to + ") or no bytes requested!"); | |
| if (to > datalength - 1) abort("only " + datalength + " bytes available! programmer error!"); | |
| var xhr = new XMLHttpRequest; | |
| xhr.open("GET", url, false); | |
| if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); | |
| 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)) abort("Couldn't load " + url + ". Status: " + xhr.status); | |
| if (xhr.response !== undefined) { | |
| return new Uint8Array(xhr.response || []) | |
| } | |
| return intArrayFromString(xhr.responseText || "", true) | |
| }; | |
| var lazyArray = this; | |
| lazyArray.setDataGetter(chunkNum => { | |
| var start = chunkNum * chunkSize; | |
| var end = (chunkNum + 1) * chunkSize - 1; | |
| end = Math.min(end, datalength - 1); | |
| if (typeof lazyArray.chunks[chunkNum] == "undefined") { | |
| lazyArray.chunks[chunkNum] = doXHR(start, end) | |
| } | |
| if (typeof lazyArray.chunks[chunkNum] == "undefined") abort("doXHR failed!"); | |
| return lazyArray.chunks[chunkNum] | |
| }); | |
| if (usesGzip || !datalength) { | |
| chunkSize = datalength = 1; | |
| datalength = this.getter(0).length; | |
| chunkSize = datalength; | |
| out("LazyFiles on gzip forces download of the whole file when length is accessed") | |
| } | |
| this._length = datalength; | |
| this._chunkSize = chunkSize; | |
| this.lengthKnown = true | |
| } | |
| get length() { | |
| if (!this.lengthKnown) { | |
| this.cacheLength() | |
| } | |
| return this._length | |
| } | |
| get chunkSize() { | |
| if (!this.lengthKnown) { | |
| this.cacheLength() | |
| } | |
| return this._chunkSize | |
| } | |
| } | |
| if (globalThis.XMLHttpRequest) { | |
| if (!ENVIRONMENT_IS_WORKER) abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc"); | |
| var lazyArray = new LazyUint8Array; | |
| var properties = { | |
| isDevice: false, | |
| contents: lazyArray | |
| } | |
| } else { | |
| var properties = { | |
| isDevice: false, | |
| url | |
| } | |
| } | |
| var node = FS.createFile(parent, name, properties, canRead, canWrite); | |
| if (properties.contents) { | |
| node.contents = properties.contents | |
| } else if (properties.url) { | |
| node.contents = null; | |
| node.url = properties.url | |
| } | |
| Object.defineProperties(node, { | |
| usedBytes: { | |
| get: function() { | |
| return this.contents.length | |
| } | |
| } | |
| }); | |
| var stream_ops = {}; | |
| for (const [key, fn] of Object.entries(node.stream_ops)) { | |
| stream_ops[key] = (...args) => { | |
| FS.forceLoadFile(node); | |
| return fn(...args) | |
| } | |
| } | |
| function writeChunks(stream, buffer, offset, length, position) { | |
| var contents = stream.node.contents; | |
| if (position >= contents.length) return 0; | |
| var size = Math.min(contents.length - position, length); | |
| if (contents.slice) { | |
| for (var i = 0; i < size; i++) { | |
| buffer[offset + i] = contents[position + i] | |
| } | |
| } else { | |
| for (var i = 0; i < size; i++) { | |
| buffer[offset + i] = contents.get(position + i) | |
| } | |
| } | |
| return size | |
| } | |
| stream_ops.read = (stream, buffer, offset, length, position) => { | |
| FS.forceLoadFile(node); | |
| return writeChunks(stream, buffer, offset, length, position) | |
| }; | |
| stream_ops.mmap = (stream, length, position, prot, flags) => { | |
| FS.forceLoadFile(node); | |
| var ptr = mmapAlloc(length); | |
| if (!ptr) { | |
| throw new FS.ErrnoError(48) | |
| } | |
| writeChunks(stream, HEAP8, ptr, length, position); | |
| return { | |
| ptr, | |
| allocated: true | |
| } | |
| }; | |
| node.stream_ops = stream_ops; | |
| return node | |
| } | |
| }; | |
| var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => { | |
| if (!ptr) return ""; | |
| var end = findStringEnd(HEAPU8, ptr, maxBytesToRead, ignoreNul); | |
| return UTF8Decoder.decode(HEAPU8.subarray(ptr, end)) | |
| }; | |
| var SYSCALLS = { | |
| DEFAULT_POLLMASK: 5, | |
| calculateAt(dirfd, path, allowEmpty) { | |
| if (PATH.isAbs(path)) { | |
| return path | |
| } | |
| var dir; | |
| if (dirfd === -100) { | |
| dir = FS.cwd() | |
| } else { | |
| var dirstream = SYSCALLS.getStreamFromFD(dirfd); | |
| dir = dirstream.path | |
| } | |
| if (path.length == 0) { | |
| if (!allowEmpty) { | |
| throw new FS.ErrnoError(44) | |
| } | |
| return dir | |
| } | |
| return dir + "/" + path | |
| }, | |
| writeStat(buf, stat) { | |
| HEAPU32[buf >> 2] = stat.dev; | |
| HEAPU32[buf + 4 >> 2] = stat.mode; | |
| HEAPU32[buf + 8 >> 2] = stat.nlink; | |
| HEAPU32[buf + 12 >> 2] = stat.uid; | |
| HEAPU32[buf + 16 >> 2] = stat.gid; | |
| HEAPU32[buf + 20 >> 2] = stat.rdev; | |
| HEAP64[buf + 24 >> 3] = BigInt(stat.size); | |
| HEAP32[buf + 32 >> 2] = 4096; | |
| HEAP32[buf + 36 >> 2] = stat.blocks; | |
| var atime = stat.atime.getTime(); | |
| var mtime = stat.mtime.getTime(); | |
| var ctime = stat.ctime.getTime(); | |
| HEAP64[buf + 40 >> 3] = BigInt(Math.floor(atime / 1e3)); | |
| HEAPU32[buf + 48 >> 2] = atime % 1e3 * 1e3 * 1e3; | |
| HEAP64[buf + 56 >> 3] = BigInt(Math.floor(mtime / 1e3)); | |
| HEAPU32[buf + 64 >> 2] = mtime % 1e3 * 1e3 * 1e3; | |
| HEAP64[buf + 72 >> 3] = BigInt(Math.floor(ctime / 1e3)); | |
| HEAPU32[buf + 80 >> 2] = ctime % 1e3 * 1e3 * 1e3; | |
| HEAP64[buf + 88 >> 3] = BigInt(stat.ino); | |
| return 0 | |
| }, | |
| writeStatFs(buf, stats) { | |
| HEAPU32[buf + 4 >> 2] = stats.bsize; | |
| HEAPU32[buf + 60 >> 2] = stats.bsize; | |
| HEAP64[buf + 8 >> 3] = BigInt(stats.blocks); | |
| HEAP64[buf + 16 >> 3] = BigInt(stats.bfree); | |
| HEAP64[buf + 24 >> 3] = BigInt(stats.bavail); | |
| HEAP64[buf + 32 >> 3] = BigInt(stats.files); | |
| HEAP64[buf + 40 >> 3] = BigInt(stats.ffree); | |
| HEAPU32[buf + 48 >> 2] = stats.fsid; | |
| HEAPU32[buf + 64 >> 2] = stats.flags; | |
| HEAPU32[buf + 56 >> 2] = stats.namelen | |
| }, | |
| doMsync(addr, stream, len, flags, offset) { | |
| if (!FS.isFile(stream.node.mode)) { | |
| throw new FS.ErrnoError(43) | |
| } | |
| if (flags & 2) { | |
| return 0 | |
| } | |
| var buffer = HEAPU8.slice(addr, addr + len); | |
| FS.msync(stream, buffer, offset, len, flags) | |
| }, | |
| getStreamFromFD(fd) { | |
| var stream = FS.getStreamChecked(fd); | |
| return stream | |
| }, | |
| varargs: undefined, | |
| getStr(ptr) { | |
| var ret = UTF8ToString(ptr); | |
| return ret | |
| } | |
| }; | |
| var ___syscall__newselect = function(nfds, readfds, writefds, exceptfds, timeout) { | |
| try { | |
| var total = 0; | |
| var srcReadLow = readfds ? HEAP32[readfds >> 2] : 0, | |
| srcReadHigh = readfds ? HEAP32[readfds + 4 >> 2] : 0; | |
| var srcWriteLow = writefds ? HEAP32[writefds >> 2] : 0, | |
| srcWriteHigh = writefds ? HEAP32[writefds + 4 >> 2] : 0; | |
| var srcExceptLow = exceptfds ? HEAP32[exceptfds >> 2] : 0, | |
| srcExceptHigh = exceptfds ? HEAP32[exceptfds + 4 >> 2] : 0; | |
| var dstReadLow = 0, | |
| dstReadHigh = 0; | |
| var dstWriteLow = 0, | |
| dstWriteHigh = 0; | |
| var dstExceptLow = 0, | |
| dstExceptHigh = 0; | |
| var allLow = (readfds ? HEAP32[readfds >> 2] : 0) | (writefds ? HEAP32[writefds >> 2] : 0) | (exceptfds ? HEAP32[exceptfds >> 2] : 0); | |
| var allHigh = (readfds ? HEAP32[readfds + 4 >> 2] : 0) | (writefds ? HEAP32[writefds + 4 >> 2] : 0) | (exceptfds ? HEAP32[exceptfds + 4 >> 2] : 0); | |
| var check = (fd, low, high, val) => fd < 32 ? low & val : high & val; | |
| for (var fd = 0; fd < nfds; fd++) { | |
| var mask = 1 << fd % 32; | |
| if (!check(fd, allLow, allHigh, mask)) { | |
| continue | |
| } | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| var flags = SYSCALLS.DEFAULT_POLLMASK; | |
| if (stream.stream_ops.poll) { | |
| var timeoutInMillis = -1; | |
| if (timeout) { | |
| var tv_sec = readfds ? HEAP32[timeout >> 2] : 0, | |
| tv_usec = readfds ? HEAP32[timeout + 4 >> 2] : 0; | |
| timeoutInMillis = (tv_sec + tv_usec / 1e6) * 1e3 | |
| } | |
| flags = stream.stream_ops.poll(stream, timeoutInMillis) | |
| } | |
| if (flags & 1 && check(fd, srcReadLow, srcReadHigh, mask)) { | |
| fd < 32 ? dstReadLow = dstReadLow | mask : dstReadHigh = dstReadHigh | mask; | |
| total++ | |
| } | |
| if (flags & 4 && check(fd, srcWriteLow, srcWriteHigh, mask)) { | |
| fd < 32 ? dstWriteLow = dstWriteLow | mask : dstWriteHigh = dstWriteHigh | mask; | |
| total++ | |
| } | |
| if (flags & 2 && check(fd, srcExceptLow, srcExceptHigh, mask)) { | |
| fd < 32 ? dstExceptLow = dstExceptLow | mask : dstExceptHigh = dstExceptHigh | mask; | |
| total++ | |
| } | |
| } | |
| if (readfds) { | |
| HEAP32[readfds >> 2] = dstReadLow; | |
| HEAP32[readfds + 4 >> 2] = dstReadHigh | |
| } | |
| if (writefds) { | |
| HEAP32[writefds >> 2] = dstWriteLow; | |
| HEAP32[writefds + 4 >> 2] = dstWriteHigh | |
| } | |
| if (exceptfds) { | |
| HEAP32[exceptfds >> 2] = dstExceptLow; | |
| HEAP32[exceptfds + 4 >> 2] = dstExceptHigh | |
| } | |
| return total | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| }; | |
| function ___syscall_chdir(path) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| FS.chdir(path); | |
| return 0 | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_faccessat(dirfd, path, amode, flags) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (amode & ~7) { | |
| return -28 | |
| } | |
| var lookup = FS.lookupPath(path, { | |
| follow: true | |
| }); | |
| var node = lookup.node; | |
| if (!node) { | |
| return -44 | |
| } | |
| var perms = ""; | |
| if (amode & 4) perms += "r"; | |
| if (amode & 2) perms += "w"; | |
| if (amode & 1) perms += "x"; | |
| if (perms && FS.nodePermissions(node, perms)) { | |
| return -2 | |
| } | |
| return 0 | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| var syscallGetVarargI = () => { | |
| var ret = HEAP32[+SYSCALLS.varargs >> 2]; | |
| SYSCALLS.varargs += 4; | |
| return ret | |
| }; | |
| var syscallGetVarargP = syscallGetVarargI; | |
| function ___syscall_fcntl64(fd, cmd, varargs) { | |
| SYSCALLS.varargs = varargs; | |
| try { | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| switch (cmd) { | |
| case 0: { | |
| var arg = syscallGetVarargI(); | |
| if (arg < 0) { | |
| return -28 | |
| } | |
| while (FS.streams[arg]) { | |
| arg++ | |
| } | |
| var newStream; | |
| newStream = FS.dupStream(stream, arg); | |
| return newStream.fd | |
| } | |
| case 1: | |
| case 2: | |
| return 0; | |
| case 3: | |
| return stream.flags; | |
| case 4: { | |
| var arg = syscallGetVarargI(); | |
| stream.flags |= arg; | |
| return 0 | |
| } | |
| case 12: { | |
| var arg = syscallGetVarargP(); | |
| var offset = 0; | |
| HEAP16[arg + offset >> 1] = 2; | |
| return 0 | |
| } | |
| case 13: | |
| case 14: | |
| return 0 | |
| } | |
| return -28 | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| var stringToUTF8 = (str, outPtr, maxBytesToWrite) => stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); | |
| function ___syscall_getcwd(buf, size) { | |
| try { | |
| if (size === 0) return -28; | |
| var cwd = FS.cwd(); | |
| var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; | |
| if (size < cwdLengthInBytes) return -68; | |
| stringToUTF8(cwd, buf, size); | |
| return cwdLengthInBytes | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_getdents64(fd, dirp, count) { | |
| try { | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| stream.getdents ||= FS.readdir(stream.path); | |
| var struct_size = 280; | |
| var pos = 0; | |
| var off = FS.llseek(stream, 0, 1); | |
| var startIdx = Math.floor(off / struct_size); | |
| var endIdx = Math.min(stream.getdents.length, startIdx + Math.floor(count / struct_size)); | |
| for (var idx = startIdx; idx < endIdx; idx++) { | |
| var id; | |
| var type; | |
| var name = stream.getdents[idx]; | |
| if (name === ".") { | |
| id = stream.node.id; | |
| type = 4 | |
| } else if (name === "..") { | |
| var lookup = FS.lookupPath(stream.path, { | |
| parent: true | |
| }); | |
| id = lookup.node.id; | |
| type = 4 | |
| } else { | |
| var child; | |
| try { | |
| child = FS.lookupNode(stream.node, name) | |
| } catch (e) { | |
| if (e?.errno === 28) { | |
| continue | |
| } | |
| throw e | |
| } | |
| id = child.id; | |
| type = FS.isChrdev(child.mode) ? 2 : FS.isDir(child.mode) ? 4 : FS.isLink(child.mode) ? 10 : 8 | |
| } | |
| HEAP64[dirp + pos >> 3] = BigInt(id); | |
| HEAP64[dirp + pos + 8 >> 3] = BigInt((idx + 1) * struct_size); | |
| HEAP16[dirp + pos + 16 >> 1] = 280; | |
| HEAP8[dirp + pos + 18] = type; | |
| stringToUTF8(name, dirp + pos + 19, 256); | |
| pos += struct_size | |
| } | |
| FS.llseek(stream, idx * struct_size, 0); | |
| return pos | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_ioctl(fd, op, varargs) { | |
| SYSCALLS.varargs = varargs; | |
| try { | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| switch (op) { | |
| case 21509: { | |
| if (!stream.tty) return -59; | |
| return 0 | |
| } | |
| case 21505: { | |
| if (!stream.tty) return -59; | |
| if (stream.tty.ops.ioctl_tcgets) { | |
| var termios = stream.tty.ops.ioctl_tcgets(stream); | |
| var argp = syscallGetVarargP(); | |
| HEAP32[argp >> 2] = termios.c_iflag || 0; | |
| HEAP32[argp + 4 >> 2] = termios.c_oflag || 0; | |
| HEAP32[argp + 8 >> 2] = termios.c_cflag || 0; | |
| HEAP32[argp + 12 >> 2] = termios.c_lflag || 0; | |
| for (var i = 0; i < 32; i++) { | |
| HEAP8[argp + i + 17] = termios.c_cc[i] || 0 | |
| } | |
| return 0 | |
| } | |
| return 0 | |
| } | |
| case 21510: | |
| case 21511: | |
| case 21512: { | |
| if (!stream.tty) return -59; | |
| return 0 | |
| } | |
| case 21506: | |
| case 21507: | |
| case 21508: { | |
| if (!stream.tty) return -59; | |
| if (stream.tty.ops.ioctl_tcsets) { | |
| var argp = syscallGetVarargP(); | |
| var c_iflag = HEAP32[argp >> 2]; | |
| var c_oflag = HEAP32[argp + 4 >> 2]; | |
| var c_cflag = HEAP32[argp + 8 >> 2]; | |
| var c_lflag = HEAP32[argp + 12 >> 2]; | |
| var c_cc = []; | |
| for (var i = 0; i < 32; i++) { | |
| c_cc.push(HEAP8[argp + i + 17]) | |
| } | |
| return stream.tty.ops.ioctl_tcsets(stream.tty, op, { | |
| c_iflag, | |
| c_oflag, | |
| c_cflag, | |
| c_lflag, | |
| c_cc | |
| }) | |
| } | |
| return 0 | |
| } | |
| case 21519: { | |
| if (!stream.tty) return -59; | |
| var argp = syscallGetVarargP(); | |
| HEAP32[argp >> 2] = 0; | |
| return 0 | |
| } | |
| case 21520: { | |
| if (!stream.tty) return -59; | |
| return -28 | |
| } | |
| case 21537: | |
| case 21531: { | |
| var argp = syscallGetVarargP(); | |
| return FS.ioctl(stream, op, argp) | |
| } | |
| case 21523: { | |
| if (!stream.tty) return -59; | |
| if (stream.tty.ops.ioctl_tiocgwinsz) { | |
| var winsize = stream.tty.ops.ioctl_tiocgwinsz(stream.tty); | |
| var argp = syscallGetVarargP(); | |
| HEAP16[argp >> 1] = winsize[0]; | |
| HEAP16[argp + 2 >> 1] = winsize[1] | |
| } | |
| return 0 | |
| } | |
| case 21524: { | |
| if (!stream.tty) return -59; | |
| return 0 | |
| } | |
| case 21515: { | |
| if (!stream.tty) return -59; | |
| return 0 | |
| } | |
| default: | |
| return -28 | |
| } | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_lstat64(path, buf) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| return SYSCALLS.writeStat(buf, FS.lstat(path)) | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_mkdirat(dirfd, path, mode) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| FS.mkdir(path, mode, 0); | |
| return 0 | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_newfstatat(dirfd, path, buf, flags) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| var nofollow = flags & 256; | |
| var allowEmpty = flags & 4096; | |
| flags = flags & ~6400; | |
| path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); | |
| return SYSCALLS.writeStat(buf, nofollow ? FS.lstat(path) : FS.stat(path)) | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_openat(dirfd, path, flags, varargs) { | |
| SYSCALLS.varargs = varargs; | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| var mode = varargs ? syscallGetVarargI() : 0; | |
| return FS.open(path, flags, mode).fd | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_readlinkat(dirfd, path, buf, bufsize) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (bufsize <= 0) return -28; | |
| var ret = FS.readlink(path); | |
| var len = Math.min(bufsize, lengthBytesUTF8(ret)); | |
| var endChar = HEAP8[buf + len]; | |
| stringToUTF8(ret, buf, bufsize + 1); | |
| HEAP8[buf + len] = endChar; | |
| return len | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_stat64(path, buf) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| return SYSCALLS.writeStat(buf, FS.stat(path)) | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |
| function ___syscall_unlinkat(dirfd, path, flags) { | |
| try { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (!flags) { | |
| FS.unlink(path) | |
| } else if (flags === 512) { | |
| FS.rmdir(path) | |
| } else { | |
| return -28 | |
| } | |
| return 0 | |
| } catch (e) { | |
| if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; | |
| return -e.errno | |
| } | |
| } | |