1
1
/*
2
- * Copyright 2014 Frank Hunleth
2
+ * Copyright 2016 Frank Hunleth
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
*/
18
18
19
19
#include "erlcmd.h"
20
+ #include "util.h"
20
21
21
- #include <err .h>
22
+ #include <stdint .h>
22
23
#include <stdlib.h>
23
24
#include <string.h>
24
25
#include <unistd.h>
25
26
27
+ #ifdef __WIN32__
28
+ // Assume that all windows platforms are little endian
29
+ #define TO_BIGENDIAN16 (X ) _byteswap_ushort(X)
30
+ #define FROM_BIGENDIAN16 (X ) _byteswap_ushort(X)
31
+ #else
32
+ // Other platforms have htons and ntohs without pulling in another library
33
+ #define TO_BIGENDIAN16 (X ) htons(X)
34
+ #define FROM_BIGENDIAN16 (X ) ntohs(X)
35
+ #endif
36
+
37
+ #ifdef __WIN32__
38
+ /*
39
+ * stdin on Windows
40
+ *
41
+ * Caveat: I'm convinced that I don't understand stdin on Windows, so take
42
+ * with a grain of salt.
43
+ *
44
+ * Here's what I know:
45
+ *
46
+ * 1. When running from a command prompt, stdin is a console. Windows
47
+ * consoles collect lines at a time. See SetConsoleMode.
48
+ * 2. The console is opened for synchronous use. I.e., overlapped I/O
49
+ * doesn't work.
50
+ * 3. Opening stdin using CONIN$ works for console use, but not
51
+ * redirected use. (Seems obvious, but lots of search hits on
52
+ * Google do this, so it seemed worthwhile to try.)
53
+ * 5. When stdin is redirected to another program, it behaves like
54
+ * a pipe. It may really be a pipe, but the docs that I've read
55
+ * don't call it one.
56
+ * 6. The pipe is opened for synchronous use.
57
+ * 7. Calling ReOpenFile to change it to support overlapped I/O
58
+ * didn't work.
59
+ * 8. Despite pipes not being listed as things you can wait on with
60
+ * WaitForMultipleObjects, it seems to work. Hopefully just a
61
+ * documentation omission...
62
+ *
63
+ * Since there seems to be no way of making stdin be overlapped I/O
64
+ * capable, I created a thread and a new pipe. The thread synchronously
65
+ * reads from stdin and writes to the new pipe. The read end of the new
66
+ * pipe supports overlapped I/O so I can use it in the main WFMO loop.
67
+ *
68
+ * If performance gets to be an issue, the pipe could be replaced with
69
+ * a shared memory/event notification setup. Until this, the pipe version
70
+ * is pretty simple albeit with a lot of data copies and process switches.
71
+ */
72
+
73
+ static DWORD WINAPI pipe_copy_thread (LPVOID lpParam )
74
+ {
75
+ struct erlcmd * handler = (struct erlcmd * ) lpParam ;
76
+
77
+ // NEED to get overlapped version of stdin reader
78
+ HANDLE real_stdin = GetStdHandle (STD_INPUT_HANDLE );
79
+
80
+ // Turn off ENABLE_LINE_INPUT (and everything else)
81
+ // This has to be done on the STD_INPUT_HANDLE rather than
82
+ // the one we use for events. Don't know why.
83
+ SetConsoleMode (real_stdin , 0 );
84
+
85
+ while (handler -> running ) {
86
+ char buffer [1024 ];
87
+ DWORD bytes_read ;
88
+ if (!ReadFile (real_stdin , buffer , sizeof (buffer ), & bytes_read , NULL )) {
89
+ debug ("ReadFile on real_stdin failed (port closed)! %d" , (int ) GetLastError ());
90
+ break ;
91
+ }
92
+
93
+ if (!WriteFile (handler -> stdin_write_pipe , buffer , bytes_read , NULL , NULL )) {
94
+ debug ("WriteFile on stdin_write_pipe failed! %d" , (int ) GetLastError ());
95
+ break ;
96
+ }
97
+ }
98
+
99
+ handler -> running = FALSE;
100
+ CloseHandle (handler -> stdin_write_pipe );
101
+ handler -> stdin_write_pipe = NULL ;
102
+
103
+ ExitThread (0 );
104
+ return 0 ;
105
+ }
106
+
107
+ static void start_async_read (struct erlcmd * handler )
108
+ {
109
+ ReadFile (handler -> h ,
110
+ handler -> buffer + handler -> index ,
111
+ sizeof (handler -> buffer ) - handler -> index ,
112
+ NULL ,
113
+ & handler -> overlapped );
114
+ }
115
+
116
+ #endif
117
+
26
118
/**
27
119
* Initialize an Erlang command handler.
28
120
*
31
123
* @param cookie optional data to pass back to the handler
32
124
*/
33
125
void erlcmd_init (struct erlcmd * handler ,
34
- void (* request_handler )(const char * req , void * cookie ),
35
- void * cookie )
126
+ void (* request_handler )(const char * req , void * cookie ),
127
+ void * cookie )
36
128
{
37
129
memset (handler , 0 , sizeof (* handler ));
38
130
39
131
handler -> request_handler = request_handler ;
40
132
handler -> cookie = cookie ;
133
+
134
+ #ifdef __WIN32__
135
+ handler -> running = TRUE;
136
+
137
+ char pipe_name [64 ];
138
+ sprintf (pipe_name ,
139
+ "\\\\.\\Pipe\\nerves-uart.%08x" ,
140
+ (unsigned int ) GetCurrentProcessId ());
141
+
142
+ handler -> stdin_read_pipe = CreateNamedPipeA (
143
+ pipe_name ,
144
+ PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED ,
145
+ PIPE_TYPE_BYTE | PIPE_WAIT ,
146
+ 1 , // Number of pipes
147
+ 4096 , // Out buffer size
148
+ 4096 , // In buffer size
149
+ 120 * 1000 , // Timeout in ms
150
+ NULL
151
+ );
152
+ if (!handler -> stdin_read_pipe )
153
+ errx (EXIT_FAILURE , "Can't create overlapped i/o stdin pipe" );
154
+
155
+ handler -> stdin_write_pipe = CreateFileA (
156
+ pipe_name ,
157
+ GENERIC_WRITE ,
158
+ 0 , // No sharing
159
+ NULL ,
160
+ OPEN_EXISTING ,
161
+ FILE_ATTRIBUTE_NORMAL ,
162
+ NULL // Template file
163
+ );
164
+ if (!handler -> stdin_write_pipe )
165
+ errx (EXIT_FAILURE , "Can't create write side of stdin pipe" );
166
+
167
+ handler -> stdin_reader_thread = CreateThread (
168
+ NULL , // default security attributes
169
+ 0 , // use default stack size
170
+ pipe_copy_thread , // thread function name
171
+ handler , // argument to thread function
172
+ 0 , // use default creation flags
173
+ NULL );
174
+ if (handler -> stdin_reader_thread == NULL ) {
175
+ errx (EXIT_FAILURE , "CreateThread failed: %d" , (int ) GetLastError ());
176
+ return ;
177
+ }
178
+ handler -> h = handler -> stdin_read_pipe ;
179
+ if (handler -> h == INVALID_HANDLE_VALUE )
180
+ errx (EXIT_FAILURE , "Can't open stdin %d" , (int ) GetLastError ());
181
+
182
+ handler -> overlapped .hEvent = CreateEvent (NULL , TRUE, FALSE, NULL );
183
+ handler -> overlapped .Offset = 0 ;
184
+ handler -> overlapped .OffsetHigh = 0 ;
185
+
186
+ start_async_read (handler );
187
+ #endif
41
188
}
42
189
43
190
/**
@@ -47,21 +194,26 @@ void erlcmd_init(struct erlcmd *handler,
47
194
*/
48
195
void erlcmd_send (char * response , size_t len )
49
196
{
50
- uint16_t be_len = htons (len - sizeof (uint16_t ));
197
+ uint16_t be_len = TO_BIGENDIAN16 (len - sizeof (uint16_t ));
51
198
memcpy (response , & be_len , sizeof (be_len ));
52
199
200
+ #ifdef __WIN32__
201
+ BOOL rc = WriteFile (GetStdHandle (STD_OUTPUT_HANDLE ), response , len , NULL , NULL );
202
+ if (!rc )
203
+ errx (EXIT_FAILURE , "WriteFile to stdout failed (Erlang exit?)" );
204
+ #else
53
205
size_t wrote = 0 ;
54
- do {
206
+ do {
55
207
ssize_t amount_written = write (STDOUT_FILENO , response + wrote , len - wrote );
56
208
if (amount_written < 0 ) {
57
209
if (errno == EINTR )
58
- continue ;
59
-
60
- err (EXIT_FAILURE , "write" );
61
- }
210
+ continue ;
62
211
63
- wrote += amount_written ;
212
+ err (EXIT_FAILURE , "write" );
213
+ }
214
+ wrote += amount_written ;
64
215
} while (wrote < len );
216
+ #endif
65
217
}
66
218
67
219
/**
@@ -72,55 +224,94 @@ static size_t erlcmd_try_dispatch(struct erlcmd *handler)
72
224
{
73
225
/* Check for length field */
74
226
if (handler -> index < sizeof (uint16_t ))
75
- return 0 ;
227
+ return 0 ;
76
228
77
229
uint16_t be_len ;
78
230
memcpy (& be_len , handler -> buffer , sizeof (uint16_t ));
79
- size_t msglen = ntohs (be_len );
231
+ size_t msglen = FROM_BIGENDIAN16 (be_len );
80
232
if (msglen + sizeof (uint16_t ) > sizeof (handler -> buffer ))
81
- errx (EXIT_FAILURE , "Message too long" );
233
+ errx (EXIT_FAILURE , "Message too long: %d bytes. Max is %d bytes" ,
234
+ (int ) (msglen + sizeof (uint16_t )), (int ) sizeof (handler -> buffer ));
82
235
83
236
/* Check whether we've received the entire message */
84
237
if (msglen + sizeof (uint16_t ) > handler -> index )
85
- return 0 ;
238
+ return 0 ;
86
239
87
240
handler -> request_handler (handler -> buffer , handler -> cookie );
88
241
89
242
return msglen + sizeof (uint16_t );
90
243
}
91
244
92
245
/**
93
- * @brief call to process any new requests from Erlang
246
+ * @brief Call to process any new requests from Erlang
247
+ *
248
+ * @return 1 if the program should exit gracefully
94
249
*/
95
- void erlcmd_process (struct erlcmd * handler ) {
96
- ssize_t amount_read = read (STDIN_FILENO , handler -> buffer + handler -> index , sizeof (handler -> buffer ) - handler -> index );
97
- if (amount_read < 0 ) {
98
- /* EINTR is ok to get, since we were interrupted by a signal. */
99
- if (errno == EINTR )
100
- return ;
101
-
102
- /* Everything else is unexpected. */
103
- err (EXIT_FAILURE , "read" );
104
- } else if (amount_read == 0 ) {
105
- /* EOF. Erlang process was terminated. This happens after a release or if there was an error. */
106
- exit (EXIT_SUCCESS );
250
+ int erlcmd_process (struct erlcmd * handler )
251
+ {
252
+ #ifdef __WIN32__
253
+ DWORD amount_read ;
254
+ BOOL rc = GetOverlappedResult (handler -> h ,
255
+ & handler -> overlapped ,
256
+ & amount_read , FALSE);
257
+
258
+ if (!rc ) {
259
+ DWORD last_error = GetLastError ();
260
+
261
+ // Check if this was a spurious event.
262
+ if (last_error == ERROR_IO_PENDING )
263
+ return 0 ;
264
+
265
+ // Error - most likely the Erlang port connected to us was closed.
266
+ // Tell the caller to exit gracefully.
267
+ return 1 ;
107
268
}
108
269
270
+ ResetEvent (handler -> overlapped .hEvent );
271
+ #else
272
+ ssize_t amount_read = read (STDIN_FILENO , handler -> buffer + handler -> index , sizeof (handler -> buffer ) - handler -> index );
273
+ if (amount_read < 0 ) {
274
+ /* EINTR is ok to get, since we were interrupted by a signal. */
275
+ if (errno == EINTR )
276
+ return 0 ;
277
+
278
+ /* Everything else is unexpected. */
279
+ err (EXIT_FAILURE , "read" );
280
+ } else if (amount_read == 0 ) {
281
+ /* EOF. Erlang process was terminated. This happens after a release or if there was an error. */
282
+ return 1 ;
283
+ }
284
+ #endif
109
285
handler -> index += amount_read ;
286
+
110
287
for (;;) {
111
- size_t bytes_processed = erlcmd_try_dispatch (handler );
112
-
113
- if (bytes_processed == 0 ) {
114
- /* Only have part of the command to process. */
115
- break ;
116
- } else if (handler -> index > bytes_processed ) {
117
- /* Processed the command and there's more data. */
118
- memmove (handler -> buffer , & handler -> buffer [bytes_processed ], handler -> index - bytes_processed );
119
- handler -> index -= bytes_processed ;
120
- } else {
121
- /* Processed the whole buffer. */
122
- handler -> index = 0 ;
123
- break ;
124
- }
288
+ size_t bytes_processed = erlcmd_try_dispatch (handler );
289
+
290
+ if (bytes_processed == 0 ) {
291
+ /* Only have part of the command to process. */
292
+ break ;
293
+ } else if (handler -> index > bytes_processed ) {
294
+ /* Processed the command and there's more data. */
295
+ memmove (handler -> buffer , & handler -> buffer [bytes_processed ], handler -> index - bytes_processed );
296
+ handler -> index -= bytes_processed ;
297
+ } else {
298
+ /* Processed the whole buffer. */
299
+ handler -> index = 0 ;
300
+ break ;
301
+ }
125
302
}
303
+
304
+ #ifdef __WIN32__
305
+ start_async_read (handler );
306
+ #endif
307
+
308
+ return 0 ;
126
309
}
310
+
311
+ #ifdef __WIN32__
312
+ HANDLE erlcmd_wfmo_event (struct erlcmd * handler )
313
+ {
314
+ return handler -> overlapped .hEvent ;
315
+ }
316
+
317
+ #endif
0 commit comments