blob: 4c2ce98d1f6965b60f1b505b69d0c3aec6c0548e [file] [log] [blame]
/**
* @license
* Copyright 2015 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
var SyscallsLibrary = {
$SYSCALLS__deps: [
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
'$PATH',
'$FS',
#endif
#if SYSCALL_DEBUG
'$ERRNO_MESSAGES'
#endif
],
$SYSCALLS: {
#if SYSCALLS_REQUIRE_FILESYSTEM
// global constants
DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}},
// shared utilities
calculateAt: function(dirfd, path, allowEmpty) {
if (PATH.isAbs(path)) {
return path;
}
// relative path
var dir;
if (dirfd === {{{ cDefine('AT_FDCWD') }}}) {
dir = FS.cwd();
} else {
var dirstream = SYSCALLS.getStreamFromFD(dirfd);
dir = dirstream.path;
}
if (path.length == 0) {
if (!allowEmpty) {
throw new FS.ErrnoError({{{ cDefine('ENOENT') }}});;
}
return dir;
}
return PATH.join2(dir, path);
},
doStat: function(func, path, buf) {
try {
var stat = func(path);
} catch (e) {
if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) {
// an error occurred while trying to look up the path; we should just report ENOTDIR
return -{{{ cDefine('ENOTDIR') }}};
}
throw e;
}
{{{ makeSetValue('buf', C_STRUCTS.stat.st_dev, 'stat.dev', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.__st_ino_truncated, 'stat.ino', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mode, 'stat.mode', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_nlink, 'stat.nlink', SIZE_TYPE) }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_uid, 'stat.uid', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_gid, 'stat.gid', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_rdev, 'stat.rdev', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_size, 'stat.size', 'i64') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_blksize, '4096', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_blocks, 'stat.blocks', 'i32') }}};
var atime = stat.atime.getTime();
var mtime = stat.mtime.getTime();
var ctime = stat.ctime.getTime();
{{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_sec, 'Math.floor(atime / 1000)', 'i64') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '(atime % 1000) * 1000', SIZE_TYPE) }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_sec, 'Math.floor(mtime / 1000)', 'i64') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '(mtime % 1000) * 1000', SIZE_TYPE) }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_sec, 'Math.floor(ctime / 1000)', 'i64') }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '(ctime % 1000) * 1000', SIZE_TYPE) }}};
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}};
return 0;
},
doMsync: function(addr, stream, len, flags, offset) {
if (!FS.isFile(stream.node.mode)) {
throw new FS.ErrnoError({{{ cDefine('ENODEV') }}});
}
if (flags & {{{ cDefine('MAP_PRIVATE') }}}) {
// MAP_PRIVATE calls need not to be synced back to underlying fs
return 0;
}
#if CAN_ADDRESS_2GB
addr >>>= 0;
#endif
var buffer = HEAPU8.slice(addr, addr + len);
FS.msync(stream, buffer, offset, len, flags);
},
#endif
// arguments handling
varargs: undefined,
get: function() {
#if ASSERTIONS
assert(SYSCALLS.varargs != undefined);
#endif
SYSCALLS.varargs += 4;
var ret = {{{ makeGetValue('SYSCALLS.varargs', '-4', 'i32') }}};
#if SYSCALL_DEBUG
dbg(' (raw: "' + ret + '")');
#endif
return ret;
},
getStr: function(ptr) {
var ret = UTF8ToString(ptr);
#if SYSCALL_DEBUG
dbg(' (str: "' + ret + '")');
#endif
return ret;
},
#if SYSCALLS_REQUIRE_FILESYSTEM
// Just like `FS.getStream` but will throw EBADF if stream is undefined.
getStreamFromFD: function(fd) {
var stream = FS.getStream(fd);
if (!stream) throw new FS.ErrnoError({{{ cDefine('EBADF') }}});
#if SYSCALL_DEBUG
dbg(' (stream: "' + stream.path + '")');
#endif
return stream;
},
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
_mmap_js__sig: 'ipiiippp',
_mmap_js__deps: ['$SYSCALLS',
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
'$FS',
#endif
],
_mmap_js: function(len, prot, flags, fd, off, allocated, addr) {
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
var res = FS.mmap(stream, len, off, prot, flags);
var ptr = res.ptr;
{{{ makeSetValue('allocated', 0, 'res.allocated', 'i32') }}};
#if CAN_ADDRESS_2GB
ptr >>>= 0;
#endif
{{{ makeSetValue('addr', 0, 'ptr', '*') }}};
return 0;
#else // no filesystem support; report lack of support
return -{{{ cDefine('ENOSYS') }}};
#endif
},
_munmap_js__deps: ['$SYSCALLS',
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
'$FS',
#endif
],
_munmap_js__sig: 'ippiiip',
_munmap_js: function(addr, len, prot, flags, fd, offset) {
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
var stream = SYSCALLS.getStreamFromFD(fd);
if (prot & {{{ cDefine('PROT_WRITE') }}}) {
SYSCALLS.doMsync(addr, stream, len, flags, offset);
}
FS.munmap(stream);
// implicitly return 0
#endif
},
__syscall_chdir__sig: 'ip',
__syscall_chdir: function(path) {
path = SYSCALLS.getStr(path);
FS.chdir(path);
return 0;
},
__syscall_chmod__sig: 'ipi',
__syscall_chmod: function(path, mode) {
path = SYSCALLS.getStr(path);
FS.chmod(path, mode);
return 0;
},
__syscall_rename__sig: 'ipp',
__syscall_rename: function(old_path, new_path) {
old_path = SYSCALLS.getStr(old_path);
new_path = SYSCALLS.getStr(new_path);
FS.rename(old_path, new_path);
return 0;
},
__syscall_rmdir__sig: 'ip',
__syscall_rmdir: function(path) {
path = SYSCALLS.getStr(path);
FS.rmdir(path);
return 0;
},
__syscall_dup__sig: 'ii',
__syscall_dup: function(fd) {
var old = SYSCALLS.getStreamFromFD(fd);
return FS.createStream(old, 0).fd;
},
__syscall_pipe__deps: ['$PIPEFS'],
__syscall_pipe__sig: 'ip',
__syscall_pipe: function(fdPtr) {
if (fdPtr == 0) {
throw new FS.ErrnoError({{{ cDefine('EFAULT') }}});
}
var res = PIPEFS.createPipe();
{{{ makeSetValue('fdPtr', 0, 'res.readable_fd', 'i32') }}};
{{{ makeSetValue('fdPtr', 4, 'res.writable_fd', 'i32') }}};
return 0;
},
__syscall_ioctl__sig: 'iiip',
__syscall_ioctl: function(fd, op, varargs) {
#if SYSCALLS_REQUIRE_FILESYSTEM == 0
#if SYSCALL_DEBUG
dbg('no-op in ioctl syscall due to SYSCALLS_REQUIRE_FILESYSTEM=0');
#endif
return 0;
#else
var stream = SYSCALLS.getStreamFromFD(fd);
switch (op) {
case {{{ cDefine('TCGETA') }}}:
case {{{ cDefine('TCGETS') }}}: {
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
#if SYSCALL_DEBUG
dbg('warning: not filling tio struct');
#endif
return 0;
}
case {{{ cDefine('TCSETA') }}}:
case {{{ cDefine('TCSETAW') }}}:
case {{{ cDefine('TCSETAF') }}}:
case {{{ cDefine('TCSETS') }}}:
case {{{ cDefine('TCSETSW') }}}:
case {{{ cDefine('TCSETSF') }}}: {
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
return 0; // no-op, not actually adjusting terminal settings
}
case {{{ cDefine('TIOCGPGRP') }}}: {
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
var argp = SYSCALLS.get();
{{{ makeSetValue('argp', 0, 0, 'i32') }}};
return 0;
}
case {{{ cDefine('TIOCSPGRP') }}}: {
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
return -{{{ cDefine('EINVAL') }}}; // not supported
}
case {{{ cDefine('FIONREAD') }}}: {
var argp = SYSCALLS.get();
return FS.ioctl(stream, op, argp);
}
case {{{ cDefine('TIOCGWINSZ') }}}: {
// TODO: in theory we should write to the winsize struct that gets
// passed in, but for now musl doesn't read anything on it
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
return 0;
}
case {{{ cDefine('TIOCSWINSZ') }}}: {
// TODO: technically, this ioctl call should change the window size.
// but, since emscripten doesn't have any concept of a terminal window
// yet, we'll just silently throw it away as we do TIOCGWINSZ
if (!stream.tty) return -{{{ cDefine('ENOTTY') }}};
return 0;
}
default: return -{{{ cDefine('EINVAL') }}}; // not supported
}
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
__syscall_symlink__sig: 'ipp',
__syscall_symlink: function(target, linkpath) {
target = SYSCALLS.getStr(target);
linkpath = SYSCALLS.getStr(linkpath);
FS.symlink(target, linkpath);
return 0;
},
__syscall_fchmod: function(fd, mode) {
FS.fchmod(fd, mode);
return 0;
},
// When building with PROXY_POSIX_SOCKETS the socket syscalls are implemented
// natively in libsockets.a.
// When building with WASMFS the socket syscalls are implemented natively in
// libwasmfs.a.
#if PROXY_POSIX_SOCKETS == 0 && WASMFS == 0
$getSocketFromFD__deps: ['$SOCKFS', '$FS'],
$getSocketFromFD: function(fd) {
var socket = SOCKFS.getSocket(fd);
if (!socket) throw new FS.ErrnoError({{{ cDefine('EBADF') }}});
#if SYSCALL_DEBUG
dbg(' (socket: "' + socket.path + '")');
#endif
return socket;
},
/** @param {boolean=} allowNull */
$getSocketAddress__deps: ['$readSockaddr', '$FS', '$DNS'],
$getSocketAddress__docs: '/** @param {boolean=} allowNull */',
$getSocketAddress: function(addrp, addrlen, allowNull) {
if (allowNull && addrp === 0) return null;
var info = readSockaddr(addrp, addrlen);
if (info.errno) throw new FS.ErrnoError(info.errno);
info.addr = DNS.lookup_addr(info.addr) || info.addr;
#if SYSCALL_DEBUG
dbg(' (socketaddress: "' + [info.addr, info.port] + '")');
#endif
return info;
},
__syscall_socket__deps: ['$SOCKFS'],
__syscall_socket: function(domain, type, protocol) {
var sock = SOCKFS.createSocket(domain, type, protocol);
#if ASSERTIONS
assert(sock.stream.fd < 64); // XXX ? select() assumes socket fd values are in 0..63
#endif
return sock.stream.fd;
},
__syscall_getsockname__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
__syscall_getsockname: function(fd, addr, addrlen) {
err("__syscall_getsockname " + fd);
var sock = getSocketFromFD(fd);
// TODO: sock.saddr should never be undefined, see TODO in websocket_sock_ops.getname
var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(sock.saddr || '0.0.0.0'), sock.sport, addrlen);
#if ASSERTIONS
assert(!errno);
#endif
return 0;
},
__syscall_getpeername__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
__syscall_getpeername: function(fd, addr, addrlen) {
var sock = getSocketFromFD(fd);
if (!sock.daddr) {
return -{{{ cDefine('ENOTCONN') }}}; // The socket is not connected.
}
var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(sock.daddr), sock.dport, addrlen);
#if ASSERTIONS
assert(!errno);
#endif
return 0;
},
__syscall_connect__deps: ['$getSocketFromFD', '$getSocketAddress'],
__syscall_connect__sig: 'iipi',
__syscall_connect: function(fd, addr, addrlen) {
var sock = getSocketFromFD(fd);
var info = getSocketAddress(addr, addrlen);
sock.sock_ops.connect(sock, info.addr, info.port);
return 0;
},
__syscall_shutdown__deps: ['$getSocketFromFD'],
__syscall_shutdown: function(fd, how) {
getSocketFromFD(fd);
return -{{{ cDefine('ENOSYS') }}}; // unsupported feature
},
__syscall_accept4__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
__syscall_accept4: function(fd, addr, addrlen, flags) {
var sock = getSocketFromFD(fd);
var newsock = sock.sock_ops.accept(sock);
if (addr) {
var errno = writeSockaddr(addr, newsock.family, DNS.lookup_name(newsock.daddr), newsock.dport, addrlen);
#if ASSERTIONS
assert(!errno);
#endif
}
return newsock.stream.fd;
},
__syscall_bind__deps: ['$getSocketFromFD', '$getSocketAddress'],
__syscall_bind__sig: 'iipi',
__syscall_bind: function(fd, addr, addrlen) {
var sock = getSocketFromFD(fd);
var info = getSocketAddress(addr, addrlen);
sock.sock_ops.bind(sock, info.addr, info.port);
return 0;
},
__syscall_listen__deps: ['$getSocketFromFD'],
__syscall_listen: function(fd, backlog) {
var sock = getSocketFromFD(fd);
sock.sock_ops.listen(sock, backlog);
return 0;
},
__syscall_recvfrom__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
__syscall_recvfrom: function(fd, buf, len, flags, addr, addrlen) {
var sock = getSocketFromFD(fd);
var msg = sock.sock_ops.recvmsg(sock, len);
if (!msg) return 0; // socket is closed
if (addr) {
var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(msg.addr), msg.port, addrlen);
#if ASSERTIONS
assert(!errno);
#endif
}
HEAPU8.set(msg.buffer, buf);
return msg.buffer.byteLength;
},
__syscall_sendto__deps: ['$getSocketFromFD', '$getSocketAddress'],
__syscall_sendto__sig: 'iipiipi',
__syscall_sendto: function(fd, message, length, flags, addr, addr_len) {
var sock = getSocketFromFD(fd);
var dest = getSocketAddress(addr, addr_len, true);
if (!dest) {
// send, no address provided
return FS.write(sock.stream, {{{ heapAndOffset('HEAP8', 'message') }}}, length);
}
// sendto an address
return sock.sock_ops.sendmsg(sock, {{{ heapAndOffset('HEAP8', 'message') }}}, length, dest.addr, dest.port);
},
__syscall_getsockopt__deps: ['$getSocketFromFD'],
__syscall_getsockopt: function(fd, level, optname, optval, optlen) {
var sock = getSocketFromFD(fd);
// Minimal getsockopt aimed at resolving https://github.com/emscripten-core/emscripten/issues/2211
// so only supports SOL_SOCKET with SO_ERROR.
if (level === {{{ cDefine('SOL_SOCKET') }}}) {
if (optname === {{{ cDefine('SO_ERROR') }}}) {
{{{ makeSetValue('optval', 0, 'sock.error', 'i32') }}};
{{{ makeSetValue('optlen', 0, 4, 'i32') }}};
sock.error = null; // Clear the error (The SO_ERROR option obtains and then clears this field).
return 0;
}
}
return -{{{ cDefine('ENOPROTOOPT') }}}; // The option is unknown at the level indicated.
},
__syscall_sendmsg__deps: ['$getSocketFromFD', '$readSockaddr', '$DNS'],
__syscall_sendmsg: function(fd, message, flags) {
var sock = getSocketFromFD(fd);
var iov = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iov, '*') }}};
var num = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iovlen, 'i32') }}};
// read the address and port to send to
var addr, port;
var name = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_name, '*') }}};
var namelen = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_namelen, 'i32') }}};
if (name) {
var info = readSockaddr(name, namelen);
if (info.errno) return -info.errno;
port = info.port;
addr = DNS.lookup_addr(info.addr) || info.addr;
}
// concatenate scatter-gather arrays into one message buffer
var total = 0;
for (var i = 0; i < num; i++) {
total += {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_len, 'i32') }}};
}
var view = new Uint8Array(total);
var offset = 0;
for (var i = 0; i < num; i++) {
var iovbase = {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_base, POINTER_TYPE) }}};
var iovlen = {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_len, 'i32') }}};
for (var j = 0; j < iovlen; j++) {
view[offset++] = {{{ makeGetValue('iovbase', 'j', 'i8') }}};
}
}
// write the buffer
return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port);
},
__syscall_recvmsg__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
__syscall_recvmsg: function(fd, message, flags) {
var sock = getSocketFromFD(fd);
var iov = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iov, POINTER_TYPE) }}};
var num = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iovlen, 'i32') }}};
// get the total amount of data we can read across all arrays
var total = 0;
for (var i = 0; i < num; i++) {
total += {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_len, 'i32') }}};
}
// try to read total data
var msg = sock.sock_ops.recvmsg(sock, total);
if (!msg) return 0; // socket is closed
// TODO honor flags:
// MSG_OOB
// Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific.
// MSG_PEEK
// Peeks at the incoming message.
// MSG_WAITALL
// Requests that the function block until the full amount of data requested can be returned. The function may return a smaller amount of data if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket.
// write the source address out
var name = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_name, '*') }}};
if (name) {
var errno = writeSockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port);
#if ASSERTIONS
assert(!errno);
#endif
}
// write the buffer out to the scatter-gather arrays
var bytesRead = 0;
var bytesRemaining = msg.buffer.byteLength;
for (var i = 0; bytesRemaining > 0 && i < num; i++) {
var iovbase = {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_base, POINTER_TYPE) }}};
var iovlen = {{{ makeGetValue('iov', '(' + C_STRUCTS.iovec.__size__ + ' * i) + ' + C_STRUCTS.iovec.iov_len, 'i32') }}};
if (!iovlen) {
continue;
}
var length = Math.min(iovlen, bytesRemaining);
var buf = msg.buffer.subarray(bytesRead, bytesRead + length);
HEAPU8.set(buf, iovbase + bytesRead);
bytesRead += length;
bytesRemaining -= length;
}
// TODO set msghdr.msg_flags
// MSG_EOR
// End of record was received (if supported by the protocol).
// MSG_OOB
// Out-of-band data was received.
// MSG_TRUNC
// Normal data was truncated.
// MSG_CTRUNC
return bytesRead;
},
#endif // ~PROXY_POSIX_SOCKETS==0
__syscall_fchdir: function(fd) {
var stream = SYSCALLS.getStreamFromFD(fd);
FS.chdir(stream.path);
return 0;
},
__syscall__newselect: function(nfds, readfds, writefds, exceptfds, timeout) {
// readfds are supported,
// writefds checks socket open status
// exceptfds not supported
// timeout is always 0 - fully async
#if ASSERTIONS
assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits // TODO: this could be 1024 based on current musl headers
assert(!exceptfds, 'exceptfds not supported');
#endif
var total = 0;
var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0),
srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0);
var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0),
srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0);
var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0),
srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0);
var dstReadLow = 0,
dstReadHigh = 0;
var dstWriteLow = 0,
dstWriteHigh = 0;
var dstExceptLow = 0,
dstExceptHigh = 0;
var allLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0) |
(writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0) |
(exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0);
var allHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0) |
(writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0) |
(exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0);
var check = function(fd, low, high, val) {
return (fd < 32 ? (low & val) : (high & val));
};
for (var fd = 0; fd < nfds; fd++) {
var mask = 1 << (fd % 32);
if (!(check(fd, allLow, allHigh, mask))) {
continue; // index isn't in the set
}
var stream = SYSCALLS.getStreamFromFD(fd);
var flags = SYSCALLS.DEFAULT_POLLMASK;
if (stream.stream_ops.poll) {
flags = stream.stream_ops.poll(stream);
}
if ((flags & {{{ cDefine('POLLIN') }}}) && check(fd, srcReadLow, srcReadHigh, mask)) {
fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask);
total++;
}
if ((flags & {{{ cDefine('POLLOUT') }}}) && check(fd, srcWriteLow, srcWriteHigh, mask)) {
fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask);
total++;
}
if ((flags & {{{ cDefine('POLLPRI') }}}) && check(fd, srcExceptLow, srcExceptHigh, mask)) {
fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask);
total++;
}
}
if (readfds) {
{{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}};
{{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}};
}
if (writefds) {
{{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}};
{{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}};
}
if (exceptfds) {
{{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}};
{{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}};
}
return total;
},
_msync_js__sig: 'ippiiip',
_msync_js: function(addr, len, prot, flags, fd, offset) {
SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, 0);
return 0;
},
__syscall_fdatasync: function(fd) {
var stream = SYSCALLS.getStreamFromFD(fd);
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
},
__syscall_poll__sig: 'ipii',
__syscall_poll: function(fds, nfds, timeout) {
var nonzero = 0;
for (var i = 0; i < nfds; i++) {
var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i;
var fd = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}};
var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
var mask = {{{ cDefine('POLLNVAL') }}};
var stream = FS.getStream(fd);
if (stream) {
mask = SYSCALLS.DEFAULT_POLLMASK;
if (stream.stream_ops.poll) {
mask = stream.stream_ops.poll(stream);
}
}
mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}};
if (mask) nonzero++;
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'mask', 'i16') }}};
}
return nonzero;
},
__syscall_getcwd__sig: 'ipp',
__syscall_getcwd: function(buf, size) {
if (size === 0) return -{{{ cDefine('EINVAL') }}};
var cwd = FS.cwd();
var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1;
if (size < cwdLengthInBytes) return -{{{ cDefine('ERANGE') }}};
stringToUTF8(cwd, buf, size);
return cwdLengthInBytes;
},
__syscall_truncate64__sig: 'ipj',
__syscall_truncate64__deps: i53ConversionDeps,
__syscall_truncate64: function(path, {{{ defineI64Param('length') }}}) {
{{{ receiveI64ParamAsI53('length', -cDefine('EOVERFLOW')) }}}
path = SYSCALLS.getStr(path);
FS.truncate(path, length);
return 0;
},
__syscall_ftruncate64__sig: 'iij',
__syscall_ftruncate64__deps: i53ConversionDeps,
__syscall_ftruncate64: function(fd, {{{ defineI64Param('length') }}}) {
{{{ receiveI64ParamAsI53('length', -cDefine('EOVERFLOW')) }}}
FS.ftruncate(fd, length);
return 0;
},
__syscall_stat64__sig: 'ipp',
__syscall_stat64: function(path, buf) {
path = SYSCALLS.getStr(path);
return SYSCALLS.doStat(FS.stat, path, buf);
},
__syscall_lstat64__sig: 'ipp',
__syscall_lstat64: function(path, buf) {
path = SYSCALLS.getStr(path);
return SYSCALLS.doStat(FS.lstat, path, buf);
},
__syscall_fstat64__sig: 'iip',
__syscall_fstat64: function(fd, buf) {
var stream = SYSCALLS.getStreamFromFD(fd);
return SYSCALLS.doStat(FS.stat, stream.path, buf);
},
__syscall_fchown32: function(fd, owner, group) {
FS.fchown(fd, owner, group);
return 0;
},
__syscall_getdents64__sig: 'iipi',
__syscall_getdents64: function(fd, dirp, count) {
var stream = SYSCALLS.getStreamFromFD(fd)
if (!stream.getdents) {
stream.getdents = FS.readdir(stream.path);
}
var struct_size = {{{ C_STRUCTS.dirent.__size__ }}};
var pos = 0;
var off = FS.llseek(stream, 0, {{{ cDefine('SEEK_CUR') }}});
var idx = Math.floor(off / struct_size);
while (idx < stream.getdents.length && pos + struct_size <= count) {
var id;
var type;
var name = stream.getdents[idx];
if (name === '.') {
id = stream.node.id;
type = 4; // DT_DIR
}
else if (name === '..') {
var lookup = FS.lookupPath(stream.path, { parent: true });
id = lookup.node.id;
type = 4; // DT_DIR
}
else {
var child = FS.lookupNode(stream.node, name);
id = child.id;
type = FS.isChrdev(child.mode) ? 2 : // DT_CHR, character device.
FS.isDir(child.mode) ? 4 : // DT_DIR, directory.
FS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link.
8; // DT_REG, regular file.
}
#if ASSERTIONS
assert(id);
#endif
{{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_ino, 'id', 'i64') }}};
{{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_off, '(idx + 1) * struct_size', 'i64') }}};
{{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_reclen, C_STRUCTS.dirent.__size__, 'i16') }}};
{{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_type, 'type', 'i8') }}};
stringToUTF8(name, dirp + pos + {{{ C_STRUCTS.dirent.d_name }}}, 256);
pos += struct_size;
idx += 1;
}
FS.llseek(stream, idx * struct_size, {{{ cDefine('SEEK_SET') }}});
return pos;
},
__syscall_fcntl64__deps: ['$setErrNo'],
__syscall_fcntl64__sig: 'iiip',
__syscall_fcntl64: function(fd, cmd, varargs) {
#if SYSCALLS_REQUIRE_FILESYSTEM == 0
#if SYSCALL_DEBUG
dbg('no-op in fcntl syscall due to SYSCALLS_REQUIRE_FILESYSTEM=0');
#endif
return 0;
#else
var stream = SYSCALLS.getStreamFromFD(fd);
switch (cmd) {
case {{{ cDefine('F_DUPFD') }}}: {
var arg = SYSCALLS.get();
if (arg < 0) {
return -{{{ cDefine('EINVAL') }}};
}
var newStream;
newStream = FS.createStream(stream, arg);
return newStream.fd;
}
case {{{ cDefine('F_GETFD') }}}:
case {{{ cDefine('F_SETFD') }}}:
return 0; // FD_CLOEXEC makes no sense for a single process.
case {{{ cDefine('F_GETFL') }}}:
return stream.flags;
case {{{ cDefine('F_SETFL') }}}: {
var arg = SYSCALLS.get();
stream.flags |= arg;
return 0;
}
case {{{ cDefine('F_GETLK') }}}:
/* case {{{ cDefine('F_GETLK64') }}}: Currently in musl F_GETLK64 has same value as F_GETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ {
{{{ assert(cDefine('F_GETLK') === cDefine('F_GETLK64')), '' }}}
var arg = SYSCALLS.get();
var offset = {{{ C_STRUCTS.flock.l_type }}};
// We're always unlocked.
{{{ makeSetValue('arg', 'offset', cDefine('F_UNLCK'), 'i16') }}};
return 0;
}
case {{{ cDefine('F_SETLK') }}}:
case {{{ cDefine('F_SETLKW') }}}:
/* case {{{ cDefine('F_SETLK64') }}}: Currently in musl F_SETLK64 has same value as F_SETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */
/* case {{{ cDefine('F_SETLKW64') }}}: Currently in musl F_SETLKW64 has same value as F_SETLKW, so omitted to avoid duplicate case blocks. If that changes, uncomment this */
{{{ assert(cDefine('F_SETLK64') === cDefine('F_SETLK')), '' }}}
{{{ assert(cDefine('F_SETLKW64') === cDefine('F_SETLKW')), '' }}}
return 0; // Pretend that the locking is successful.
case {{{ cDefine('F_GETOWN_EX') }}}:
case {{{ cDefine('F_SETOWN') }}}:
return -{{{ cDefine('EINVAL') }}}; // These are for sockets. We don't have them fully implemented yet.
case {{{ cDefine('F_GETOWN') }}}:
// musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fcntl() returns that, and we set errno ourselves.
setErrNo({{{ cDefine('EINVAL') }}});
return -1;
default: {
#if SYSCALL_DEBUG
dbg('warning: fcntl unrecognized command ' + cmd);
#endif
return -{{{ cDefine('EINVAL') }}};
}
}
#endif // SYSCALLS_REQUIRE_FILESYSTEM
},
__syscall_statfs64__sig: 'ippp',
__syscall_statfs64: function(path, size, buf) {
path = SYSCALLS.getStr(path);
#if ASSERTIONS
assert(size === {{{ C_STRUCTS.statfs.__size__ }}});
#endif
// NOTE: None of the constants here are true. We're just returning safe and
// sane values.
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, '4096', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, '4096', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, '1000000', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, '500000', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, '500000', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'FS.nextInode', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, '1000000', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, '42', 'i32') }}};
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, '2', 'i32') }}}; // ST_NOSUID
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, '255', 'i32') }}};
return 0;
},
__syscall_fstatfs64__deps: ['__syscall_statfs64'],
__syscall_fstatfs64: function(fd, size, buf) {
var stream = SYSCALLS.getStreamFromFD(fd);
return ___syscall_statfs64(0, size, buf);
},
__syscall_fadvise64__nothrow: true,
__syscall_fadvise64__proxy: false,
__syscall_fadvise64: function(fd, offset, len, advice) {
return 0; // your advice is important to us (but we can't use it)
},
__syscall_openat__sig: 'iipip',
__syscall_openat: function(dirfd, path, flags, varargs) {
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
var mode = varargs ? SYSCALLS.get() : 0;
return FS.open(path, flags, mode).fd;
},
__syscall_mkdirat__sig: 'iipi',
__syscall_mkdirat: function(dirfd, path, mode) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
// remove a trailing slash, if one - /a/b/ has basename of '', but
// we want to create b in the context of this function
path = PATH.normalize(path);
if (path[path.length-1] === '/') path = path.substr(0, path.length-1);
FS.mkdir(path, mode, 0);
return 0;
},
__syscall_mknodat__sig: 'iipii',
__syscall_mknodat: function(dirfd, path, mode, dev) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
// we don't want this in the JS API as it uses mknod to create all nodes.
switch (mode & {{{ cDefine('S_IFMT') }}}) {
case {{{ cDefine('S_IFREG') }}}:
case {{{ cDefine('S_IFCHR') }}}:
case {{{ cDefine('S_IFBLK') }}}:
case {{{ cDefine('S_IFIFO') }}}:
case {{{ cDefine('S_IFSOCK') }}}:
break;
default: return -{{{ cDefine('EINVAL') }}};
}
FS.mknod(path, mode, dev);
return 0;
},
__syscall_fchownat__sig: 'iipiii',
__syscall_fchownat: function(dirfd, path, owner, group, flags) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
path = SYSCALLS.getStr(path);
var nofollow = flags & {{{ cDefine('AT_SYMLINK_NOFOLLOW') }}};
flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') }}});
#if ASSERTIONS
assert(flags === 0);
#endif
path = SYSCALLS.calculateAt(dirfd, path);
(nofollow ? FS.lchown : FS.chown)(path, owner, group);
return 0;
},
__syscall_newfstatat__sig: 'iippi',
__syscall_newfstatat: function(dirfd, path, buf, flags) {
path = SYSCALLS.getStr(path);
var nofollow = flags & {{{ cDefine('AT_SYMLINK_NOFOLLOW') }}};
var allowEmpty = flags & {{{ cDefine('AT_EMPTY_PATH') }}};
flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') | cDefine('AT_EMPTY_PATH') | cDefine('AT_NO_AUTOMOUNT') }}});
#if ASSERTIONS
assert(!flags, 'unknown flags in __syscall_newfstatat: ' + flags);
#endif
path = SYSCALLS.calculateAt(dirfd, path, allowEmpty);
return SYSCALLS.doStat(nofollow ? FS.lstat : FS.stat, path, buf);
},
__syscall_unlinkat__sig: 'iipi',
__syscall_unlinkat: function(dirfd, path, flags) {
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
if (flags === 0) {
FS.unlink(path);
} else if (flags === {{{ cDefine('AT_REMOVEDIR') }}}) {
FS.rmdir(path);
} else {
abort('Invalid flags passed to unlinkat');
}
return 0;
},
__syscall_renameat__sig: 'iipip',
__syscall_renameat: function(olddirfd, oldpath, newdirfd, newpath) {
oldpath = SYSCALLS.getStr(oldpath);
newpath = SYSCALLS.getStr(newpath);
oldpath = SYSCALLS.calculateAt(olddirfd, oldpath);
newpath = SYSCALLS.calculateAt(newdirfd, newpath);
FS.rename(oldpath, newpath);
return 0;
},
__syscall_linkat__nothrow: true,
__syscall_linkat__proxy: false,
__syscall_linkat: function(olddirfd, oldpath, newdirfd, newpath, flags) {
return -{{{ cDefine('EMLINK') }}}; // no hardlinks for us
},
__syscall_symlinkat: function(target, newdirfd, linkpath) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
linkpath = SYSCALLS.calculateAt(newdirfd, linkpath);
FS.symlink(target, linkpath);
return 0;
},
__syscall_readlinkat__sig: 'vippp',
__syscall_readlinkat: function(dirfd, path, buf, bufsize) {
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
if (bufsize <= 0) return -{{{ cDefine('EINVAL') }}};
var ret = FS.readlink(path);
var len = Math.min(bufsize, lengthBytesUTF8(ret));
var endChar = HEAP8[buf+len];
stringToUTF8(ret, buf, bufsize+1);
// readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!)
// stringToUTF8() always appends a null byte, so restore the character under the null byte after the write.
HEAP8[buf+len] = endChar;
return len;
},
__syscall_fchmodat__sig: 'iipip',
__syscall_fchmodat: function(dirfd, path, mode, varargs) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
path = SYSCALLS.getStr(path);
path = SYSCALLS.calculateAt(dirfd, path);
FS.chmod(path, mode);
return 0;
},
__syscall_faccessat__sig: 'iipii',
__syscall_faccessat: function(dirfd, path, amode, flags) {
#if SYSCALL_DEBUG
dbg('warning: untested syscall');
#endif
path = SYSCALLS.getStr(path);
#if ASSERTIONS
assert(flags === 0);
#endif
path = SYSCALLS.calculateAt(dirfd, path);
if (amode & ~{{{ cDefine('S_IRWXO') }}}) {
// need a valid mode
return -{{{ cDefine('EINVAL') }}};
}
var lookup = FS.lookupPath(path, { follow: true });
var node = lookup.node;
if (!node) {
return -{{{ cDefine('ENOENT') }}};
}
var perms = '';
if (amode & {{{ cDefine('R_OK') }}}) perms += 'r';
if (amode & {{{ cDefine('W_OK') }}}) perms += 'w';
if (amode & {{{ cDefine('X_OK') }}}) perms += 'x';
if (perms /* otherwise, they've just passed F_OK */ && FS.nodePermissions(node, perms)) {
return -{{{ cDefine('EACCES') }}};
}
return 0;
},
__syscall_utimensat__sig: 'iippi',
__syscall_utimensat__deps: ['$readI53FromI64'],
__syscall_utimensat: function(dirfd, path, times, flags) {
path = SYSCALLS.getStr(path);
#if ASSERTIONS
assert(flags === 0);
#endif
path = SYSCALLS.calculateAt(dirfd, path, true);
if (!times) {
var atime = Date.now();
var mtime = atime;
} else {
var seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
var nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
atime = (seconds*1000) + (nanoseconds/(1000*1000));
times += {{{ C_STRUCTS.timespec.__size__ }}};
seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
mtime = (seconds*1000) + (nanoseconds/(1000*1000));
}
FS.utime(path, atime, mtime);
return 0;
},
__syscall_fallocate__deps: i53ConversionDeps,
__syscall_fallocate: function(fd, mode, {{{ defineI64Param('offset') }}}, {{{ defineI64Param('len') }}}) {
{{{ receiveI64ParamAsI53('offset', -cDefine('EOVERFLOW')) }}}
{{{ receiveI64ParamAsI53('len', -cDefine('EOVERFLOW')) }}}
var stream = SYSCALLS.getStreamFromFD(fd)
#if ASSERTIONS
assert(mode === 0);
#endif
FS.allocate(stream, offset, len);
return 0;
},
__syscall_dup3: function(fd, suggestFD, flags) {
var old = SYSCALLS.getStreamFromFD(fd);
#if ASSERTIONS
assert(!flags);
#endif
if (old.fd === suggestFD) return -{{{ cDefine('EINVAL') }}};
var suggest = FS.getStream(suggestFD);
if (suggest) FS.close(suggest);
return FS.createStream(old, suggestFD, suggestFD + 1).fd;
},
};
function wrapSyscallFunction(x, library, isWasi) {
if (x[0] === '$' || isJsLibraryConfigIdentifier(x)) {
return;
}
var t = library[x];
if (typeof t == 'string') return;
t = t.toString();
// If a syscall uses FS, but !SYSCALLS_REQUIRE_FILESYSTEM, then the user
// has disabled the filesystem or we have proven some other way that this will
// not be called in practice, and do not need that code.
if (!SYSCALLS_REQUIRE_FILESYSTEM && t.includes('FS.')) {
t = modifyFunction(t, function(name, args, body) {
return 'function ' + name + '(' + args + ') {\n' +
(ASSERTIONS ? "abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM');\n" : '') +
'}';
});
}
var isVariadic = !isWasi && t.includes(', varargs');
#if SYSCALLS_REQUIRE_FILESYSTEM == 0
var canThrow = false;
#else
var canThrow = library[x + '__nothrow'] !== true;
#endif
var pre = '', post = '';
if (isVariadic) {
pre += 'SYSCALLS.varargs = varargs;\n';
}
#if SYSCALL_DEBUG
if (isVariadic) {
if (canThrow) {
post += 'finally { SYSCALLS.varargs = undefined; }\n';
} else {
post += 'SYSCALLS.varargs = undefined;\n';
}
}
pre += "dbg('syscall! " + x + ": [' + Array.prototype.slice.call(arguments) + ']');\n";
pre += "var canWarn = true;\n";
pre += "var ret = (function() {\n";
post += "})();\n";
post += "if (ret && ret < 0 && canWarn) {\n";
post += " dbg('error: syscall may have failed with ' + (-ret) + ' (' + ERRNO_MESSAGES[-ret] + ')');\n";
post += "}\n";
post += "dbg('syscall return: ' + ret);\n";
post += "return ret;\n";
#endif
delete library[x + '__nothrow'];
var handler = '';
if (canThrow) {
pre += 'try {\n';
handler +=
"} catch (e) {\n" +
" if (typeof FS == 'undefined' || !(e instanceof FS.ErrnoError)) throw e;\n";
#if SYSCALL_DEBUG
handler +=
" dbg('error: syscall failed with ' + e.errno + ' (' + ERRNO_MESSAGES[e.errno] + ')');\n" +
" canWarn = false;\n";
#endif
// Musl syscalls are negated.
if (isWasi) {
handler += " return e.errno;\n";
} else {
// Musl syscalls are negated.
handler += " return -e.errno;\n";
}
handler += "}\n";
}
post = handler + post;
if (pre || post) {
t = modifyFunction(t, function(name, args, body) {
return `function ${name}(${args}) {\n${pre}${body}${post}}\n`;
});
}
library[x] = eval('(' + t + ')');
if (!library[x + '__deps']) library[x + '__deps'] = [];
library[x + '__deps'].push('$SYSCALLS');
#if USE_PTHREADS
// Most syscalls need to happen on the main JS thread (e.g. because the
// filesystem is in JS and on that thread). Proxy synchronously to there.
// There are some exceptions, syscalls that we know are ok to just run in
// any thread; those are marked as not being proxied with
// __proxy: false
// A syscall without a return value could perhaps be proxied asynchronously
// instead of synchronously, and marked with
// __proxy: 'async'
// (but essentially all syscalls do have return values).
if (library[x + '__proxy'] === undefined) {
library[x + '__proxy'] = 'sync';
}
#endif
}
for (var x in SyscallsLibrary) {
wrapSyscallFunction(x, SyscallsLibrary, false);
}
mergeInto(LibraryManager.library, SyscallsLibrary);