diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | src/library.js | 764 | ||||
-rw-r--r-- | src/library_fs.js | 12 | ||||
-rw-r--r-- | src/library_sockfs.js | 554 | ||||
-rw-r--r-- | src/settings.js | 1 | ||||
-rwxr-xr-x | tests/runner.py | 4 | ||||
-rw-r--r-- | tests/sockets/test_sockets_msg.h | 1 | ||||
-rw-r--r-- | tests/sockets/test_sockets_select_server_down_client.c | 2 | ||||
-rw-r--r-- | tests/test_sockets.py | 110 |
10 files changed, 1043 insertions, 412 deletions
@@ -4,6 +4,8 @@ *.bc src/relooper*.js +node_modules/ + # Ignore generated files src/relooper.js src/relooper.js.raw.js diff --git a/package.json b/package.json new file mode 100644 index 00000000..157885fb --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ws": "~0.4.28" + } +} diff --git a/src/library.js b/src/library.js index b5542609..307df9c6 100644 --- a/src/library.js +++ b/src/library.js @@ -600,31 +600,32 @@ LibraryManager.library = { // poll.h // ========================================================================== + __DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}}, __pollfd_struct_layout: Runtime.generateStructInfo([ ['i32', 'fd'], ['i16', 'events'], ['i16', 'revents']]), - poll__deps: ['$FS', '__pollfd_struct_layout'], + poll__deps: ['$FS', '__DEFAULT_POLLMASK', '__pollfd_struct_layout'], poll: function(fds, nfds, timeout) { // int poll(struct pollfd fds[], nfds_t nfds, int timeout); // http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html - // NOTE: This is pretty much a no-op mimicking glibc. var offsets = ___pollfd_struct_layout; var nonzero = 0; for (var i = 0; i < nfds; i++) { var pollfd = fds + ___pollfd_struct_layout.__size__ * i; var fd = {{{ makeGetValue('pollfd', 'offsets.fd', 'i32') }}}; var events = {{{ makeGetValue('pollfd', 'offsets.events', 'i16') }}}; - var revents = 0; + var mask = {{{ cDefine('POLLNVAL') }}}; var stream = FS.getStream(fd); if (stream) { - if (events & {{{ cDefine('POLLIN') }}}) revents |= {{{ cDefine('POLLIN') }}}; - if (events & {{{ cDefine('POLLOUT') }}}) revents |= {{{ cDefine('POLLOUT') }}}; - } else { - if (events & {{{ cDefine('POLLNVAL') }}}) revents |= {{{ cDefine('POLLNVAL') }}}; + mask = ___DEFAULT_POLLMASK; + if (stream.stream_ops.poll) { + mask = stream.stream_ops.poll(stream); + } } - if (revents) nonzero++; - {{{ makeSetValue('pollfd', 'offsets.revents', 'revents', 'i16') }}} + mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) nonzero++; + {{{ makeSetValue('pollfd', 'offsets.revents', 'mask', 'i16') }}} } return nonzero; }, @@ -723,7 +724,7 @@ LibraryManager.library = { FS.close(stream); return 0; } catch (e) { - FS.handleFSError(e);; + FS.handleFSError(e); return -1; } }, @@ -1006,9 +1007,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _recv(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -1139,9 +1142,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _send(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -7537,7 +7542,7 @@ LibraryManager.library = { ['i32', 'sin6_family'], ['i16', 'sin6_port'], ['i32', 'sin6_flowinfo'], - ['b128', 'sin6_addr'], + ['b16', 'sin6_addr'], ['i32', 'sin6_scope_id'] ]), msghdr_layout: Runtime.generateStructInfo([ @@ -7549,6 +7554,10 @@ LibraryManager.library = { ['i32', 'msg_controllen'], ['i32', 'msg_flags'], ]), + iovec_layout: Runtime.generateStructInfo([ + ['i8*', 'iov_base'], + ['i32', 'iov_len'] + ]) }, #if SOCKET_WEBRTC @@ -7994,424 +8003,471 @@ LibraryManager.library = { return {}; }, - socket__deps: ['$FS', '$Sockets'], + socket__deps: ['$FS', '$SOCKFS'], socket: function(family, type, protocol) { - var stream = type == {{{ cDefine('SOCK_STREAM') }}}; - if (protocol) { - assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp + var sock = SOCKFS.createSocket(family, type, protocol); + assert(sock.stream.fd < 64); // select() assumes socket fd values are in 0..63 + return sock.stream.fd; + }, + + socketpair__deps: ['$ERRNO_CODES', '__setErrNo'], + socketpair: function(domain, type, protocol, sv) { + // int socketpair(int domain, int type, int protocol, int sv[2]); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html + ___setErrNo(ERRNO_CODES.EOPNOTSUPP); + return -1; + }, + + shutdown__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + shutdown: function(fd, how) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var stream = FS.createStream({ - connected: false, - stream: stream, - socket: true, - stream_ops: {} - }); - assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 - return stream.fd; + _close(fd); }, - connect__deps: ['$FS', '$Sockets', '_inet_ntop4_raw', 'ntohs', '_lookup_addr'], - connect: function(fd, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - // TODO support ipv6 - info.connected = true; - info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); - info.port = _htons(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); - info.host = __inet_ntop4_raw(info.addr); - // The incoming address could perhaps be a fake address generated by gethostbyname, - // look it up to be sure. - var lookup = __lookup_addr(info.host); - if (lookup) { - info.host = lookup; + bind__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_addr', '_read_sockaddr'], + bind: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - try { - console.log('opening ws://' + info.host + ':' + info.port); - info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']); - info.socket.binaryType = 'arraybuffer'; - var i32Temp = new Uint32Array(1); - var i8Temp = new Uint8Array(i32Temp.buffer); + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = __lookup_addr(info.addr) || info.addr; - info.inQueue = []; - info.hasData = function() { return info.inQueue.length > 0 } - if (!info.stream) { - var partialBuffer = null; // in datagram mode, inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message - } + try { + sock.sock_ops.bind(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } + }, - info.socket.onmessage = function(event) { - assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data! - var data = new Uint8Array(event.data); // make a typed array view on the array buffer -#if SOCKET_DEBUG - Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]); -#endif - if (info.stream) { - info.inQueue.push(data); - } else { - // we added headers with message sizes, read those to find discrete messages - if (partialBuffer) { - // append to the partial buffer - var newBuffer = new Uint8Array(partialBuffer.length + data.length); - newBuffer.set(partialBuffer); - newBuffer.set(data, partialBuffer.length); - // forget the partial buffer and work on data - data = newBuffer; - partialBuffer = null; - } - var currPos = 0; - while (currPos+4 < data.length) { - i8Temp.set(data.subarray(currPos, currPos+4)); - var currLen = i32Temp[0]; - assert(currLen > 0); - if (currPos + 4 + currLen > data.length) { - break; // not enough data has arrived - } - currPos += 4; -#if SOCKET_DEBUG - Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]); -#endif - info.inQueue.push(data.subarray(currPos, currPos+currLen)); - currPos += currLen; - } - // If data remains, buffer it - if (currPos < data.length) { - partialBuffer = data.subarray(currPos); - } - } - } - function send(data) { - // TODO: if browser accepts views, can optimize this -#if SOCKET_DEBUG - Module.print('sender actually sending ' + Array.prototype.slice.call(data)); -#endif - // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning - info.socket.send(data.buffer); - } - var outQueue = []; - var intervalling = false, interval; - function trySend() { - if (info.socket.readyState != info.socket.OPEN) { - if (!intervalling) { - intervalling = true; - console.log('waiting for socket in order to send'); - interval = setInterval(trySend, 100); - } - return; - } - for (var i = 0; i < outQueue.length; i++) { - send(outQueue[i]); - } - outQueue.length = 0; - if (intervalling) { - intervalling = false; - clearInterval(interval); - } - } - info.sender = function(data) { - if (!info.stream) { - // add a header with the message size - var header = new Uint8Array(4); - i32Temp[0] = data.length; - header.set(i8Temp); - outQueue.push(header); - } - outQueue.push(new Uint8Array(data)); - trySend(); - }; - } catch(e) { - Module.printErr('Error in connect(): ' + e); - ___setErrNo(ERRNO_CODES.EACCES); + connect__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_addr', '_read_sockaddr'], + connect: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - // always "fail" in non-blocking mode - ___setErrNo(ERRNO_CODES.EINPROGRESS); - return -1; + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = __lookup_addr(info.addr) || info.addr; + + try { + sock.sock_ops.connect(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - recv__deps: ['$FS'], - recv: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + listen__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + listen: function(fd, backlog) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (!info.hasData()) { - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - // socket has closed - return 0; - } else { - // else, our socket is in a valid state but truly has nothing available - ___setErrNo(ERRNO_CODES.EAGAIN); - return -1; - } + try { + sock.sock_ops.listen(sock, backlog); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } -#endif - var buffer = info.inQueue.shift(); -#if SOCKET_DEBUG - Module.print('recv: ' + [Array.prototype.slice.call(buffer)]); -#endif - if (len < buffer.length) { - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - info.inQueue.unshift(buffer.subarray(len)); -#if SOCKET_DEBUG - Module.print('recv: put back: ' + (len - buffer.length)); -#endif + }, + + accept__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', 'socket'], + accept: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + var newfd = _socket(sock.family, sock.type, sock.protocol); + var newsock = SOCKFS.getSocket(newfd); + assert(newsock); + try { + sock.sock_ops.accept(sock, newsock, sock.stream.flags); + if (addrp) { + var res = __write_sockaddr(addr, newsock.family, __lookup_name(newsock.daddr), newsock.dport); + assert(!res.errno); } - buffer = buffer.subarray(0, len); + return newfd; + } catch (e) { + FS.handleFSError(e); + return -1; } - HEAPU8.set(buffer, buf); - return buffer.length; }, - send__deps: ['$FS'], - send: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + getsockname__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_lookup_name', '_inet_pton_raw'], + getsockname: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - ___setErrNo(ERRNO_CODES.ENOTCONN); - return -1; - } else if (info.socket.readyState === WebSocket.CONNECTING) { - ___setErrNo(ERRNO_CODES.EAGAIN); + try { + var info = sock.sock_ops.getname(sock); + var res = __write_sockaddr(addr, sock.family, __lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); return -1; } -#endif - info.sender(HEAPU8.subarray(buf, buf+len)); - return len; }, - sendmsg__deps: ['$FS', '$Sockets', 'connect'], - sendmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + getpeername__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_lookup_name', '_inet_pton_raw'], + getpeername: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg vecs: ' + num); -#endif - var totalSize = 0; - for (var i = 0; i < num; i++) { - totalSize += {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; + try { + var info = sock.sock_ops.getname(sock, true); + var res = __write_sockaddr(addr, sock.family, __lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } - var buffer = new Uint8Array(totalSize); - var ret = 0; - for (var i = 0; i < num; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg curr size: ' + currNum); -#endif - if (!currNum) continue; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; - buffer.set(HEAPU8.subarray(currBuf, currBuf+currNum), ret); - ret += currNum; + }, + + send__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'write'], + send: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - info.sender(buffer); // send all the iovs as a single message - return ret; + // TODO honor flags + return _write(fd, buf, len); }, - recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'], - recvmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { -#if SOCKET_DEBUG - Module.print('recvmsg connecting'); -#endif - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + recv__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'read'], + recv: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - if (!info.hasData()) { - ___setErrNo(ERRNO_CODES.EWOULDBLOCK); + // TODO honor flags + return _read(fd, buf, len); + }, + + sendto__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_addr', '_read_sockaddr'], + sendto: function(fd, message, length, flags, dest_addr, dest_len) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var buffer = info.inQueue.shift(); - var bytes = buffer.length; -#if SOCKET_DEBUG - Module.print('recvmsg bytes: ' + bytes); -#endif - // write source - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_addr', 'info.addr', 'i32') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_port', '_htons(info.port)', 'i16') }}}; - // write data - var ret = bytes; - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; - var bufferPos = 0; - for (var i = 0; i < num && bytes > 0; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg loop ' + [i, num, bytes, currNum]); -#endif - if (!currNum) continue; - currNum = Math.min(currNum, bytes); // XXX what should happen when we partially fill a buffer..? - bytes -= currNum; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg call recv ' + currNum); -#endif - HEAPU8.set(buffer.subarray(bufferPos, bufferPos + currNum), currBuf); - bufferPos += currNum; + + // read the address and port to send to + var info = __read_sockaddr(dest_addr, dest_len); + if (info.errno) { + ___setErrNo(info.errno); + return -1; } - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - if (bufferPos < bytes) { - info.inQueue.unshift(buffer.subarray(bufferPos)); -#if SOCKET_DEBUG - Module.print('recvmsg: put back: ' + (bytes - bufferPos)); -#endif - } + var port = info.port; + var addr = __lookup_addr(info.addr) || info.addr; + + // send the message + try { + var slab = {{{ makeGetSlabs('message', 'i8', true) }}}; + return sock.sock_ops.sendmsg(sock, slab, message, length, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; } - return ret; }, - recvfrom__deps: ['$FS', 'connect', 'recv'], + recvfrom__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_name', '_write_sockaddr'], recvfrom: function(fd, buf, len, flags, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - //var name = {{{ makeGetValue('addr', '0', '*') }}}; - _connect(fd, addr, addrlen); + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - return _recv(fd, buf, len, flags); - }, - shutdown__deps: ['$FS'], - shutdown: function(fd, how) { - var stream = FS.getStream(fd); - if (!stream) return -1; - stream.socket.close(); - FS.closeStream(stream); - }, + // read from the socket + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, len); + } catch (e) { + FS.handleFSError(e); + return -1; + } - ioctl__deps: ['$FS'], - ioctl: function(fd, request, varargs) { - var info = FS.getStream(fd); - if (!info) return -1; - var bytes = 0; - if (info.hasData()) { - bytes = info.inQueue[0].length; + if (!msg) { + // socket is closed + return 0; } - var dest = {{{ makeGetValue('varargs', '0', 'i32') }}}; - {{{ makeSetValue('dest', '0', 'bytes', 'i32') }}}; - return 0; + + // write the source address out + if (addr) { + var res = __write_sockaddr(addr, sock.family, __lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // write the buffer out + HEAPU8.set(msg.buffer, buf); + + return msg.buffer.byteLength; }, - setsockopt: function(d, level, optname, optval, optlen) { - console.log('ignoring setsockopt command'); - return 0; + sendmsg__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_addr', '_read_sockaddr'], + sendmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', '*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + + // read the address and port to send to + var addr; + var port; + var name = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_name', '*') }}}; + var namelen = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}; + if (name) { + var info = __read_sockaddr(name, namelen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + port = info.port; + addr = __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', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + var view = new Uint8Array(total); + var offset = 0; + for (var i = 0; i < num; i++) { + var iovbase = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + for (var j = 0; j < iovlen; j++) { + view[offset++] = {{{ makeGetValue('iovbase', 'j', 'i8') }}}; + } + } + + // write the buffer + try { + return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - bind__deps: ['connect'], - bind: function(fd, addr, addrlen) { - _connect(fd, addr, addrlen); - return 0; + recvmsg__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo', '_lookup_name', '_inet_pton_raw', '_write_sockaddr'], + recvmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.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', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + + // try to read total data + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, total); + } catch (e) { + FS.handleFSError(e); + return -1; + } + + if (!msg) { + // socket is closed + return 0; + } + + // 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', 'Sockets.msghdr_layout.msg_name', '*') }}}; + if (name) { + var res = __write_sockaddr(name, sock.family, __lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // 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', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.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; }, - listen: function(fd, backlog) { + setsockopt: function(fd, level, optname, optval, optlen) { + console.log('ignoring setsockopt command'); return 0; }, - accept__deps: ['$FS', '$Sockets'], - accept: function(fd, addr, addrlen) { - // TODO: webrtc queued incoming connections, etc. - // For now, the model is that bind does a connect, and we "accept" that one connection, - // which has host:port the same as ours. We also return the same socket fd. - var info = FS.getStream(fd); - if (!info) return -1; - if (addr) { - setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); - setValue(addr + Sockets.sockaddr_in_layout.sin_port, info.port, 'i32'); - setValue(addrlen, Sockets.sockaddr_in_layout.__size__, 'i32'); - } - return fd; - }, + // ========================================================================== + // select.h + // ========================================================================== - select__deps: ['$FS'], + select__deps: ['$FS', '__DEFAULT_POLLMASK'], select: function(nfds, readfds, writefds, exceptfds, timeout) { // readfds are supported, // writefds checks socket open status // exceptfds not supported // timeout is always 0 - fully async - assert(!exceptfds); - - var errorCondition = 0; + assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits + assert(!exceptfds, 'exceptfds not supported'); - function canRead(info) { - return (info.hasData && info.hasData()) || - info.socket.readyState == WebSocket.CLOSING || // let recv return 0 once closed - info.socket.readyState == WebSocket.CLOSED; - } + 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); + + function get(fd, low, high, val) { + return (fd < 32 ? (low & val) : (high & val)); + } + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32); + if (!(get(fd, allLow, allHigh, mask))) { + continue; // index isn't in the set + } - function canWrite(info) { - return info.socket && (info.socket.readyState == info.socket.OPEN); - } + var stream = FS.getStream(fd); + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } - function checkfds(nfds, fds, can) { - if (!fds) return 0; + var flags = ___DEFAULT_POLLMASK; - var bitsSet = 0; - var dstLow = 0; - var dstHigh = 0; - var srcLow = {{{ makeGetValue('fds', 0, 'i32') }}}; - var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}}; - nfds = Math.min(64, nfds); // fd sets have 64 bits + if (stream.stream_ops.poll) { + flags = stream.stream_ops.poll(stream); + } - for (var fd = 0; fd < nfds; fd++) { - var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh; - if (int_ & mask) { - // index is in the set, check if it is ready for read - var info = FS.getStream(fd); - if (!info) { - ___setErrNo(ERRNO_CODES.EBADF); - return -1; - } - if (can(info)) { - // set bit - fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); - bitsSet++; - } - } + if ((flags & {{{ cDefine('POLLIN') }}}) && get(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLOUT') }}}) && get(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask); + total++; } + if ((flags & {{{ cDefine('POLLPRI') }}}) && get(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } - {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}}; - {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}}; - return bitsSet; + 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; + }, - var totalHandles = checkfds(nfds, readfds, canRead) + checkfds(nfds, writefds, canWrite); - if (errorCondition) { + // ========================================================================== + // sys/ioctl.h + // ========================================================================== + + ioctl__deps: ['$FS'], + ioctl: function(fd, request, varargs) { + var stream = FS.getStream(fd); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - return totalHandles; } + var arg = {{{ makeGetValue('varargs', '0', 'i32') }}}; + return FS.ioctl(stream, request, arg); }, #endif - socketpair__deps: ['__setErrNo', '$ERRNO_CODES'], - socketpair: function(domain, type, protocol, sv) { - // int socketpair(int domain, int type, int protocol, int sv[2]); - // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html - ___setErrNo(ERRNO_CODES.EOPNOTSUPP); - return -1; - }, - // pty.h openpty: function() { throw 'openpty: TODO' }, diff --git a/src/library_fs.js b/src/library_fs.js index 63ad7c8d..77066059 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -159,6 +159,9 @@ mergeInto(LibraryManager.library, { isFIFO: function(mode) { return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, // // paths @@ -400,6 +403,9 @@ mergeInto(LibraryManager.library, { getStream: function(fd) { return FS.streams[fd]; }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. createStream: function(stream, fd_start, fd_end) { var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; @@ -1459,6 +1465,12 @@ mergeInto(LibraryManager.library, { throw new FS.errnoError(ERRNO_CODES.ENODEV); } return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags); + }, + ioctl: function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); + } + return stream.stream_ops.ioctl(stream, cmd, arg); } } }); diff --git a/src/library_sockfs.js b/src/library_sockfs.js index 13118b71..0736224d 100644 --- a/src/library_sockfs.js +++ b/src/library_so |