Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

8968 lines (7183 sloc) 195.914 kb
/* File: "os_io.c" */
/* Copyright (c) 1994-2012 by Marc Feeley, All Rights Reserved. */
/*
* This module implements the operating system specific routines
* related to I/O.
*/
#define ___INCLUDED_FROM_OS_IO
#define ___VERSION 406004
#include "gambit.h"
#include "os_base.h"
#include "os_io.h"
#include "os_tty.h"
#include "os_files.h"
#include "setup.h"
#include "c_intf.h"
/*---------------------------------------------------------------------------*/
___io_module ___io_mod =
{
0,
0
#ifdef ___IO_MODULE_INIT
___IO_MODULE_INIT
#endif
};
/*---------------------------------------------------------------------------*/
/* Device groups. */
___SCMOBJ ___device_group_setup
___P((___device_group **dgroup),
(dgroup)
___device_group **dgroup;)
{
___SCMOBJ e;
___device_group *g;
g = ___CAST(___device_group*,
___alloc_mem (sizeof (___device_group)));
if (g == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
g->list = NULL;
*dgroup = g;
return ___FIX(___NO_ERR);
}
void ___device_group_cleanup
___P((___device_group *dgroup),
(dgroup)
___device_group *dgroup;)
{
while (dgroup->list != NULL)
if (___device_cleanup (dgroup->list) != ___FIX(___NO_ERR))
break;
___free_mem (dgroup);
}
void ___device_add_to_group
___P((___device_group *dgroup,
___device *dev),
(dgroup,
dev)
___device_group *dgroup;
___device *dev;)
{
___device *head = dgroup->list;
dev->group = dgroup;
if (head == NULL)
{
dev->next = dev;
dev->prev = dev;
dgroup->list = dev;
}
else
{
___device *tail = head->prev;
dev->next = head;
dev->prev = tail;
tail->next = dev;
head->prev = dev;
}
}
void ___device_remove_from_group
___P((___device *dev),
(dev)
___device *dev;)
{
___device_group *dgroup = dev->group;
___device *prev = dev->prev;
___device *next = dev->next;
if (prev == dev)
dgroup->list = NULL;
else
{
if (dgroup->list == dev)
dgroup->list = next;
prev->next = next;
next->prev = prev;
dev->next = dev;
dev->prev = dev;
}
dev->group = NULL;
}
___device_group *___global_device_group ___PVOID
{
return ___io_mod.dgroup;
}
/*---------------------------------------------------------------------------*/
/* Nonblocking pipes */
#ifdef USE_PUMPS
___HIDDEN ___SCMOBJ ___nonblocking_pipe_setup
___P((___nonblocking_pipe *pipe,
int size),
(pipe,
size)
___nonblocking_pipe *pipe;
int size;)
{
___U8 *buffer;
HANDLE mutex;
HANDLE revent;
HANDLE wevent;
buffer = ___CAST(___U8*,
___alloc_mem (size));
if (buffer == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
mutex = CreateMutex (NULL, /* can't inherit */
FALSE, /* unlocked */
NULL); /* no name */
if (mutex == NULL)
{
___SCMOBJ e = err_code_from_GetLastError ();
___free_mem (buffer);
return e;
}
revent = CreateEvent (NULL, /* can't inherit */
TRUE, /* manual reset */
FALSE, /* not signaled */
NULL); /* no name */
if (revent == NULL)
{
___SCMOBJ e = err_code_from_GetLastError ();
CloseHandle (mutex); /* ignore error */
___free_mem (buffer);
return e;
}
wevent = CreateEvent (NULL, /* can't inherit */
TRUE, /* manual reset */
FALSE, /* not signaled */
NULL); /* no name */
if (wevent == NULL)
{
___SCMOBJ e = err_code_from_GetLastError ();
CloseHandle (revent); /* ignore error */
CloseHandle (mutex); /* ignore error */
___free_mem (buffer);
return e;
}
pipe->mutex = mutex;
pipe->revent = revent;
pipe->wevent = wevent;
pipe->rerr = ___FIX(___NO_ERR);
pipe->werr = ___FIX(___NO_ERR);
pipe->oob = 0;
pipe->rd = 0;
pipe->wr = 0;
pipe->size = size;
pipe->buffer = buffer;
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_cleanup
___P((___nonblocking_pipe *pipe),
(pipe)
___nonblocking_pipe *pipe;)
{
CloseHandle (pipe->wevent); /* ignore error */
CloseHandle (pipe->revent); /* ignore error */
CloseHandle (pipe->mutex); /* ignore error */
___free_mem (pipe->buffer);
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_set_reader_err
___P((___nonblocking_pipe *pipe,
___SCMOBJ err),
(pipe,
err)
___nonblocking_pipe *pipe;
___SCMOBJ err;)
{
if (WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
/* note: the reader error indicator may get overwritten */
pipe->rerr = err;
SetEvent (pipe->wevent); /* ignore error */
if (pipe->werr == ___FIX(___NO_ERR))
ResetEvent (pipe->revent); /* ignore error */
ReleaseMutex (pipe->mutex); /* ignore error */
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_set_writer_err
___P((___nonblocking_pipe *pipe,
___SCMOBJ err),
(pipe,
err)
___nonblocking_pipe *pipe;
___SCMOBJ err;)
{
if (WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
/* note: the writer error indicator may get overwritten */
pipe->werr = err;
SetEvent (pipe->revent); /* ignore error */
if (pipe->rerr == ___FIX(___NO_ERR))
ResetEvent (pipe->wevent); /* ignore error */
ReleaseMutex (pipe->mutex); /* ignore error */
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_write_oob
___P((___nonblocking_pipe *pipe,
___nonblocking_pipe_oob_msg *oob_msg),
(pipe,
oob_msg)
___nonblocking_pipe *pipe;
___nonblocking_pipe_oob_msg *oob_msg;)
{
if (WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
if (pipe->werr != ___FIX(___NO_ERR) || pipe->oob)
{
ReleaseMutex (pipe->mutex); /* ignore error */
return ___ERR_CODE_EAGAIN;
}
pipe->oob = 1;
pipe->oob_msg = *oob_msg;
if (pipe->rerr == ___FIX(___NO_ERR))
{
SetEvent (pipe->revent); /* ignore error */
ResetEvent (pipe->wevent); /* ignore error */
}
ReleaseMutex (pipe->mutex); /* ignore error */
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_read_ready_wait
___P((___nonblocking_pipe *pipe),
(pipe)
___nonblocking_pipe *pipe;)
{
___SCMOBJ werr;
if (WaitForSingleObject (pipe->revent, INFINITE) == WAIT_FAILED ||
WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
werr = pipe->werr;
if (werr != ___FIX(___NO_ERR))
{
pipe->werr = ___FIX(___NO_ERR);
if (pipe->rerr != ___FIX(___NO_ERR) ||
(pipe->rd == pipe->wr && !pipe->oob))
ResetEvent (pipe->revent); /* ignore error */
}
ReleaseMutex (pipe->mutex); /* ignore error */
return werr;
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_write_ready_wait
___P((___nonblocking_pipe *pipe),
(pipe)
___nonblocking_pipe *pipe;)
{
___SCMOBJ rerr;
if (WaitForSingleObject (pipe->wevent, INFINITE) == WAIT_FAILED ||
WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
rerr = pipe->rerr;
if (rerr != ___FIX(___NO_ERR))
{
pipe->rerr = ___FIX(___NO_ERR);
ResetEvent (pipe->wevent); /* ignore error */
}
ReleaseMutex (pipe->mutex); /* ignore error */
return rerr;
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_read
___P((___nonblocking_pipe *pipe,
___U8 *buf,
___stream_index len,
___stream_index *len_done,
___nonblocking_pipe_oob_msg *oob_msg),
(pipe,
buf,
len,
len_done,
oob_msg)
___nonblocking_pipe *pipe;
___U8 *buf;
___stream_index len;
___stream_index *len_done;
___nonblocking_pipe_oob_msg *oob_msg;)
{
___SCMOBJ rerr;
___SCMOBJ werr;
DWORD rd;
DWORD wr;
___U8 *p;
DWORD end;
DWORD n;
DWORD i;
if (len <= 0)
return ___FIX(___UNKNOWN_ERR);
if (WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
rerr = pipe->rerr;
werr = pipe->werr;
rd = pipe->rd;
wr = pipe->wr;
if (rerr != ___FIX(___NO_ERR))
{
/* there is a reader error */
if (werr == ___FIX(___NO_ERR))
werr = ___ERR_CODE_EAGAIN;
else
{
pipe->werr = ___FIX(___NO_ERR);
ResetEvent (pipe->revent); /* ignore error */
}
ReleaseMutex (pipe->mutex); /* ignore error */
return werr;
}
/* there is no reader error */
if (rd == wr)
{
/* no bytes in FIFO buffer */
if (pipe->oob)
{
/* out-of-band present */
*oob_msg = pipe->oob_msg;
pipe->oob = 0;
if (werr == ___FIX(___NO_ERR))
{
ResetEvent (pipe->revent); /* ignore error */
#if 0
/******************zzzzzzzzzzzzzz****/
SetEvent (pipe->wevent); /* ignore error */
#endif
}
ReleaseMutex (pipe->mutex); /* ignore error */
*len_done = 0;
return ___FIX(___NO_ERR);
}
/* out-of-band not present */
if (werr != ___FIX(___NO_ERR))
{
/* there is a writer error */
pipe->werr = ___FIX(___NO_ERR);
ResetEvent (pipe->revent); /* ignore error */
ReleaseMutex (pipe->mutex); /* ignore error */
return werr;
}
/* there is no writer error */
SetEvent (pipe->wevent); /* ignore error */
ReleaseMutex (pipe->mutex); /* ignore error */
return ___ERR_CODE_EAGAIN;
}
/* at least one byte in FIFO buffer */
end = pipe->size - rd; /* number of bytes from rd to end */
n = wr + end; /* number of bytes available */
if (n >= pipe->size)
n -= pipe->size;
if (n > ___CAST(DWORD,len)) /* don't transfer more than len */
n = len;
*len_done = n;
p = pipe->buffer + rd; /* prepare transfer source */
rd = rd + n;
if (rd >= pipe->size)
rd -= pipe->size;
pipe->rd = rd;
if (werr == ___FIX(___NO_ERR) && !pipe->oob)
{
/* there is no writer error and out-of-band not present */
if (rd == wr) /* the FIFO will be empty? */
ResetEvent (pipe->revent); /* ignore error */
/* the FIFO will not be full */
SetEvent (pipe->wevent); /* ignore error */
}
if (n <= end)
{
/* only need to transfer n bytes starting from original rd */
for (i=n; i>0; i--)
*buf++ = *p++;
}
else
{
/* need to transfer end bytes starting from original rd */
for (i=end; i>0; i--)
*buf++ = *p++;
/* and to transfer n-end bytes starting from 0 */
p = pipe->buffer;
for (i=n-end; i>0; i--)
*buf++ = *p++;
}
ReleaseMutex (pipe->mutex); /* ignore error */
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___nonblocking_pipe_write
___P((___nonblocking_pipe *pipe,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(pipe,
buf,
len,
len_done)
___nonblocking_pipe *pipe;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___SCMOBJ rerr;
___SCMOBJ werr;
DWORD rd;
DWORD wr;
___U8 *p;
DWORD end;
DWORD n;
DWORD i;
if (len <= 0)
return ___FIX(___UNKNOWN_ERR);
if (WaitForSingleObject (pipe->mutex, INFINITE) == WAIT_FAILED)
return err_code_from_GetLastError ();
rerr = pipe->rerr;
werr = pipe->werr;
rd = pipe->rd;
wr = pipe->wr;
if (werr != ___FIX(___NO_ERR))
{
/* there is a writer error */
if (rerr == ___FIX(___NO_ERR))
rerr = ___ERR_CODE_EAGAIN;
else
{
pipe->rerr = ___FIX(___NO_ERR);
ResetEvent (pipe->wevent); /* ignore error */
}
ReleaseMutex (pipe->mutex); /* ignore error */
return rerr;
}
/* there is no writer error */
if (rerr != ___FIX(___NO_ERR))
{
/* there is a reader error */
pipe->rerr = ___FIX(___NO_ERR);
if (rd != wr || pipe->oob) /* FIFO is not empty */
SetEvent (pipe->revent); /* ignore error */
ResetEvent (pipe->wevent); /* ignore error */
ReleaseMutex (pipe->mutex); /* ignore error */
return rerr;
}
/* there is no reader error */
if (wr + 1 - rd == 0 || wr + 1 - rd == pipe->size || pipe->oob)
{
/* FIFO buffer is full or out-of-band present */
ReleaseMutex (pipe->mutex); /* ignore error */
return ___ERR_CODE_EAGAIN;
}
/* FIFO buffer is not full and out-of-band not present */
end = pipe->size - wr; /* number of bytes from wr to end */
n = rd + end - 1; /* number of bytes available */
if (n >= pipe->size)
n -= pipe->size;
if (n > ___CAST(DWORD,len)) /* don't transfer more than len */
n = len;
*len_done = n;
p = pipe->buffer + wr; /* prepare transfer source */
wr = wr + n;
if (wr >= pipe->size)
wr -= pipe->size;
pipe->wr = wr;
if (wr + 1 - rd == 0 || wr + 1 - rd == pipe->size) /* FIFO will be full? */
ResetEvent (pipe->wevent); /* ignore error */
/* FIFO will not be empty */
SetEvent (pipe->revent); /* ignore error */
if (n <= end)
{
/* only need to transfer n bytes starting from original wr */
for (i=n; i>0; i--)
*p++ = *buf++;
}
else
{
/* need to transfer end bytes starting from original wr */
for (i=end; i>0; i--)
*p++ = *buf++;
/* and to transfer n-end bytes starting from 0 */
p = pipe->buffer;
for (i=n-end; i>0; i--)
*p++ = *buf++;
}
ReleaseMutex (pipe->mutex); /* ignore error */
return ___FIX(___NO_ERR);
}
#endif
/*---------------------------------------------------------------------------*/
/* Operations on I/O devices. */
/* Miscellaneous utility functions. */
#ifdef USE_POSIX
#ifdef USE_sigaction
typedef sigset_t sigset_type;
#else
typedef int sigset_type;
#endif
___HIDDEN sigset_type block_signal
___P((int signum),
(signum)
int signum;)
{
sigset_type oldmask;
#ifdef USE_sigaction
sigset_type toblock;
sigemptyset (&toblock);
sigaddset (&toblock, signum);
sigprocmask (SIG_BLOCK, &toblock, &oldmask);
#endif
#ifdef USE_signal
oldmask = sigblock (sigmask (signum));
#endif
return oldmask;
}
___HIDDEN void restore_sigmask
___P((sigset_type oldmask),
(oldmask)
sigset_type oldmask;)
{
#ifdef USE_sigaction
sigprocmask (SIG_SETMASK, &oldmask, 0);
#endif
#ifdef USE_signal
sigsetmask (oldmask);
#endif
}
/*
* Some system calls can be interrupted by a signal and fail with
* errno == EINTR. The following functions are wrappers for system
* calls which may be interrupted. They simply ignore the EINTR and
* retry the operation.
*
* TODO: add wrappers for all the system calls which can fail
* with EINTR. Also, move these functions to a central place.
*/
pid_t waitpid_no_EINTR
___P((pid_t pid,
int *stat_loc,
int options),
(pid,
stat_loc,
options)
pid_t pid;
int *stat_loc;
int options;)
{
pid_t result;
for (;;)
{
result = waitpid (pid, stat_loc, options);
if (result >= 0 || errno != EINTR)
break;
}
return result;
}
ssize_t read_no_EINTR
___P((int fd,
void *buf,
size_t len),
(fd,
buf,
len)
int fd;
void *buf;
size_t len;)
{
char *p = ___CAST(char*,buf);
ssize_t result = 0;
int n;
while (result < len)
{
n = read (fd, p+result, len-result);
if (n > 0)
result += n;
else if (n == 0)
break;
else if (errno != EINTR)
return n; /* this forgets that some bytes were transferred */
}
return result;
}
int close_no_EINTR
___P((int fd),
(fd)
int fd;)
{
int result;
for (;;)
{
result = close (fd);
if (result >= 0 || errno != EINTR)
break;
}
return result;
}
int dup_no_EINTR
___P((int fd),
(fd)
int fd;)
{
int result;
for (;;)
{
result = dup (fd);
if (result >= 0 || errno != EINTR)
break;
}
return result;
}
int dup2_no_EINTR
___P((int fd,
int fd2),
(fd,
fd2)
int fd;
int fd2;)
{
int result;
for (;;)
{
result = dup2 (fd, fd2);
if (result >= 0 || errno != EINTR)
break;
}
return result;
}
int set_fd_blocking_mode
___P((int fd,
___BOOL blocking),
(fd,
blocking)
int fd;
___BOOL blocking;)
{
int fl;
if ((fl = fcntl (fd, F_GETFL, 0)) >= 0)
fl = fcntl (fd,
F_SETFL,
blocking ? (fl & ~O_NONBLOCK) : (fl | O_NONBLOCK));
return fl;
}
#endif
/*---------------------------------------------------------------------------*/
/* Generic device operations. */
___SCMOBJ ___device_select
___P((___device **devs,
int nb_read_devs,
int nb_write_devs,
___time timeout),
(devs,
nb_read_devs,
nb_write_devs,
timeout)
___device **devs;
int nb_read_devs;
int nb_write_devs;
___time timeout;)
{
int nb_devs;
___device_select_state state;
int pass;
int dev_list;
int i;
int prev;
___time delta;
nb_devs = nb_read_devs + nb_write_devs;
state.devs = devs;
state.timeout = timeout;
state.relative_timeout = POS_INFINITY;
#ifdef USE_select
state.highest_fd_plus_1 = 0;
FD_ZERO(&state.readfds);
FD_ZERO(&state.writefds);
FD_ZERO(&state.exceptfds);
#endif
#ifdef USE_MsgWaitForMultipleObjects
state.message_queue_mask = 0;
state.message_queue_dev_pos = -1;
state.wait_objs_buffer[0] = ___io_mod.abort_select;
state.wait_objs_buffer[1] = ___time_mod.heartbeat_thread;
state.nb_wait_objs = 2;
#endif
if (nb_devs > 0)
{
state.devs_next[nb_devs-1] = -1;
for (i=nb_devs-2; i>=0; i--)
state.devs_next[i] = i+1;
dev_list = 0;
}
else
dev_list = -1;
pass = ___SELECT_PASS_1;
while (dev_list != -1)
{
i = dev_list;
prev = -1;
while (i != -1)
{
___SCMOBJ e;
___device *d = devs[i];
if ((e = ___device_select_virt
(d,
i>=nb_read_devs,
i,
pass,
&state))
== ___FIX(___NO_ERR))
{
prev = i;
i = state.devs_next[i];
}
else
{
int j;
if (e != ___FIX(___SELECT_SETUP_DONE))
return e;
j = state.devs_next[i];
if (prev == -1)
dev_list = j;
else
state.devs_next[prev] = j;
#ifdef USE_MsgWaitForMultipleObjects
state.devs_next[i] = -1;
#endif
i = j;
}
}
pass++;
}
___absolute_time_to_relative_time (state.timeout, &delta);
if (___time_less (state.relative_timeout, delta))
{
delta = state.relative_timeout;
state.timeout = ___time_mod.time_neg_infinity;
}
else
state.relative_timeout = NEG_INFINITY;
#ifdef USE_select
{
struct timeval delta_tv_struct;
struct timeval *delta_tv = &delta_tv_struct;
int result;
___absolute_time_to_nonnegative_timeval (delta, &delta_tv);
if (delta_tv != NULL &&
state.highest_fd_plus_1 == 0)
{
/*
* ___device_select is only being called for sleeping until a
* certain timeout or interrupt occurs. This is a case that
* can be optimized.
*/
if (delta_tv->tv_sec < 0 ||
(delta_tv->tv_sec == 0 &&
delta_tv->tv_usec == 0))
{
/*
* The timeout has already passed, so we don't need to
* sleep. This simple optimization avoids doing a system
* call to the select or nanosleep functions (which can be
* expensive on some operating systems).
*/
result = 0;
goto select_done;
}
#ifdef USE_nanosleep
else
{
/*
* For better timeout resolution, the nanosleep function
* is used instead of the select function. On some
* operating systems (e.g. OpenBSD 4.5) the nanosleep
* function can be more expensive than a call to select,
* but the better timeout resolution outweighs the run
* time cost.
*/
struct timespec delta_ts_struct;
delta_ts_struct.tv_sec = delta_tv->tv_sec;
delta_ts_struct.tv_nsec = delta_tv->tv_usec * 1000;
result = nanosleep (&delta_ts_struct, NULL);
goto select_done;
}
#endif
}
/*
* Heartbeat interrupts must be disabled in case they are based on the
* real-time timer. This is needed to bypass issues in two buggy
* operating systems:
*
* - On MacOS X, the virtual-time timer does not fire at the correct
* rate (apparently this happens only on machines with more than
* one core).
*
* - On CYGWIN, the select system call can be interrupted by the
* timer and in some cases the error "No child processes" will
* be returned by select.
*/
___disable_heartbeat_interrupts ();
result =
select (state.highest_fd_plus_1,
&state.readfds,
&state.writefds,
&state.exceptfds,
delta_tv);
___enable_heartbeat_interrupts ();
select_done:
if (result < 0)
return err_code_from_errno ();
state.timeout_reached = (result == 0);
}
#endif
#ifdef USE_MsgWaitForMultipleObjects
{
DWORD delta_msecs;
int first_iteration = TRUE;
___absolute_time_to_nonnegative_msecs (delta, &delta_msecs);
state.timeout_reached = 0;
while (state.nb_wait_objs > 0 || state.message_queue_mask != 0)
{
DWORD n;
n = MsgWaitForMultipleObjects
(state.nb_wait_objs,
state.wait_objs_buffer,
FALSE,
delta_msecs,
state.message_queue_mask);
if (n == WAIT_FAILED)
return err_code_from_GetLastError ();
if ((n - WAIT_OBJECT_0) <= WAIT_OBJECT_0 + state.nb_wait_objs)
n -= WAIT_OBJECT_0;
else if (n >= WAIT_ABANDONED_0 &&
n <= WAIT_ABANDONED_0+state.nb_wait_objs-1)
n -= WAIT_ABANDONED_0;
else
{
/* n == WAIT_TIMEOUT */
/*
* The call to MsgWaitForMultipleObjects timed out. Mark
* the appropriate device "ready".
*/
if (first_iteration)
{
/* first call to MsgWaitForMultipleObjects */
state.timeout_reached = 1;
}
break;
}
if (n == state.nb_wait_objs)
{
/*
* The message queue contains a message that is of interest.
* Mark the appropriate device "ready".
*/
i = state.message_queue_dev_pos;
if (i >= 0)
state.devs_next[i] = 0;
/*
* Don't check if other devices are ready because this might
* cause an infinite loop.
*/
break;
}
else if (n == 0)
{
/*
* The call to ___device_select must be aborted because the
* abort_select event is set. This occurs when an interrupt
* (such as a CTRL-C user interrupt) needs to be serviced
* promptly by the main program.
*/
ResetEvent (___io_mod.abort_select); /* ignore error */
return ___FIX(___ERRNO_ERR(EINTR));
}
else if (n == 1)
{
/*
* The heartbeat thread has died. This is normally due to
* the program being terminated abruptly by the user (for
* example by using the thread manager or the "shutdown"
* item in the start menu). When this happens we must
* initiate a clean termination of the program.
*/
return ___FIX(___UNKNOWN_ERR);
}
else
{
/* Mark the appropriate device "ready". */
i = state.wait_obj_to_dev_pos[n];
if (i >= 0)
state.devs_next[i] = 0;
/* Prepare to check remaining devices. */
state.nb_wait_objs--;
state.wait_objs_buffer[n] =
state.wait_objs_buffer[state.nb_wait_objs];
state.wait_obj_to_dev_pos[n] =
state.wait_obj_to_dev_pos[state.nb_wait_objs];
}
first_iteration = FALSE;
delta_msecs = 0; /* next MsgWaitForMultipleObjects will only poll */
}
}
#endif
for (i=nb_devs-1; i>=0; i--)
{
___SCMOBJ e;
___device *d = devs[i];
if (d != NULL)
if ((e = ___device_select_virt
(d,
i>=nb_read_devs,
i,
___SELECT_PASS_CHECK,
&state))
!= ___FIX(___NO_ERR))
return e;
}
return ___FIX(___NO_ERR);
}
void ___device_select_add_relative_timeout
___P((___device_select_state *state,
int i,
___F64 seconds),
(state,
i,
seconds)
___device_select_state *state;
int i;
___F64 seconds;)
{
if (seconds < state->relative_timeout)
state->relative_timeout = seconds;
}
void ___device_select_add_timeout
___P((___device_select_state *state,
int i,
___time timeout),
(state,
i,
timeout)
___device_select_state *state;
int i;
___time timeout;)
{
if (___time_less (timeout, state->timeout))
state->timeout = timeout;
}
#ifdef USE_select
void ___device_select_add_fd
___P((___device_select_state *state,
int fd,
___BOOL for_writing),
(state,
fd,
for_writing)
___device_select_state *state;
int fd;
___BOOL for_writing;)
{
if (for_writing)
FD_SET(fd, &state->writefds);
else
FD_SET(fd, &state->readfds);
if (fd >= state->highest_fd_plus_1)
state->highest_fd_plus_1 = fd+1;
}
#endif
#ifdef USE_MsgWaitForMultipleObjects
void ___device_select_add_wait_obj
___P((___device_select_state *state,
int i,
HANDLE wait_obj),
(state,
i,
wait_obj)
___device_select_state *state;
int i;
HANDLE wait_obj;)
{
DWORD j = state->nb_wait_objs;
if (j < MAXIMUM_WAIT_OBJECTS)
{
state->wait_objs_buffer[j] = wait_obj;
state->wait_obj_to_dev_pos[j] = i;
state->nb_wait_objs = j+1;
}
}
#endif
___SCMOBJ ___device_force_output
___P((___device *self,
int level),
(self,
level)
___device *self;
int level;)
{
return ___device_force_output_virt (self, level);
}
___SCMOBJ ___device_close
___P((___device *self,
int direction),
(self,
direction)
___device *self;
int direction;)
{
return ___device_close_virt (self, direction);
}
___HIDDEN void device_transfer_close_responsibility
___P((___device *self),
(self)
___device *self;)
{
/*
* Transfer responsibility for closing device to the runtime system.
*/
self->close_direction = self->direction;
}
___HIDDEN void device_add_ref
___P((___device *self),
(self)
___device *self;)
{
#ifdef USE_PUMPS
InterlockedIncrement (&self->refcount);
#else
self->refcount++;
#endif
}
___SCMOBJ ___device_release
___P((___device *self),
(self)
___device *self;)
{
___SCMOBJ e = ___FIX(___NO_ERR);
if (
#ifdef USE_PUMPS
InterlockedDecrement (&self->refcount) == 0
#else
--self->refcount == 0
#endif
)
{
e = ___device_release_virt (self);
___free_mem (self);
}
return e;
}
___SCMOBJ ___device_cleanup
___P((___device *self),
(self)
___device *self;)
{
___SCMOBJ e;
___device *devs[1];
if (self->group == NULL)
return ___FIX(___UNKNOWN_ERR);
___device_remove_from_group (self);
for (;;)
{
e = ___device_close (self, ___DIRECTION_RD);
if (e == ___FIX(___NO_ERR))
break;
if (e != ___ERR_CODE_EAGAIN)
return e;
devs[0] = self;
e = ___device_select (devs, 1, 0, ___time_mod.time_pos_infinity);
if (e != ___FIX(___NO_ERR))
return e;
}
for (;;)
{
e = ___device_close (self, ___DIRECTION_WR);
if (e == ___FIX(___NO_ERR))
break;
if (e != ___ERR_CODE_EAGAIN)
return e;
devs[0] = self;
e = ___device_select (devs, 0, 1, ___time_mod.time_pos_infinity);
if (e != ___FIX(___NO_ERR))
return e;
}
return ___device_release (self);
}
/*
* Procedure called by the Scheme runtime when a device is no longer
* reachable.
*/
___SCMOBJ ___device_cleanup_from_ptr
___P((void *ptr),
(ptr)
void *ptr;)
{
return ___device_cleanup (___CAST(___device*,ptr));
}
/* - - - - - - - - - - - - - - - - - - */
/* Timer device. */
/*
* Timer devices are not particularly useful, given that the scheduler
* implements timeouts. Use this as an example.
*/
___HIDDEN int device_timer_kind
___P((___device *self),
(self)
___device *self;)
{
return ___TIMER_KIND;
}
___HIDDEN ___SCMOBJ device_timer_select_virt
___P((___device *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_timer *d = ___CAST(___device_timer*,self);
if (pass == ___SELECT_PASS_1)
{
if (___time_less (d->expiry, state->timeout))
state->timeout = d->expiry;
return ___FIX(___SELECT_SETUP_DONE);
}
/* pass == ___SELECT_PASS_CHECK */
if (state->timeout_reached)
{
if (___time_equal (d->expiry, state->timeout))
state->devs[i] = NULL;
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ device_timer_release_virt
___P((___device *self),
(self)
___device *self;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ device_timer_force_output_virt
___P((___device *self,
int level),
(self,
level)
___device *self;
int level;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ device_timer_close_virt
___P((___device *self,
int direction),
(self,
direction)
___device *self;
int direction;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___device_timer_vtbl ___device_timer_table =
{
{
device_timer_kind,
device_timer_select_virt,
device_timer_release_virt,
device_timer_force_output_virt,
device_timer_close_virt
}
};
___SCMOBJ ___device_timer_setup
___P((___device_timer **dev,
___device_group *dgroup),
(dev,
dgroup)
___device_timer **dev;
___device_group *dgroup;)
{
___device_timer *d;
d = ___CAST(___device_timer*,
___alloc_mem (sizeof (___device_timer)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
d->base.vtbl = &___device_timer_table;
d->base.refcount = 1;
d->base.direction = ___DIRECTION_RD | ___DIRECTION_WR;
d->base.close_direction = 0; /* prevent closing on errors */
d->base.read_stage = ___STAGE_OPEN;
d->base.write_stage = ___STAGE_OPEN;
d->expiry = ___time_mod.time_pos_infinity;
*dev = d;
___device_add_to_group (dgroup, &d->base);
return ___FIX(___NO_ERR);
}
void ___device_timer_set_expiry
___P((___device_timer *dev,
___time expiry),
(dev,
expiry)
___device_timer *dev;
___time expiry;)
{
dev->expiry = expiry;
}
/* - - - - - - - - - - - - - - - - - - */
/* Byte stream devices. */
#ifdef USE_PUMPS
___HIDDEN ___SCMOBJ ___device_stream_pump_setup
___P((___device_stream_pump **pump,
DWORD committed_stack_size,
LPTHREAD_START_ROUTINE proc,
LPVOID arg),
(pump,
committed_stack_size,
proc,
arg)
___device_stream_pump **pump;
DWORD committed_stack_size;
LPTHREAD_START_ROUTINE proc;
LPVOID arg;)
{
___SCMOBJ e;
___device_stream_pump *p;
HANDLE thread_handle;
DWORD thread_id;
p = ___CAST(___device_stream_pump*,
___alloc_mem (sizeof (___device_stream_pump)));
if (p == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
e = ___nonblocking_pipe_setup (&p->pipe, PIPE_BUFFER_SIZE+1);
if (e != ___FIX(___NO_ERR))
{
___free_mem (p);
return e;
}
*pump = p; /* set before thread created to avoid race condition */
thread_handle =
CreateThread (NULL, /* no security attributes */
committed_stack_size, /* committed stack size */
proc, /* thread procedure */
arg, /* argument to thread procedure */
0, /* use default creation flags */
&thread_id);
if (thread_handle == NULL ||
!SetThreadPriority (thread_handle, PUMP_PRIORITY))
{
e = err_code_from_GetLastError ();
___nonblocking_pipe_cleanup (&p->pipe);
___free_mem (p);
*pump = NULL; /* make sure caller does not think a pump was created */
return e;
}
p->thread = thread_handle;
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_stream_pump_reader_kill
___P((___device_stream_pump *pump),
(pump)
___device_stream_pump *pump;)
{
return ___nonblocking_pipe_set_reader_err
(&pump->pipe,
___FIX(___KILL_PUMP));
}
___HIDDEN ___SCMOBJ ___device_stream_pump_writer_kill
___P((___device_stream_pump *pump),
(pump)
___device_stream_pump *pump;)
{
return ___nonblocking_pipe_set_writer_err
(&pump->pipe,
___FIX(___KILL_PUMP));
}
___HIDDEN ___SCMOBJ ___device_stream_pump_wait
___P((___device_stream_pump *pump),
(pump)
___device_stream_pump *pump;)
{
DWORD code;
code = WaitForSingleObject (pump->thread, 0);
if (code == WAIT_FAILED)
return err_code_from_GetLastError ();
if (code == WAIT_TIMEOUT)
return ___ERR_CODE_EAGAIN;
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_stream_pump_cleanup
___P((___device_stream_pump *pump),
(pump)
___device_stream_pump *pump;)
{
CloseHandle (pump->thread); /* ignore error */
___nonblocking_pipe_cleanup (&pump->pipe); /* ignore error */
___free_mem (pump);
return ___FIX(___NO_ERR);
}
#endif
___SCMOBJ ___device_stream_select_virt
___P((___device *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_stream *d = ___CAST(___device_stream*,self);
#ifdef USE_PUMPS
int stage = (for_writing
? d->base.write_stage
: d->base.read_stage);
___device_stream_pump *p = (for_writing
? d->write_pump
: d->read_pump);
if (p != NULL)
{
if (pass == ___SELECT_PASS_1)
{
HANDLE wait_obj;
if (stage != ___STAGE_OPEN)
wait_obj = p->thread;
else
{
if (for_writing)
wait_obj = p->pipe.wevent;
else
wait_obj = p->pipe.revent;
}
___device_select_add_wait_obj (state, i, wait_obj);
return ___FIX(___SELECT_SETUP_DONE);
}
/* pass == ___SELECT_PASS_CHECK */
if (stage != ___STAGE_OPEN)
state->devs[i] = NULL;
else
{
if (state->devs_next[i] != -1)
state->devs[i] = NULL;
}
return ___FIX(___NO_ERR);
}
#endif
return ___device_stream_select_raw_virt
(d,
for_writing,
i,
pass,
state);
}
___SCMOBJ ___device_stream_release_virt
___P((___device *self),
(self)
___device *self;)
{
___SCMOBJ e;
___device_stream *d = ___CAST(___device_stream*,self);
e = ___device_stream_release_raw_virt (d);
#ifdef USE_PUMPS
{
___device_stream_pump *p;
p = d->read_pump;
if (p != NULL)
___device_stream_pump_cleanup (p); /* ignore error */
p = d->write_pump;
if (p != NULL)
___device_stream_pump_cleanup (p); /* ignore error */
}
#endif
return e;
}
___SCMOBJ ___device_stream_force_output_virt
___P((___device *self,
int level),
(self,
level)
___device *self;
int level;)
{
___device_stream *d = ___CAST(___device_stream*,self);
#ifdef USE_PUMPS
{
___device_stream_pump *p = d->write_pump;
if (p != NULL)
{
___nonblocking_pipe_oob_msg oob_msg;
oob_msg.op = OOB_FORCE_OUTPUT0 + level;
return ___nonblocking_pipe_write_oob (&p->pipe, &oob_msg);
}
}
#endif
return ___device_stream_force_output_raw_virt (d, level);
}
___SCMOBJ ___device_stream_close_virt
___P((___device *self,
int direction),
(self,
direction)
___device *self;
int direction;)
{
___device_stream *d = ___CAST(___device_stream*,self);
#ifdef USE_PUMPS
if (direction & ___DIRECTION_RD)
{
___device_stream_pump *p = d->read_pump;
if (p != NULL)
___device_stream_pump_reader_kill (p);
}
if (direction & ___DIRECTION_WR)
{
___device_stream_pump *p = d->write_pump;
if (p != NULL)
___device_stream_pump_writer_kill (p);
}
#endif
return ___device_stream_close_raw_virt (d, direction);
}
___SCMOBJ ___device_stream_seek
___P((___device_stream *self,
___stream_index *pos,
int whence),
(self,
pos,
whence)
___device_stream *self;
___stream_index *pos;
int whence;)
{
#ifdef USE_PUMPS
{
___device_stream_pump *p = self->write_pump;
if (p != NULL)
{
___nonblocking_pipe_oob_msg oob_msg;
oob_msg.op = OOB_SEEK_ABS + whence;
oob_msg.stream_index_param = *pos;
return ___nonblocking_pipe_write_oob (&p->pipe, &oob_msg);
}
}
#endif
return ___device_stream_seek_raw_virt (self, pos, whence);
}
___SCMOBJ ___device_stream_read
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
#ifdef USE_PUMPS
{
___device_stream_pump *p = self->read_pump;
if (p != NULL)
{
___nonblocking_pipe_oob_msg oob_msg;
return ___nonblocking_pipe_read
(&p->pipe,
buf,
len,
len_done,
&oob_msg);
}
}
#endif
return ___device_stream_read_raw_virt (self, buf, len, len_done);
}
___SCMOBJ ___device_stream_write
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
#ifdef USE_PUMPS
{
___device_stream_pump *p = self->write_pump;
if (p != NULL)
return ___nonblocking_pipe_write (&p->pipe, buf, len, len_done);
}
#endif
return ___device_stream_write_raw_virt (self, buf, len, len_done);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef USE_PUMPS
___HIDDEN DWORD WINAPI ___device_stream_read_pump_proc
___P((LPVOID param),
(param)
LPVOID param;)
{
___device_stream *dev = ___CAST(___device_stream*,param);
___nonblocking_pipe *p = &dev->read_pump->pipe;
___SCMOBJ e;
___stream_index len;
___stream_index n;
___stream_index i;
___U8 buf[PIPE_BUFFER_SIZE];
___nonblocking_pipe_oob_msg oob_msg;
for (;;)
{
/* wait until the reader needs some data */
e = ___nonblocking_pipe_write_ready_wait (p);
/* read some characters from device */
if (e == ___FIX(___NO_ERR))
e = ___device_stream_read_raw_virt (dev, buf, PIPE_BUFFER_SIZE, &len);
if (e == ___FIX(___NO_ERR))
{
if (len == 0)
{
/* we reached the end-of-stream */
oob_msg.op = OOB_EOS;
while ((e = ___nonblocking_pipe_write_oob (p, &oob_msg))
== ___ERR_CODE_EAGAIN)
{
/* suspend thread until operation can be performed */
e = ___nonblocking_pipe_write_ready_wait (p);
if (e != ___FIX(___NO_ERR))
break;
}
if (e != ___FIX(___NO_ERR))
break;
}
else
{
/* write to the pipe the bytes that were read */
i = 0;
while (i < len)
{
while ((e = ___nonblocking_pipe_write (p, buf+i, len-i, &n))
== ___ERR_CODE_EAGAIN)
{
/* suspend thread until operation can be performed */
e = ___nonblocking_pipe_write_ready_wait (p);
if (e != ___FIX(___NO_ERR))
break;
}
if (e != ___FIX(___NO_ERR))
break;
i += n;
}
}
}
if (e != ___FIX(___NO_ERR))
{
if (e == ___FIX(___KILL_PUMP)) /* terminate? */
break;
if (e == ___ERR_CODE_EAGAIN)
continue;
/* report the failure back through the pipe */
e = ___nonblocking_pipe_set_writer_err (p, e);
if (e != ___FIX(___NO_ERR))
{
/*
* The failure could not be reported. To avoid an
* infinite loop the thread is terminated.
*/
ExitThread (0);
}
}
}
___device_release (&dev->base); /* ignore error */
return 0;
}
___HIDDEN DWORD WINAPI ___device_stream_write_pump_proc
___P((LPVOID param),
(param)
LPVOID param;)
{
___device_stream *dev = ___CAST(___device_stream*,param);
___nonblocking_pipe *p = &dev->write_pump->pipe;
___SCMOBJ e;
___stream_index len;
___stream_index n;
___stream_index i;
___U8 buf[PIPE_BUFFER_SIZE];
___nonblocking_pipe_oob_msg oob_msg;
for (;;)
{
/* get from the pipe some bytes to write to the device */
while ((e = ___nonblocking_pipe_read
(p,
buf,
PIPE_BUFFER_SIZE,
&len,
&oob_msg))
== ___ERR_CODE_EAGAIN)
{
/* suspend thread until operation can be performed */
e = ___nonblocking_pipe_read_ready_wait (p);
if (e != ___FIX(___NO_ERR))
break;
}
if (e == ___FIX(___NO_ERR))
{
if (len > 0)
{
/* write to the device the bytes that were read from the pipe */
i = 0;
while (i < len)
{
e = ___device_stream_write_raw_virt (dev, buf+i, len-i, &n);
if (e != ___FIX(___NO_ERR))
break;
i += n;
}
}
else
{
switch (oob_msg.op)
{
case OOB_FORCE_OUTPUT0:
case OOB_FORCE_OUTPUT1:
case OOB_FORCE_OUTPUT2:
case OOB_FORCE_OUTPUT3:
#ifdef ___DEBUG
___printf ("***** got OOB_FORCE_OUTPUT%d\n",
oob_msg.op - OOB_FORCE_OUTPUT0);
#endif
e = ___device_stream_force_output_raw_virt (dev, oob_msg.op - OOB_FORCE_OUTPUT0);
break;
case OOB_SEEK_ABS:
case OOB_SEEK_REL:
case OOB_SEEK_REL_END:
#ifdef ___DEBUG
___printf ("***** got OOB_SEEK %d %d\n",
oob_msg.stream_index_param,
oob_msg.op - OOB_SEEK_ABS);
#endif
e = ___device_stream_seek_raw_virt
(dev,
&oob_msg.stream_index_param,
oob_msg.op - OOB_SEEK_ABS);
break;
case OOB_EOS:
#ifdef ___DEBUG
___printf ("***** got OOB_EOS\n");
#endif
break;
}
}
}
if (e != ___FIX(___NO_ERR))
{
if (e == ___FIX(___KILL_PUMP)) /* terminate? */
break;
/* report the failure back through the pipe */
e = ___nonblocking_pipe_set_reader_err (p, e);
if (e != ___FIX(___NO_ERR))
{
/*
* The failure could not be reported. To avoid an
* infinite loop the thread is terminated.
*/
ExitThread (0);
}
}
}
___device_release (&dev->base); /* ignore error */
return 0;
}
#endif
___SCMOBJ ___device_stream_setup
___P((___device_stream *dev,
___device_group *dgroup,
int direction,
int pumps_on),
(dev,
dgroup,
direction,
pumps_on)/*********************/
___device_stream *dev;
___device_group *dgroup;
int direction;
int pumps_on;)
{
dev->base.refcount = 1;
dev->base.direction = direction;
dev->base.close_direction = 0; /* prevent closing on errors */
dev->base.read_stage = ___STAGE_CLOSED;
dev->base.write_stage = ___STAGE_CLOSED;
#ifdef USE_PUMPS
dev->read_pump = NULL;
dev->write_pump = NULL;
#endif
___device_add_to_group (dgroup, &dev->base);
if (direction & ___DIRECTION_RD)
{
dev->base.read_stage = ___STAGE_OPEN;
#ifdef USE_PUMPS
if (pumps_on & ___DIRECTION_RD)
{
___SCMOBJ e;
device_add_ref (&dev->base);
if ((e = ___device_stream_pump_setup
(&dev->read_pump,
65536,
___device_stream_read_pump_proc,
dev))
!= ___FIX(___NO_ERR))
{
___device_release (&dev->base); /* ignore error */
___device_cleanup (&dev->base); /* ignore error */
return e;
}
}
#endif
}
if (direction & ___DIRECTION_WR)
{
dev->base.write_stage = ___STAGE_OPEN;
#ifdef USE_PUMPS
if (pumps_on & ___DIRECTION_WR)
{
___SCMOBJ e;
device_add_ref (&dev->base);
if ((e = ___device_stream_pump_setup
(&dev->write_pump,
65536,
___device_stream_write_pump_proc,
dev))
!= ___FIX(___NO_ERR))
{
___device_release (&dev->base); /* ignore error */
___device_cleanup (&dev->base); /* ignore error */
return e;
}
}
#endif
}
return ___FIX(___NO_ERR);
}
/*---------------------------------------------------------------------------*/
/* Serial stream device */
#ifdef USE_WIN32
typedef struct ___device_serial_struct
{
___device_stream base;
HANDLE h;
} ___device_serial;
typedef struct ___device_serial_vtbl_struct
{
___device_stream_vtbl base;
} ___device_serial_vtbl;
___HIDDEN int ___device_serial_kind
___P((___device *self),
(self)
___device *self;)
{
return ___SERIAL_DEVICE_KIND;
}
___HIDDEN ___SCMOBJ ___device_serial_close_raw_virt
___P((___device_stream *self,
int direction),
(self,
direction)
___device_stream *self;
int direction;)
{
___device_serial *d = ___CAST(___device_serial*,self);
int is_not_closed = 0;
if (d->base.base.read_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_RD;
if (d->base.base.write_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_WR;
if (is_not_closed == 0)
return ___FIX(___NO_ERR);
if (is_not_closed == (___DIRECTION_RD|___DIRECTION_WR))
{
d->base.base.read_stage = ___STAGE_CLOSED;
d->base.base.write_stage = ___STAGE_CLOSED;
if ((d->base.base.close_direction & (___DIRECTION_RD|___DIRECTION_WR))
== (___DIRECTION_RD|___DIRECTION_WR))
{
if (!CloseHandle (d->h))
return err_code_from_GetLastError ();
}
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_select_raw_virt
___P((___device_stream *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device_stream *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_serial *d = ___CAST(___device_serial*,self);
int stage = (for_writing
? d->base.base.write_stage
: d->base.base.read_stage);
if (pass == ___SELECT_PASS_1)
{
if (stage != ___STAGE_OPEN)
state->timeout = ___time_mod.time_neg_infinity;
return ___FIX(___SELECT_SETUP_DONE);
}
/* pass == ___SELECT_PASS_CHECK */
if (stage != ___STAGE_OPEN)
state->devs[i] = NULL;
else
{
if (state->devs_next[i] != -1)
state->devs[i] = NULL;
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_release_raw_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_force_output_raw_virt
___P((___device_stream *self,
int level),
(self,
level)
___device_stream *self;
int level;)
{
___device_serial *d = ___CAST(___device_serial*,self);
if (d->base.base.write_stage == ___STAGE_OPEN)
{
if (level > 0)
{
if (!FlushFileBuffers (d->h))
return err_code_from_GetLastError ();
}
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_seek_raw_virt
___P((___device_stream *self,
___stream_index *pos,
int whence),
(self,
pos,
whence)
___device_stream *self;
___stream_index *pos;
int whence;)
{
return ___FIX(___INVALID_OP_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_read_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_serial *d = ___CAST(___device_serial*,self);
if (d->base.base.read_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
{
DWORD n;
if (!ReadFile (d->h, buf, len, &n, NULL))
return err_code_from_GetLastError ();
if (n == 0)
return ___ERR_CODE_EAGAIN;
*len_done = n;
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_write_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_serial *d = ___CAST(___device_serial*,self);
if (d->base.base.write_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
{
DWORD n;
if (!WriteFile (d->h, buf, len, &n, NULL))
{
/*
* Even though WriteFile has reported a failure, the operation
* was executed correctly (i.e. len_done contains the number
* of bytes written) if GetLastError returns ERROR_SUCCESS.
*/
if (GetLastError () != ERROR_SUCCESS)
return err_code_from_GetLastError ();
}
*len_done = n;
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_serial_width_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(80);
}
___HIDDEN ___SCMOBJ ___device_serial_default_options_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
int char_encoding_errors = ___CHAR_ENCODING_ERRORS_ON;
int char_encoding = ___CHAR_ENCODING_ISO_8859_1;
int eol_encoding = ___EOL_ENCODING_LF;
int buffering = ___FULL_BUFFERING;
return ___FIX(___STREAM_OPTIONS(char_encoding_errors,
char_encoding,
eol_encoding,
buffering,
char_encoding_errors,
char_encoding,
eol_encoding,
buffering));
}
___HIDDEN ___SCMOBJ ___device_serial_options_set_virt
___P((___device_stream *self,
___SCMOBJ options),
(self,
options)
___device_stream *self;
___SCMOBJ options;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___device_serial_vtbl ___device_serial_table =
{
{
{
___device_serial_kind,
___device_stream_select_virt,
___device_stream_release_virt,
___device_stream_force_output_virt,
___device_stream_close_virt
},
___device_serial_select_raw_virt,
___device_serial_release_raw_virt,
___device_serial_force_output_raw_virt,
___device_serial_close_raw_virt,
___device_serial_seek_raw_virt,
___device_serial_read_raw_virt,
___device_serial_write_raw_virt,
___device_serial_width_virt,
___device_serial_default_options_virt,
___device_serial_options_set_virt
}
};
___HIDDEN ___SCMOBJ ___device_serial_set_comm_state
___P((___device_serial *dev,
LPCTSTR def),
(dev,
def)
___device_serial *dev;
LPCTSTR def;)
{
DCB dcb;
FillMemory (&dcb, sizeof (dcb), 0);
if (!GetCommState (dev->h, &dcb) ||
!BuildCommDCB (def, &dcb) ||
!SetCommState (dev->h, &dcb))
return err_code_from_GetLastError ();
return ___FIX(___NO_ERR);
}
___SCMOBJ ___device_serial_setup_from_handle
___P((___device_serial **dev,
___device_group *dgroup,
HANDLE h,
int direction),
(dev,
dgroup,
h,
direction)
___device_serial **dev;
___device_group *dgroup;
HANDLE h;
int direction;)
{
___device_serial *d;
___SCMOBJ e;
COMMTIMEOUTS cto;
d = ___CAST(___device_serial*,
___alloc_mem (sizeof (___device_serial)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
d->base.base.vtbl = &___device_serial_table;
d->h = h;
*dev = d;
e = ___device_serial_set_comm_state (d, _T("baud=38400 parity=N data=8 stop=1"));
if (e != ___FIX(___NO_ERR))
{
___free_mem (d);
return e;
}
/*
* Setup serial device so that ReadFile will return as soon as a
* character is available.
*/
cto.ReadIntervalTimeout = MAXDWORD;
cto.ReadTotalTimeoutMultiplier = MAXDWORD;
cto.ReadTotalTimeoutConstant = 1; /* wait no more than 1 ms */
cto.WriteTotalTimeoutMultiplier = 0;
cto.WriteTotalTimeoutConstant = 0;
if (!SetCommTimeouts (h, &cto)
#ifdef USE_BIG_SERIAL_BUFFERS
|| !SetupComm (h, 65536, 65536))
#endif
)
{
e = err_code_from_GetLastError ();
___free_mem (d);
return e;
}
return ___device_stream_setup
(&d->base,
dgroup,
direction,
___DIRECTION_RD|___DIRECTION_WR);
}
#endif
/*---------------------------------------------------------------------------*/
/* Pipe stream device */
/*
* Pipes may be unidirectional or bidirectional. Bidirectional pipes
* are implemented with 2 OS pipes: a "write" pipe and a "read" pipe.
*/
typedef struct ___device_pipe_struct
{
___device_stream base;
#ifdef USE_POSIX
int fd_wr; /* file descriptor for "write" pipe (-1 if none) */
int fd_rd; /* file descriptor for "read" pipe (-1 if none) */
#endif
#ifdef USE_WIN32
HANDLE h_wr; /* handle for "write" pipe (NULL if none) */
HANDLE h_rd; /* handle for "read" pipe (NULL if none) */
int poll_interval_nsecs; /* interval between read attempts */
#endif
} ___device_pipe;
typedef struct ___device_pipe_vtbl_struct
{
___device_stream_vtbl base;
} ___device_pipe_vtbl;
___HIDDEN int ___device_pipe_kind
___P((___device *self),
(self)
___device *self;)
{
return ___PIPE_DEVICE_KIND;
}
___SCMOBJ ___device_pipe_cleanup
___P((___device_pipe *dev),
(dev)
___device_pipe *dev;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_close_raw_virt
___P((___device_stream *self,
int direction),
(self,
direction)
___device_stream *self;
int direction;)
{
___device_pipe *d = ___CAST(___device_pipe*,self);
int is_not_closed = 0;
if (d->base.base.read_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_RD;
if (d->base.base.write_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_WR;
if (is_not_closed == 0)
return ___FIX(___NO_ERR);
if (is_not_closed & direction & ___DIRECTION_RD)
{
/* Close "read" pipe */
d->base.base.read_stage = ___STAGE_CLOSED;
if ((d->base.base.close_direction & ___DIRECTION_RD)
== ___DIRECTION_RD)
{
#ifdef USE_POSIX
if (d->fd_rd >= 0 &&
d->fd_rd != d->fd_wr &&
close_no_EINTR (d->fd_rd) < 0)
return err_code_from_errno ();
#endif
#ifdef USE_WIN32
if (d->h_rd != NULL &&
d->h_rd != d->h_wr)
CloseHandle (d->h_rd); /* ignore error */
#endif
}
}
if (is_not_closed & direction & ___DIRECTION_WR)
{
/* Close "write" pipe */
d->base.base.write_stage = ___STAGE_CLOSED;
if ((d->base.base.close_direction & ___DIRECTION_WR)
== ___DIRECTION_WR)
{
#ifdef USE_POSIX
if (d->fd_wr >= 0 &&
close_no_EINTR (d->fd_wr) < 0)
return err_code_from_errno ();
#endif
#ifdef USE_WIN32
if (d->h_wr != NULL)
CloseHandle (d->h_wr); /* ignore error */
#endif
}
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_select_raw_virt
___P((___device_stream *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device_stream *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_pipe *d = ___CAST(___device_pipe*,self);
int stage = (for_writing
? d->base.base.write_stage
: d->base.base.read_stage);
if (pass == ___SELECT_PASS_1)
{
if (stage != ___STAGE_OPEN)
state->timeout = ___time_mod.time_neg_infinity;
else
{
#ifdef USE_POSIX
if (for_writing)
{
if (d->fd_wr >= 0)
___device_select_add_fd (state, d->fd_wr, 1);
}
else
{
if (d->fd_rd >= 0)
___device_select_add_fd (state, d->fd_rd, 0);
}
#endif
#ifdef USE_WIN32
if (for_writing)
{
if (d->h_wr != NULL)
___device_select_add_wait_obj (state, i, d->h_wr);
}
else
{
if (d->h_rd != NULL)
{
int interval = d->poll_interval_nsecs * 6 / 5;
if (interval < 1000000)
interval = 1000000; /* min interval = 0.001 secs */
else if (interval > 200000000)
interval = 200000000; /* max interval = 0.2 sec */
d->poll_interval_nsecs = interval;
___device_select_add_relative_timeout (state, i, interval * 1e-9);
}
}
#endif
}
return ___FIX(___SELECT_SETUP_DONE);
}
/* pass == ___SELECT_PASS_CHECK */
if (stage != ___STAGE_OPEN)
state->devs[i] = NULL;
else
{
#ifdef USE_POSIX
if (for_writing)
{
if (d->fd_wr < 0 || FD_ISSET(d->fd_wr, &state->writefds))
state->devs[i] = NULL;
}
else
{
if (d->fd_rd < 0 || FD_ISSET(d->fd_rd, &state->readfds))
state->devs[i] = NULL;
}
#endif
#ifdef USE_WIN32
if (for_writing)
{
if (d->h_wr != NULL && state->devs_next[i] != -1)
state->devs[i] = NULL;
}
else
{
if (d->h_rd != NULL)
state->devs[i] = NULL;
}
#endif
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_release_raw_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_force_output_raw_virt
___P((___device_stream *self,
int level),
(self,
level)
___device_stream *self;
int level;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_seek_raw_virt
___P((___device_stream *self,
___stream_index *pos,
int whence),
(self,
pos,
whence)
___device_stream *self;
___stream_index *pos;
int whence;)
{
return ___FIX(___INVALID_OP_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_read_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_pipe *d = ___CAST(___device_pipe*,self);
___SCMOBJ e = ___FIX(___NO_ERR);
if (d->base.base.read_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
#ifdef USE_POSIX
if (d->fd_rd < 0)
*len_done = 0;
else
{
int n = 0;
if ((n = read (d->fd_rd, buf, len)) < 0)
{
#if 0
if (errno == EIO) errno = EAGAIN;
#else
if (errno == EIO) /* on linux, treating EIO as EAGAIN gives an infinite loop */
n = 0;
else
#endif
e = err_code_from_errno ();
}
*len_done = n;
}
#endif
#ifdef USE_WIN32
if (d->h_rd == NULL)
*len_done = 0;
else
{
DWORD n = 0;
if (!PeekNamedPipe (d->h_rd, NULL, 0, NULL, &n, NULL))
e = err_code_from_GetLastError ();
else if (n == 0)
e = ___ERR_CODE_EAGAIN;
else
{
if (len > n)
len = n;
if (!ReadFile (d->h_rd, buf, len, &n, NULL))
e = err_code_from_GetLastError ();
else
d->poll_interval_nsecs = 0;
}
if (e == ___FIX(___WIN32_ERR(ERROR_BROKEN_PIPE)))
e = ___FIX(___NO_ERR); /* generate end-of-file on broken pipe */
*len_done = n;
}
#endif
return e;
}
___HIDDEN ___SCMOBJ ___device_pipe_write_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_pipe *d = ___CAST(___device_pipe*,self);
if (d->base.base.write_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
#ifdef USE_POSIX
if (d->fd_wr < 0)
*len_done = len;
else
{
int n;
if ((n = write (d->fd_wr, buf, len)) < 0)
return err_code_from_errno ();
*len_done = n;
}
#endif
#ifdef USE_WIN32
if (d->h_wr == NULL)
*len_done = len;
else
{
DWORD n;
if (!WriteFile (d->h_wr, buf, len, &n, NULL))
return err_code_from_GetLastError ();
*len_done = n;
}
#endif
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_pipe_width_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(80);
}
___HIDDEN ___SCMOBJ ___device_pipe_default_options_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
int char_encoding_errors = ___CHAR_ENCODING_ERRORS_ON;
int char_encoding = ___CHAR_ENCODING_ISO_8859_1;
int eol_encoding = ___EOL_ENCODING_LF;
int buffering = ___FULL_BUFFERING;
return ___FIX(___STREAM_OPTIONS(char_encoding_errors,
char_encoding,
eol_encoding,
buffering,
char_encoding_errors,
char_encoding,
eol_encoding,
buffering));
}
___HIDDEN ___SCMOBJ ___device_pipe_options_set_virt
___P((___device_stream *self,
___SCMOBJ options),
(self,
options)
___device_stream *self;
___SCMOBJ options;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___device_pipe_vtbl ___device_pipe_table =
{
{
{
___device_pipe_kind,
___device_stream_select_virt,
___device_stream_release_virt,
___device_stream_force_output_virt,
___device_stream_close_virt
},
___device_pipe_select_raw_virt,
___device_pipe_release_raw_virt,
___device_pipe_force_output_raw_virt,
___device_pipe_close_raw_virt,
___device_pipe_seek_raw_virt,
___device_pipe_read_raw_virt,
___device_pipe_write_raw_virt,
___device_pipe_width_virt,
___device_pipe_default_options_virt,
___device_pipe_options_set_virt
}
};
#ifdef USE_POSIX
___HIDDEN ___SCMOBJ ___device_pipe_setup_from_fd
___P((___device_pipe **dev,
___device_group *dgroup,
int fd_rd,
int fd_wr,
int direction),
(dev,
dgroup,
fd_rd,
fd_wr,
direction)
___device_pipe **dev;
___device_group *dgroup;
int fd_rd;
int fd_wr;
int direction;)
{
___device_pipe *d;
d = ___CAST(___device_pipe*,
___alloc_mem (sizeof (___device_pipe)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
d->base.base.vtbl = &___device_pipe_table;
d->fd_rd = fd_rd;
d->fd_wr = fd_wr;
*dev = d;
return ___device_stream_setup
(&d->base,
dgroup,
direction,
0);
}
#endif
#ifdef USE_WIN32
___HIDDEN ___SCMOBJ ___device_pipe_setup_from_handle
___P((___device_pipe **dev,
___device_group *dgroup,
HANDLE h_rd,
HANDLE h_wr,
int direction,
int pumps_on),
(dev,
dgroup,
h_rd,
h_wr,
direction,
pumps_on)
___device_pipe **dev;
___device_group *dgroup;
HANDLE h_rd;
HANDLE h_wr;
int direction;
int pumps_on;)
{
___device_pipe *d;
d = ___CAST(___device_pipe*,
___alloc_mem (sizeof (___device_pipe)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
d->base.base.vtbl = &___device_pipe_table;
d->h_rd = h_rd;
d->h_wr = h_wr;
d->poll_interval_nsecs = 0;
*dev = d;
return ___device_stream_setup
(&d->base,
dgroup,
direction,
pumps_on);
}
#endif
/*---------------------------------------------------------------------------*/
/* Process stream device */
typedef struct ___device_process_struct
{
___device_pipe base;
#ifdef USE_POSIX
pid_t pid; /* pid of the process */
#endif
#ifdef USE_WIN32
PROCESS_INFORMATION pi; /* process information */
#endif
int status; /* process status */
___BOOL got_status; /* was the status retrieved? */
___BOOL cleanuped; /* has process been cleaned-up? */
} ___device_process;
typedef struct ___device_process_vtbl_struct
{
___device_stream_vtbl base;
} ___device_process_vtbl;
___HIDDEN int ___device_process_kind
___P((___device *self),
(self)
___device *self;)
{
return ___PROCESS_DEVICE_KIND;
}
___SCMOBJ ___device_process_cleanup
___P((___device_process *dev),
(dev)
___device_process *dev;)
{
if (!dev->cleanuped)
{
dev->cleanuped = 1;
#ifdef USE_POSIX
#endif
#ifdef USE_WIN32
CloseHandle (dev->pi.hProcess); /* ignore error */
CloseHandle (dev->pi.hThread); /* ignore error */
#endif
}
return ___FIX(___NO_ERR);
}
___SCMOBJ ___device_process_status_set
___P((___device_process *dev,
int status),
(dev,
status)
___device_process *dev;
int status;)
{
___SCMOBJ e = ___FIX(___NO_ERR);
if (!dev->got_status)
{
dev->status = status;
dev->got_status = 1;
e = ___device_process_cleanup (dev); /* ignore error */
}
return e;
}
___SCMOBJ ___device_process_status_poll
___P((___device_process *dev),
(dev)
___device_process *dev;)
{
if (!dev->got_status)
{
#ifdef USE_POSIX
/*
* The process status is updated asynchronously by
* sigchld_signal_handler.
*/
#endif
#ifdef USE_WIN32
DWORD status;
if (!GetExitCodeProcess (dev->pi.hProcess, &status))
return err_code_from_GetLastError ();
if (status != STILL_ACTIVE)
___device_process_status_set (dev, status << 8); /* ignore error */
#endif
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_process_close_raw_virt
___P((___device_stream *self,
int direction),
(self,
direction)
___device_stream *self;
int direction;)
{
___device_process *d = ___CAST(___device_process*,self);
___SCMOBJ e = ___device_pipe_close_raw_virt (self, direction);
if (e == ___FIX(___NO_ERR))
{
if (d->base.base.base.read_stage == ___STAGE_CLOSED &&
d->base.base.base.write_stage == ___STAGE_CLOSED)
___device_process_status_poll (d); /* ignore error */
}
return e;
}
___HIDDEN ___SCMOBJ ___device_process_select_raw_virt
___P((___device_stream *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device_stream *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
return ___device_pipe_select_raw_virt (self, for_writing, i, pass, state);
}
___HIDDEN ___SCMOBJ ___device_process_release_raw_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
___device_process *d = ___CAST(___device_process*,self);
___SCMOBJ e1 = ___device_pipe_release_raw_virt (self);
___SCMOBJ e2 = ___device_process_cleanup (d);
if (e1 == ___FIX(___NO_ERR))
e1 = e2;
return e1;
}
___HIDDEN ___SCMOBJ ___device_process_force_output_raw_virt
___P((___device_stream *self,
int level),
(self,
level)
___device_stream *self;
int level;)
{
return ___device_pipe_force_output_raw_virt (self, level);
}
___HIDDEN ___SCMOBJ ___device_process_seek_raw_virt
___P((___device_stream *self,
___stream_index *pos,
int whence),
(self,
pos,
whence)
___device_stream *self;
___stream_index *pos;
int whence;)
{
return ___device_pipe_seek_raw_virt (self, pos, whence);
}
___HIDDEN ___SCMOBJ ___device_process_read_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
return ___device_pipe_read_raw_virt (self, buf, len, len_done);
}
___HIDDEN ___SCMOBJ ___device_process_write_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
return ___device_pipe_write_raw_virt (self, buf, len, len_done);
}
___HIDDEN ___SCMOBJ ___device_process_width_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(80);
}
___HIDDEN ___SCMOBJ ___device_process_default_options_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___device_pipe_default_options_virt (self);
}
___HIDDEN ___SCMOBJ ___device_process_options_set_virt
___P((___device_stream *self,
___SCMOBJ options),
(self,
options)
___device_stream *self;
___SCMOBJ options;)
{
return ___device_pipe_options_set_virt (self, options);
}
___HIDDEN ___device_process_vtbl ___device_process_table =
{
{
{
___device_process_kind,
___device_stream_select_virt,
___device_stream_release_virt,
___device_stream_force_output_virt,
___device_stream_close_virt
},
___device_process_select_raw_virt,
___device_process_release_raw_virt,
___device_process_force_output_raw_virt,
___device_process_close_raw_virt,
___device_process_seek_raw_virt,
___device_process_read_raw_virt,
___device_process_write_raw_virt,
___device_process_width_virt,
___device_process_default_options_virt,
___device_process_options_set_virt
}
};
#ifdef USE_POSIX
___SCMOBJ ___device_process_setup_from_pid
___P((___device_process **dev,
___device_group *dgroup,
pid_t pid,
int fd_stdin,
int fd_stdout,
int direction),
(dev,
dgroup,
pid,
fd_stdin,
fd_stdout,
direction)
___device_process **dev;
___device_group *dgroup;
pid_t pid;
int fd_stdin;
int fd_stdout;
int direction;)
{
___device_process *d;
d = ___CAST(___device_process*,
___alloc_mem (sizeof (___device_process)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
/*
* Setup file descriptors to perform nonblocking I/O.
*/
if ((fd_stdout >= 0 &&
(direction & ___DIRECTION_RD) &&
(set_fd_blocking_mode (fd_stdout, 0) < 0)) ||
(fd_stdin >= 0 &&
(direction & ___DIRECTION_WR) &&
(set_fd_blocking_mode (fd_stdin, 0) < 0)))
{
___SCMOBJ e = err_code_from_errno ();
___free_mem (d);
return e;
}
d->base.base.base.vtbl = &___device_process_table;
d->base.fd_rd = fd_stdout;
d->base.fd_wr = fd_stdin;
d->pid = pid;
d->status = -1;
d->got_status = 0;
d->cleanuped = 0;
*dev = d;
return ___device_stream_setup
(&d->base.base,
dgroup,
direction,
0);
}
#endif
#ifdef USE_WIN32
___SCMOBJ ___device_process_setup_from_process
___P((___device_process **dev,
___device_group *dgroup,
PROCESS_INFORMATION pi,
HANDLE hstdin,
HANDLE hstdout,
int direction),
(dev,
dgroup,
pi,
hstdin,
hstdout,
direction)
___device_process **dev;
___device_group *dgroup;
PROCESS_INFORMATION pi;
HANDLE hstdin;
HANDLE hstdout;
int direction;)
{
___device_process *d;
d = ___CAST(___device_process*,
___alloc_mem (sizeof (___device_process)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
d->base.base.base.vtbl = &___device_process_table;
d->base.h_rd = hstdout;
d->base.h_wr = hstdin;
d->pi = pi;
d->status = -1;
d->got_status = 0;
d->cleanuped = 0;
*dev = d;
return ___device_stream_setup
(&d->base.base,
dgroup,
direction,
0);
}
#endif
/*---------------------------------------------------------------------------*/
#ifdef USE_NETWORKING
/* Socket utilities */
#ifdef USE_POSIX
#define SOCKET_TYPE int
#define SOCKET_CALL_ERROR(s) ((s) < 0)
#define SOCKET_CALL_ERROR2(s) ((s) < 0)
#define CONNECT_IN_PROGRESS (errno == EINPROGRESS)
#define CONNECT_WOULD_BLOCK (errno == EAGAIN)
#define NOT_CONNECTED(e) ((e) == ___FIX(___ERRNO_ERR(ENOTCONN)))
#define CLOSE_SOCKET(s) close_no_EINTR (s)
#define ERR_CODE_FROM_SOCKET_CALL err_code_from_errno ()
#define IOCTL_SOCKET(s,cmd,argp) ioctl (s,cmd,argp)
#define SOCKET_LEN_TYPE socklen_t
#endif
#ifdef USE_WIN32
#define SOCKET_TYPE SOCKET
#define SOCKET_CALL_ERROR(s) ((s) == SOCKET_ERROR)
#define SOCKET_CALL_ERROR2(s) ((s) == INVALID_SOCKET)
#define CONNECT_IN_PROGRESS ((WSAGetLastError () == WSAEALREADY) || \
(WSAGetLastError () == WSAEISCONN))
#define CONNECT_WOULD_BLOCK ((WSAGetLastError () == WSAEWOULDBLOCK) || \
(WSAGetLastError () == WSAEINVAL))
#define NOT_CONNECTED(e) ((e) == ___FIX(___WIN32_ERR(WSAENOTCONN)))
#define CLOSE_SOCKET(s) closesocket (s)
#define ERR_CODE_FROM_SOCKET_CALL err_code_from_WSAGetLastError ()
#define IOCTL_SOCKET(s,cmd,argp) ioctlsocket (s,cmd,argp)
#define SOCKET_LEN_TYPE int
#endif
#ifdef SHUT_RD
#define SHUTDOWN_RD SHUT_RD
#else
#ifdef SD_RECEIVE
#define SHUTDOWN_RD SD_RECEIVE
#else
#define SHUTDOWN_RD 0
#endif
#endif
#ifdef SHUT_WR
#define SHUTDOWN_WR SHUT_WR
#else
#ifdef SD_SEND
#define SHUTDOWN_WR SD_SEND
#else
#define SHUTDOWN_WR 1
#endif
#endif
#endif
/*---------------------------------------------------------------------------*/
#ifdef USE_NETWORKING
/* TCP client stream device */
typedef struct ___device_tcp_client_struct
{
___device_stream base;
SOCKET_TYPE s;
struct sockaddr server_addr;
SOCKET_LEN_TYPE server_addrlen;
int try_connect_again;
int connect_done;
#ifdef USE_POSIX
int try_connect_interval_nsecs;
#endif
#ifdef USE_WIN32
long io_events; /* used by ___device_tcp_client_select_raw_virt */
HANDLE io_event; /* used by ___device_tcp_client_select_raw_virt */
#endif
} ___device_tcp_client;
typedef struct ___device_tcp_client_vtbl_struct
{
___device_stream_vtbl base;
} ___device_tcp_client_vtbl;
___HIDDEN int try_connect
___P((___device_tcp_client *dev),
(dev)
___device_tcp_client *dev;)
{
if (!SOCKET_CALL_ERROR(connect (dev->s,
&dev->server_addr,
dev->server_addrlen)) ||
CONNECT_IN_PROGRESS || /* establishing connection in background */
dev->try_connect_again == 2) /* last connect attempt? */
{
dev->try_connect_again = 0; /* we're done waiting */
return 0;
}
if (CONNECT_WOULD_BLOCK) /* connect can't be performed now */
return 0;
return -1;
}
___HIDDEN int ___device_tcp_client_kind
___P((___device *self),
(self)
___device *self;)
{
return ___TCP_CLIENT_DEVICE_KIND;
}
___HIDDEN ___SCMOBJ ___device_tcp_client_close_raw_virt
___P((___device_stream *self,
int direction),
(self,
direction)
___device_stream *self;
int direction;)
{
___device_tcp_client *d = ___CAST(___device_tcp_client*,self);
int is_not_closed = 0;
if (d->base.base.read_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_RD;
if (d->base.base.write_stage != ___STAGE_CLOSED)
is_not_closed |= ___DIRECTION_WR;
if (is_not_closed == 0)
return ___FIX(___NO_ERR);
if ((is_not_closed & ~direction) == 0)
{
/* Close socket when both sides are closed. */
d->base.base.read_stage = ___STAGE_CLOSED; /* avoid multiple closes */
d->base.base.write_stage = ___STAGE_CLOSED;
#ifdef USE_WIN32
if (d->io_event != NULL)
CloseHandle (d->io_event); /* ignore error */
#endif
if ((d->base.base.close_direction & (___DIRECTION_RD|___DIRECTION_WR))
== (___DIRECTION_RD|___DIRECTION_WR))
{
if (CLOSE_SOCKET(d->s) != 0)
return ERR_CODE_FROM_SOCKET_CALL;
}
}
else if (is_not_closed & direction & ___DIRECTION_RD)
{
/* Shutdown receiving side. */
if ((d->base.base.close_direction & ___DIRECTION_RD)
== ___DIRECTION_RD)
{
if (shutdown (d->s, SHUTDOWN_RD) != 0)
{
___SCMOBJ e = ERR_CODE_FROM_SOCKET_CALL;
if (!NOT_CONNECTED(e))
return e;
}
}
d->base.base.read_stage = ___STAGE_CLOSED;
}
else if (is_not_closed & direction & ___DIRECTION_WR)
{
/* Shutdown sending side. */
if ((d->base.base.close_direction & ___DIRECTION_WR)
== ___DIRECTION_WR)
{
if (shutdown (d->s, SHUTDOWN_WR) != 0)
{
___SCMOBJ e = ERR_CODE_FROM_SOCKET_CALL;
if (!NOT_CONNECTED(e))
return e;
}
}
d->base.base.write_stage = ___STAGE_CLOSED;
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_select_raw_virt
___P((___device_stream *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device_stream *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_tcp_client *d = ___CAST(___device_tcp_client*,self);
int stage = (for_writing
? d->base.base.write_stage
: d->base.base.read_stage);
if (pass == ___SELECT_PASS_1)
{
if (stage != ___STAGE_OPEN)
{
state->timeout = ___time_mod.time_neg_infinity;
return ___FIX(___SELECT_SETUP_DONE);
}
else
{
#ifdef USE_POSIX
if (d->try_connect_again != 0)
{
int interval = d->try_connect_interval_nsecs * 6 / 5;
if (interval > 200000000) /* max interval = 0.2 sec */
interval = 200000000;
d->try_connect_interval_nsecs = interval;
___device_select_add_relative_timeout (state, i, interval * 1e-9);
}
else
___device_select_add_fd (state, d->s, for_writing);
return ___FIX(___SELECT_SETUP_DONE);
#endif
#ifdef USE_WIN32
d->io_events = 0;
return ___FIX(___NO_ERR);
#endif
}
}
#ifdef USE_WIN32
else if (pass == ___SELECT_PASS_2)
{
if (d->try_connect_again != 0)
d->io_events = (FD_CONNECT | FD_CLOSE);
else if (for_writing)
d->io_events |= (FD_WRITE | FD_CLOSE);
else
d->io_events |= (FD_READ | FD_CLOSE);
return ___FIX(___NO_ERR);
}
else if (pass == ___SELECT_PASS_3)
{
HANDLE wait_obj = d->io_event;
ResetEvent (wait_obj); /* ignore error */
WSAEventSelect (d->s, wait_obj, d->io_events);
___device_select_add_wait_obj (state, i, wait_obj);
return ___FIX(___SELECT_SETUP_DONE);
}
#endif
/* pass == ___SELECT_PASS_CHECK */
if (stage != ___STAGE_OPEN)
state->devs[i] = NULL;
else
{
#ifdef USE_POSIX
if (d->try_connect_again != 0 ||
(for_writing
? FD_ISSET(d->s, &state->writefds)
: FD_ISSET(d->s, &state->readfds)))
{
d->connect_done = 1;
state->devs[i] = NULL;
}
#endif
#ifdef USE_WIN32
if (state->devs_next[i] != -1)
{
state->devs[i] = NULL;
if (d->try_connect_again != 0)
{
d->connect_done = 1;
d->try_connect_again = 2;
}
}
#endif
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_release_raw_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_force_output_raw_virt
___P((___device_stream *self,
int level),
(self,
level)
___device_stream *self;
int level;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_seek_raw_virt
___P((___device_stream *self,
___stream_index *pos,
int whence),
(self,
pos,
whence)
___device_stream *self;
___stream_index *pos;
int whence;)
{
return ___FIX(___INVALID_OP_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_read_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_tcp_client *d = ___CAST(___device_tcp_client*,self);
int n;
if (d->base.base.read_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
if (d->try_connect_again != 0)
{
if (try_connect (d) == 0)
{
if (d->try_connect_again != 0)
return ___ERR_CODE_EAGAIN;
}
else
return ERR_CODE_FROM_SOCKET_CALL;
}
if (SOCKET_CALL_ERROR(n = recv (d->s, ___CAST(char*,buf), len, 0)))
{
___SCMOBJ e = ERR_CODE_FROM_SOCKET_CALL;
if (NOT_CONNECTED(e) && !d->connect_done)
e = ___ERR_CODE_EAGAIN;
return e;
}
*len_done = n;
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_write_raw_virt
___P((___device_stream *self,
___U8 *buf,
___stream_index len,
___stream_index *len_done),
(self,
buf,
len,
len_done)
___device_stream *self;
___U8 *buf;
___stream_index len;
___stream_index *len_done;)
{
___device_tcp_client *d = ___CAST(___device_tcp_client*,self);
int n;
if (d->base.base.write_stage != ___STAGE_OPEN)
return ___FIX(___CLOSED_DEVICE_ERR);
if (d->try_connect_again != 0)
{
if (try_connect (d) == 0)
{
if (d->try_connect_again != 0)
return ___ERR_CODE_EAGAIN;
}
else
return ERR_CODE_FROM_SOCKET_CALL;
}
if (SOCKET_CALL_ERROR(n = send (d->s, ___CAST(char*,buf), len, 0)))
{
___SCMOBJ e = ERR_CODE_FROM_SOCKET_CALL;
if (NOT_CONNECTED(e) && !d->connect_done)
e = ___ERR_CODE_EAGAIN;
return e;
}
*len_done = n;
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_width_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
return ___FIX(80);
}
___HIDDEN ___SCMOBJ ___device_tcp_client_default_options_virt
___P((___device_stream *self),
(self)
___device_stream *self;)
{
int char_encoding_errors = ___CHAR_ENCODING_ERRORS_ON;
int char_encoding = ___CHAR_ENCODING_ISO_8859_1;
int eol_encoding = ___EOL_ENCODING_LF;
int buffering = ___FULL_BUFFERING;
return ___FIX(___STREAM_OPTIONS(char_encoding_errors,
char_encoding,
eol_encoding,
buffering,
char_encoding_errors,
char_encoding,
eol_encoding,
buffering));
}
___HIDDEN ___SCMOBJ ___device_tcp_client_options_set_virt
___P((___device_stream *self,
___SCMOBJ options),
(self,
options)
___device_stream *self;
___SCMOBJ options;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___device_tcp_client_vtbl ___device_tcp_client_table =
{
{
{
___device_tcp_client_kind,
___device_stream_select_virt,
___device_stream_release_virt,
___device_stream_force_output_virt,
___device_stream_close_virt
},
___device_tcp_client_select_raw_virt,
___device_tcp_client_release_raw_virt,
___device_tcp_client_force_output_raw_virt,
___device_tcp_client_close_raw_virt,
___device_tcp_client_seek_raw_virt,
___device_tcp_client_read_raw_virt,
___device_tcp_client_write_raw_virt,
___device_tcp_client_width_virt,
___device_tcp_client_default_options_virt,
___device_tcp_client_options_set_virt
}
};
#define ___SOCK_KEEPALIVE_FLAG(options) (((options) & 1) != 0)
#define ___SOCK_NO_COALESCE_FLAG(options) (((options) & 2) != 0)
#define ___SOCK_REUSE_ADDRESS_FLAG(options) (((options) & 2048) != 0)
___HIDDEN ___SCMOBJ create_tcp_socket
___P((SOCKET_TYPE *sock,
int options),
(sock,
options)
SOCKET_TYPE *sock;
int options;)
{
int keepalive_flag = ___SOCK_KEEPALIVE_FLAG(options);
int no_coalesce_flag = ___SOCK_NO_COALESCE_FLAG(options);
int reuse_address_flag = ___SOCK_REUSE_ADDRESS_FLAG(options);
SOCKET_TYPE s;
if (SOCKET_CALL_ERROR2(s = socket (AF_INET, SOCK_STREAM, 0)))
return ERR_CODE_FROM_SOCKET_CALL;
#ifndef TCP_NODELAY
#define TCP_NODELAY 1
#endif
if ((keepalive_flag != 0 &&
setsockopt (s, /* keep connection alive or not */
SOL_SOCKET,
SO_KEEPALIVE,
___CAST(char*,&keepalive_flag),
sizeof (keepalive_flag)) != 0) ||
(reuse_address_flag != 0 &&
setsockopt (s, /* allow reusing the same address */
SOL_SOCKET,
SO_REUSEADDR,
___CAST(char*,&reuse_address_flag),
sizeof (reuse_address_flag)) != 0) ||
(no_coalesce_flag != 0 &&
setsockopt (s, /* enable or disable packet coalescing algorithm */
IPPROTO_TCP,
TCP_NODELAY,
___CAST(char*,&no_coalesce_flag),
sizeof (no_coalesce_flag)) != 0))
{
___SCMOBJ e = ERR_CODE_FROM_SOCKET_CALL;
CLOSE_SOCKET(s); /* ignore error */
return e;
}
*sock = s;
return ___FIX(___NO_ERR);
}
___HIDDEN int set_socket_non_blocking
___P((SOCKET_TYPE s),
(s)
SOCKET_TYPE s;)
{
#ifndef USE_ioctl
#undef FIONBIO
#endif
#ifdef FIONBIO
unsigned long param = 1;
return SOCKET_CALL_ERROR(IOCTL_SOCKET(s, FIONBIO, &param));
#else
return set_fd_blocking_mode (s, 0);
#endif
}
___SCMOBJ ___device_tcp_client_setup_from_socket
___P((___device_tcp_client **dev,
___device_group *dgroup,
SOCKET_TYPE s,
struct sockaddr *server_addr,
SOCKET_LEN_TYPE server_addrlen,
int try_connect_again,
int direction),
(dev,
dgroup,
s,
server_addr,
server_addrlen,
try_connect_again,
direction)
___device_tcp_client **dev;
___device_group *dgroup;
SOCKET_TYPE s;
struct sockaddr *server_addr;
SOCKET_LEN_TYPE server_addrlen;
int try_connect_again;
int direction;)
{
___SCMOBJ e;
___device_tcp_client *d;
d = ___CAST(___device_tcp_client*,
___alloc_mem (sizeof (___device_tcp_client)));
if (d == NULL)
return ___FIX(___HEAP_OVERFLOW_ERR);
/*
* Setup socket to perform nonblocking I/O.
*/
if (set_socket_non_blocking (s) != 0) /* set nonblocking mode */
{
e = ERR_CODE_FROM_SOCKET_CALL;
___free_mem (d);
return e;
}
d->base.base.vtbl = &___device_tcp_client_table;
d->s = s;
d->server_addr = *server_addr;
d->server_addrlen = server_addrlen;
d->try_connect_again = try_connect_again;
d->connect_done = 0;
#ifdef USE_POSIX
d->try_connect_interval_nsecs = 1000000; /* 0.001 secs */
#endif
#ifdef USE_WIN32
d->io_event =
CreateEvent (NULL, /* can't inherit */
TRUE, /* manual reset */
FALSE, /* not signaled */
NULL); /* no name */
if (d->io_event == NULL)
{
e = err_code_from_GetLastError ();
___free_mem (d);
return e;
}
#endif
*dev = d;
return ___device_stream_setup
(&d->base,
dgroup,
direction,
0);
}
___SCMOBJ ___device_tcp_client_setup_from_sockaddr
___P((___device_tcp_client **dev,
___device_group *dgroup,
struct sockaddr *server_addr,
SOCKET_LEN_TYPE server_addrlen,
int options,
int direction),
(dev,
dgroup,
server_addr,
server_addrlen,
options,
direction)
___device_tcp_client **dev;
___device_group *dgroup;
struct sockaddr *server_addr;
SOCKET_LEN_TYPE server_addrlen;
int options;
int direction;)
{
___SCMOBJ e;
SOCKET_TYPE s;
___device_tcp_client *d;
if ((e = create_tcp_socket (&s, options)) != ___FIX(___NO_ERR))
return e;
if ((e = ___device_tcp_client_setup_from_socket
(&d,
dgroup,
s,
server_addr,
server_addrlen,
1,
direction))
!= ___FIX(___NO_ERR))
{
CLOSE_SOCKET(s); /* ignore error */
return e;
}
device_transfer_close_responsibility (___CAST(___device*,d));
*dev = d;
if (try_connect (d) != 0)
{
e = ERR_CODE_FROM_SOCKET_CALL;
___device_cleanup (&d->base.base); /* ignore error */
return e;
}
return ___FIX(___NO_ERR);
}
#endif
/*---------------------------------------------------------------------------*/
#ifdef USE_NETWORKING
/* TCP server device. */
typedef struct ___device_tcp_server_struct
{
___device base;
SOCKET_TYPE s;
#ifdef USE_WIN32
HANDLE io_event; /* used by ___device_tcp_server_select_raw_virt */
#endif
} ___device_tcp_server;
typedef struct ___device_tcp_server_vtbl_struct
{
___device_vtbl base;
} ___device_tcp_server_vtbl;
___HIDDEN int ___device_tcp_server_kind
___P((___device *self),
(self)
___device *self;)
{
return ___TCP_SERVER_DEVICE_KIND;
}
___HIDDEN ___SCMOBJ ___device_tcp_server_close_virt
___P((___device *self,
int direction),
(self,
direction)
___device *self;
int direction;)
{
___device_tcp_server *d = ___CAST(___device_tcp_server*,self);
if (d->base.read_stage == ___STAGE_CLOSED)
return ___FIX(___NO_ERR);
if (direction & ___DIRECTION_RD)
{
d->base.read_stage = ___STAGE_CLOSED; /* avoid multiple closes */
#ifdef USE_WIN32
if (d->io_event != NULL)
CloseHandle (d->io_event); /* ignore error */
#endif
if ((d->base.close_direction & ___DIRECTION_RD)
== ___DIRECTION_RD)
{
if (CLOSE_SOCKET(d->s) != 0)
return ERR_CODE_FROM_SOCKET_CALL;
}
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_server_select_virt
___P((___device *self,
___BOOL for_writing,
int i,
int pass,
___device_select_state *state),
(self,
for_writing,
i,
pass,
state)
___device *self;
___BOOL for_writing;
int i;
int pass;
___device_select_state *state;)
{
___device_tcp_server *d = ___CAST(___device_tcp_server*,self);
int stage = (for_writing
? d->base.write_stage
: d->base.read_stage);
if (pass == ___SELECT_PASS_1)
{
if (stage != ___STAGE_OPEN)
state->timeout = ___time_mod.time_neg_infinity;
else
{
#ifdef USE_POSIX
___device_select_add_fd (state, d->s, for_writing);
#endif
#ifdef USE_WIN32
HANDLE wait_obj = d->io_event;
ResetEvent (wait_obj); /* ignore error */
WSAEventSelect (d->s, wait_obj, FD_ACCEPT);
___device_select_add_wait_obj (state, i, wait_obj);
#endif
}
return ___FIX(___SELECT_SETUP_DONE);
}
/* pass == ___SELECT_PASS_CHECK */
if (stage != ___STAGE_OPEN)
state->devs[i] = NULL;
else
{
#ifdef USE_POSIX
if (FD_ISSET(d->s, &state->readfds))
state->devs[i] = NULL;
#endif
#ifdef USE_WIN32
if (state->devs_next[i] != -1)
state->devs[i] = NULL;
#endif
}
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_server_release_virt
___P((___device *self),
(self)
___device *self;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___SCMOBJ ___device_tcp_server_force_output_virt
___P((___device *self,
int level),
(self,
level)
___device *self;
int level;)
{
return ___FIX(___NO_ERR);
}
___HIDDEN ___device_tcp_server_vtbl ___device_tcp_server_table =
{
{
___device_tcp_server_kind,
___device_tcp_server_select_virt,
___device_tcp_server_release_virt,
___device_tcp_server_force_output_virt,
___device_tcp_server_close_virt
}
};