]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | /* |
11fdf7f2 | 2 | * Example debug transport using a Linux/Unix TCP socket |
7c673cae | 3 | * |
11fdf7f2 | 4 | * Provides a TCP server socket which a debug client can connect to. |
7c673cae | 5 | * After that data is just passed through. |
7c673cae FG |
6 | */ |
7 | ||
8 | #include <stdio.h> | |
9 | #include <string.h> | |
10 | #include <sys/socket.h> | |
11fdf7f2 | 11 | #include <netinet/in.h> |
7c673cae FG |
12 | #include <unistd.h> |
13 | #include <poll.h> | |
14 | #include <errno.h> | |
15 | #include "duktape.h" | |
16 | ||
11fdf7f2 | 17 | #if !defined(DUK_DEBUG_PORT) |
7c673cae FG |
18 | #define DUK_DEBUG_PORT 9091 |
19 | #endif | |
20 | ||
21 | #if 0 | |
22 | #define DEBUG_PRINTS | |
23 | #endif | |
24 | ||
25 | static int server_sock = -1; | |
26 | static int client_sock = -1; | |
27 | ||
28 | /* | |
11fdf7f2 | 29 | * Transport init and finish |
7c673cae FG |
30 | */ |
31 | ||
32 | void duk_trans_socket_init(void) { | |
33 | struct sockaddr_in addr; | |
34 | int on; | |
35 | ||
36 | server_sock = socket(AF_INET, SOCK_STREAM, 0); | |
37 | if (server_sock < 0) { | |
11fdf7f2 TL |
38 | fprintf(stderr, "%s: failed to create server socket: %s\n", |
39 | __FILE__, strerror(errno)); | |
7c673cae FG |
40 | fflush(stderr); |
41 | goto fail; | |
42 | } | |
43 | ||
44 | on = 1; | |
45 | if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0) { | |
11fdf7f2 TL |
46 | fprintf(stderr, "%s: failed to set SO_REUSEADDR for server socket: %s\n", |
47 | __FILE__, strerror(errno)); | |
7c673cae FG |
48 | fflush(stderr); |
49 | goto fail; | |
50 | } | |
51 | ||
52 | memset((void *) &addr, 0, sizeof(addr)); | |
53 | addr.sin_family = AF_INET; | |
54 | addr.sin_addr.s_addr = INADDR_ANY; | |
55 | addr.sin_port = htons(DUK_DEBUG_PORT); | |
56 | ||
57 | if (bind(server_sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { | |
11fdf7f2 TL |
58 | fprintf(stderr, "%s: failed to bind server socket: %s\n", |
59 | __FILE__, strerror(errno)); | |
7c673cae FG |
60 | fflush(stderr); |
61 | goto fail; | |
62 | } | |
63 | ||
64 | listen(server_sock, 1 /*backlog*/); | |
65 | return; | |
66 | ||
67 | fail: | |
68 | if (server_sock >= 0) { | |
69 | (void) close(server_sock); | |
70 | server_sock = -1; | |
71 | } | |
72 | } | |
73 | ||
11fdf7f2 TL |
74 | void duk_trans_socket_finish(void) { |
75 | if (client_sock >= 0) { | |
76 | (void) close(client_sock); | |
77 | client_sock = -1; | |
78 | } | |
79 | if (server_sock >= 0) { | |
80 | (void) close(server_sock); | |
81 | server_sock = -1; | |
82 | } | |
83 | } | |
84 | ||
7c673cae FG |
85 | void duk_trans_socket_waitconn(void) { |
86 | struct sockaddr_in addr; | |
87 | socklen_t sz; | |
88 | ||
89 | if (server_sock < 0) { | |
11fdf7f2 TL |
90 | fprintf(stderr, "%s: no server socket, skip waiting for connection\n", |
91 | __FILE__); | |
7c673cae FG |
92 | fflush(stderr); |
93 | return; | |
94 | } | |
95 | if (client_sock >= 0) { | |
96 | (void) close(client_sock); | |
97 | client_sock = -1; | |
98 | } | |
99 | ||
100 | fprintf(stderr, "Waiting for debug connection on port %d\n", (int) DUK_DEBUG_PORT); | |
101 | fflush(stderr); | |
102 | ||
103 | sz = (socklen_t) sizeof(addr); | |
104 | client_sock = accept(server_sock, (struct sockaddr *) &addr, &sz); | |
105 | if (client_sock < 0) { | |
11fdf7f2 TL |
106 | fprintf(stderr, "%s: accept() failed, skip waiting for connection: %s\n", |
107 | __FILE__, strerror(errno)); | |
7c673cae FG |
108 | fflush(stderr); |
109 | goto fail; | |
110 | } | |
111 | ||
112 | fprintf(stderr, "Debug connection established\n"); | |
113 | fflush(stderr); | |
114 | ||
115 | /* XXX: For now, close the listen socket because we won't accept new | |
116 | * connections anyway. A better implementation would allow multiple | |
117 | * debug attaches. | |
118 | */ | |
119 | ||
120 | if (server_sock >= 0) { | |
121 | (void) close(server_sock); | |
122 | server_sock = -1; | |
123 | } | |
124 | return; | |
125 | ||
126 | fail: | |
127 | if (client_sock >= 0) { | |
128 | (void) close(client_sock); | |
129 | client_sock = -1; | |
130 | } | |
131 | } | |
132 | ||
133 | /* | |
134 | * Duktape callbacks | |
135 | */ | |
136 | ||
11fdf7f2 | 137 | /* Duktape debug transport callback: (possibly partial) read. */ |
7c673cae FG |
138 | duk_size_t duk_trans_socket_read_cb(void *udata, char *buffer, duk_size_t length) { |
139 | ssize_t ret; | |
140 | ||
141 | (void) udata; /* not needed by the example */ | |
142 | ||
143 | #if defined(DEBUG_PRINTS) | |
144 | fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n", | |
145 | __func__, (void *) udata, (void *) buffer, (long) length); | |
146 | fflush(stderr); | |
147 | #endif | |
148 | ||
149 | if (client_sock < 0) { | |
150 | return 0; | |
151 | } | |
152 | ||
153 | if (length == 0) { | |
154 | /* This shouldn't happen. */ | |
11fdf7f2 TL |
155 | fprintf(stderr, "%s: read request length == 0, closing connection\n", |
156 | __FILE__); | |
7c673cae FG |
157 | fflush(stderr); |
158 | goto fail; | |
159 | } | |
160 | ||
161 | if (buffer == NULL) { | |
162 | /* This shouldn't happen. */ | |
11fdf7f2 TL |
163 | fprintf(stderr, "%s: read request buffer == NULL, closing connection\n", |
164 | __FILE__); | |
7c673cae FG |
165 | fflush(stderr); |
166 | goto fail; | |
167 | } | |
168 | ||
169 | /* In a production quality implementation there would be a sanity | |
170 | * timeout here to recover from "black hole" disconnects. | |
171 | */ | |
172 | ||
173 | ret = read(client_sock, (void *) buffer, (size_t) length); | |
174 | if (ret < 0) { | |
11fdf7f2 TL |
175 | fprintf(stderr, "%s: debug read failed, closing connection: %s\n", |
176 | __FILE__, strerror(errno)); | |
7c673cae FG |
177 | fflush(stderr); |
178 | goto fail; | |
179 | } else if (ret == 0) { | |
11fdf7f2 TL |
180 | fprintf(stderr, "%s: debug read failed, ret == 0 (EOF), closing connection\n", |
181 | __FILE__); | |
7c673cae FG |
182 | fflush(stderr); |
183 | goto fail; | |
184 | } else if (ret > (ssize_t) length) { | |
11fdf7f2 TL |
185 | fprintf(stderr, "%s: debug read failed, ret too large (%ld > %ld), closing connection\n", |
186 | __FILE__, (long) ret, (long) length); | |
7c673cae FG |
187 | fflush(stderr); |
188 | goto fail; | |
189 | } | |
190 | ||
191 | return (duk_size_t) ret; | |
192 | ||
193 | fail: | |
194 | if (client_sock >= 0) { | |
195 | (void) close(client_sock); | |
196 | client_sock = -1; | |
197 | } | |
198 | return 0; | |
199 | } | |
200 | ||
11fdf7f2 | 201 | /* Duktape debug transport callback: (possibly partial) write. */ |
7c673cae FG |
202 | duk_size_t duk_trans_socket_write_cb(void *udata, const char *buffer, duk_size_t length) { |
203 | ssize_t ret; | |
204 | ||
205 | (void) udata; /* not needed by the example */ | |
206 | ||
207 | #if defined(DEBUG_PRINTS) | |
208 | fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n", | |
11fdf7f2 | 209 | __func__, (void *) udata, (const void *) buffer, (long) length); |
7c673cae FG |
210 | fflush(stderr); |
211 | #endif | |
212 | ||
213 | if (client_sock < 0) { | |
214 | return 0; | |
215 | } | |
216 | ||
217 | if (length == 0) { | |
218 | /* This shouldn't happen. */ | |
11fdf7f2 TL |
219 | fprintf(stderr, "%s: write request length == 0, closing connection\n", |
220 | __FILE__); | |
7c673cae FG |
221 | fflush(stderr); |
222 | goto fail; | |
223 | } | |
224 | ||
225 | if (buffer == NULL) { | |
226 | /* This shouldn't happen. */ | |
11fdf7f2 TL |
227 | fprintf(stderr, "%s: write request buffer == NULL, closing connection\n", |
228 | __FILE__); | |
7c673cae FG |
229 | fflush(stderr); |
230 | goto fail; | |
231 | } | |
232 | ||
233 | /* In a production quality implementation there would be a sanity | |
234 | * timeout here to recover from "black hole" disconnects. | |
235 | */ | |
236 | ||
237 | ret = write(client_sock, (const void *) buffer, (size_t) length); | |
238 | if (ret <= 0 || ret > (ssize_t) length) { | |
11fdf7f2 TL |
239 | fprintf(stderr, "%s: debug write failed, closing connection: %s\n", |
240 | __FILE__, strerror(errno)); | |
7c673cae FG |
241 | fflush(stderr); |
242 | goto fail; | |
243 | } | |
244 | ||
245 | return (duk_size_t) ret; | |
246 | ||
247 | fail: | |
248 | if (client_sock >= 0) { | |
249 | (void) close(client_sock); | |
250 | client_sock = -1; | |
251 | } | |
252 | return 0; | |
253 | } | |
254 | ||
255 | duk_size_t duk_trans_socket_peek_cb(void *udata) { | |
256 | struct pollfd fds[1]; | |
257 | int poll_rc; | |
258 | ||
259 | (void) udata; /* not needed by the example */ | |
260 | ||
261 | #if defined(DEBUG_PRINTS) | |
262 | fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); | |
263 | fflush(stderr); | |
264 | #endif | |
265 | ||
11fdf7f2 TL |
266 | if (client_sock < 0) { |
267 | return 0; | |
268 | } | |
269 | ||
7c673cae FG |
270 | fds[0].fd = client_sock; |
271 | fds[0].events = POLLIN; | |
272 | fds[0].revents = 0; | |
273 | ||
274 | poll_rc = poll(fds, 1, 0); | |
275 | if (poll_rc < 0) { | |
11fdf7f2 TL |
276 | fprintf(stderr, "%s: poll returned < 0, closing connection: %s\n", |
277 | __FILE__, strerror(errno)); | |
7c673cae FG |
278 | fflush(stderr); |
279 | goto fail; /* also returns 0, which is correct */ | |
280 | } else if (poll_rc > 1) { | |
11fdf7f2 TL |
281 | fprintf(stderr, "%s: poll returned > 1, treating like 1\n", |
282 | __FILE__); | |
7c673cae FG |
283 | fflush(stderr); |
284 | return 1; /* should never happen */ | |
285 | } else if (poll_rc == 0) { | |
286 | return 0; /* nothing to read */ | |
287 | } else { | |
288 | return 1; /* something to read */ | |
289 | } | |
290 | ||
291 | fail: | |
292 | if (client_sock >= 0) { | |
293 | (void) close(client_sock); | |
294 | client_sock = -1; | |
295 | } | |
296 | return 0; | |
297 | } | |
298 | ||
299 | void duk_trans_socket_read_flush_cb(void *udata) { | |
11fdf7f2 TL |
300 | (void) udata; /* not needed by the example */ |
301 | ||
7c673cae FG |
302 | #if defined(DEBUG_PRINTS) |
303 | fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); | |
304 | fflush(stderr); | |
305 | #endif | |
306 | ||
7c673cae FG |
307 | /* Read flush: Duktape may not be making any more read calls at this |
308 | * time. If the transport maintains a receive window, it can use a | |
309 | * read flush as a signal to update the window status to the remote | |
310 | * peer. A read flush is guaranteed to occur before Duktape stops | |
311 | * reading for a while; it may occur in other situations as well so | |
312 | * it's not a 100% reliable indication. | |
313 | */ | |
314 | ||
315 | /* This TCP transport requires no read flush handling so ignore. | |
316 | * You can also pass a NULL to duk_debugger_attach() and not | |
317 | * implement this callback at all. | |
318 | */ | |
319 | } | |
320 | ||
321 | void duk_trans_socket_write_flush_cb(void *udata) { | |
11fdf7f2 TL |
322 | (void) udata; /* not needed by the example */ |
323 | ||
7c673cae FG |
324 | #if defined(DEBUG_PRINTS) |
325 | fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata); | |
326 | fflush(stderr); | |
327 | #endif | |
328 | ||
7c673cae FG |
329 | /* Write flush. If the transport combines multiple writes |
330 | * before actually sending, a write flush is an indication | |
331 | * to write out any pending bytes: Duktape may not be doing | |
332 | * any more writes on this occasion. | |
333 | */ | |
334 | ||
335 | /* This TCP transport requires no write flush handling so ignore. | |
336 | * You can also pass a NULL to duk_debugger_attach() and not | |
337 | * implement this callback at all. | |
338 | */ | |
339 | return; | |
340 | } |