diff options
-rw-r--r-- | src/library.js | 1333 | ||||
-rw-r--r-- | src/preamble.js | 40 | ||||
-rw-r--r-- | tests/runner.py | 50 | ||||
-rw-r--r-- | tests/unistd/dup.out | 12 | ||||
-rw-r--r-- | tests/unistd/isatty.js | 2 |
5 files changed, 930 insertions, 507 deletions
diff --git a/src/library.js b/src/library.js index 3e346280..1ef8d0ec 100644 --- a/src/library.js +++ b/src/library.js @@ -18,8 +18,12 @@ LibraryManager.library = { // File system base. // ========================================================================== - $FS__deps: ['$ERRNO_CODES', '__setErrNo'], - $FS__postset: 'FS.ignorePermissions = false;', + 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: { @@ -240,7 +244,8 @@ LibraryManager.library = { // 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. + // 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.'); @@ -262,11 +267,11 @@ LibraryManager.library = { 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 || ''); + obj.contents = intArrayFromString(xhr.responseText || '', true); } else if (typeof read !== 'undefined') { // Command-line. try { - obj.contents = intArrayFromString(read(obj.url)); + obj.contents = intArrayFromString(read(obj.url), true); } catch (e) { success = false; } @@ -275,6 +280,103 @@ LibraryManager.library = { } 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; } }, @@ -307,14 +409,20 @@ LibraryManager.library = { var contents = []; for (var key in target.contents) contents.push(key); FS.streams[id] = { - isFolder: true, 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, - // An index into contents. Special values: -2 is ".", -1 is "..". - position: -2, // Each stream has its own area for readdir() returns. currentEntry: _malloc(___dirent_struct_layout.__size__) }; @@ -324,7 +432,7 @@ LibraryManager.library = { closedir: function(dirp) { // int closedir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html - if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) { + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { return ___setErrNo(ERRNO_CODES.EBADF); } else { _free(FS.streams[dirp].currentEntry); @@ -336,7 +444,7 @@ LibraryManager.library = { telldir: function(dirp) { // long int telldir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html - if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) { + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { return ___setErrNo(ERRNO_CODES.EBADF); } else { return FS.streams[dirp].position; @@ -346,7 +454,7 @@ LibraryManager.library = { 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].isFolder) { + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { ___setErrNo(ERRNO_CODES.EBADF); } else { var entries = 0; @@ -368,7 +476,7 @@ LibraryManager.library = { 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].isFolder) { + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { return ___setErrNo(ERRNO_CODES.EBADF); } var stream = FS.streams[dirp]; @@ -381,10 +489,10 @@ LibraryManager.library = { var name, inode; if (loc === -2) { name = '.'; - inode = 1; // Really undefined. + inode = 1; // Really undefined. } else if (loc === -1) { name = '..'; - inode = 1; // Really undefined. + inode = 1; // Really undefined. } else { name = stream.contents[loc]; inode = stream.object.contents[name].inodeNumber; @@ -398,9 +506,9 @@ LibraryManager.library = { {{{ makeSetValue('entry + offsets.d_name', 'i', 'name.charCodeAt(i)', 'i8') }}} } {{{ makeSetValue('entry + offsets.d_name', 'i', '0', 'i8') }}} - var type = stream.isDevice ? 2 // DT_CHR, character device. - : stream.isFolder ? 4 // DT_DIR, directory. - : stream.link !== undefined ? 10 // DT_LNK, symbolic link. + 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*') }}} @@ -411,7 +519,7 @@ LibraryManager.library = { 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].isFolder) { + if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } else { @@ -721,7 +829,7 @@ LibraryManager.library = { // ========================================================================== __flock_struct_layout: Runtime.generateStructInfo(null, '%struct.flock'), - open__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + 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 @@ -738,6 +846,7 @@ LibraryManager.library = { var isAppend = Boolean(oflag & 0x400); // O_APPEND. // Verify path. + var origPath = path; path = FS.analyzePath(Pointer_stringify(path)); if (!path.parentExists) { ___setErrNo(path.error); @@ -779,18 +888,46 @@ LibraryManager.library = { 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; - FS.streams[id] = { - isFolder: false, - path: path.path, - object: target, - position: 0, - isRead: isRead, - isWrite: isWrite, - isAppend: isAppend - }; + 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'], @@ -965,6 +1102,9 @@ LibraryManager.library = { // int close(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/close.html if (FS.streams[fildes]) { + if (FS.streams[fildes].currentEntry) { + _free(FS.streams[fildes].currentEntry); + } delete FS.streams[fildes]; return 0; } else { @@ -1220,6 +1360,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } else { + stream.ungotten = []; stream.position = position; return position; } @@ -1255,12 +1396,19 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } else { + var bytesRead = 0; + while (stream.ungotten.length && nbyte > 0) { + {{{ makeSetValue('buf++', '0', 'stream.ungotten.pop()', 'i8') }}} + nbyte--; + bytesRead++; + } var contents = stream.object.contents; var size = Math.min(contents.length - offset, nbyte); for (var i = 0; i < size; i++) { {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}} + bytesRead++; } - return i; + return bytesRead; } }, read__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'pread'], @@ -1278,8 +1426,15 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EINVAL); return -1; } else { + var bytesRead; if (stream.object.isDevice) { if (stream.object.input) { + bytesRead = 0; + while (stream.ungotten.length && nbyte > 0) { + {{{ makeSetValue('buf++', '0', 'stream.ungotten.pop()', 'i8') }}} + nbyte--; + bytesRead++; + } for (var i = 0; i < nbyte; i++) { try { var result = stream.object.input(); @@ -1288,15 +1443,16 @@ LibraryManager.library = { return -1; } if (result === null || result === undefined) break; + bytesRead++; {{{ makeSetValue('buf', 'i', 'result', 'i8') }}} } - return i; + return bytesRead; } else { ___setErrNo(ERRNO_CODES.ENXIO); return -1; } } else { - var bytesRead = _pread(fildes, buf, nbyte, stream.position); + bytesRead = _pread(fildes, buf, nbyte, stream.position); if (bytesRead != -1) stream.position += bytesRead; return bytesRead; } @@ -1444,6 +1600,7 @@ LibraryManager.library = { for (var i = 0; i < nbyte; i++) { contents[offset + i] = {{{ makeGetValue('buf', 'i', 'i8') }}}; } + stream.object.timestamp = new Date(); return i; } }, @@ -1472,6 +1629,7 @@ LibraryManager.library = { return -1; } } + stream.object.timestamp = new Date(); return i; } else { ___setErrNo(ERRNO_CODES.ENXIO); @@ -1891,7 +2049,7 @@ LibraryManager.library = { // We must control this entirely. So we don't even need to do // unfreeable allocations - the HEAP is ours, from STATICTOP up. // TODO: We could in theory slice off the top of the HEAP when - // sbrk gets a negative increment in |bytes|... + // sbrk gets a negative increment in |bytes|... var self = _sbrk; if (!self.STATICTOP) { STATICTOP = alignMemoryPage(STATICTOP); @@ -1909,97 +2067,114 @@ LibraryManager.library = { __01lseek64_: 'lseek', // ========================================================================== + // stdio.h + // ========================================================================== - _scanString: function() { - // Supports %x, %4x, %d.%d, %s - var str = Pointer_stringify(arguments[0]); - var stri = 0; - var fmt = Pointer_stringify(arguments[1]); - var fmti = 0; - var args = Array.prototype.slice.call(arguments, 2); + // TODO: Document. + _scanString: function(format, get, unget, args) { + // Supports %x, %4x, %d.%d, %s. + // TODO: Support all format specifiers. + format = Pointer_stringify(format); + var formatIndex = 0; var argsi = 0; var fields = 0; - while (fmti < fmt.length) { - if (fmt[fmti] === '%') { - fmti++; - var max_ = parseInt(fmt[fmti]); - if (!isNaN(max_)) fmti++; - var type = fmt[fmti]; - fmti++; + for (var formatIndex = 0; formatIndex < format.length; formatIndex++) { + var next = get(); + if (next <= 0) return fields; // End of input. + if (format[formatIndex] === '%') { + formatIndex++; + var maxSpecifierStart = formatIndex; + while (format[formatIndex].charCodeAt(0) >= '0'.charCodeAt(0) && + format[formatIndex].charCodeAt(0) <= '9'.charCodeAt(0)) { + formatIndex++; + } + var max_; + if (formatIndex != maxSpecifierStart) { + max_ = parseInt(format.slice(maxSpecifierStart, formatIndex), 10); + } + // TODO: Handle type size modifier. + var type = format[formatIndex]; + formatIndex++; var curr = 0; - while ((curr < max_ || isNaN(max_)) && stri+curr < str.length) { - if ((type === 'd' && parseInt(str[stri+curr]) >= 0) || - (type === 'x' && parseInt(str[stri+curr].replace(/[a-fA-F]/, 5)) >= 0) || + var buffer = []; + while ((curr < max_ || isNaN(max_)) && next > 0) { + if ((type === 'd' && next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) || + (type === 'x' && (next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0) || + next >= 'a'.charCodeAt(0) && next <= 'f'.charCodeAt(0) || + next >= 'A'.charCodeAt(0) && next <= 'F'.charCodeAt(0))) || (type === 's')) { - curr++; + buffer.push(String.fromCharCode(next)); + next = get(); } else { break; } } - if (curr === 0) return 0; // failure - var text = str.substr(stri, curr); - stri += curr; + if (buffer.length === 0) return 0; // Failure. + var text = buffer.join(''); switch (type) { - case 'd': { - {{{ makeSetValue('args[argsi]', '0', 'parseInt(text)', 'i32') }}} + case 'd': + {{{ makeSetValue('args.shift()', '0', 'parseInt(text, 10)', 'i32') }}} break; - } - case 'x': { - {{{ makeSetValue('args[argsi]', '0', 'eval("0x" + text)', 'i32') }}} + case 'x': + {{{ makeSetValue('args.shift()', '0', 'parseInt(text, 16)', 'i32') }}} break; - } - case 's': { + case 's': var array = intArrayFromString(text); + var buf = args.shift(); for (var j = 0; j < array.length; j++) { - {{{ makeSetValue('args[argsi]', 'j', 'array[j]', 'i8') }}} + {{{ makeSetValue('buf', 'j', 'array[j]', 'i8') }}} } break; - } } - argsi++; fields++; - } else { // not '%' - if (fmt[fmti] === str[stri]) { - fmti++; - stri++; - } else { - break; + } else { + // Not a specifier. + if (format[formatIndex].charCodeAt(0) !== next) { + unget(next); + return fields; } } } - return { fields: fields, bytes: stri }; - }, - sscanf__deps: ['_scanString'], - sscanf: function() { - return __scanString.apply(null, arguments).fields; - }, - - _formatString__deps: ['$STDIO', 'isdigit'], - _formatString: function() { - var cStyle = false; - var textIndex = arguments[0]; - var argIndex = 1; - if (textIndex < 0) { - cStyle = true; - textIndex = -textIndex; - argIndex = arguments[1]; - } else { - var _arguments = arguments; - } - function getNextArg(isFloat, size) { - var ret; - if (!cStyle) { - ret = _arguments[argIndex]; - argIndex++; - } else { - if (isFloat) { - ret = {{{ makeGetValue(0, 'argIndex', 'double') }}}; + return fields; + }, + // Performs prtinf-style formatting. + // isVarArgs: Whether the arguments are passed in varargs style, i.e. the + // third parameter is an address of the start of the argument list. + // format: A pointer to the format string. + // Returns the resulting string string as a character array. + _formatString: function(isVarArgs, format/*, ...*/) { + var textIndex = format; + var argIndex = 0; + var getNextArg; + if (isVarArgs) { + var varArgStart = arguments[2]; + getNextArg = function(type) { + var ret; + if (type === 'double') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'double') }}}; + } else if (type === 'float') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'float') }}}; + } else if (type === 'i64') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'i64') }}}; + } else if (type === 'i32') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'i32') }}}; + } else if (type === 'i16') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'i16') }}}; + } else if (type === 'i8') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'i8') }}}; + } else if (type[type.length - 1] === '*') { + ret = {{{ makeGetValue('varArgStart', 'argIndex', 'void*') }}}; } else { - ret = {{{ makeGetValue(0, 'argIndex', 'i32') }}}; + throw new Error('Unknown formatString argument type: ' + type); } - argIndex += {{{ QUANTUM_SIZE === 1 ? 1 : 'Math.max(4, size || 0)' }}}; - } - return +ret; // +: boolean=>int + argIndex += Runtime.getNativeFieldSize(type); + return Number(ret); + }; + } else { + var args = arguments; + getNextArg = function() { + return Number(args[2 + argIndex++]); + }; } var ret = []; @@ -2043,11 +2218,11 @@ LibraryManager.library = { // Handle width. var width = 0; if (next == '*'.charCodeAt(0)) { - width = getNextArg(); + width = getNextArg('i32'); textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; } else { - while (_isdigit(next)) { + while (next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) { width = width * 10 + (next - '0'.charCodeAt(0)); textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; @@ -2062,12 +2237,13 @@ LibraryManager.library = { textIndex++; next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; if (next == '*'.charCodeAt(0)) { - precision = getNextArg(); + precision = getNextArg('i32'); textIndex++; } else { while(1) { var precisionChr = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}}; - if (!_isdigit(precisionChr)) break; + if (precisionChr < '0'.charCodeAt(0) || + precisionChr > '9'.charCodeAt(0)) break; precision = precision * 10 + (precisionChr - '0'.charCodeAt(0)); textIndex++; } @@ -2118,9 +2294,9 @@ LibraryManager.library = { if (['d', 'i', 'u', 'o', 'x', 'X', 'p'].indexOf(String.fromCharCode(next)) != -1) { // Integer. var signed = next == 'd'.charCodeAt(0) || next == 'i'.charCodeAt(0); - var currArg = getNextArg(false, argSize); - // Truncate to requested size. argSize = argSize || 4; + var currArg = getNextArg('i' + (argSize * 8)); + // Truncate to requested size. if (argSize <= 4) { var limit = Math.pow(256, argSize) - 1; currArg = (signed ? reSign : unSign)(currArg & limit, argSize * 8); @@ -2196,7 +2372,7 @@ LibraryManager.library = { }); } else if (['f', 'F', 'e', 'E', 'g', 'G'].indexOf(String.fromCharCode(next)) != -1) { // Float. - var currArg = getNextArg(true, argSize); + var currArg = getNextArg(argSize === 4 ? 'float' : 'double'); var argText; if (isNaN(currArg)) { @@ -2281,7 +2457,7 @@ LibraryManager.library = { }); } else if (next == 's'.charCodeAt(0)) { // String. - var arg = getNextArg(); + var arg = getNextArg('i8*'); var copiedString; if (arg) { copiedString = String_copy(arg); @@ -2304,14 +2480,15 @@ LibraryManager.library = { } } else if (next == 'c'.charCodeAt(0)) { // Character. - if (flagLeftAlign) ret.push(getNextArg()); + if (flagLeftAlign) ret.push(getNextArg('i8')); while (--width > 0) { ret.push(' '.charCodeAt(0)); } - if (!flagLeftAlign) ret.push(getNextArg()); + if (!flagLeftAlign) ret.push(getNextArg('i8')); } else if (next == 'n'.charCodeAt(0)) { // Write the length written so far to the next parameter. - {{{ makeSetValue('getNextArg()', '0', 'ret.length', 'i32') }}} + var ptr = getNextArg('i32*'); + {{{ makeSetValue('ptr', '0', 'ret.length', 'i32') }}} } else if (next == '%'.charCodeAt(0)) { // Literal percent sign. ret.push(curr); @@ -2329,372 +2506,626 @@ LibraryManager.library = { textIndex += 1; } } - return allocate(ret.concat(0), 'i8', ALLOC_STACK); // NB: Stored on the stack - //var len = ret.length+1; - //var ret = allocate(ret.concat(0), 0, ALLOC_STACK); // NB: Stored on the stack - //STACKTOP -= len; // XXX horrible hack. we rewind the stack, to 'undo' the alloc we just did. - // // the point is that this works if nothing else allocs on the stack before - // // the string is read, which should be true - it is very transient, see the *printf* functions below. - //return ret; - }, - - printf__deps: ['_formatString'], - printf: function() { - __print__(Pointer_stringify(__formatString.apply(null, arguments))); - }, - - sprintf__deps: ['strcpy', '_formatString'], - sprintf: function() { - var str = arguments[0]; - var args = Array.prototype.slice.call(arguments, 1); - _strcpy(str, __formatString.apply(null, args)); // not terribly efficient - }, - - snprintf__deps: ['strncpy', '_formatString'], - snprintf: function() { - var str = arguments[0]; - var num = arguments[1]; - var args = Array.prototype.slice.call(arguments, 2); - _strncpy(str, __formatString.apply(null, args), num); // not terribly efficient - }, - - puts: function(p) { - __print__(Pointer_stringify(p) + '\n'); - }, - - putc: 'fputc', - _IO_putc: 'fputc', - - putchar: function(p) { - __print__(String.fromCharCode(p)); - }, - _ZNSo3putEc: 'putchar', - - _ZNSo5flushEv: function() { - __print__('\n'); + return ret; }, - - vsprintf__deps: ['strcpy', '_formatString'], - vsprintf: function(dst, src, ptr) { - _strcpy(dst, __formatString(-src, ptr)); + // NOTE: Invalid stream pointers passed to these functions would cause a crash + // in native code. We, on the other hand, just ignore them, since it's + // easier. + clearerr__deps: ['$FS'], + clearerr: function(stream) { + // void clearerr(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/clearerr.html + if (stream in FS.streams) FS.streams[stream].error = false; }, - - vsnprintf__deps: ['_formatString'], - vsnprintf: function(dst, num, src, ptr) { - var text = __formatString(-src, ptr); // |-|src tells formatstring to use C-style params (typically they are from varargs) - var i; - for (i = 0; i < num; i++) { - {{{ makeCopyValues('dst+i', 'text+i', 1, 'i8') }}} - if ({{{ makeGetValue('dst', 'i', 'i8') }}} == 0) break; + fclose__deps: ['close', 'fsync'], + fclose: function(stream) { + // int fclose(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fclose.html + _fsync(stream); + return _close(stream); + }, + fdopen__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + fdopen: function(fildes, mode) { + // FILE *fdopen(int fildes, const char *mode); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fdopen.html + if (fildes in FS.streams) { + var stream = FS.streams[fildes]; + mode = Pointer_stringify(mode); + if ((mode.indexOf('w') != -1 && !stream.isWrite) || + (mode.indexOf('r') != -1 && !stream.isRead) || + (mode.indexOf('a') != -1 && !stream.isAppend) || + (mode.indexOf('+') != -1 && (!stream.isRead || !stream.isWrite))) { + ___setErrNo(ERRNO_CODES.EINVAL); + return 0; + } else { + stream.error = false; + stream.eof = false; + return fildes; + } + } else { + ___setErrNo(ERRNO_CODES.EBADF); + return 0; } - return i; // Actually, should return how many *would* have been written, if the |num| had not stopped us. }, - - fileno: function(file) { - return file; + feof__deps: ['$FS'], + feof: function(stream) { + // int feof(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/feof.html + return Number(stream in FS.streams && FS.streams[stream].eof); }, - - clearerr: function(stream) { + ferror__deps: ['$FS'], + ferror: function(stream) { + // int ferror(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/ferror.html + return Number(stream in FS.streams && FS.streams[stream].error); }, - - flockfile: function(file) { + fflush__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + fflush: function(stream) { + // int fflush(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fflush.html + var flush = function(filedes) { + // Right now we write all data directly, except for output devices. + if (filedes in FS.streams && FS.streams[filedes].object.output) { + FS.streams[filedes].object.output(null); + } + }; + try { + if (stream === 0) { + for (var i in FS.streams) flush(i); + } else { + flush(stream); + } + return 0; + } catch (e) { + ___setErrNo(ERRNO_CODES.EIO); + return -1; + } }, - - funlockfile: function(file) { + fgetc__deps: ['$FS', 'read'], + fgetc: function(stream) { + // int fgetc(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetc.html + if (!(stream in FS.streams)) return -1; + if (!_fgetc.buffer) _fgetc.buffer = _malloc(1); + var streamObj = FS.streams[stream]; + if (streamObj.eof || streamObj.error) return -1; + var ret = _read(stream, _fgetc.buffer, 1); + if (ret == 0) { + streamObj.eof = true; + return -1; + } else if (ret == -1) { + streamObj.error = true; + return -1; + } else { + return {{{ makeGetValue('_fgetc.buffer', '0', 'i8') }}}; + } }, - - stdin: 0, - stdout: 0, - stderr: 0, - - $STDIO__postset: 'STDIO.init()', - $STDIO__deps: ['stdin', 'stdout', 'stderr'], - $STDIO: { - streams: {}, - filenames: {}, - counter: 1, - SEEK_SET: 0, /* Beginning of file. */ - SEEK_CUR: 1, /* Current position. */ - SEEK_END: 2, /* End of file. */ - init: function() { - _stdin = allocate([0], 'void*', ALLOC_STATIC); - {{{ makeSetValue('_stdin', '0', "STDIO.prepare('<<stdin>>', null, null, true)", 'i32') }}}; - if (Module.stdin) { - // Make sure stdin returns a newline - var orig = Module.stdin; - Module.stdin = function stdinFixed(prompt) { - var ret = orig(prompt); - if (ret[ret.length-1] !== '\n') ret = ret + '\n'; - return ret; - } + getc: 'fgetc', + getc_unlocked: 'fgetc', + getchar__deps: ['fgetc', 'stdin'], + getchar: function() { + // int getchar(void); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/getchar.html + return _fgetc({{{ makeGetValue('_stdin', '0', 'void*') }}}); + }, + fgetpos__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + fgetpos: function(stream, pos) { + // int fgetpos(FILE *restrict stream, fpos_t *restrict pos); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgetpos.html + if (stream in FS.streams) { + stream = FS.streams[stream]; + if (stream.object.isDevice) { + ___setErrNo(ERRNO_CODES.ESPIPE); + return -1; } else { - Module.stdin = function stdin(prompt) { - return window.prompt(prompt) || ''; - }; + {{{ makeSetValue('pos', '0', 'stream.position', 'i32') }}} + var state = (stream.eof ? 1 : 0) + (stream.error ? 2 : 0); + {{{ makeSetValue('pos', Runtime.getNativeFieldSize('i32'), 'state', 'i32') }}} + return 0; } - - _stdout = allocate([0], 'void*', ALLOC_STATIC); - {{{ makeSetValue('_stdout', '0', "STDIO.prepare('<<stdout>>', null, true)", 'i32') }}}; - - _stderr = allocate([0], 'void*', ALLOC_STATIC); - {{{ makeSetValue('_stderr', '0', "STDIO.prepare('<<stderr>>', null, true)", 'i32') }}}; - }, - cleanFilename: function(filename) { - return filename.replace('./', ''); - }, - prepare: function(filename, data, print_, interactiveInput) { - filename = STDIO.cleanFilename(filename); - var stream = STDIO.counter++; - STDIO.streams[stream] = { - filename: filename, - data: data ? data : [], - position: 0, - eof: 0, - error: 0, - interactiveInput: interactiveInput, // true for stdin - on the web, we allow interactive input - print: print_ // true for stdout and stderr - we print when receiving data for them - }; - STDIO.filenames[filename] = stream; - return stream; - }, - open: function(filename) { - filename = STDIO.cleanFilename(filename); - var stream = STDIO.filenames[filename]; - if (!stream) { - // Not already cached; try to load it right now - try { - return STDIO.prepare(filename, readBinary(filename)); - } catch(e) { - return 0; - } + } else { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + }, + fgets__deps: ['fgetc'], + fgets: function(s, n, stream) { + // char *fgets(char *restrict s, int n, FILE *restrict stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fgets.html + if (!(stream in FS.streams)) return 0; + var streamObj = FS.streams[stream]; + if (streamObj.error || streamObj.eof) return 0; + for (var i = 0; i < n - 1; i++) { + var byte = _fgetc(stream); + if (byte == -1) { + if (streamObj.error) return 0; + else if (streamObj.eof) break; + } else if (byte == '\n'.charCodeAt(0)) { + break; } - var info = STDIO.streams[stream]; - info.position = info.error = info.eof = 0; - return stream; - }, - read: function(stream, ptr, size) { - var info = STDIO.streams[stream]; - if (!info) return -1; - if (info.interactiveInput) { - for (var i = 0; i < size; i++) { - if (info.data.length === 0) { - info.data = intArrayFromString(Module.stdin(PRINTBUFFER.length > 0 ? PRINTBUFFER : '?')).map(function(x) { return x === 0 ? 10 : x }); // change 0 to newline - PRINTBUFFER = ''; - if (info.data.length === 0) return i; - } - {{{ makeSetValue('ptr', '0', 'info.data.shift()', 'i8') }}} - ptr++; - } - return size; + {{{ makeSetValue('s', 'i', 'byte', 'i8') }}} + } + {{{ makeSetValue('s', 'i', '0', 'i8') }}} + return s; + }, + gets__deps: ['fgets'], + gets: function(s) { + // char *gets(char *s); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/gets.html + return _fgets(s, 1e6, {{{ makeGetValue('_stdin', '0', 'void*') }}}); + }, + fileno: function(stream) { + // int fileno(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fileno.html + // We use file descriptor numbers and FILE* streams interchangeably. + return stream; |