diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/intertyper.js | 11 | ||||
-rw-r--r-- | src/jsifier.js | 7 | ||||
-rw-r--r-- | src/library.js | 3425 | ||||
-rw-r--r-- | src/modules.js | 62 | ||||
-rw-r--r-- | src/preamble.js | 64 | ||||
-rw-r--r-- | src/runtime.js | 21 |
6 files changed, 2909 insertions, 681 deletions
diff --git a/src/intertyper.js b/src/intertyper.js index b6ff89f5..95b1fcd3 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -19,10 +19,13 @@ function intertyper(data, parseFunctions, baseLineNum) { } // If the source contains debug info as LLVM metadata, process that out (and save the debugging info for later) - if (/!\d+ = metadata .*/.exec(data[data.length-1])) { // Fast test to see if we have metadata. If this fails when it shouldn't, we should generalize - data = Debugging.processMetadata(data); - //print(data.join('\n')); - //dprint(JSON.stringify(Debugging)); + for (var i = data.length-1; i >= 0; i--) { + if (/^!\d+ = metadata .*/.exec(data[i])) { + data = Debugging.processMetadata(data); + //print(data.join('\n')); + //dprint(JSON.stringify(Debugging)); + break; + } } substrate = new Substrate('Intertyper'); diff --git a/src/jsifier.js b/src/jsifier.js index b712e7ae..c0f7ccb1 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -11,7 +11,12 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { var libFuncsToInclude; if (INCLUDE_FULL_LIBRARY) { assert(!BUILD_AS_SHARED_LIB, 'Cannot have both INCLUDE_FULL_LIBRARY and BUILD_AS_SHARED_LIB set.') - libFuncsToInclude = keys(LibraryManager.library); + libFuncsToInclude = []; + for (var key in LibraryManager.library) { + if (!key.match(/__(deps|postset)$/)) { + libFuncsToInclude.push(key); + } + } } else { libFuncsToInclude = ['memset', 'malloc', 'free']; } diff --git a/src/library.js b/src/library.js index c900c90d..1ef8d0ec 100644 --- a/src/library.js +++ b/src/library.js @@ -15,99 +15,2166 @@ LibraryManager.library = { // ========================================================================== + // File system base. + // ========================================================================== + + stdin: 0, + stdout: 0, + stderr: 0, + + $FS__deps: ['$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr'], + $FS__postset: 'FS.init();', + $FS: { + // The main file system tree. All the contents are inside this. + root: { + read: true, + write: false, + isFolder: true, + isDevice: false, + timestamp: new Date(), + inodeNumber: 1, + contents: {} + }, + // The path to the current folder. + currentPath: '/', + // The inode to assign to the next created object. + nextInode: 2, + // The file creation mask used by the program. + cmask: 0x1ff, // S_IRWXU | S_IRWXG | S_IRWXO. + // 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: [null], + // Whether we are currently ignoring permissions. Useful when preparing the + // filesystem and creating files inside read-only folders. + ignorePermissions: true, + // 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 + }; + 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('/')); + return FS.analyzePath([link].concat(path).join('/'), + dontResolveLastLink, linksVisited + 1); + } + traversed.push(target); + if (path.length == 0) { + ret.exists = true; + ret.path = traversed.join('/'); + ret.object = current; + } + } + return ret; + } + 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 as still resolved. + findObject: function(path, dontResolveLastLink) { + 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 (!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: new Date(), + 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 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 = []; + for (var i = 0; i < data.length; i++) dataArray.push(data.charCodeAt(i)); + data = dataArray; + } + var properties = {isDevice: false, contents: data}; + return FS.createFile(parent, name, properties, canRead, canWrite); + }, + // Creates a file record for lazy-loading from a URL. + createLazyFile: function(parent, name, url, canRead, canWrite) { + var properties = {isDevice: false, url: url}; + return FS.createFile(parent, name, properties, canRead, canWrite); + }, + // Creates a link to a sepcific 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 || + 'contents' in obj) return true; + var success = true; + if (typeof XMLHttpRequest !== 'undefined') { + // Browser. + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', obj.url, false); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); // Binary. + xhr.send(null); + if (xhr.status != 200 && xhr.status != 0) success = false; + obj.contents = intArrayFromString(xhr.responseText || '', true); + } else if (typeof read !== 'undefined') { + // Command-line. + try { + obj.contents = intArrayFromString(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; + }, + // Initializes the filesystems with stdin/stdout/stderr devices, given + // optional handlers. + init: function(input, output, error) { + // Make sure we initialize only once. + if (FS.init.initialized) return; + else FS.init.initialized = true; + + // Default handlers. + if (!input) input = function() { + if (!input.cache) { + var result; + if (window && typeof window.prompt == 'function') { + // Browser. + result = window.prompt('Input: '); + } else if (typeof readline == 'function') { + // Command line. + result = readline(); + } + if (!result) return null; + input.cache = intArrayFromString(result + '\n', true); + } + return input.cache.shift(); + }; + if (!output) output = function(val) { + if (!output.printer) { + if (typeof print == 'function') { + // Either console or custom print function defined. + output.printer = print; + } else if (console && typeof console.log == 'function') { + // Browser-like environment with a console. + output.printer = console.log; + } else { + // Fallback to a harmless no-op. + output.printer = function() {}; + } + } + if (!output.buffer) output.buffer = []; + if (val === null || val === '\n'.charCodeAt(0)) { + output.printer(output.buffer.join('')); + output.buffer = []; + } else { + output.buffer.push(String.fromCharCode(val)); + } + }; + if (!error) error = output; + + // Create the temporary folder. + FS.createFolder('/', 'tmp', true, true); + + // Create the I/O devices. + var devFolder = FS.createFolder('/', 'dev', true, false); + 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, error); + + // Create default streams. + FS.streams[1] = { + path: '/dev/stdin', + object: stdin, + position: 0, + isRead: true, + isWrite: false, + isAppend: false, + error: false, + eof: false, + ungotten: [] + }; + FS.streams[2] = { + path: '/dev/stdout', + object: stdout, + position: 0, + isRead: false, + isWrite: true, + isAppend: false, + error: false, + eof: false, + ungotten: [] + }; + FS.streams[3] = { + path: '/dev/stderr', + object: stderr, + position: 0, + isRead: false, + isWrite: true, + isAppend: false, + error: false, + eof: false, + ungotten: [] + }; + _stdin = allocate([1], 'void*', ALLOC_STATIC); + _stdout = allocate([2], 'void*', ALLOC_STATIC); + _stderr = allocate([3], 'void*', ALLOC_STATIC); + + // Once initialized, permissions start having effect. + FS.ignorePermissions = false; + } + }, + + // ========================================================================== + // dirent.h + // ========================================================================== + + __dirent_struct_layout: Runtime.generateStructInfo(null, '%struct.dirent'), + opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], + 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) { + ___setErrNo(ERRNO_CODES.ENOENT); + return 0; + } + var target = FS.findObject(path); + if (target === null) return 0; + if (!target.isFolder) { + ___setErrNo(ERRNO_CODES.ENOTDIR); + return 0; + } else if (!target.read) { + ___setErrNo(ERRNO_CODES.EACCES); + return 0; + } + var id = FS.streams.length; + 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__) + }; + return id; + }, + closedir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + 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); + delete FS.streams[dirp]; + return 0; + } + }, + 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; + } + }, + seekdir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + 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; + } + } + }, + rewinddir__deps: ['seekdir'], + rewinddir: function(dirp) { + // void rewinddir(DIR *dirp); + // http://pubs.opengroup.org/onlinepubs/007908799/xsh/rewinddir.html + _seekdir(dirp, -2); + }, + readdir_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], + readdir_r: function(dirp, entry, result) { + // int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); + // http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { + 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; + if (loc === -2) { + name = '.'; + inode = 1; // Really undefined. + } else if (loc === -1) { + name = '..'; + inode = 1; // Really undefined. + } else { + name = stream.contents[loc]; + inode = stream.object.contents[name].inodeNumber; + } + 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') }}} + var type = stream.object.isDevice ? 2 // DT_CHR, character device. + : stream.object.isFolder ? 4 // DT_DIR, directory. + : stream.object.link !== undefined ? 10 // DT_LNK, symbolic link. + : 8; // DT_REG, regular file. + {{{ makeSetValue('entry', 'offsets.d_type', 'type', 'i8') }}} + {{{ makeSetValue('result', '0', 'entry', 'i8*') }}} + } + 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) { + ___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: Check if we need to link any aliases. + + // ========================================================================== + // utime.h + // ========================================================================== + + __utimbuf_struct_layout: Runtime.generateStructInfo(null, '%struct.utimbuf'), + utime__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__utimbuf_struct_layout'], + 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') }}} + time = new Date(time * 1000); + } else { + time = new Date(); + } + var file = FS.findObject(Pointer_stringify(path)); + if (file === null) return -1; + if (!file.write) { + ___setErrNo(ERRNO_CODES.EPERM); + return -1; + } + file.timestamp = time; + return 0; + }, + + // ========================================================================== + // libgen.h + // ========================================================================== + + __libgenSplitName: function(path) { + if (path === 0 || {{{ makeGetValue('path', 0, 'i8') }}} === 0) { + // Null or empty results in '.'. + var me = ___libgenSplitName; + if (!me.ret) { + me.ret = allocate(['.'.charCodeAt(0), 0], 'i8', ALLOC_STATIC); + } + return [me.ret, -1]; + } else { + var slash = '/'.charCodeAt(0); + var allSlashes = true; + var slashPositions = []; + for (var i = 0; {{{ makeGetValue('path', 'i', 'i8') }}} !== 0; i++) { + if ({{{ makeGetValue('path', 'i', 'i8') }}} === slash) { + slashPositions.push(i); + } else { + allSlashes = false; + } + } + var length = i; + if (allSlashes) { + // All slashes result in a single slash. + {{{ 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') }}} + length--; + } + return [path, slashPositions.pop()]; + } + } + }, + basename__deps: ['__libgenSplitName'], + basename: function(path) { + // char *basename(char *path); + // http://pubs.opengroup.org/onlinepubs/007908799/xsh/basename.html + var result = ___libgenSplitName(path); + return result[0] + result[1] + 1; + }, + __xpg_basename: 'basename', + dirname__deps: ['__libgenSplitName'], + dirname: function(path) { + // char *dirname(char *path); + // http://pubs.opengroup.org/onlinepubs/007908799/xsh/dirname.html + var result = ___libgenSplitName(path); + if (result[1] == 0) { + {{{ makeSetValue('result[0]', 1, '0', 'i8') }}} + } else if (result[1] !== -1) { + {{{ makeSetValue('result[0]', 'result[1]', '0', 'i8') }}} + } + return result[0]; + }, + + // ========================================================================== + // sys/stat.h + // ========================================================================== + + __stat_struct_layout: Runtime.generateStructInfo(null, '%struct.stat'), + stat__deps: ['$FS', '__stat_struct_layout'], + stat: function(path, buf, dontResolveLastLink) { + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/stat.html + // int stat(const char *path, struct stat *buf); + // NOTE: dontResolveLastLink is a shortcut for lstat(). It should never be + // used in client code. + var obj = FS.findObject(Pointer_stringify(path), dontResolveLastLink); + if (obj === null || !FS.forceLoadFile(obj)) return -1; + + var offsets = ___stat_struct_layout; + + // Constants. + {{{ makeSetValue('buf', 'offsets.st_nlink', '1', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_uid', '0', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_gid', '0', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_blksize', '4096', 'i32') }}} + + // Variables. + {{{ makeSetValue('buf', 'offsets.st_ino', 'obj.inodeNumber', 'i32') }}} + var time = Math.floor(obj.timestamp.getTime() / 1000); + if (offsets.st_atime === undefined) { + offsets.st_atime = offsets.st_atim.tv_sec; + offsets.st_mtime = offsets.st_mtim.tv_sec; + offsets.st_ctime = offsets.st_ctim.tv_sec; + var nanosec = (obj.timestamp.getTime() % 1000) * 1000; + {{{ makeSetValue('buf', 'offsets.st_atim.tv_nsec', 'nanosec', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_mtim.tv_nsec', 'nanosec', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_ctim.tv_nsec', 'nanosec', 'i32') }}} + } + {{{ makeSetValue('buf', 'offsets.st_atime', 'time', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_mtime', 'time', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_ctime', 'time', 'i32') }}} + var mode = 0; + var size = 0; + var blocks = 0; + var dev = 0; + var rdev = 0; + if (obj.isDevice) { + // Device numbers reuse inode numbers. + dev = rdev = obj.inodeNumber; + size = blocks = 0; + mode = 0x2000; // S_IFCHR. + } else { + dev = 1; + rdev = 0; + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + if (obj.isFolder) { + size = 4096; + blocks = 1; + mode = 0x4000; // S_IFDIR. + } else { + var data = obj.contents || obj.link; + size = data.length; + blocks = Math.ceil(data.length / 4096); + mode = obj.link === undefined ? 0x8000 : 0xA000; // S_IFREG, S_IFLNK. + } + } + {{{ makeSetValue('buf', 'offsets.st_dev', 'dev', 'i64') }}} + {{{ makeSetValue('buf', 'offsets.st_rdev', 'rdev', 'i64') }}} + // NOTE: These two may be i64, depending on compilation options. + {{{ makeSetValue('buf', 'offsets.st_size', 'size', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.st_blocks', 'blocks', 'i32') }}} + if (obj.read) mode |= 0x16D; // S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH. + if (obj.write) mode |= 0x92; // S_IWUSR | S_IWGRP | S_IWOTH. + {{{ makeSetValue('buf', 'offsets.st_mode', 'mode', 'i32') }}} + + return 0; + }, + lstat__deps: ['stat'], + lstat: function(path, buf) { + // int lstat(const char *path, struct stat *buf); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/lstat.html + return _stat(path, buf, true); + }, + fstat__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'stat'], + fstat: function(fildes, buf) { + // int fstat(int fildes, struct stat *buf); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fstat.html + if (!FS.streams[fildes]) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } else { + var pathArray = intArrayFromString(FS.streams[fildes].path); + var pathPtr = allocate(pathArray, 'i8', ALLOC_NORMAL); + var result = _stat(pathPtr, buf); + _free(pathPtr); + return result; + } + }, + mknod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + mknod: function(path, mode, dev) { + // int mknod(const char *path, mode_t mode, dev_t dev); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mknod.html + if (dev !== 0 || !(mode & 0xC000)) { // S_IFREG | S_IFDIR. + // Can't create devices or pipes through mknod(). + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } else { + var properties = {contents: [], isFolder: Boolean(mode & 0x4000)}; // S_IFDIR. + path = FS.analyzePath(Pointer_stringify(path)); + try { + FS.createObject(path.parentObject, path.name, properties, + mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + return 0; + } catch (e) { + return -1; + } + } + }, + mkdir__deps: ['mknod'], + mkdir: function(path, mode) { + // int mkdir(const char *path, mode_t mode); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html + return _mknod(path, 0x4000 | (mode & 0x180), 0); // S_IFDIR, S_IRUSR | S_IWUSR. + }, + mkfifo__deps: ['__setErrNo', '$ERRNO_CODES'], + mkfifo: function(path, mode) { + // int mkfifo(const char *path, mode_t mode); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkfifo.html + // NOTE: We support running only a single process, and named pipes require + // blocking, which we can't provide. The error code is not very + // accurate, but it's the closest among those allowed in the standard + // and unlikely to result in retries. + ___setErrNo(ERRNO_CODES.EROFS); + return -1; + }, + chmod__deps: ['$FS'], + chmod: function(path, mode) { + // int chmod(const char *path, mode_t mode); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/chmod.html + var obj = FS.findObject(Pointer_stringify(path)); + if (obj === null) return -1; + obj.read = mode & 0x100; // S_IRUSR. + obj.write = mode & 0x80; // S_IWUSR. + obj.timestamp = new Date(); + return 0; + }, + fchmod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chmod'], + fchmod: function(fildes, mode) { + // int fchmod(int fildes, mode_t mode); + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fchmod.html + if (!FS.streams[fildes]) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } else { + var pathArray = intArrayFromString(FS.streams[fildes].path); + var pathPtr = allocate(pathArray, 'i8', ALLOC_NORMAL); + var result = _chmod(pathPtr, mode); + _free(pathPtr); + return result; + } + }, + // TODO: Test once cmask is used. + umask__deps: ['$FS'], + umask: function(newMask) { + var oldMask = FS.cmask; + FS.cmask = newMask; + return oldMask; + }, + __01fstat64_: 'fstat', + __01stat64_: 'stat', + __01lstat64_: 'lstat', + // TODO: Check if other aliases are needed. + + // ========================================================================== + // sys/statvfs.h + // ========================================================================== + + __statvfs_struct_layout: Runtime.generateStructInfo(null, '%struct.statvfs'), + statvfs__deps: ['$FS', '__statvfs_struct_layout'], + statvfs: function(path, buf) { + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/stat.html + // int statvfs(const char *restrict path, struct statvfs *restrict buf); + var offsets = ___statvfs_struct_layout; + // NOTE: None of the constants here are true. We're just returning safe and + // sane values. + {{{ makeSetValue('buf', 'offsets.f_bsize', '4096', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_frsize', '4096', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_blocks', '1000000', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_bfree', '500000', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_bavail', '500000', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_files', 'FS.nextInode', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_ffree', '1000000', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_favail', '1000000', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_fsid', '42', 'i32') }}} + {{{ makeSetValue('buf', 'offsets.f_flag', '2', 'i32') }}} // ST_NOSUID + {{{ makeSetValue('buf', 'offsets.f_namemax', '255', 'i32') }}} + return 0; + }, + fstatvfs__deps: ['statvfs'], + fstatvfs: function(fildes, buf) { + // int fstatvfs(int fildes, struct statvfs *buf); + // http://pubs.opengroup.org/onlinepubs/009604499/functions/statvfs.html + return _statvfs(0, buf); + }, + + // ========================================================================== + // fcntl.h + // ========================================================================== + + __flock_struct_layout: Runtime.generateStructInfo(null, '%struct.flock'), + open__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__dirent_struct_layout'], + open: function(path, oflag, mode) { + // int open(const char *path, int oflag, ...); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/open.html + // NOTE: This implementation tries to mimic glibc rather that strictly + // following the POSIX standard. + + // Simplify flags. + var accessMode = oflag & 0x3; // O_ACCMODE. + var isWrite = accessMode != 0x0; // O_RDONLY. + var isRead = accessMode != 0x1; // O_WRONLY. + var isCreate = Boolean(oflag & 0x40); // O_CREAT. + var isExistCheck = Boolean(oflag & 0x80); // O_EXCL. + var isTruncate = Boolean(oflag & 0x200); // O_TRUNC. + var isAppend = Boolean(oflag & 0x400); // O_APPEND. + + // Verify path. + var origPath = path; + path = FS.analyzePath(Pointer_stringify(path)); + if (!path.parentExists) { + ___setErrNo(path.error); + return -1; + } + var target = path.object || null; + + // Verify the file exists, create if needed and allowed. + if (target) { + if (isCreate && isExistCheck) { + ___setErrNo(ERRNO_CODES.EEXIST); + return -1; + } + if ((isWrite || isCreate || isTruncate) && target.isFolder) { + ___setErrNo(ERRNO_CODES.EISDIR); + return -1; + } + if (isRead && !target.read || isWrite && !target.write) { + ___setErrNo(ERRNO_CODES.EACCES); + return -1; + } + if (isTruncate && !target.isDevice) { + target.contents = []; + } else { + if (!FS.forceLoadFile(target)) { + ___setErrNo(ERRNO_CODES.EIO); + return -1; + } + } + } else { + if (!isCreate) { + ___setErrNo(ERRNO_CODES.ENOENT); + return -1; + } + if (!path.parentObject.write) { + ___setErrNo(ERRNO_CODES.EACCES); + return -1; + } + target = FS.createDataFile(path.parentObject, path.name, [], + mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + } + // Actually create an open stream. + var id = FS.streams.length; + if (target.isFolder) { + var entryBuffer = 0; + if (___dirent_struct_layout) { + entryBuffer = _malloc(___dirent_struct_layout.__size__); + } + var contents = []; + for (var key in target.contents) contents.push(key); + FS.streams[id] = { + path: 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: entryBuffer + }; + } else { + FS.streams[id] = { + path: path.path, + object: target, + position: 0, + isRead: isRead, + isWrite: isWrite, + isAppend: isAppend, + error: false, + eof: false, + ungotten: [] + }; + } + return id; + }, + creat__deps: ['open'], |