Packages

Elixir wrapper for Snap7, for communication with Siemens PLC's.

Current section

Files

Jump to
snapex7 src erlcmd.c
Raw

src/erlcmd.c

/*
* Copyright 2016 Frank Hunleth
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Common Erlang->C port communications code
*/
#include "erlcmd.h"
#include "util.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __WIN32__
// Assume that all windows platforms are little endian
#define TO_BIGENDIAN16(X) _byteswap_ushort(X)
#define FROM_BIGENDIAN16(X) _byteswap_ushort(X)
#else
// Other platforms have htons and ntohs without pulling in another library
#define TO_BIGENDIAN16(X) htons(X)
#define FROM_BIGENDIAN16(X) ntohs(X)
#endif
#ifdef __WIN32__
/*
* stdin on Windows
*
* Caveat: I'm convinced that I don't understand stdin on Windows, so take
* with a grain of salt.
*
* Here's what I know:
*
* 1. When running from a command prompt, stdin is a console. Windows
* consoles collect lines at a time. See SetConsoleMode.
* 2. The console is opened for synchronous use. I.e., overlapped I/O
* doesn't work.
* 3. Opening stdin using CONIN$ works for console use, but not
* redirected use. (Seems obvious, but lots of search hits on
* Google do this, so it seemed worthwhile to try.)
* 5. When stdin is redirected to another program, it behaves like
* a pipe. It may really be a pipe, but the docs that I've read
* don't call it one.
* 6. The pipe is opened for synchronous use.
* 7. Calling ReOpenFile to change it to support overlapped I/O
* didn't work.
* 8. Despite pipes not being listed as things you can wait on with
* WaitForMultipleObjects, it seems to work. Hopefully just a
* documentation omission...
*
* Since there seems to be no way of making stdin be overlapped I/O
* capable, I created a thread and a new pipe. The thread synchronously
* reads from stdin and writes to the new pipe. The read end of the new
* pipe supports overlapped I/O so I can use it in the main WFMO loop.
*
* If performance gets to be an issue, the pipe could be replaced with
* a shared memory/event notification setup. Until this, the pipe version
* is pretty simple albeit with a lot of data copies and process switches.
*/
static DWORD WINAPI pipe_copy_thread(LPVOID lpParam)
{
struct erlcmd *handler = (struct erlcmd *) lpParam;
// NEED to get overlapped version of stdin reader
HANDLE real_stdin = GetStdHandle(STD_INPUT_HANDLE);
// Turn off ENABLE_LINE_INPUT (and everything else)
// This has to be done on the STD_INPUT_HANDLE rather than
// the one we use for events. Don't know why.
SetConsoleMode(real_stdin, 0);
while (handler->running) {
char buffer[1024];
DWORD bytes_read;
if (!ReadFile(real_stdin, buffer, sizeof(buffer), &bytes_read, NULL)) {
debug("ReadFile on real_stdin failed (port closed)! %d", (int) GetLastError());
break;
}
if (!WriteFile(handler->stdin_write_pipe, buffer, bytes_read, NULL, NULL)) {
debug("WriteFile on stdin_write_pipe failed! %d", (int) GetLastError());
break;
}
}
handler->running = FALSE;
CloseHandle(handler->stdin_write_pipe);
handler->stdin_write_pipe = NULL;
ExitThread(0);
return 0;
}
static void start_async_read(struct erlcmd *handler)
{
ReadFile(handler->h,
handler->buffer + handler->index,
sizeof(handler->buffer) - handler->index,
NULL,
&handler->overlapped);
}
#endif
/**
* Initialize an Erlang command handler.
*
* @param handler the structure to initialize
* @param request_handler callback for each message received
* @param cookie optional data to pass back to the handler
*/
void erlcmd_init(struct erlcmd *handler,
void (*request_handler)(const char *req, void *cookie),
void *cookie)
{
memset(handler, 0, sizeof(*handler));
handler->request_handler = request_handler;
handler->cookie = cookie;
#ifdef __WIN32__
handler->running = TRUE;
char pipe_name[64];
sprintf(pipe_name,
"\\\\.\\Pipe\\nerves-uart.%08x",
(unsigned int) GetCurrentProcessId());
handler->stdin_read_pipe = CreateNamedPipeA(
pipe_name,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
1, // Number of pipes
4096, // Out buffer size
4096, // In buffer size
120 * 1000, // Timeout in ms
NULL
);
if (!handler->stdin_read_pipe)
errx(EXIT_FAILURE, "Can't create overlapped i/o stdin pipe");
handler->stdin_write_pipe = CreateFileA(
pipe_name,
GENERIC_WRITE,
0, // No sharing
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL // Template file
);
if (!handler->stdin_write_pipe)
errx(EXIT_FAILURE, "Can't create write side of stdin pipe");
handler->stdin_reader_thread = CreateThread(
NULL, // default security attributes
0, // use default stack size
pipe_copy_thread, // thread function name
handler, // argument to thread function
0, // use default creation flags
NULL);
if (handler->stdin_reader_thread == NULL) {
errx(EXIT_FAILURE, "CreateThread failed: %d", (int) GetLastError());
return;
}
handler->h = handler->stdin_read_pipe;
if (handler->h == INVALID_HANDLE_VALUE)
errx(EXIT_FAILURE, "Can't open stdin %d", (int) GetLastError());
handler->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
handler->overlapped.Offset = 0;
handler->overlapped.OffsetHigh = 0;
start_async_read(handler);
#endif
}
/**
* @brief Synchronously send a response back to Erlang
*
* @param response what to send back
*/
void erlcmd_send(char *response, size_t len)
{
uint16_t be_len = TO_BIGENDIAN16(len - sizeof(uint16_t));
memcpy(response, &be_len, sizeof(be_len));
#ifdef __WIN32__
BOOL rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), response, len, NULL, NULL);
if (!rc)
errx(EXIT_FAILURE, "WriteFile to stdout failed (Erlang exit?)");
#else
size_t wrote = 0;
do {
ssize_t amount_written = write(STDOUT_FILENO, response + wrote, len - wrote);
if (amount_written < 0) {
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "write");
}
wrote += amount_written;
} while (wrote < len);
#endif
}
/**
* @brief Dispatch commands in the buffer
* @return the number of bytes processed
*/
static size_t erlcmd_try_dispatch(struct erlcmd *handler)
{
/* Check for length field */
if (handler->index < sizeof(uint16_t))
return 0;
uint16_t be_len;
memcpy(&be_len, handler->buffer, sizeof(uint16_t));
size_t msglen = FROM_BIGENDIAN16(be_len);
if (msglen + sizeof(uint16_t) > sizeof(handler->buffer))
errx(EXIT_FAILURE, "Message too long: %d bytes. Max is %d bytes",
(int) (msglen + sizeof(uint16_t)), (int) sizeof(handler->buffer));
/* Check whether we've received the entire message */
if (msglen + sizeof(uint16_t) > handler->index)
return 0;
handler->request_handler(handler->buffer, handler->cookie);
return msglen + sizeof(uint16_t);
}
/**
* @brief Call to process any new requests from Erlang
*
* @return 1 if the program should exit gracefully
*/
int erlcmd_process(struct erlcmd *handler)
{
#ifdef __WIN32__
DWORD amount_read;
BOOL rc = GetOverlappedResult(handler->h,
&handler->overlapped,
&amount_read, FALSE);
if (!rc) {
DWORD last_error = GetLastError();
// Check if this was a spurious event.
if (last_error == ERROR_IO_PENDING)
return 0;
// Error - most likely the Erlang port connected to us was closed.
// Tell the caller to exit gracefully.
return 1;
}
ResetEvent(handler->overlapped.hEvent);
#else
ssize_t amount_read = read(STDIN_FILENO, handler->buffer + handler->index, sizeof(handler->buffer) - handler->index);
if (amount_read < 0) {
/* EINTR is ok to get, since we were interrupted by a signal. */
if (errno == EINTR)
return 0;
/* Everything else is unexpected. */
err(EXIT_FAILURE, "read");
} else if (amount_read == 0) {
/* EOF. Erlang process was terminated. This happens after a release or if there was an error. */
return 1;
}
#endif
handler->index += amount_read;
for (;;) {
size_t bytes_processed = erlcmd_try_dispatch(handler);
if (bytes_processed == 0) {
/* Only have part of the command to process. */
break;
} else if (handler->index > bytes_processed) {
/* Processed the command and there's more data. */
memmove(handler->buffer, &handler->buffer[bytes_processed], handler->index - bytes_processed);
handler->index -= bytes_processed;
} else {
/* Processed the whole buffer. */
handler->index = 0;
break;
}
}
#ifdef __WIN32__
start_async_read(handler);
#endif
return 0;
}
#ifdef __WIN32__
HANDLE erlcmd_wfmo_event(struct erlcmd *handler)
{
return handler->overlapped.hEvent;
}
#endif