blob: 07c87430fe6669a731b91ac9f1554198cb827f17 [file] [log] [blame]
/**
* @license
* Copyright 2019 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
var WasiLibrary = {
#if !MINIMAL_RUNTIME
$ExitStatus__docs: '/** @constructor */',
$ExitStatus: function(status) {
this.name = 'ExitStatus';
this.message = `Program terminated with exit(${status})`;
this.status = status;
},
proc_exit__deps: ['$ExitStatus'],
#endif
proc_exit__nothrow: true,
proc_exit: (code) => {
#if MINIMAL_RUNTIME
throw `exit(${code})`;
#else
#if RUNTIME_DEBUG
dbg(`proc_exit: ${code}`);
#endif
EXITSTATUS = code;
if (!keepRuntimeAlive()) {
var doneExit = () => {
#if expectToReceiveOnModule('onExit')
if (Module['onExit']) Module['onExit'](code);
#endif
ABORT = true;
}
#if PTHREADS
PThread.terminateAllThreads().then(doneExit);
#else
doneExit();
#endif
}
quit_(code, new ExitStatus(code));
#endif // MINIMAL_RUNTIME
},
sched_yield__nothrow: true,
sched_yield: () => {
return 0;
},
random_get__deps: ['getentropy'],
random_get: (buf, buf_len) => {
_getentropy(buf, buf_len);
return 0;
},
$getEnvStrings__deps: ['$ENV', '$getExecutableName'],
$getEnvStrings: () => {
if (!getEnvStrings.strings) {
// Default values.
#if !DETERMINISTIC
// Browser language detection #8751
var lang = ((typeof navigator == 'object' && navigator.languages && navigator.languages[0]) || 'C').replace('-', '_') + '.UTF-8';
#else
// Deterministic language detection, ignore the browser's language.
var lang = 'C.UTF-8';
#endif
var env = {
#if !PURE_WASI
'USER': 'web_user',
'LOGNAME': 'web_user',
'PATH': '/',
'PWD': '/',
'HOME': '/home/web_user',
'LANG': lang,
'_': getExecutableName()
#endif
};
// Apply the user-provided values, if any.
for (var x in ENV) {
// x is a key in ENV; if ENV[x] is undefined, that means it was
// explicitly set to be so. We allow user code to do that to
// force variables with default values to remain unset.
if (ENV[x] === undefined) delete env[x];
else env[x] = ENV[x];
}
var strings = [];
for (var x in env) {
strings.push(`${x}=${env[x]}`);
}
getEnvStrings.strings = strings;
}
return getEnvStrings.strings;
},
environ_sizes_get__deps: ['$getEnvStrings'],
environ_sizes_get__nothrow: true,
environ_sizes_get: (penviron_count, penviron_buf_size) => {
var strings = getEnvStrings();
{{{ makeSetValue('penviron_count', 0, 'strings.length', SIZE_TYPE) }}};
var bufSize = 0;
strings.forEach(function(string) {
bufSize += string.length + 1;
});
{{{ makeSetValue('penviron_buf_size', 0, 'bufSize', SIZE_TYPE) }}};
return 0;
},
environ_get__deps: ['$getEnvStrings', '$stringToAscii'],
environ_get__nothrow: true,
environ_get: (__environ, environ_buf) => {
var bufSize = 0;
getEnvStrings().forEach(function(string, i) {
var ptr = environ_buf + bufSize;
{{{ makeSetValue('__environ', `i*${POINTER_SIZE}`, 'ptr', POINTER_TYPE) }}};
stringToAscii(string, ptr);
bufSize += string.length + 1;
});
return 0;
},
// In normal (non-standalone) mode arguments are passed direclty
// to main, and the `mainArgs` global does not exist.
#if STANDALONE_WASM
args_sizes_get__nothrow: true,
args_sizes_get: (pargc, pargv_buf_size) => {
#if MAIN_READS_PARAMS
{{{ makeSetValue('pargc', 0, 'mainArgs.length', SIZE_TYPE) }}};
var bufSize = 0;
mainArgs.forEach(function(arg) {
bufSize += arg.length + 1;
});
{{{ makeSetValue('pargv_buf_size', 0, 'bufSize', SIZE_TYPE) }}};
#else
{{{ makeSetValue('pargc', 0, '0', SIZE_TYPE) }}};
#endif
return 0;
},
args_get__nothrow: true,
args_get__deps: ['$stringToAscii'],
args_get: (argv, argv_buf) => {
#if MAIN_READS_PARAMS
var bufSize = 0;
mainArgs.forEach(function(arg, i) {
var ptr = argv_buf + bufSize;
{{{ makeSetValue('argv', `i*${POINTER_SIZE}`, 'ptr', POINTER_TYPE) }}};
stringToAscii(arg, ptr);
bufSize += arg.length + 1;
});
#endif
return 0;
},
#endif
$checkWasiClock: (clock_id) => {
return clock_id == {{{ cDefs.__WASI_CLOCKID_REALTIME }}} ||
clock_id == {{{ cDefs.__WASI_CLOCKID_MONOTONIC }}} ||
clock_id == {{{ cDefs.__WASI_CLOCKID_PROCESS_CPUTIME_ID }}} ||
clock_id == {{{ cDefs.__WASI_CLOCKID_THREAD_CPUTIME_ID }}};
},
// TODO: the i64 in the API here must be legalized for this JS code to run,
// but the wasm file can't be legalized in standalone mode, which is where
// this is needed. To get this code to be usable as a JS shim we need to
// either wait for BigInt support or to legalize on the client.
clock_time_get__i53abi: true,
clock_time_get__nothrow: true,
clock_time_get__deps: ['emscripten_get_now', '$nowIsMonotonic', '$checkWasiClock'],
clock_time_get: (clk_id, ignored_precision, ptime) => {
if (!checkWasiClock(clk_id)) {
return {{{ cDefs.EINVAL }}};
}
var now;
// all wasi clocks but realtime are monotonic
if (clk_id === {{{ cDefs.__WASI_CLOCKID_REALTIME }}}) {
now = Date.now();
} else if (nowIsMonotonic) {
now = _emscripten_get_now();
} else {
return {{{ cDefs.ENOSYS }}};
}
// "now" is in ms, and wasi times are in ns.
var nsec = Math.round(now * 1000 * 1000);
{{{ makeSetValue('ptime', 0, 'nsec >>> 0', 'i32') }}};
{{{ makeSetValue('ptime', 4, '(nsec / Math.pow(2, 32)) >>> 0', 'i32') }}};
return 0;
},
clock_res_get__nothrow: true,
clock_res_get__deps: ['emscripten_get_now', 'emscripten_get_now_res', '$nowIsMonotonic', '$checkWasiClock'],
clock_res_get: (clk_id, pres) => {
if (!checkWasiClock(clk_id)) {
return {{{ cDefs.EINVAL }}};
}
var nsec;
// all wasi clocks but realtime are monotonic
if (clk_id === {{{ cDefs.CLOCK_REALTIME }}}) {
nsec = 1000 * 1000; // educated guess that it's milliseconds
} else if (nowIsMonotonic) {
nsec = _emscripten_get_now_res();
} else {
return {{{ cDefs.ENOSYS }}};
}
{{{ makeSetValue('pres', 0, 'nsec >>> 0', 'i32') }}};
{{{ makeSetValue('pres', 4, '(nsec / Math.pow(2, 32)) >>> 0', 'i32') }}};
return 0;
},
#if SYSCALLS_REQUIRE_FILESYSTEM
$doReadv__docs: '/** @param {number=} offset */',
$doReadv: (stream, iov, iovcnt, offset) => {
var ret = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_base, '*') }}};
var len = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_len, '*') }}};
iov += {{{ C_STRUCTS.iovec.__size__ }}};
var curr = FS.read(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset);
if (curr < 0) return -1;
ret += curr;
if (curr < len) break; // nothing more to read
if (typeof offset !== 'undefined') {
offset += curr;
}
}
return ret;
},
$doWritev__docs: '/** @param {number=} offset */',
$doWritev: (stream, iov, iovcnt, offset) => {
var ret = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_base, '*') }}};
var len = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_len, '*') }}};
iov += {{{ C_STRUCTS.iovec.__size__ }}};
var curr = FS.write(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset);
if (curr < 0) return -1;
ret += curr;
if (typeof offset !== 'undefined') {
offset += curr;
}
}
return ret;
},
#else
// MEMFS filesystem disabled lite handling of stdout and stderr:
$printCharBuffers: [null, [], []], // 1 => stdout, 2 => stderr
$printCharBuffers__internal: true,
$printChar__internal: true,
$printChar__deps: ['$printCharBuffers', '$UTF8ArrayToString'],
$printChar: (stream, curr) => {
var buffer = printCharBuffers[stream];
#if ASSERTIONS
assert(buffer);
#endif
if (curr === 0 || curr === {{{ charCode('\n') }}}) {
(stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
buffer.length = 0;
} else {
buffer.push(curr);
}
},
#endif // SYSCALLS_REQUIRE_FILESYSTEM
#if SYSCALLS_REQUIRE_FILESYSTEM
fd_write__deps: ['$doWritev'],
#elif (!MINIMAL_RUNTIME || EXIT_RUNTIME)
$flush_NO_FILESYSTEM__deps: ['$printChar', '$printCharBuffers'],
$flush_NO_FILESYSTEM: () => {
// flush anything remaining in the buffers during shutdown
#if hasExportedSymbol('fflush')
_fflush(0);
#endif
if (printCharBuffers[1].length) printChar(1, {{{ charCode("\n") }}});
if (printCharBuffers[2].length) printChar(2, {{{ charCode("\n") }}});
},
fd_write__deps: ['$flush_NO_FILESYSTEM', '$printChar'],
fd_write__postset: () => {
addAtExit('flush_NO_FILESYSTEM()');
},
#else
fd_write__deps: ['$printChar'],
#endif
fd_write: (fd, iov, iovcnt, pnum) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
var num = doWritev(stream, iov, iovcnt);
#else
// hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0
var num = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_base, '*') }}};
var len = {{{ makeGetValue('iov', C_STRUCTS.iovec.iov_len, '*') }}};
iov += {{{ C_STRUCTS.iovec.__size__ }}};
for (var j = 0; j < len; j++) {
printChar(fd, HEAPU8[ptr+j]);
}
num += len;
}
#endif // SYSCALLS_REQUIRE_FILESYSTEM
{{{ makeSetValue('pnum', 0, 'num', SIZE_TYPE) }}};
return 0;
},
#if SYSCALLS_REQUIRE_FILESYSTEM
fd_pwrite__deps: ['$doWritev'],
#endif
fd_pwrite__i53abi: true,
fd_pwrite: (fd, iov, iovcnt, offset, pnum) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
if (isNaN(offset)) return {{{ cDefs.EOVERFLOW }}};
var stream = SYSCALLS.getStreamFromFD(fd)
var num = doWritev(stream, iov, iovcnt, offset);
{{{ makeSetValue('pnum', 0, 'num', SIZE_TYPE) }}};
return 0;
#elif ASSERTIONS
abort('fd_pwrite called without SYSCALLS_REQUIRE_FILESYSTEM');
#else
return {{{ cDefs.ENOSYS }}};
#endif
},
fd_close: (fd) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
FS.close(stream);
return 0;
#elif PROXY_POSIX_SOCKETS
// close() is a tricky function because it can be used to close both regular file descriptors
// and POSIX network socket handles, hence an implementation would need to track for each
// file descriptor which kind of item it is. To simplify, when using PROXY_POSIX_SOCKETS
// option, use shutdown() to close a socket, and this function should behave like a no-op.
warnOnce('To close sockets with PROXY_POSIX_SOCKETS bridge, prefer to use the function shutdown() that is proxied, instead of close()')
return 0;
#elif ASSERTIONS
abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM');
#else
return {{{ cDefs.ENOSYS }}};
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
#if SYSCALLS_REQUIRE_FILESYSTEM
fd_read__deps: ['$doReadv'],
#endif
fd_read: (fd, iov, iovcnt, pnum) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
var num = doReadv(stream, iov, iovcnt);
{{{ makeSetValue('pnum', 0, 'num', SIZE_TYPE) }}};
return 0;
#elif ASSERTIONS
abort('fd_read called without SYSCALLS_REQUIRE_FILESYSTEM');
#else
return {{{ cDefs.ENOSYS }}};
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
#if SYSCALLS_REQUIRE_FILESYSTEM
fd_pread__deps: ['$doReadv'],
#endif
fd_pread__i53abi: true,
fd_pread: (fd, iov, iovcnt, offset, pnum) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
if (isNaN(offset)) return {{{ cDefs.EOVERFLOW }}};
var stream = SYSCALLS.getStreamFromFD(fd)
var num = doReadv(stream, iov, iovcnt, offset);
{{{ makeSetValue('pnum', 0, 'num', SIZE_TYPE) }}};
return 0;
#elif ASSERTIONS
abort('fd_pread called without SYSCALLS_REQUIRE_FILESYSTEM');
#else
return {{{ cDefs.ENOSYS }}};
#endif
},
fd_seek__i53abi: true,
fd_seek: (fd, offset, whence, newOffset) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
if (isNaN(offset)) return {{{ cDefs.EOVERFLOW }}};
var stream = SYSCALLS.getStreamFromFD(fd);
FS.llseek(stream, offset, whence);
{{{ makeSetValue('newOffset', '0', 'stream.position', 'i64') }}};
if (stream.getdents && offset === 0 && whence === {{{ cDefs.SEEK_SET }}}) stream.getdents = null; // reset readdir state
return 0;
#else
return {{{ cDefs.ESPIPE }}};
#endif
},
$wasiRightsToMuslOFlags: (rights) => {
#if SYSCALL_DEBUG
dbg(`wasiRightsToMuslOFlags: ${rights}`);
#endif
if ((rights & {{{ cDefs.__WASI_RIGHTS_FD_READ }}}) && (rights & {{{ cDefs.__WASI_RIGHTS_FD_WRITE }}})) {
return {{{ cDefs.O_RDWR }}};
}
if (rights & {{{ cDefs.__WASI_RIGHTS_FD_READ }}}) {
return {{{ cDefs.O_RDONLY }}};
}
if (rights & {{{ cDefs.__WASI_RIGHTS_FD_WRITE }}}) {
return {{{ cDefs.O_WRONLY }}};
}
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
},
$wasiOFlagsToMuslOFlags: (oflags) => {
var musl_oflags = 0;
if (oflags & {{{ cDefs.__WASI_OFLAGS_CREAT }}}) {
musl_oflags |= {{{ cDefs.O_CREAT }}};
}
if (oflags & {{{ cDefs.__WASI_OFLAGS_TRUNC }}}) {
musl_oflags |= {{{ cDefs.O_TRUNC }}};
}
if (oflags & {{{ cDefs.__WASI_OFLAGS_DIRECTORY }}}) {
musl_oflags |= {{{ cDefs.O_DIRECTORY }}};
}
if (oflags & {{{ cDefs.__WASI_OFLAGS_EXCL }}}) {
musl_oflags |= {{{ cDefs.O_EXCL }}};
}
return musl_oflags;
},
#if PURE_WASI
// preopen maps open file descriptors to pathname.
// In emscripten we already have a VFS layer so (for now) we expose the entire
// VFS to the wasi API.
$preopens: "{3: '/'}",
path_open__sig: 'iiiiiiiiii',
path_open__deps: ['$wasiRightsToMuslOFlags', '$wasiOFlagsToMuslOFlags', '$preopens'],
path_open: (fd, dirflags, path, path_len, oflags,
fs_rights_base, fs_rights_inherting,
fdflags, opened_fd) => {
if (!(fd in preopens)) {
return {{{ cDefs.EBADF }}};
}
var pathname = UTF8ToString(path, path_len);
var musl_oflags = wasiRightsToMuslOFlags(Number(fs_rights_base));
#if SYSCALL_DEBUG
dbg(`oflags1: ${ptrToString(musl_oflags)}`);
#endif
musl_oflags |= wasiOFlagsToMuslOFlags(Number(oflags));
#if SYSCALL_DEBUG
dbg(`oflags2: ${ptrToString(musl_oflags)}`);
#endif
var stream = FS.open(pathname, musl_oflags);
{{{ makeSetValue('opened_fd', '0', 'stream.fd', 'i32') }}};
return 0;
},
fd_prestat_dir_name__deps: ['$preopens'],
fd_prestat_dir_name__sig: 'iiii',
fd_prestat_dir_name__nothrow: true,
fd_prestat_dir_name: (fd, path, path_len) => {
if (!(fd in preopens)) {
return {{{ cDefs.EBADF }}};
}
var preopen_path = preopens[fd];
stringToUTF8Array(preopen_path, HEAP8, path, path_len)
#if SYSCALL_DEBUG
dbg(`fd_prestat_dir_name -> "${preopen_path}"`);
#endif
return 0;
},
fd_prestat_get__deps: ['$preopens'],
fd_prestat_get__sig: 'iii',
fd_prestat_get__nothrow: true,
fd_prestat_get: (fd, stat_buf) => {
if (!(fd in preopens)) {
return {{{ cDefs.EBADF }}};
}
var preopen = preopens[fd];
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_prestat_t.pr_type, cDefs.__WASI_PREOPENTYPE_DIR, 'i8') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_prestat_t.u + C_STRUCTS.__wasi_prestat_dir_t.pr_name_len, 'preopen.length', 'i64') }}};
return 0;
},
fd_fdstat_set_flags__sig: 'iii',
fd_fdstat_set_flags: (fd, flags) => {
// TODO(sbc): implement
var stream = SYSCALLS.getStreamFromFD(fd);
return 0;
},
fd_filestat_get__sig: 'iii',
fd_filestat_get: (fd, stat_buf) => {
// TODO(sbc): implement
var stream = SYSCALLS.getStreamFromFD(fd);
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.dev, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.ino, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.filetype, '0', 'i8') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.nlink, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.size, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.atim, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.mtim, '0', 'i64') }}};
{{{ makeSetValue('stat_buf', C_STRUCTS.__wasi_filestat_t.ctim, '0', 'i64') }}};
return 0;
},
#endif
#if PURE_WASI
fd_fdstat_get__deps: ['$preopens'],
#endif
fd_fdstat_get: (fd, pbuf) => {
var rightsBase = 0;
var rightsInheriting = 0;
var flags = 0;
#if PURE_WASI
if (fd in preopens) {
var type = {{{ cDefs.__WASI_FILETYPE_DIRECTORY }}};
rightsBase = {{{ cDefs.__WASI_RIGHTS_PATH_CREATE_FILE |
cDefs.__WASI_RIGHTS_PATH_OPEN }}};
rightsInheriting = {{{ cDefs.__WASI_RIGHTS_FD_READ |
cDefs.__WASI_RIGHTS_FD_WRITE }}}
} else
#endif
{
#if SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
// All character devices are terminals (other things a Linux system would
// assume is a character device, like the mouse, we have special APIs for).
var type = stream.tty ? {{{ cDefs.__WASI_FILETYPE_CHARACTER_DEVICE }}} :
FS.isDir(stream.mode) ? {{{ cDefs.__WASI_FILETYPE_DIRECTORY }}} :
FS.isLink(stream.mode) ? {{{ cDefs.__WASI_FILETYPE_SYMBOLIC_LINK }}} :
{{{ cDefs.__WASI_FILETYPE_REGULAR_FILE }}};
#else
// Hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0. We support at
// least stdin, stdout, stderr in a simple way.
#if ASSERTIONS
assert(fd == 0 || fd == 1 || fd == 2);
#endif
var type = {{{ cDefs.__WASI_FILETYPE_CHARACTER_DEVICE }}};
if (fd == 0) {
rightsBase = {{{ cDefs.__WASI_RIGHTS_FD_READ }}};
} else if (fd == 1 || fd == 2) {
rightsBase = {{{ cDefs.__WASI_RIGHTS_FD_WRITE }}};
}
flags = {{{ cDefs.__WASI_FDFLAGS_APPEND }}};
#endif
}
{{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_filetype, 'type', 'i8') }}};
{{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_flags, 'flags', 'i16') }}};
{{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_base, 'rightsBase', 'i64') }}};
{{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_inheriting, 'rightsInheriting', 'i64') }}};
return 0;
},
fd_sync: (fd) => {
#if SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
#if ASYNCIFY
return Asyncify.handleSleep(function(wakeUp) {
var mount = stream.node.mount;
if (!mount.type.syncfs) {
// We write directly to the file system, so there's nothing to do here.
wakeUp(0);
return;
}
mount.type.syncfs(mount, false, function(err) {
if (err) {
wakeUp(function() { return {{{ cDefs.EIO }}} });
return;
}
wakeUp(0);
});
});
#else
if (stream.stream_ops && stream.stream_ops.fsync) {
return stream.stream_ops.fsync(stream);
}
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
#endif // ASYNCIFY
#elif ASSERTIONS
abort('fd_sync called without SYSCALLS_REQUIRE_FILESYSTEM');
#else
return {{{ cDefs.ENOSYS }}};
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
};
for (var x in WasiLibrary) {
wrapSyscallFunction(x, WasiLibrary, true);
}
mergeInto(LibraryManager.library, WasiLibrary);