diff options
-rw-r--r-- | src/library.js | 362 | ||||
-rw-r--r-- | tests/env/output.txt | 29 | ||||
-rw-r--r-- | tests/env/src.c | 43 | ||||
-rw-r--r-- | tests/runner.py | 32 |
4 files changed, 411 insertions, 55 deletions
diff --git a/src/library.js b/src/library.js index 18988de6..87879856 100644 --- a/src/library.js +++ b/src/library.js @@ -335,7 +335,7 @@ LibraryManager.library = { 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); + FS.createDevice(devFolder, 'tty', input, output); // Create default streams. FS.streams[1] = { @@ -3184,10 +3184,6 @@ LibraryManager.library = { return ret; }, - getenv: function(name_) { - return 0; // TODO - }, - strtod__deps: ['isspace', 'isdigit'], strtod: function(str, endptr) { // Skip space. @@ -3269,6 +3265,139 @@ LibraryManager.library = { _free(temp); }, + // NOTE: The global environment array is rebuilt from scratch after every + // change, which is inefficient, but should not be a bottleneck unless + // no malloc is used. + __environ: null, + __buildEnvironment__deps: ['__environ'], + __buildEnvironment: function(env) { + if (___environ === null) ___environ = allocate([0], "i8**", ALLOC_STATIC); + var ptrSize = {{{ Runtime.getNativeFieldSize('i8*') }}}; + var envPtr = {{{ makeGetValue('___environ', '0', 'i8**') }}}; + // Clear old. + if (envPtr !== 0) { + var cur = envPtr; + while ({{{ makeGetValue('cur', '0', 'i8*') }}} !== 0) { + _free({{{ makeGetValue('cur', '0', 'i8*') }}}); + cur += ptrSize; + } + _free(envPtr); + } + // Collect key=value lines. + var strings = []; + for (var key in env) { + if (typeof env[key] === 'string') { + strings.push(key + '=' + env[key]); + } + } + // Make new. + envPtr = _malloc(ptrSize * (strings.length + 1)); + {{{ makeSetValue('___environ', '0', 'envPtr', 'i8**') }}} + for (var i = 0; i < strings.length; i++) { + var line = strings[i]; + var ptr = _malloc(line.length + 1); + for (var j = 0; j < line.length; j++) { + {{{ makeSetValue('ptr', 'j', 'line.charCodeAt(j)', 'i8') }}} + } + {{{ makeSetValue('ptr', 'j', '0', 'i8') }}} + {{{ makeSetValue('envPtr', 'i * ptrSize', 'ptr', 'i8*') }}} + } + {{{ makeSetValue('envPtr', 'strings.length * ptrSize', '0', 'i8*') }}} + }, + $ENV__deps: ['__buildEnvironment'], + $ENV__postset: '___buildEnvironment(ENV);', + $ENV: { + 'USER': 'root', + 'PATH': '/', + 'PWD': '/', + 'HOME': '/', + 'LANG': 'en_US.UTF-8', + '_': './this.program' + }, + getenv__deps: ['$ENV'], + getenv: function(name) { + // char *getenv(const char *name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/getenv.html + if (name === 0) return 0; + name = Pointer_stringify(name); + if (!ENV.hasOwnProperty(name)) return 0; + + if (_getenv.ret) _free(_getenv.ret); + _getenv.ret = allocate(intArrayFromString(ENV[name]), 'i8', ALLOC_NORMAL); + return _getenv.ret; + }, + clearenv__deps: ['$ENV', '__buildEnvironment'], + clearenv: function(name) { + // int clearenv (void); + // http://www.gnu.org/s/hello/manual/libc/Environment-Access.html#index-clearenv-3107 + ENV = {}; + ___buildEnvironment(ENV); + return 0; + }, + setenv__deps: ['$ENV', '__buildEnvironment', '$ERRNO_CODES', '__setErrNo'], + setenv: function(envname, envval, overwrite) { + // int setenv(const char *envname, const char *envval, int overwrite); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/setenv.html + if (envname === 0) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + var name = Pointer_stringify(envname); + var val = Pointer_stringify(envval); + if (name === '' || name.indexOf('=') !== -1) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + if (ENV.hasOwnProperty(name) && !overwrite) return 0; + ENV[name] = val; + ___buildEnvironment(ENV); + return 0; + }, + unsetenv__deps: ['$ENV', '__buildEnvironment', '$ERRNO_CODES', '__setErrNo'], + unsetenv: function(name) { + // int unsetenv(const char *name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/unsetenv.html + if (name === 0) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + name = Pointer_stringify(name); + if (name === '' || name.indexOf('=') !== -1) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + if (ENV.hasOwnProperty(name)) { + delete ENV[name]; + ___buildEnvironment(ENV); + } + return 0; + }, + putenv__deps: ['$ENV', '__buildEnvironment', '$ERRNO_CODES', '__setErrNo'], + putenv: function(string) { + // int putenv(char *string); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/putenv.html + // WARNING: According to the standard (and the glibc implementation), the + // string is taken by reference so future changes are reflected. + // We copy it instead, possibly breaking some uses. + if (string === 0) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + string = Pointer_stringify(string); + var splitPoint = string.indexOf('=') + if (string === '' || string.indexOf('=') === -1) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + var name = string.slice(0, splitPoint); + var value = string.slice(splitPoint + 1); + if (!(name in ENV) || ENV[name] !== value) { + ENV[name] = value; + ___buildEnvironment(ENV); + } + return 0; + }, + // ========================================================================== // string.h // ========================================================================== @@ -3513,59 +3642,75 @@ LibraryManager.library = { // ctype.h // ========================================================================== + isascii: function(chr) { + return chr >= 0 && (chr & 0x80) == 0; + }, + toascii: function(chr) { + return chr & 0x7F; + }, + toupper: function(chr) { + if (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) { + return chr - 'a'.charCodeAt(0) + 'A'.charCodeAt(0); + } else { + return chr; + } + }, + _toupper: 'toupper', + tolower: function(chr) { + if (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)) { + return chr - 'A'.charCodeAt(0) + 'a'.charCodeAt(0); + } else { + return chr; + } + }, + _tolower: 'tolower', + // The following functions are defined as macros in glibc. + islower: function(chr) { + return chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0); + }, + isupper: function(chr) { + return chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0); + }, + isalpha: function(chr) { + return (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) || + (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)); + }, isdigit: function(chr) { return chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0); }, - isxdigit: function(chr) { return (chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0)) || (chr >= 'a'.charCodeAt(0) && chr <= 'f'.charCodeAt(0)) || (chr >= 'A'.charCodeAt(0) && chr <= 'F'.charCodeAt(0)); }, - - isalpha: function(chr) { - return (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) || - (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)); - }, - isalnum: function(chr) { return (chr >= '0'.charCodeAt(0) && chr <= '9'.charCodeAt(0)) || (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) || (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)); }, - + ispunct: function(chr) { + return (chr >= '!'.charCodeAt(0) && chr <= '/'.charCodeAt(0)) || + (chr >= ':'.charCodeAt(0) && chr <= '@'.charCodeAt(0)) || + (chr >= '['.charCodeAt(0) && chr <= '`'.charCodeAt(0)) || + (chr >= '{'.charCodeAt(0) && chr <= '~'.charCodeAt(0)); + }, isspace: function(chr) { return chr in { 32: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0 }; }, - + isblank: function(chr) { + return chr == ' '.charCodeAt(0) || chr == '\t'.charCodeAt(0); + }, iscntrl: function(chr) { - return (chr >= 0 && chr <= 0x1f) || chr === 0x7f; + return (chr >= 0 && chr <= 0x1F) || chr === 0x7F; }, - isprint__deps: ['iscntrl'], isprint: function(chr) { return !_iscntrl(chr); }, - - toupper: function(chr) { - if (chr >= 'a'.charCodeAt(0) && chr <= 'z'.charCodeAt(0)) { - return chr - 'a'.charCodeAt(0) + 'A'.charCodeAt(0); - } - return chr; - }, - - tolower: function(chr) { - if (chr >= 'A'.charCodeAt(0) && chr <= 'Z'.charCodeAt(0)) { - return chr - 'A'.charCodeAt(0) + 'a'.charCodeAt(0); - } - return chr; - }, - - // ========================================================================== - // ctype.h Linux specifics - // ========================================================================== - - __ctype_b_loc: function() { // http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib---ctype-b-loc.html + isgraph: 'isprint', + // Lookup table for glibc ctype implementation. + __ctype_b_loc: function() { + // http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib---ctype-b-loc.html var me = ___ctype_b_loc; if (!me.ret) { var values = [ @@ -3597,7 +3742,9 @@ LibraryManager.library = { return me.ret; }, + // ========================================================================== // LLVM specifics + // ========================================================================== llvm_va_copy: function(ppdest, ppsrc) { {{{ makeCopyValues('ppdest', 'ppsrc', QUANTUM_SIZE, 'null') }}} @@ -3746,6 +3893,10 @@ LibraryManager.library = { atanf: 'Math.atan', atan2: 'Math.atan2', atan2f: 'Math.atan2', + exp: 'Math.exp', + expf: 'Math.exp', + log: 'Math.log', + logf: 'Math.log', sqrt: 'Math.sqrt', sqrtf: 'Math.sqrt', fabs: 'Math.abs', @@ -3754,6 +3905,8 @@ LibraryManager.library = { ceilf: 'Math.ceil', floor: 'Math.floor', floorf: 'Math.floor', + pow: 'Math.pow', + powf: 'Math.powf', llvm_sqrt_f32: 'Math.sqrt', llvm_sqrt_f64: 'Math.sqrt', llvm_pow_f32: 'Math.pow', @@ -3763,14 +3916,23 @@ LibraryManager.library = { llvm_exp_f32: 'Math.exp', llvm_exp_f64: 'Math.exp', ldexp: function(x, exp_) { - return x*Math.pow(2, exp_); + return x * Math.pow(2, exp_); }, + ldexpf: 'ldexp', + scalb: 'ldexp', + scalbn: 'ldexp', + scalbnf: 'ldexp', + scalbln: 'ldexp', + scalblnf: 'ldexp', modf: function(x, intpart) { {{{ makeSetValue('intpart', 0, 'Math.floor(x)', 'double') }}} return x - {{{ makeGetValue('intpart', 0, 'double') }}}; }, - + modff: function(x, intpart) { + {{{ makeSetValue('intpart', 0, 'Math.floor(x)', 'float') }}} + return x - {{{ makeGetValue('intpart', 0, 'float') }}}; + }, frexp: function(x, exp_addr) { var sig = 0, exp_ = 0; if (x !== 0) { @@ -3782,63 +3944,151 @@ LibraryManager.library = { {{{ makeSetValue('exp_addr', 0, 'exp_', 'i32') }}} return sig; }, - + frexpf: 'frexp', finite: function(x) { return isFinite(x); }, __finite: 'finite', - isinf: function(x) { return !isNaN(x) && !isFinite(x); }, __isinf: 'isinf', - isnan: function(x) { return isNaN(x); }, __isnan: 'isnan', - copysign: function(a, b) { - if (a<0 === b<0) return a; + if (a < 0 === b < 0) return a; return -a; }, - + copysignf: 'copysign', hypot: function(a, b) { return Math.sqrt(a*a + b*b); }, - + hypotf: 'hypot', sinh: function(x) { var p = Math.pow(Math.E, x); return (p - (1 / p)) / 2; }, - + sinhf: 'sinh', cosh: function(x) { var p = Math.pow(Math.E, x); return (p + (1 / p)) / 2; }, - + coshf: 'cosh', tanh__deps: ['sinh', 'cosh'], tanh: function(x) { return _sinh(x) / _cosh(x); }, - + tanhf: 'tanh', asinh: function(x) { return Math.log(x + Math.sqrt(x * x + 1)); }, - + asinhf: 'asinh', acosh: function(x) { return Math.log(x * 1 + Math.sqrt(x * x - 1)); }, - + acoshf: 'acosh', atanh: function(x) { return Math.log((1 + x) / (1 - x)) / 2; }, - - // LLVM internal math - + atanhf: 'atanh', exp2: function(x) { return Math.pow(2, x); }, + exp2f: 'exp2', + expm1: function(x) { + return Math.exp(x) - 1; + }, + expm1f: 'expm1', + round: function(x) { + return (x < 0) ? -Math.round(-x) : Math.round(x); + }, + roundf: 'round', + lround: 'round', + lroundf: 'round', + llround: 'round', + llroundf: 'round', + rint: function(x) { + return (x > 0) ? -Math.round(-x) : Math.round(x); + }, + rintf: 'rint', + lrint: 'rint', + lrintf: 'rint', + llrint: 'rint', + llrintf: 'rint', + nearbyint: 'rint', + nearbyintf: 'rint', + trunc: function(x) { + return (x < 0) ? Math.ceil(x) : Math.floor(x); + }, + truncf: 'trunc', + fdim: function(x, y) { + return (x > y) ? x - y : 0; + }, + fdimf: 'fdim', + fmax: function(x, y) { + return isNaN(x) ? y : isNaN(y) ? x : Math.max(x, y); + }, + fmaxf: 'fmax', + fmin: function(x, y) { + return isNaN(x) ? y : isNaN(y) ? x : Math.max(x, y); + }, + fminf: 'fmin', + fma: function(x, y, z) { + return x * y + z; + }, + fmaf: 'fma', + fmod: function(x, y) { + return x % y; + }, + fmodf: 'fmod', + remainder: 'fmod', + remainderf: 'fmod', + log10: function(x) { + return Math.log(x) / Math.LN10; + }, + log10f: 'log10', + log1p: function(x) { + return Math.log(1 + x); + }, + log1pf: 'log1p', + log2: function(x) { + return Math.log(x) / Math.LN2; + }, + log2f: 'log2', + nan: function(x) { + return NaN; + }, + nanf: 'nan', + + // ========================================================================== + // sys/utsname.h + // ========================================================================== + + __utsname_struct_layout: Runtime.generateStructInfo(null, '%struct.utsname'), + uname__deps: ['__utsname_struct_layout'], + uname: function(name) { + // int uname(struct utsname *name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/uname.html + if (name === 0) { + return -1; + } else { + var copyString = function(element, value) { + var offset = ___utsname_struct_layout[element]; + for (var i = 0; i < value.length; i++) { + {{{ makeSetValue('name', 'offset + i', 'value.charCodeAt(i)', 'i8') }}} + } + {{{ makeSetValue('name', 'offset + i', '0', 'i8') }}} + }; + copyString('sysname', 'Emscripten'); + copyString('nodename', 'emscripten'); + copyString('release', '1.0'); + copyString('version', '#1'); + copyString('machine', 'x86-JS'); + return 0; + } + }, // ========================================================================== // dlfcn.h - Dynamic library loading @@ -4165,8 +4415,9 @@ LibraryManager.library = { {{{ makeSetValue('_tzname', QUANTUM_SIZE, 'summerNamePtr', 'i32') }}} }, + stime__deps: ['$ERRNO_CODES', '__setErrNo'], stime: function(when) { - // TODO: Set errno. + ___setErrNo(ERRNO_CODES.EPERM); return -1; }, @@ -4451,8 +4702,9 @@ LibraryManager.library = { }, // ========================================================================== - // ** emscripten.h ** + // emscripten.h // ========================================================================== + emscripten_run_script: function(ptr) { eval(Pointer_stringify(ptr)); }, diff --git a/tests/env/output.txt b/tests/env/output.txt new file mode 100644 index 00000000..fa342d48 --- /dev/null +++ b/tests/env/output.txt @@ -0,0 +1,29 @@ +List: +USER=root +PATH=/ +PWD=/ +HOME=/ +LANG=en_US.UTF-8 +_=./this.program + +getenv(PATH): / +getenv(NONEXISTENT): (null) +setenv/0(PATH) ret: 0 +getenv(PATH) after setenv/0: / +setenv/1(PATH) ret: 0 +getenv(PATH) after setenv/1: test2 +setenv(SET_VALUE) ret: 0 +setenv(SET_VALUE2) ret: 0 +getenv(SET_VALUE): test3 +getenv(SET_VALUE2): test4 +putenv(PUT_VALUE) ret: 0 +getenv(PUT_VALUE): test5 +getenv(PUT_VALUE) after alteration: test5 +unsetenv(PUT_VALUE) ret: 0 +getenv(PUT_VALUE) after unsetenv: (null) +setenv(0) ret: -1 +setenv('') ret: -1 +setenv(X=Y) ret: -1 +unsetenv(0) ret: -1 +unsetenv('') ret: -1 +unsetenv(X=Y) ret: -1 diff --git a/tests/env/src.c b/tests/env/src.c new file mode 100644 index 00000000..46419495 --- /dev/null +++ b/tests/env/src.c @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main() { + printf("List:\n"); + for (char** str = __environ; *str; str++) { + printf("%s\n", *str); + } + printf("\n"); + + printf("getenv(PATH): %s\n", getenv("PATH")); + printf("getenv(NONEXISTENT): %s\n", getenv("NONEXISTENT")); + + printf("setenv/0(PATH) ret: %d\n", setenv("PATH", "test", 0)); + printf("getenv(PATH) after setenv/0: %s\n", getenv("PATH")); + printf("setenv/1(PATH) ret: %d\n", setenv("PATH", "test2", 1)); + printf("getenv(PATH) after setenv/1: %s\n", getenv("PATH")); + + printf("setenv(SET_VALUE) ret: %d\n", setenv("SET_VALUE", "test3", 0)); + printf("setenv(SET_VALUE2) ret: %d\n", setenv("SET_VALUE2", "test4", 1)); + printf("getenv(SET_VALUE): %s\n", getenv("SET_VALUE")); + printf("getenv(SET_VALUE2): %s\n", getenv("SET_VALUE2")); + + char buffer[] = "PUT_VALUE=test5"; + printf("putenv(PUT_VALUE) ret: %d\n", putenv(buffer)); + printf("getenv(PUT_VALUE): %s\n", getenv("PUT_VALUE")); + // NOTE: This should change the value. In our implementation, it doesn't. + buffer[10] = 'Q'; + printf("getenv(PUT_VALUE) after alteration: %s\n", getenv("PUT_VALUE")); + + printf("unsetenv(PUT_VALUE) ret: %d\n", unsetenv("PUT_VALUE")); + printf("getenv(PUT_VALUE) after unsetenv: %s\n", getenv("PUT_VALUE")); + + printf("setenv(0) ret: %d\n", setenv(0, "foo", 1)); + printf("setenv('') ret: %d\n", setenv("", "foo", 1)); + printf("setenv(X=Y) ret: %d\n", setenv("X=Y", "foo", 1)); + printf("unsetenv(0) ret: %d\n", unsetenv(0)); + printf("unsetenv('') ret: %d\n", unsetenv("")); + printf("unsetenv(X=Y) ret: %d\n", unsetenv("X=Y")); + + return 0; +} diff --git a/tests/runner.py b/tests/runner.py index a0c95c7b..32234ba2 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -2575,6 +2575,38 @@ if 'benchmark' not in sys.argv: expected = open(path_from_root('tests', 'unistd', 'misc.out'), 'r').read() self.do_test(src, expected) + def test_uname(self): + src = r''' + #include <stdio.h> + #include <sys/utsname.h> + + int main() { + struct utsname u; + printf("ret: %d\n", uname(&u)); + printf("sysname: %s\n", u.sysname); + printf("nodename: %s\n", u.nodename); + printf("release: %s\n", u.release); + printf("version: %s\n", u.version); + printf("machine: %s\n", u.machine); + printf("invalid: %d\n", uname(0)); + return 0; + } + ''' + expected = ''' + ret: 0 + sysname: Emscripten + nodename: emscripten + release: 1.0 + version: #1 + machine: x86-JS + ''' + self.do_test(src, re.sub('(^|\n)\s+', '\\1', expected)) + + def test_env(self): + src = open(path_from_root('tests', 'env', 'src.c'), 'r').read() + expected = open(path_from_root('tests', 'env', 'output.txt'), 'r').read() + self.do_test(src, expected) + ### 'Big' tests def test_fannkuch(self): |