/*
* Author: Bjorn Gustavsson
* Purpose: A port program to be used for testing the open_port bif.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef __WIN32__
#include <unistd.h>
#include <sys/time.h>
#define O_BINARY 0
#define _setmode(fd, mode)
#endif
#ifdef __WIN32__
#include "windows.h"
#include "winbase.h"
#endif
#define REDIR_STDOUT(fd) if (dup2(fd, 1) == -1) { \
fprintf(stderr, "%s: failed to duplicate handle %d to 1: %d\n", \
port_data->progname, fd, errno); \
exit(1); \
}
#define MAIN(argc, argv) main(argc, argv)
extern int errno;
typedef struct {
char* progname; /* Name of this program (from argv[0]). */
int header_size; /* Number of bytes in each packet header:
* 1, 2, or 4, or 0 for a continuous byte stream. */
int fd_from_erl; /* File descriptor from Erlang. */
int fd_to_erl; /* File descriptor to Erlang. */
unsigned char* io_buf; /* Buffer for file i/o. */
int io_buf_size; /* Current size of i/o buffer. */
int delay_mode; /* If set, this program will wait 5 seconds
* after reading the header for a packet
* before reading the rest.
*/
int break_mode; /* If set, this program will close standard
* input, which should case broken pipe
* error in the writer.
*/
int quit_mode; /* If set, this program will exit
* just after reading the packet header.
*/
int slow_writes; /* Writes back the reply in chunks with
* sleeps in between. The value is the
* chunk size. If 0, normal writes are done.
*/
char* output_file; /* File into which the result will be written. */
int no_packet_loop; /* No packet loop. */
int limited_bytecount; /* Only answer a limited number of bytes, then exit (stream mode) */
} PORT_TEST_DATA;
PORT_TEST_DATA* port_data;
static int packet_loop();
static void reply();
static void write_reply();
static void ensure_buf_big_enough();
static int readn();
static void delay(unsigned ms);
static void dump(unsigned char* buf, int sz, int max);
static void replace_stdout(char* filename);
static void generate_reply(char* spec);
#ifndef HAVE_STRERROR
extern int sys_nerr;
#ifndef sys_errlist /* sys_errlist is sometimes defined to
call a function on win32 */
extern char *sys_errlist[];
#endif
char*
strerror(err)
int err;
{
static char msgstr[1024];
if (err == 0) {
msgstr[0] = '\0';
} else if (0 < err && err < sys_nerr) {
strcpy(msgstr, sys_errlist[err]);
} else {
sprintf(msgstr, "Unknown error %d", err);
}
return msgstr;
}
#endif
MAIN(argc, argv)
int argc;
char *argv[];
{
int ret;
if((port_data = (PORT_TEST_DATA *) malloc(sizeof(PORT_TEST_DATA))) == NULL) {
fprintf(stderr, "Couldn't malloc for port_data");
exit(1);
}
port_data->header_size = 0;
port_data->io_buf_size = 0;
port_data->delay_mode = 0;
port_data->break_mode = 0;
port_data->quit_mode = 0;
port_data->slow_writes = 0;
port_data->output_file = NULL;
port_data->no_packet_loop = 0;
port_data->progname = argv[0];
port_data->fd_from_erl = 0;
port_data->fd_to_erl = 1;
port_data->limited_bytecount = 0;
_setmode(0, _O_BINARY);
_setmode(1, _O_BINARY);
while (argc > 1 && argv[1][0] == '-') {
switch (argv[1][1]) {
case 'b': /* Break mode. */
port_data->break_mode = 1;
break;
case 'c': /* Close standard output. */
close(port_data->fd_to_erl);
break;
case 'd': /* Delay mode. */
port_data->delay_mode = 1;
break;
case 'n': /* No packet loop. */
port_data->no_packet_loop = 1;
break;
case 'o': /* Output to file. */
port_data->output_file = argv[1]+2;
break;
case 'q': /* Quit mode. */
port_data->quit_mode = 1;
break;
case 'r': /* Generate reply. */
generate_reply(argv[1]+2);
break;
case 's': /* Slow writes. */
port_data->slow_writes = atoi(argv[1]+2);
break;
case 'h': /* Header size for packets. */
switch (argv[1][2]) {
case '0': port_data->header_size = 0; break;
case '1': port_data->header_size = 1; break;
case '2': port_data->header_size = 2; break;
case '4': port_data->header_size = 4; break;
case '\0':
fprintf(stderr, "%s: missing header size for -h\n", port_data->progname);
return 1;
default:
fprintf(stderr, "%s: illegal packet header size: %c\n",
port_data->progname, argv[1][2]);
return 1;
}
break;
case 'e':
port_data->fd_to_erl = 2;
break;
case 'l':
port_data->limited_bytecount = atoi(argv[1]+2);
break;
default:
fprintf(stderr, "Unrecognized switch: %s\n", argv[1]);
free(port_data);
exit(1);
}
argc--, argv++;
}
if (argc > 1) {
/* XXX Add error printout here */
}
if (port_data->no_packet_loop){
free(port_data);
exit(0);
}
/*
* If an output file was given, let it replace standard output.
*/
if (port_data->output_file)
replace_stdout(port_data->output_file);
ret = packet_loop();
if(port_data->io_buf_size > 0)
free(port_data->io_buf);
free(port_data);
return ret;
}
static int
packet_loop(void)
{
int total_read = 0;
port_data->io_buf = (unsigned char*) malloc(1); /* Allocate once, so realloc works (SunOS) */
for (;;) {
int packet_length; /* Length of current packet. */
int i;
int bytes_read; /* Number of bytes read. */
/*
* Read the packet header, if any.
*/
if (port_data->header_size == 0) {
if(port_data->limited_bytecount &&
port_data->limited_bytecount - total_read < 4096)
packet_length = port_data->limited_bytecount - total_read;
else
packet_length = 4096;
} else {
ensure_buf_big_enough(port_data->header_size);
if (readn(port_data->fd_from_erl, port_data->io_buf, port_data->header_size) != port_data->header_size) {
return(1);
}
/*
* Get the length of this packet.
*/
packet_length = 0;
for (i = 0; i < port_data->header_size; i++)
packet_length = (packet_length << 8) | port_data->io_buf[i];
}
/*
* Delay if delay mode.
*/
if (port_data->delay_mode) {
delay(5000L);
}
if (port_data->quit_mode) {
return(1);
} else if (port_data->break_mode) {
close(0);
delay(32000L);
return(1);
}
/*
* Read the packet itself.
*/
ensure_buf_big_enough(packet_length+4+1); /* At least five bytes. */
port_data->io_buf[4] = '\0';
if (port_data->header_size == 0) {
bytes_read = read(port_data->fd_from_erl, port_data->io_buf+4, packet_length);
if (bytes_read == 0)
return(1);
if (bytes_read < 0) {
fprintf(stderr, "Error reading %d bytes: %s\n",
packet_length, strerror(errno));
return(1);
}
total_read += bytes_read;
} else {
bytes_read = readn(port_data->fd_from_erl, port_data->io_buf+4, packet_length);
if (bytes_read != packet_length) {
fprintf(stderr, "%s: couldn't read packet of length %d\r\n",
port_data->progname, packet_length);
return(1);
}
}
/*
* Act on the command.
*/
if (port_data->header_size == 0) {
reply(port_data->io_buf+4, bytes_read);
if(port_data->limited_bytecount &&
port_data->limited_bytecount <= total_read){
delay(5000L);
return(0);
}
} else {
switch (port_data->io_buf[4]) {
case 'p': /* ping */
port_data->io_buf[4] = 'P';
reply(port_data->io_buf+4, bytes_read);
break;
case 'e': /* echo */
reply(port_data->io_buf+4, bytes_read);
break;
default:
fprintf(stderr, "%s: bad packet of length %d received: ",
port_data->progname, bytes_read);
dump(port_data->io_buf+4, bytes_read, 10);
fprintf(stderr, "\r\n");
return(1);
}
}
}
}
/*
* Sends a packet back to Erlang.
*/
static void
reply(buf, size)
char* buf; /* Buffer with reply. The four bytes before
* this pointer must be allocated so that
* this function can put the header there.
*/
int size; /* Size of buffer to send. */
{
int n; /* Temporary to hold size. */
int i; /* Loop counter. */
/*
* Fill the header starting with the least significant byte
* (this will work even if there is no header).
*/
n = size;
for (i = 0; i < port_data->header_size; i++) {
*--buf = (char) n; /* Store least significant byte. */
n = n >> 8;
}
size += port_data->header_size;
write_reply(buf, size);
}
static void
write_reply(buf, size)
char* buf; /* Buffer with reply. Must contain header. */
int size; /* Size of buffer to send. */
{
int n; /* Temporary to hold size. */
if (port_data->slow_writes <= 0) { /* Normal, "fast", write. */
write(port_data->fd_to_erl, buf, size);
} else {
/*
* Write chunks with delays in between.
*/
while (size > 0) {
n = size > port_data->slow_writes ? port_data->slow_writes : size;
write(port_data->fd_to_erl, buf, n);
size -= n;
buf += n;
if (size)
delay(500L);
}
}
}
/*
* Ensures that our I/O buffer is big enough for the packet to come.
*/
static void
ensure_buf_big_enough(size)
int size; /* Needed size of buffer. */
{
if (port_data->io_buf_size >= size)
return;
port_data->io_buf = (unsigned char*) realloc(port_data->io_buf, size);
if (port_data->io_buf == NULL) {
fprintf(stderr, "%s: insufficient memory for i/o buffer of size %d\n",
port_data->progname, size);
exit(1);
}
port_data->io_buf_size = size;
}
/*
* Reads len number of bytes.
*/
static int
readn(fd, buf, len)
int fd; /* File descriptor to read from. */
unsigned char *buf; /* Store in this buffer. */
int len; /* Number of bytes to read. */
{
int n; /* Byte count in last read call. */
int sofar; /* Bytes read so far. */
sofar = 0;
do {
if ((n = read(fd, buf+sofar, len-sofar)) <= 0)
/* error or EOF in read */
return(n);
sofar += n;
} while (sofar < len);
return sofar;
}
static void
replace_stdout(filename)
char* filename; /* Name of file to replace standard output. */
{
int fd;
fd = open(filename, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, 0666);
if (fd == -1) {
fprintf(stderr, "%s: failed to open %s for writing: %d\n",
port_data->progname, filename, errno);
exit(1);
}
REDIR_STDOUT(fd);
}
static void
dump(buf, sz, max)
unsigned char* buf;
int sz;
int max;
{
int i, imax;
char comma[5];
comma[0] = ',';
comma[1] = '\0';
if (!sz)
return;
if (sz > max)
imax = max;
else
imax = sz;
for (i=0; i<imax; i++) {
if (i == imax-1) {
if (sz > max)
strcpy(comma, ",...");
else
comma[0] = 0;
}
if (isdigit(buf[i])) {
fprintf(stderr, "%u%s", (int)(buf[i]), comma);
} else {
if (isalpha(buf[i])) {
fprintf(stderr, "%c%s", buf[i], comma);
}
else {
fprintf(stderr, "%u%s", (int)(buf[i]), comma);
}
}
}
}
/*
* Delays (sleeps) the given number of milli-seconds.
*/
static void
delay(unsigned ms)
{
#ifdef __WIN32__
Sleep(ms);
#else
struct timeval t;
t.tv_sec = ms/1000;
t.tv_usec = (ms % 1000) * 1000;
select(0, NULL, NULL, NULL, &t);
#endif
}
/*
* Generates a reply buffer given the specification.
*
* <packet-bytes>,<start-character>,<increment>,<size>
*
* Where:
* <packet-bytes> is
*/
static void
generate_reply(spec)
char* spec; /* Specification for reply. */
{
typedef struct item {
int start; /* Start character. */
int incrementer; /* How much to increment. */
size_t size; /* Size of reply buffer. */
} Item;
Item items[256];
int last;
int cur;
size_t total_size;
char* buf; /* Reply buffer. */
char* s; /* Current pointer into buffer. */
int c;
total_size = 0;
last = 0;
while (*spec) {
char* colon;
items[last].incrementer = 1;
items[last].start = *spec++;
items[last].size = atoi(spec);
total_size += port_data->header_size+items[last].size;
last++;
if ((colon = strchr(spec, ':')) == NULL) {
spec += strlen(spec);
} else {
*colon = '\0';
spec = colon+1;
}
}
buf = (char *) malloc(total_size);
if (buf == NULL) {
fprintf(stderr, "%s: insufficent memory for reply buffer of size %d\n",
port_data->progname, total_size);
exit(1);
}
s = buf;
for (cur = 0; cur < last; cur++) {
int i;
size_t n;
n = items[cur].size;
s += port_data->header_size;
for (i = 0; i < port_data->header_size; i++) {
*--s = (char) n; /* Store least significant byte. */
n = n >> 8;
}
s += port_data->header_size;
c = items[cur].start;
for (i = 0; i < items[cur].size; i++) {
*s++ = c;
c++;
if (c > 126) {
c = 33;
}
}
}
write_reply(buf, s-buf);
}