Skip to content

Commit 2a543c8

Browse files
committed
Erlang - C driver test
1 parent e8fb179 commit 2a543c8

13 files changed

+711
-52
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# The directory Mix will write compiled artifacts to.
22
.elixir_ls/
3+
.vscode/
34
/_build/
45

56
# If you run "mix test --cover", coverage assets end up here.

Makefile

+7-5
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ OBJ = $(SRC:.c=.o)
4747

4848
.PHONY: all clean
4949

50-
all: priv/snap7
50+
all: snap7 priv/snap7
5151

52-
53-
54-
priv/snap7: $(OBJ) snap7
52+
priv/snap7: $(OBJ)
53+
@echo $(OBJ)
5554
mkdir -p priv
56-
$(CC) -O3 -v $(OBJ) -L$(SRC_PATH) -I$(SRC_PATH) -lsnap $(ERL_LDFLAGS) $(LDFLAGS) -o $@
55+
#$(CC) -O3 -v $(OBJ) -L$(SRC_PATH) -I$(SRC_PATH) -lsnap $(ERL_LDFLAGS) $(LDFLAGS) -o $@
56+
$(CC) -O3 src/erlcmd.o src/s7_client.o src/util.o -L$(SRC_PATH) -I$(SRC_PATH) -lsnap $(ERL_LDFLAGS) $(LDFLAGS) -o priv/s7_client
57+
$(CC) -O3 src/erlcmd.o src/s7_server.o src/util.o -L$(SRC_PATH) -I$(SRC_PATH) -lsnap $(ERL_LDFLAGS) $(LDFLAGS) -o priv/s7_server
58+
$(CC) -O3 src/erlcmd.o src/s7_partner.o src/util.o -L$(SRC_PATH) -I$(SRC_PATH) -lsnap $(ERL_LDFLAGS) $(LDFLAGS) -o priv/s7_partner
5759

5860
snap7:
5961
make -C $(SNAP7_PATH)$(OS_PATH) -f $(TARGET).mk install LibInstall=../../../libsnap.so

priv/s7_client

55.3 KB
Binary file not shown.

priv/s7_partner

22.2 KB
Binary file not shown.

priv/s7_server

22.2 KB
Binary file not shown.

src/erlcmd.c

+233-42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014 Frank Hunleth
2+
* Copyright 2016 Frank Hunleth
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,12 +17,104 @@
1717
*/
1818

1919
#include "erlcmd.h"
20+
#include "util.h"
2021

21-
#include <err.h>
22+
#include <stdint.h>
2223
#include <stdlib.h>
2324
#include <string.h>
2425
#include <unistd.h>
2526

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+
26118
/**
27119
* Initialize an Erlang command handler.
28120
*
@@ -31,13 +123,68 @@
31123
* @param cookie optional data to pass back to the handler
32124
*/
33125
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)
36128
{
37129
memset(handler, 0, sizeof(*handler));
38130

39131
handler->request_handler = request_handler;
40132
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
41188
}
42189

43190
/**
@@ -47,21 +194,26 @@ void erlcmd_init(struct erlcmd *handler,
47194
*/
48195
void erlcmd_send(char *response, size_t len)
49196
{
50-
uint16_t be_len = htons(len - sizeof(uint16_t));
197+
uint16_t be_len = TO_BIGENDIAN16(len - sizeof(uint16_t));
51198
memcpy(response, &be_len, sizeof(be_len));
52199

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
53205
size_t wrote = 0;
54-
do {
206+
do {
55207
ssize_t amount_written = write(STDOUT_FILENO, response + wrote, len - wrote);
56208
if (amount_written < 0) {
57209
if (errno == EINTR)
58-
continue;
59-
60-
err(EXIT_FAILURE, "write");
61-
}
210+
continue;
62211

63-
wrote += amount_written;
212+
err(EXIT_FAILURE, "write");
213+
}
214+
wrote += amount_written;
64215
} while (wrote < len);
216+
#endif
65217
}
66218

67219
/**
@@ -72,55 +224,94 @@ static size_t erlcmd_try_dispatch(struct erlcmd *handler)
72224
{
73225
/* Check for length field */
74226
if (handler->index < sizeof(uint16_t))
75-
return 0;
227+
return 0;
76228

77229
uint16_t be_len;
78230
memcpy(&be_len, handler->buffer, sizeof(uint16_t));
79-
size_t msglen = ntohs(be_len);
231+
size_t msglen = FROM_BIGENDIAN16(be_len);
80232
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));
82235

83236
/* Check whether we've received the entire message */
84237
if (msglen + sizeof(uint16_t) > handler->index)
85-
return 0;
238+
return 0;
86239

87240
handler->request_handler(handler->buffer, handler->cookie);
88241

89242
return msglen + sizeof(uint16_t);
90243
}
91244

92245
/**
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
94249
*/
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;
107268
}
108269

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
109285
handler->index += amount_read;
286+
110287
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+
}
125302
}
303+
304+
#ifdef __WIN32__
305+
start_async_read(handler);
306+
#endif
307+
308+
return 0;
126309
}
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

Comments
 (0)