| /* |
| * Example debug transport using a Linux/Unix TCP socket |
| * |
| * Provides a TCP server socket which a debug client can connect to. |
| * After that data is just passed through. |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <poll.h> |
| #include <errno.h> |
| #include "duktape.h" |
| |
| #if !defined(DUK_DEBUG_PORT) |
| #define DUK_DEBUG_PORT 9091 |
| #endif |
| |
| #if 0 |
| #define DEBUG_PRINTS |
| #endif |
| |
| static int server_sock = -1; |
| static int client_sock = -1; |
| |
| /* |
| * Transport init and finish |
| */ |
| |
| void duk_trans_socket_init(void) { |
| struct sockaddr_in addr; |
| int on; |
| |
| server_sock = socket(AF_INET, SOCK_STREAM, 0); |
| if (server_sock < 0) { |
| fprintf(stderr, "%s: failed to create server socket: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| on = 1; |
| if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0) { |
| fprintf(stderr, "%s: failed to set SO_REUSEADDR for server socket: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| memset((void *) &addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = INADDR_ANY; |
| addr.sin_port = htons(DUK_DEBUG_PORT); |
| |
| if (bind(server_sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| fprintf(stderr, "%s: failed to bind server socket: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| listen(server_sock, 1 /*backlog*/); |
| return; |
| |
| fail: |
| if (server_sock >= 0) { |
| (void) close(server_sock); |
| server_sock = -1; |
| } |
| } |
| |
| void duk_trans_socket_finish(void) { |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| if (server_sock >= 0) { |
| (void) close(server_sock); |
| server_sock = -1; |
| } |
| } |
| |
| void duk_trans_socket_waitconn(void) { |
| struct sockaddr_in addr; |
| socklen_t sz; |
| |
| if (server_sock < 0) { |
| fprintf(stderr, "%s: no server socket, skip waiting for connection\n", |
| __FILE__); |
| fflush(stderr); |
| return; |
| } |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| |
| fprintf(stderr, "Waiting for debug connection on port %d\n", (int) DUK_DEBUG_PORT); |
| fflush(stderr); |
| |
| sz = (socklen_t) sizeof(addr); |
| client_sock = accept(server_sock, (struct sockaddr *) &addr, &sz); |
| if (client_sock < 0) { |
| fprintf(stderr, "%s: accept() failed, skip waiting for connection: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| fprintf(stderr, "Debug connection established\n"); |
| fflush(stderr); |
| |
| /* XXX: For now, close the listen socket because we won't accept new |
| * connections anyway. A better implementation would allow multiple |
| * debug attaches. |
| */ |
| |
| if (server_sock >= 0) { |
| (void) close(server_sock); |
| server_sock = -1; |
| } |
| return; |
| |
| fail: |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| } |
| |
| /* |
| * Duktape callbacks |
| */ |
| |
| /* Duktape debug transport callback: (possibly partial) read. */ |
| duk_size_t duk_trans_socket_read_cb(void *udata, char *buffer, duk_size_t length) { |
| ssize_t ret; |
| |
| (void) udata; /* not needed by the example */ |
| |
| #if defined(DEBUG_PRINTS) |
| fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n", |
| __func__, (void *) udata, (void *) buffer, (long) length); |
| fflush(stderr); |
| #endif |
| |
| if (client_sock < 0) { |
| return 0; |
| } |
| |
| if (length == 0) { |
| /* This shouldn't happen. */ |
| fprintf(stderr, "%s: read request length == 0, closing connection\n", |
| __FILE__); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| if (buffer == NULL) { |
| /* This shouldn't happen. */ |
| fprintf(stderr, "%s: read request buffer == NULL, closing connection\n", |
| __FILE__); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| /* In a production quality implementation there would be a sanity |
| * timeout here to recover from "black hole" disconnects. |
| */ |
| |
| ret = read(client_sock, (void *) buffer, (size_t) length); |
| if (ret < 0) { |
| fprintf(stderr, "%s: debug read failed, closing connection: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } else if (ret == 0) { |
| fprintf(stderr, "%s: debug read failed, ret == 0 (EOF), closing connection\n", |
| __FILE__); |
| fflush(stderr); |
| goto fail; |
| } else if (ret > (ssize_t) length) { |
| fprintf(stderr, "%s: debug read failed, ret too large (%ld > %ld), closing connection\n", |
| __FILE__, (long) ret, (long) length); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| return (duk_size_t) ret; |
| |
| fail: |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| return 0; |
| } |
| |
| /* Duktape debug transport callback: (possibly partial) write. */ |
| duk_size_t duk_trans_socket_write_cb(void *udata, const char *buffer, duk_size_t length) { |
| ssize_t ret; |
| |
| (void) udata; /* not needed by the example */ |
| |
| #if defined(DEBUG_PRINTS) |
| fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n", |
| __func__, (void *) udata, (const void *) buffer, (long) length); |
| fflush(stderr); |
| #endif |
| |
| if (client_sock < 0) { |
| return 0; |
| } |
| |
| if (length == 0) { |
| /* This shouldn't happen. */ |
| fprintf(stderr, "%s: write request length == 0, closing connection\n", |
| __FILE__); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| if (buffer == NULL) { |
| /* This shouldn't happen. */ |
| fprintf(stderr, "%s: write request buffer == NULL, closing connection\n", |
| __FILE__); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| /* In a production quality implementation there would be a sanity |
| * timeout here to recover from "black hole" disconnects. |
| */ |
| |
| ret = write(client_sock, (const void *) buffer, (size_t) length); |
| if (ret <= 0 || ret > (ssize_t) length) { |
| fprintf(stderr, "%s: debug write failed, closing connection: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; |
| } |
| |
| return (duk_size_t) ret; |
| |
| fail: |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| return 0; |
| } |
| |
| duk_size_t duk_trans_socket_peek_cb(void *udata) { |
| struct pollfd fds[1]; |
| int poll_rc; |
| |
| (void) udata; /* not needed by the example */ |
| |
| #if defined(DEBUG_PRINTS) |
| fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); |
| fflush(stderr); |
| #endif |
| |
| if (client_sock < 0) { |
| return 0; |
| } |
| |
| fds[0].fd = client_sock; |
| fds[0].events = POLLIN; |
| fds[0].revents = 0; |
| |
| poll_rc = poll(fds, 1, 0); |
| if (poll_rc < 0) { |
| fprintf(stderr, "%s: poll returned < 0, closing connection: %s\n", |
| __FILE__, strerror(errno)); |
| fflush(stderr); |
| goto fail; /* also returns 0, which is correct */ |
| } else if (poll_rc > 1) { |
| fprintf(stderr, "%s: poll returned > 1, treating like 1\n", |
| __FILE__); |
| fflush(stderr); |
| return 1; /* should never happen */ |
| } else if (poll_rc == 0) { |
| return 0; /* nothing to read */ |
| } else { |
| return 1; /* something to read */ |
| } |
| |
| fail: |
| if (client_sock >= 0) { |
| (void) close(client_sock); |
| client_sock = -1; |
| } |
| return 0; |
| } |
| |
| void duk_trans_socket_read_flush_cb(void *udata) { |
| (void) udata; /* not needed by the example */ |
| |
| #if defined(DEBUG_PRINTS) |
| fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); |
| fflush(stderr); |
| #endif |
| |
| /* Read flush: Duktape may not be making any more read calls at this |
| * time. If the transport maintains a receive window, it can use a |
| * read flush as a signal to update the window status to the remote |
| * peer. A read flush is guaranteed to occur before Duktape stops |
| * reading for a while; it may occur in other situations as well so |
| * it's not a 100% reliable indication. |
| */ |
| |
| /* This TCP transport requires no read flush handling so ignore. |
| * You can also pass a NULL to duk_debugger_attach() and not |
| * implement this callback at all. |
| */ |
| } |
| |
| void duk_trans_socket_write_flush_cb(void *udata) { |
| (void) udata; /* not needed by the example */ |
| |
| #if defined(DEBUG_PRINTS) |
| fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); |
| fflush(stderr); |
| #endif |
| |
| /* Write flush. If the transport combines multiple writes |
| * before actually sending, a write flush is an indication |
| * to write out any pending bytes: Duktape may not be doing |
| * any more writes on this occasion. |
| */ |
| |
| /* This TCP transport requires no write flush handling so ignore. |
| * You can also pass a NULL to duk_debugger_attach() and not |
| * implement this callback at all. |
| */ |
| return; |
| } |