/* ``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.
*
* The Initial Developer of the Original Code is Ericsson Utvecklings AB.
* Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
* AB. All Rights Reserved.''
*
* $Id$
*/
#ifndef UNIX
#if !defined(__WIN32__)
#define UNIX 1
#endif
#endif
#ifdef UNIX
#include <errno.h>
#include <stdio.h>
#include <stdlib.h> /* rand */
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_POLL_H
# include <poll.h>
#endif
#endif /* UNIX */
#include "erl_driver.h"
#define CHKIO_STOP 0
#define CHKIO_USE_FALLBACK_POLLSET 1
#define CHKIO_BAD_FD_IN_POLLSET 2
#define CHKIO_FD_CHANGE 4
#define CHKIO_STEAL 5
#define CHKIO_STEAL_AUX 6
#define CHKIO_SMP_SELECT 7
#define CHKIO_DRV_USE 8
#define CHKIO_FALLBACK_FDS 10
#define TRACEF(x) /*erts_printf x*/
#ifdef UNIX
typedef struct {
int fd;
int cnt;
} ChkioFallbackFd;
typedef struct {
ChkioFallbackFd dev_null[CHKIO_FALLBACK_FDS];
ChkioFallbackFd dev_zero[CHKIO_FALLBACK_FDS];
ChkioFallbackFd pipe_in[CHKIO_FALLBACK_FDS];
ChkioFallbackFd pipe_out[CHKIO_FALLBACK_FDS];
} ChkioFallbackData;
typedef struct {
int fds[2];
int same_fd;
} ChkioFdChange;
typedef struct {
int fds[2];
} ChkioBadFdInPollset;
typedef struct {
int driver_select_fds[2];
} ChkioSteal;
typedef struct {
int driver_select_fds[2];
} ChkioStealAux;
typedef struct chkio_smp_select {
struct chkio_smp_select* next;
int read_fd;
int write_fd;
int next_read;
int next_write;
int first_write;
enum {Closed, Opened, Selected, Waiting, WaitingDone} state;
int wasSelected;
unsigned rand_state;
}ChkioSmpSelect;
ChkioSmpSelect* smp_pipes;
unsigned smp_pipes_cnt;
ErlDrvMutex* smp_pipes_mtx;
typedef struct {
int script_line;
int fd_in;
int fd_out;
int fd_pipe[2];
volatile int fd_stop_select;
int timeouts_left;
void* expected_callback;
int expected_fd;
}ChkioDrvUse;
static ChkioDrvUse drv_use_singleton;
typedef struct {
ErlDrvPort port;
ErlDrvTermData id;
int test;
void *test_data;
} ChkioDrvData;
#endif /* UNIX */
static int chkio_drv_init(void);
static void chkio_drv_finish(void);
static ErlDrvData chkio_drv_start(ErlDrvPort, char *);
static void chkio_drv_stop(ErlDrvData);
static void chkio_drv_ready_input(ErlDrvData, ErlDrvEvent);
static void chkio_drv_ready_output(ErlDrvData, ErlDrvEvent);
static ErlDrvSSizeT chkio_drv_control(ErlDrvData, unsigned int,
char *, ErlDrvSizeT, char **, ErlDrvSizeT);
static void chkio_drv_timeout(ErlDrvData);
static void chkio_drv_stop_select(ErlDrvEvent, void*);
static ErlDrvEntry chkio_drv_entry = {
chkio_drv_init,
chkio_drv_start,
chkio_drv_stop,
NULL, /* output */
chkio_drv_ready_input,
chkio_drv_ready_output,
"chkio_drv",
chkio_drv_finish,
NULL, /* handle */
chkio_drv_control,
chkio_drv_timeout,
NULL, /* outputv */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL, /* unused_event_callback */
ERL_DRV_EXTENDED_MARKER,
ERL_DRV_EXTENDED_MAJOR_VERSION,
ERL_DRV_EXTENDED_MINOR_VERSION,
ERL_DRV_FLAG_USE_PORT_LOCKING,
NULL,/* void *handle2 */
NULL,/* process_exit */
chkio_drv_stop_select
};
#ifdef UNIX
static void chkio_drv_use(ChkioDrvData *cddp, void* callback);
static void
stop_use_fallback_pollset(ChkioDrvData *cddp)
{
int i;
ChkioFallbackData *cbdp = (ChkioFallbackData *) cddp->test_data;
if (cbdp) {
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
if (cbdp->dev_null[i].fd >= 0) {
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->dev_null[i].fd,
DO_WRITE,
0) != 0) {
fprintf(stderr,
"%s:%d: Failed to deselect dev_null fd=%d\n",
__FILE__, __LINE__, cbdp->dev_null[i].fd);
abort();
}
close(cbdp->dev_null[i].fd);
}
if (cbdp->dev_zero[i].fd >= 0) {
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->dev_zero[i].fd,
DO_READ,
0) != 0) {
fprintf(stderr,
"%s:%d: Failed to deselct dev_zero fd=%d\n",
__FILE__, __LINE__, cbdp->dev_zero[i].fd);
abort();
}
close(cbdp->dev_zero[i].fd);
}
if (cbdp->pipe_in[i].fd >= 0) {
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->pipe_in[i].fd,
DO_READ,
0) != 0) {
fprintf(stderr,
"%s:%d: Failed to deselect pipe_in fd=%d\n",
__FILE__, __LINE__, cbdp->pipe_in[i].fd);
abort();
}
close(cbdp->pipe_in[i].fd);
}
if (cbdp->pipe_out[i].fd >= 0) {
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->pipe_out[i].fd,
DO_WRITE,
0) != 0) {
fprintf(stderr,
"%s:%d: Failed to deselect pipe_out fd=%d\n",
__FILE__, __LINE__, cbdp->pipe_out[i].fd);
abort();
}
close(cbdp->pipe_out[i].fd);
}
}
driver_free((void *) cbdp);
cddp->test_data = NULL;
}
cddp->test = CHKIO_STOP;
}
static void
stop_fd_change(ChkioDrvData *cddp)
{
if (cddp->test_data) {
ChkioFdChange *cfcp = (ChkioFdChange *) cddp->test_data;
cddp->test_data = NULL;
driver_cancel_timer(cddp->port);
if (cfcp->fds[0] >= 0) {
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) cfcp->fds[0], DO_READ, 0);
close(cfcp->fds[0]);
close(cfcp->fds[1]);
}
driver_free((void *) cfcp);
}
}
static void
stop_bad_fd_in_pollset(ChkioDrvData *cddp)
{
if (cddp->test_data) {
ChkioBadFdInPollset *bfipp = (ChkioBadFdInPollset *) cddp->test_data;
cddp->test_data = NULL;
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) bfipp->fds[0], DO_WRITE, 0);
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) bfipp->fds[1], DO_READ, 0);
driver_free((void *) bfipp);
}
}
static void
stop_steal(ChkioDrvData *cddp)
{
if (cddp->test_data) {
ChkioSteal *csp = cddp->test_data;
cddp->test_data = NULL;
if (csp->driver_select_fds[0] >= 0)
driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csp->driver_select_fds[0],
DO_READ,
0);
if (csp->driver_select_fds[1] >= 0)
driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csp->driver_select_fds[1],
DO_WRITE,
0);
driver_free(csp);
}
}
static void
stop_steal_aux(ChkioDrvData *cddp)
{
if (cddp->test_data) {
ChkioStealAux *csap = cddp->test_data;
cddp->test_data = NULL;
if (csap->driver_select_fds[0] >= 0)
close(csap->driver_select_fds[0]);
if (csap->driver_select_fds[1] >= 0)
close(csap->driver_select_fds[1]);
driver_free(csap);
}
}
static void free_smp_select(ChkioSmpSelect* pip, ErlDrvPort port)
{
switch (pip->state) {
case WaitingDone:
case Waiting: {
int word;
fprintf(stderr, "Closing pipe in state Waiting*. Event lost?\r\n");
for (;;) {
int bytes = read(pip->read_fd, &word, sizeof(word));
if (bytes != sizeof(word)) {
if (bytes != 0) {
fprintf(stderr, "Failed to read from pipe, bytes=%d, errno=%d\r\n",
bytes, errno);
}
break;
}
fprintf(stderr, "Read from pipe: %d\r\n", word);
}
abort();
}
case Selected:
case Opened:
TRACEF(("%T: Close pipe [%d->%d]\n", driver_mk_port(port), pip->write_fd,
pip->read_fd));
if (pip->wasSelected)
driver_select(port, (ErlDrvEvent)(ErlDrvSInt)pip->read_fd, DO_READ|ERL_DRV_USE, 0);
else
close(pip->read_fd);
close(pip->write_fd);
pip->state = Closed;
break;
case Closed:
break;
}
driver_free(pip);
}
static void
stop_smp_select(ChkioDrvData *cddp)
{
ChkioSmpSelect* pip = (ChkioSmpSelect*)cddp->test_data;
if (pip) free_smp_select(pip, cddp->port);
erl_drv_mutex_lock(smp_pipes_mtx);
if (smp_pipes_cnt > 0 && --smp_pipes_cnt == 0) {
while (smp_pipes) {
ChkioSmpSelect* next = smp_pipes->next;
free_smp_select(smp_pipes, cddp->port);
smp_pipes = next;
}
}
erl_drv_mutex_unlock(smp_pipes_mtx);
}
#endif /* UNIX */
/* -------------------------------------------------------------------------
** Entry functions
**/
DRIVER_INIT(chkio_drv)
{
return &chkio_drv_entry;
}
static int
chkio_drv_init(void)
{
#ifdef UNIX
smp_pipes_mtx = erl_drv_mutex_create("smp_pipes_mtx");
#endif
return 0;
}
static void
chkio_drv_finish(void)
{
#ifdef UNIX
erl_drv_mutex_destroy(smp_pipes_mtx);
#endif
}
static ErlDrvData
chkio_drv_start(ErlDrvPort port, char *command)
{
#ifndef UNIX
return NULL;
#else
ChkioDrvData *cddp = driver_alloc(sizeof(ChkioDrvData));
if (!cddp) {
errno = ENOMEM;
return ERL_DRV_ERROR_ERRNO;
}
cddp->port = port;
cddp->id = driver_mk_port(port);
cddp->test = CHKIO_STOP;
cddp->test_data = NULL;
drv_use_singleton.fd_stop_select = -2; /* disable stop_select asserts */
return (ErlDrvData) cddp;
#endif
}
static void
chkio_drv_stop(ErlDrvData drv_data) {
#ifdef UNIX
int fd;
ChkioDrvData *cddp = (ChkioDrvData *) drv_data;
switch (cddp->test) {
case CHKIO_STOP:
break;
case CHKIO_USE_FALLBACK_POLLSET:
stop_use_fallback_pollset(cddp);
break;
case CHKIO_BAD_FD_IN_POLLSET:
stop_bad_fd_in_pollset(cddp);
break;
case CHKIO_FD_CHANGE:
stop_fd_change(cddp);
break;
case CHKIO_STEAL:
stop_steal(cddp);
break;
case CHKIO_STEAL_AUX:
stop_steal_aux(cddp);
break;
case CHKIO_SMP_SELECT:
stop_smp_select(cddp);
break;
case CHKIO_DRV_USE:
chkio_drv_use(cddp, chkio_drv_stop);
break;
default:
fprintf(stderr, "%s:%d: Invalid state\n", __FILE__, __LINE__);
abort();
break;
}
cddp->test = CHKIO_STOP;
/* Make sure erts_poll() will handle update requests soon */
fd = open("/dev/null", O_WRONLY);
if (fd < 0) {
fprintf(stderr, "%s:%d: Failed to open /dev/null\n",
__FILE__, __LINE__);
}
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) fd, DO_WRITE, 1);
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) fd, DO_WRITE, 0);
close(fd);
driver_free((void *) cddp);
#endif
}
static void
chkio_drv_ready_output(ErlDrvData drv_data, ErlDrvEvent event)
{
#ifdef UNIX
ChkioDrvData *cddp = (ChkioDrvData *) drv_data;
int fd = (int) (ErlDrvSInt) event;
switch (cddp->test) {
case CHKIO_USE_FALLBACK_POLLSET: {
int i;
int fd_found = 0;
ChkioFallbackData *cbdp = (ChkioFallbackData *) cddp->test_data;
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
if (cbdp->dev_null[i].fd == fd) {
cbdp->dev_null[i].cnt++;
fd_found = 1;
break;
}
if (cbdp->pipe_out[i].fd == fd) {
cbdp->pipe_out[i].cnt++;
fd_found = 1;
break;
}
}
if (!fd_found)
driver_failure_atom(cddp->port, "output_fd_not_found");
break;
}
case CHKIO_STEAL:
break;
case CHKIO_STEAL_AUX:
break;
case CHKIO_DRV_USE:
chkio_drv_use(cddp, chkio_drv_ready_output);
break;
default:
driver_failure_atom(cddp->port, "unexpected_ready_output");
break;
}
#endif
}
static void
chkio_drv_ready_input(ErlDrvData drv_data, ErlDrvEvent event)
{
#ifdef UNIX
ChkioDrvData *cddp = (ChkioDrvData *) drv_data;
int fd = (int) (ErlDrvSInt) event;
switch (cddp->test) {
case CHKIO_USE_FALLBACK_POLLSET: {
int i;
int fd_found = 0;
ChkioFallbackData *cbdp = (ChkioFallbackData *) cddp->test_data;
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
if (cbdp->dev_zero[i].fd == fd) {
cbdp->dev_zero[i].cnt++;
fd_found = 1;
break;
}
if (cbdp->pipe_in[i].fd == fd) {
cbdp->pipe_in[i].cnt++;
fd_found = 1;
break;
}
}
if (!fd_found)
driver_failure_atom(cddp->port, "input_fd_not_found");
break;
}
case CHKIO_FD_CHANGE:
/* This may be triggered when an fd is closed while being selected on. */
break;
case CHKIO_STEAL:
break;
case CHKIO_STEAL_AUX:
break;
case CHKIO_SMP_SELECT: {
ChkioSmpSelect* pip = (ChkioSmpSelect*) cddp->test_data;
int word=123456, bytes;
unsigned inPipe, n;
if (pip == NULL) {
printf("Read event on uninitiated pipe %d\n", fd);
abort();
}
if (pip->state != Selected && pip->state != Waiting && pip->state != WaitingDone) {
printf("Read event on pipe in strange state %d\n", pip->state);
abort();
}
TRACEF(("Got read event on fd=%d, state=%d\n", fd, pip->state));
inPipe = (pip->next_write - pip->next_read);
if (inPipe == 0) {
bytes = read(pip->read_fd, &word, sizeof(word));
printf("Unexpected empty pipe: ptr=%p, fds=%d->%d, read bytes=%d, word=%d, written=%d\n",
pip, pip->write_fd, pip->read_fd,
bytes, word, (pip->next_write - pip->first_write));
/*abort();
Allow unexpected events as it's been seen to be triggered by epoll
on Linux. Most of the time the unwanted events are filtered by
the erl_check_io layer. But when fd's are reused the events may
slip up to the driver.
*/
break;
}
n = rand_r(&pip->rand_state) % (inPipe*4);
if (n > inPipe) n = inPipe;
TRACEF(("Read %u of %u words in pipe\n", n, inPipe));
for (; n; n--) {
bytes = read(pip->read_fd, &word, sizeof(word));
if (bytes != sizeof(word)) {
printf("Failed to read from pipe, ret=%u errno=%d\n", bytes, errno);
abort();
}
if (word != pip->next_read) {
printf("Unexpected word in pipe %d, expected %d\n", word, pip->next_read);
abort();
}
TRACEF(("Read %d from fd=%d\n", word, fd));
pip->next_read++;
}
if (pip->state == WaitingDone) {
if (pip->next_write == pip->next_read) {
/* All data read, send {Port, done} */
ErlDrvTermData spec[] = {ERL_DRV_PORT, driver_mk_port(cddp->port),
ERL_DRV_ATOM, driver_mk_atom("done"),
ERL_DRV_TUPLE, 2};
erl_drv_output_term(driver_mk_port(cddp->port),
spec, sizeof(spec) / sizeof(spec[0]));
pip->state = Selected;
}
}
else {
pip->state = Selected; /* not Waiting anymore */
}
break;
}
case CHKIO_DRV_USE:
chkio_drv_use(cddp, chkio_drv_ready_input);
break;
default:
driver_failure_atom(cddp->port, "unexpected_ready_input");
break;
}
#endif
}
static void
chkio_drv_timeout(ErlDrvData drv_data)
{
#ifdef UNIX
ChkioDrvData *cddp = (ChkioDrvData *) drv_data;
switch (cddp->test) {
case CHKIO_FD_CHANGE: {
ChkioFdChange *cfcp = cddp->test_data;
int in_fd = cfcp->fds[0];
int out_fd = cfcp->fds[1];
if (in_fd >= 0) {
if (driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) in_fd, DO_READ, 0) < 0)
driver_failure_atom(cddp->port, "deselect_failed");
(void) write(out_fd, (void *) "!", 1);
close(out_fd);
close(in_fd);
}
if (pipe(cfcp->fds) < 0) {
driver_failure_posix(cddp->port, errno);
}
else {
if (driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) cfcp->fds[0],
DO_READ, 1) < 0)
driver_failure_atom(cddp->port, "select_failed");
if (cfcp->fds[0] == in_fd)
cfcp->same_fd++;
if (driver_set_timer(cddp->port, 10) < 0)
driver_failure_atom(cddp->port, "set_timer_failed");
}
break;
}
case CHKIO_DRV_USE:
chkio_drv_use(cddp, chkio_drv_timeout);
break;
default:
driver_failure_atom(cddp->port, "unexpected_driver_timeout");
break;
}
#endif /* UNIX */
}
static ErlDrvSSizeT
chkio_drv_control(ErlDrvData drv_data,
unsigned int command,
char *buf, ErlDrvSizeT len,
char **rbuf, ErlDrvSizeT rlen)
{
char *res_str;
ErlDrvSSizeT res_len = -1;
#ifndef UNIX
#ifdef __WIN32__
res_str = "skip: windows_different";
#else
res_str = "nyiftos";
#endif
#else
ChkioDrvData *cddp = (ChkioDrvData *) drv_data;
res_len = 0;
switch (command) {
case CHKIO_STOP: {
/*
* --- STOP BEGIN ---------------------------------------------------
*/
switch (cddp->test) {
case CHKIO_STOP:
driver_failure_atom(cddp->port, "stop_when_stopped");
break;
case CHKIO_USE_FALLBACK_POLLSET: {
char *c;
int i;
ChkioFallbackData *cbdp = (ChkioFallbackData *) cddp->test_data;
c = driver_alloc(sizeof(char)*(4*20+21*CHKIO_FALLBACK_FDS*8));
if (!c)
return 0;
*rbuf = c;
c += sprintf(c, "/dev/null: ");
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
c += sprintf(c, "%d=%d ",
cbdp->dev_null[i].fd,
cbdp->dev_null[i].cnt);
}
c += sprintf(c, "\n/dev/zero: ");
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
c += sprintf(c, "%d=%d ",
cbdp->dev_zero[i].fd,
cbdp->dev_zero[i].cnt);
}
c += sprintf(c, "\npipe_in: ");
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
c += sprintf(c, "%d=%d ",
cbdp->pipe_in[i].fd,
cbdp->pipe_in[i].cnt);
}
c += sprintf(c, "\npipe_out: ");
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
c += sprintf(c, "%d=%d ",
cbdp->pipe_out[i].fd,
cbdp->pipe_out[i].cnt);
}
c += sprintf(c, "\n");
res_len = (int) (c - *rbuf);
stop_use_fallback_pollset(cddp);
break;
}
case CHKIO_BAD_FD_IN_POLLSET:
res_str = "ok";
res_len = -1;
stop_bad_fd_in_pollset(cddp);
break;
case CHKIO_FD_CHANGE: {
ChkioFdChange *cfcp = cddp->test_data;
if (!cfcp->same_fd)
driver_failure_atom(cddp->port, "never_same_fd");
else {
char *c = driver_alloc(sizeof(char)*30);
if (!c)
driver_failure_posix(cddp->port, ENOMEM);
else {
*rbuf = c;
res_len = sprintf(c, "same_fd=%d\n", cfcp->same_fd);
}
}
stop_fd_change(cddp);
break;
}
case CHKIO_STEAL:
stop_steal(cddp);
res_str = "ok";
res_len = -1;
break;
case CHKIO_STEAL_AUX:
stop_steal_aux(cddp);
res_str = "ok";
res_len = -1;
break;
default:
driver_failure_atom(cddp->port, "invalid_state");
break;
}
break;
}
/*
* --- STOP END -----------------------------------------------------
*/
case CHKIO_USE_FALLBACK_POLLSET: {
ChkioFallbackData *cbdp = driver_alloc(sizeof(ChkioFallbackData));
cddp->test_data = (void *) cbdp;
if (!cbdp)
driver_failure_posix(cddp->port, ENOMEM);
else {
int i;
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
cbdp->dev_null[i].fd = -1;
cbdp->dev_null[i].cnt = 0;
cbdp->dev_zero[i].fd = -1;
cbdp->dev_zero[i].cnt = 0;
cbdp->pipe_in[i].fd = -1;
cbdp->pipe_in[i].cnt = 0;
cbdp->pipe_out[i].fd = -1;
cbdp->pipe_out[i].cnt = 0;
}
for (i = 0; i < CHKIO_FALLBACK_FDS; i++) {
int fds[2];
cbdp->dev_null[i].fd = open("/dev/null", O_WRONLY);
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->dev_null[i].fd,
DO_WRITE,
1) != 0) {
driver_failure_posix(cddp->port, errno);
break;
}
cbdp->dev_zero[i].fd = open("/dev/zero", O_RDONLY);
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->dev_zero[i].fd,
DO_READ,
1) != 0) {
driver_failure_posix(cddp->port, errno);
break;
}
if (pipe(fds) < 0)
driver_failure_posix(cddp->port, errno);
cbdp->pipe_in[i].fd = fds[0];
cbdp->pipe_out[i].fd = fds[1];
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->pipe_in[i].fd,
DO_READ,
1) != 0) {
driver_failure_posix(cddp->port, EIO);
break;
}
if (i % 2 == 0)
(void) write(cbdp->pipe_out[i].fd, "!", 1);
if (driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) cbdp->pipe_out[i].fd,
DO_WRITE,
1) != 0) {
driver_failure_posix(cddp->port, EIO);
break;
}
}
res_str = "ok";
res_len = -1;
}
break;
}
case CHKIO_BAD_FD_IN_POLLSET: {
int i;
int error = 0;
int fds[11];
for (i = 0; i < 11; i++)
fds[i] = -1;
/* We open a bunch of fds and use the last ones so we decrease the
risk of selecting on a fd that someone else just opened */
for (i = 0; i < 10; i++) {
fds[i] = open("/dev/null", O_WRONLY);
if (fds[i] < 0) {
error = 1;
driver_failure_posix(cddp->port, errno);
break;
}
}
fds[10] = open("/dev/zero", O_RDONLY);
if (fds[10] < 0) {
error = 1;
driver_failure_posix(cddp->port, errno);
}
for (i = 0; i < 11; i++) {
if (fds[i] >= 0)
close(fds[i]);
}
if (!error) {
ChkioBadFdInPollset *bfipp;
bfipp = driver_alloc(sizeof(ChkioBadFdInPollset));
if (!bfipp)
driver_failure_posix(cddp->port, ENOMEM);
else {
bfipp->fds[0] = fds[9];
bfipp->fds[1] = fds[10];
cddp->test_data = (void *) bfipp;
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) fds[9], DO_WRITE, 1);
driver_select(cddp->port, (ErlDrvEvent) (ErlDrvSInt) fds[10], DO_READ, 1);
}
}
res_str = "ok";
res_len = -1;
break;
}
case CHKIO_FD_CHANGE: {
ChkioFdChange *cfcp = driver_alloc(sizeof(ChkioFdChange));
if (!cfcp)
driver_failure_posix(cddp->port, ENOMEM);
else {
cfcp->fds[0] = -1;
cfcp->fds[1] = -1;
cfcp->same_fd = 0;
cddp->test_data = cfcp;
driver_set_timer(cddp->port, 1);
res_str = "ok";
res_len = -1;
}
break;
}
case CHKIO_STEAL: {
ChkioSteal *csp = driver_alloc(sizeof(ChkioSteal));
char *c = driver_alloc(sizeof(char)*len+1);
if (!c || !csp) {
if (c)
driver_free(c);
if (csp)
driver_free(csp);
driver_failure_posix(cddp->port, ENOMEM);
res_str = "error";
res_len = -1;
}
else {
int driver_select_fds[2];
cddp->test_data = csp;
memcpy(c, buf, len);
c[len] = '\0';
if (sscanf(c,
"fds:%d:%d",
&driver_select_fds[0],
&driver_select_fds[1]) != 2)
driver_failure_atom(cddp->port, "bad_input");
else {
int res = 0;
csp->driver_select_fds[0] = driver_select_fds[0]; /* In */
csp->driver_select_fds[1] = driver_select_fds[1]; /* Out */
/* Steal with driver_select() */
if (res >= 0) {
res = driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csp->driver_select_fds[0],
DO_READ,
1);
if (res < 0)
driver_failure_atom(cddp->port,
"driver_select_failed_to_steal");
}
if (res >= 0) {
res = driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csp->driver_select_fds[1],
DO_WRITE,
1);
if (res < 0)
driver_failure_atom(cddp->port,
"driver_select_failed_to_steal");
}
res_str = res >= 0 ? "ok" : "error";
res_len = -1;
}
driver_free(c);
}
break;
}
case CHKIO_STEAL_AUX: {
int read_fd;
int write_fd;
read_fd = open("/dev/zero", O_RDONLY);
write_fd = open("/dev/null", O_WRONLY);
if (read_fd < 0 || write_fd < 0) {
if (read_fd < 0)
close(read_fd);
if (write_fd < 0)
close(write_fd);
driver_failure_posix(cddp->port, errno);
}
else {
ChkioStealAux *csap = driver_alloc(sizeof(ChkioStealAux));
if (!csap) {
driver_failure_posix(cddp->port, ENOMEM);
res_str = "error";
res_len = -1;
}
else {
int res;
cddp->test_data = csap;
csap->driver_select_fds[0] = read_fd;
csap->driver_select_fds[1] = write_fd;
res = driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csap->driver_select_fds[0],
DO_READ,
1);
if (res < 0)
driver_failure_atom(cddp->port, "driver_select_failed");
if (res >= 0) {
res = driver_select(cddp->port,
(ErlDrvEvent) (ErlDrvSInt) csap->driver_select_fds[1],
DO_WRITE,
1);
if (res < 0)
driver_failure_atom(cddp->port, "driver_select_failed");
}
if (res < 0) {
res_str = "error";
res_len = -1;
}
else {
char *c = driver_alloc(sizeof(char)*(3+4*21+1));
if (!c) {
res_str = "error";
res_len = -1;
driver_failure_posix(cddp->port, ENOMEM);
}
else {
*rbuf = c;
res_len = sprintf(c,
"fds:%d:%d",
csap->driver_select_fds[0],
csap->driver_select_fds[1]);
}
}
}
}
break;
}
case CHKIO_SMP_SELECT: {
ChkioSmpSelect* pip = (ChkioSmpSelect*) cddp->test_data;
if (len == 4 && memcmp(buf, "done", 4) == 0) {
if (pip && pip->state == Waiting) {
pip->state = WaitingDone;
res_str = "wait";
}
else
res_str = "ok";
res_len = -1;
break;
}
if (pip == NULL) {
erl_drv_mutex_lock(smp_pipes_mtx);
if (smp_pipes) {
pip = smp_pipes;
smp_pipes = smp_pipes->next;
}
else {
cddp->test_data = driver_alloc(sizeof(ChkioSmpSelect));
pip = (ChkioSmpSelect*) cddp->test_data;
pip->state = Closed;
pip->rand_state = 1;
smp_pipes_cnt++;
}
erl_drv_mutex_unlock(smp_pipes_mtx);
}
res_str = NULL;
{
int op = rand_r(&pip->rand_state);
switch (pip->state) {
case Closed: {
int fds[2], flags;
if (pipe(fds) < 0 ||
(flags = fcntl(fds[0], F_GETFL, 0)) < 0 ||
fcntl(fds[0], F_SETFL, flags|O_NONBLOCK) < 0) {
driver_failure_posix(cddp->port, errno);
break;
}
TRACEF(("%T: Created pipe [%d->%d]\n", cddp->id, fds[1], fds[0]));
pip->read_fd = fds[0];
pip->write_fd = fds[1];
pip->state = Opened;
pip->wasSelected = 0;
pip->next_write = pip->next_read = rand_r(&pip->rand_state) % 1024;
pip->first_write = pip->next_write;
if (op & 1) break;
op >>= 1;
}/*fall through*/
case Opened: {
if (op & 1) {
TRACEF(("%T: Write %d to opened pipe [%d->%d]\n", cddp->id,
pip->next_write, pip->write_fd, pip->read_fd));
if (write(pip->write_fd, &pip->next_write, sizeof(int)) != sizeof(int)) {
fprintf(stderr, "Failed to write to pipe fd=%d, errno=%d\n", pip->write_fd, errno);
abort();
}
pip->next_write++;
}
op >>= 1;
if (pip->wasSelected && (op & 1)) {
TRACEF(("%T: Close pipe [%d->%d]\n", cddp->id, pip->write_fd,
pip->read_fd));
if (driver_select(cddp->port, (ErlDrvEvent)(ErlDrvSInt)pip->read_fd,
DO_READ|ERL_DRV_USE, 0)
|| close(pip->write_fd)) {
fprintf(stderr, "Failed to close pipe, errno=%d\n", errno);
abort();
}
pip->state = Closed;
break;
}
else {
TRACEF(("%T: Select on pipe [%d->%d]\n", cddp->id,
pip->write_fd, pip->read_fd));
if (driver_select(cddp->port, (ErlDrvEvent)(ErlDrvSInt)pip->read_fd,
DO_READ|ERL_DRV_USE, 1)) {
fprintf(stderr, "driver_select failed for fd=%d\n", pip->read_fd);
abort();
}
pip->state = Selected;
pip->wasSelected = 1;
op >>= 1;
if (pip->next_write != pip->next_read) { /* pipe not empty */
if (op & 1) {
pip->state = Waiting; /* Wait for reader */
break;
}
op >>= 1;
}
}
}/*fall through*/
case Selected:
if (op & 1) {
TRACEF(("%T: Write %d to selected pipe [%d->%d]\n", cddp->id,
pip->next_write, pip->write_fd, pip->read_fd));
if (write(pip->write_fd, &pip->next_write, sizeof(int)) != sizeof(int)) {
fprintf(stderr, "Failed to write to pipe fd=%d, errno=%d\n", pip->write_fd, errno);
abort();
}
pip->next_write++;
}
op >>= 1;
if (op & 1) {
TRACEF(("%T: Deselect on pipe [%d->%d]\n", cddp->id, pip->write_fd, pip->read_fd));
if (driver_select(cddp->port, (ErlDrvEvent)(ErlDrvSInt)pip->read_fd, DO_READ, 0)) {
fprintf(stderr, "driver_(de)select failed for fd=%d\n", pip->read_fd);
abort();
}
pip->state = Opened;
}
op >>= 1;
if (op & 1) {
TRACEF(("%T: Write %d to pipe [%d->%d] state=%d\n", cddp->id,
pip->next_write, pip->write_fd, pip->read_fd, pip->state));
if (write(pip->write_fd, &pip->next_write, sizeof(int)) != sizeof(int)) {
fprintf(stderr, "Failed to write to pipe fd=%d, errno=%d\n", pip->write_fd, errno);
abort();
}
pip->next_write++;
}
break;
case Waiting:
res_str = "yield";
res_len = -1;
break;
default:
fprintf(stderr, "Strange state %d\n", pip->state);
abort();
}
if (pip->state == Opened) { /* share unselected pipes with others */
erl_drv_mutex_lock(smp_pipes_mtx);
pip->next = smp_pipes;
smp_pipes = pip;
erl_drv_mutex_unlock(smp_pipes_mtx);
cddp->test_data = NULL;
}
else {
cddp->test_data = pip;
}
}
if (!res_str) {
res_str = "ok";
res_len = -1;
}
break;
}
case CHKIO_DRV_USE:
chkio_drv_use(cddp, chkio_drv_control);
res_str = "ok";
res_len = -1;
break;
default:
driver_failure_atom(cddp->port, "invalid_state");
break;
}
cddp->test = command;
#endif /* UNIX */
if (res_len >= 0)
return res_len;
res_len = strlen(res_str);
if (res_len > rlen) {
char *abuf = driver_alloc(sizeof(char)*res_len);
if (!abuf)
return 0;
*rbuf = abuf;
}
memcpy((void *) *rbuf, (void *) res_str, res_len);
return res_len;
}
#ifdef UNIX
#define ASSERT(cond) \
do{ \
if (!(cond)) { assert_failed(cddp->port, #cond, __LINE__); return; } \
/*else fprintf(stderr, "Assertion '%s' at line %d: OK\r\n", #cond, __LINE__);*/ \
}while(0)
static void assert_print(char* str, int line)
{
fprintf(stderr, "Assertion '%s' at line %d: FAILED\r\n", str, line);
}
static void assert_failed(ErlDrvPort port, char* str, int line)
{
char buf[30];
size_t bufsz = sizeof(buf);
assert_print(str,line);
if (erl_drv_getenv("ERL_ABORT_ON_FAILURE", buf, &bufsz) == 0
&& (strcmp("true", buf) == 0 || strcmp("yes", buf) == 0)) {
abort();
}
else {
snprintf(buf,sizeof(buf),"failed_at_line_%d",line);
driver_failure_atom(port,buf);
}
}
#define my_driver_select(PORT,FD,MODE,ON) \
do{ if(driver_select(PORT, (ErlDrvEvent)(long)FD, MODE, ON) != 0) { \
assert_failed(cddp->port, "driver_select", __LINE__); \
return; \
} \
}while(0)
static void chkio_drv_use(ChkioDrvData *cddp, void* callback)
{
ChkioDrvUse* cdu = (ChkioDrvUse*) cddp->test_data;
int fd_stop_select = -1;
/*fprintf(stderr, "Callback: %p\r\n", callback);*/
if (cdu == NULL) {
int ret;
ASSERT(callback == chkio_drv_control);
cdu = &drv_use_singleton;
ASSERT(cdu->script_line == 0);
cddp->test_data = cdu;
cdu->fd_stop_select = -1;
cdu->script_line = 1;
cdu->fd_in = open("/dev/zero", O_RDONLY);
ASSERT(cdu->fd_in > 0);
cdu->fd_out = open("/dev/null", O_WRONLY);
ASSERT(cdu->fd_out > 0);
ret = pipe(cdu->fd_pipe);
ASSERT(ret == 0);
}
else {
if (callback == chkio_drv_timeout) {
if (cdu->fd_stop_select >= 0) {
fd_stop_select = cdu->fd_stop_select;
cdu->fd_stop_select = -1;
fprintf(stderr,"timeout detected stop_select fd=%d\r\n", fd_stop_select);
callback = chkio_drv_stop_select;
ASSERT(fd_stop_select == cdu->expected_fd);
}
else if (--cdu->timeouts_left > 0) {
driver_set_timer(cddp->port, 100);
return;
}
}
ASSERT(callback == cdu->expected_callback);
}
#define NEXT_CALLBACK(fn) \
cdu->expected_callback = fn; \
/*fprintf(stderr, "Next expected callback: %p\r\n", fn);*/ \
cdu->script_line = __LINE__; break; case __LINE__: \
fprintf(stderr, "Script line %d\r\n", cdu->script_line)
switch (cdu->script_line) {
case 1:
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_READ|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_ready_input);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_READ|ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_in;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_WRITE|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_ready_output);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_WRITE|ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_out;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_READ|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_ready_input);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_READ, 0);
NEXT_CALLBACK(chkio_drv_timeout);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_WRITE|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_ready_output);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_WRITE, 0);
NEXT_CALLBACK(chkio_drv_timeout);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_in;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_out;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_READ, 1);
NEXT_CALLBACK(chkio_drv_ready_input);
my_driver_select(cddp->port, cdu->fd_in, ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_in;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_WRITE, 1);
NEXT_CALLBACK(chkio_drv_ready_output);
my_driver_select(cddp->port, cdu->fd_out, ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_out;
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_pipe[0], ERL_DRV_READ|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_timeout);
my_driver_select(cddp->port, cdu->fd_pipe[0], ERL_DRV_USE, 0);
my_driver_select(cddp->port, cdu->fd_pipe[0], ERL_DRV_READ|ERL_DRV_USE, 1);
/* stop_select may or may not have been called up until now.
In either case it should not be called from here on. */
cdu->fd_stop_select = -1;
NEXT_CALLBACK(chkio_drv_timeout);
my_driver_select(cddp->port, cdu->fd_pipe[0], ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_pipe[0];
NEXT_CALLBACK(chkio_drv_stop_select);
/* switch off USE again */
my_driver_select(cddp->port, cdu->fd_pipe[0], ERL_DRV_USE, 0);
cdu->expected_fd = cdu->fd_pipe[0];
NEXT_CALLBACK(chkio_drv_stop_select);
my_driver_select(cddp->port, cdu->fd_pipe[1], ERL_DRV_READ|ERL_DRV_WRITE|ERL_DRV_USE, 1);
NEXT_CALLBACK(chkio_drv_ready_output);
/* ERL_DRV_USE_NO_CALLBACK does not clear all */
my_driver_select(cddp->port, cdu->fd_pipe[1], ERL_DRV_READ|ERL_DRV_USE_NO_CALLBACK, 0);
NEXT_CALLBACK(chkio_drv_ready_output);
my_driver_select(cddp->port, cdu->fd_pipe[1], ERL_DRV_WRITE|ERL_DRV_USE_NO_CALLBACK, 0);
NEXT_CALLBACK(chkio_drv_timeout);
cdu->script_line = 0; /* The End */
cdu->expected_callback = chkio_drv_stop;
break;
case 0: /* close port */
ASSERT(cdu->fd_stop_select < 0);
close(cdu->fd_in); cdu->fd_in = -1;
close(cdu->fd_out); cdu->fd_out = -1;
close(cdu->fd_pipe[0]); cdu->fd_pipe[0] = -1;
close(cdu->fd_pipe[1]); cdu->fd_pipe[1] = -1;
/*driver_free(cdu); No, it's static */
return;
default:
ASSERT(0);
}
if (cdu->script_line) {
driver_set_timer(cddp->port, 100);
cdu->timeouts_left = 5;
}
else {
if (callback != chkio_drv_timeout) {
driver_cancel_timer(cddp->port);
}
driver_output(cddp->port, "TheEnd", 6);
}
}
#endif /* UNIX */
static void chkio_drv_stop_select(ErlDrvEvent e, void* null)
{
#ifdef UNIX
/*fprintf(stderr,"STOP_SELECT\r\n");*/
if (!(null == NULL)) {
assert_print("null==NULL", __LINE__); abort();
}
if (!(drv_use_singleton.fd_stop_select < 0)) {
assert_print("fd_stop_select<0", __LINE__); abort();
}
/* fd_stop_select counting is disabled if this is set to -2 */
if (drv_use_singleton.fd_stop_select == -2) {
TRACEF(("closing %d\n", (int)(long)e));
close((int)(long)e);
} else
drv_use_singleton.fd_stop_select = (int)(long)e;
/* Can't call chkio_drv_use directly here. That could even be recursive.
* Next timeout will detect it instead.
*/
#endif /* UNIX */
}