diff options
Diffstat (limited to 'erts/emulator/drivers')
22 files changed, 24168 insertions, 0 deletions
diff --git a/erts/emulator/drivers/common/efile_drv.c b/erts/emulator/drivers/common/efile_drv.c new file mode 100644 index 0000000000..95510a16b2 --- /dev/null +++ b/erts/emulator/drivers/common/efile_drv.c @@ -0,0 +1,3138 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations. + * + * This file is generic, and does the work of decoding the commands + * and encoding the responses. System-specific functions are found in + * the unix_efile.c and win_efile.c files. + */ + +/* Operations */ + +#define FILE_OPEN 1 /* Essential for startup */ +#define FILE_READ 2 +#define FILE_LSEEK 3 +#define FILE_WRITE 4 +#define FILE_FSTAT 5 /* Essential for startup */ +#define FILE_PWD 6 /* Essential for startup */ +#define FILE_READDIR 7 /* Essential for startup */ +#define FILE_CHDIR 8 +#define FILE_FSYNC 9 +#define FILE_MKDIR 10 +#define FILE_DELETE 11 +#define FILE_RENAME 12 +#define FILE_RMDIR 13 +#define FILE_TRUNCATE 14 +#define FILE_READ_FILE 15 /* Essential for startup */ +#define FILE_WRITE_INFO 16 +#define FILE_LSTAT 19 +#define FILE_READLINK 20 +#define FILE_LINK 21 +#define FILE_SYMLINK 22 +#define FILE_CLOSE 23 +#define FILE_PWRITEV 24 +#define FILE_PREADV 25 +#define FILE_SETOPT 26 +#define FILE_IPREAD 27 +#define FILE_ALTNAME 28 +#define FILE_READ_LINE 29 + +/* Return codes */ + +#define FILE_RESP_OK 0 +#define FILE_RESP_ERROR 1 +#define FILE_RESP_DATA 2 +#define FILE_RESP_NUMBER 3 +#define FILE_RESP_INFO 4 +#define FILE_RESP_NUMERR 5 +#define FILE_RESP_LDATA 6 +#define FILE_RESP_N2DATA 7 +#define FILE_RESP_EOF 8 + +/* Options */ + +#define FILE_OPT_DELAYED_WRITE 0 +#define FILE_OPT_READ_AHEAD 1 + +/* IPREAD variants */ + +#define IPREAD_S32BU_P32BU 0 + +/* Limits */ + +#define FILE_SEGMENT_READ (256*1024) +#define FILE_SEGMENT_WRITE (256*1024) + +/* Internal */ + +/* Set to 1 to test having read_ahead implicitly for read_line */ +#define ALWAYS_READ_LINE_AHEAD 0 + + +/* Must not be possible to get from malloc()! */ +#define FILE_FD_INVALID ((Sint)(-1)) + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include "sys.h" +#include "erl_driver.h" +#include "erl_efile.h" +#include "erl_threads.h" +#include "zlib.h" +#include "gzio.h" +#include <ctype.h> +#include <sys/types.h> + +extern void erl_exit(int n, char *fmt, _DOTS_); + +static ErlDrvSysInfo sys_info; + + +/*#define TRACE 1*/ +#ifdef TRACE +# define TRACE_C(c) (putchar(c)) +# define TRACE_S(s) (fputs((s), stdout)) +# define TRACE_F(args) (printf args) +#else +# define TRACE_C(c) ((void)(0)) +# define TRACE_S(s) ((void)(0)) +# define TRACE_F(args) ((void)(0)) +#endif + + +#ifdef USE_THREADS +#define IF_THRDS if (sys_info.async_threads > 0) +#ifdef HARDDEBUG /* HARDDEBUG in io.c is expected too */ +#define TRACE_DRIVER fprintf(stderr, "Efile: ") +#else +#define TRACE_DRIVER +#endif +#define MUTEX_INIT(m, p) do { IF_THRDS { TRACE_DRIVER; (m = driver_pdl_create(p)); } } while (0) +#define MUTEX_LOCK(m) do { IF_THRDS { TRACE_DRIVER; driver_pdl_lock(m); } } while (0) +#define MUTEX_UNLOCK(m) do { IF_THRDS { TRACE_DRIVER; driver_pdl_unlock(m); } } while (0) +#else +#define MUTEX_INIT(m, p) +#define MUTEX_LOCK(m) +#define MUTEX_UNLOCK(m) +#endif + + + +#if 0 +/* Experimental, for forcing all file operations to use the same thread. */ +static unsigned file_fixed_key = 1; +#define KEY(desc) (&file_fixed_key) +#else +#define KEY(desc) (&(desc)->key) +#endif + + + +#if MAXPATHLEN >= BUFSIZ +#define RESBUFSIZE MAXPATHLEN+1 +#else +#define RESBUFSIZE BUFSIZ +#endif + +#define GET_TIME(i, b) \ + (i).year = get_int32((b) + 0 * 4); \ + (i).month = get_int32((b) + 1 * 4); \ + (i).day = get_int32((b) + 2 * 4); \ + (i).hour = get_int32((b) + 3 * 4); \ + (i).minute = get_int32((b) + 4 * 4); \ + (i).second = get_int32((b) + 5 * 4) + +#define PUT_TIME(i, b) \ + put_int32((i).year, (b) + 0 * 4); \ + put_int32((i).month, (b) + 1 * 4); \ + put_int32((i).day, (b) + 2 * 4); \ + put_int32((i).hour, (b) + 3 * 4); \ + put_int32((i).minute,(b) + 4 * 4); \ + put_int32((i).second,(b) + 5 * 4) + + +#if ALWAYS_READ_LINE_AHEAD +#define DEFAULT_LINEBUF_SIZE 2048 +#else +#define DEFAULT_LINEBUF_SIZE 512 /* Small, it's usually discarded anyway */ +#endif + +typedef unsigned char uchar; + +static ErlDrvData file_start(ErlDrvPort port, char* command); +static int file_init(void); +static void file_stop(ErlDrvData); +static void file_output(ErlDrvData, char* buf, int len); +static int file_control(ErlDrvData, unsigned int command, + char* buf, int len, char **rbuf, int rlen); +static void file_timeout(ErlDrvData); +static void file_outputv(ErlDrvData, ErlIOVec*); +static void file_async_ready(ErlDrvData, ErlDrvThreadData); +static void file_flush(ErlDrvData); + + + +enum e_timer {timer_idle, timer_again, timer_write}; + +struct t_data; + +typedef struct { + Sint fd; + ErlDrvPort port; + unsigned key; /* Async queue key */ + unsigned flags; /* Original flags from FILE_OPEN. */ + void (*invoke)(void *); + struct t_data *d; + void (*free)(void *); + struct t_data *cq_head; /* Queue of incoming commands */ + struct t_data *cq_tail; /* -""- */ + enum e_timer timer_state; + size_t read_bufsize; + ErlDrvBinary *read_binp; + size_t read_offset; + size_t read_size; + size_t write_bufsize; + unsigned long write_delay; + int write_error; + Efile_error write_errInfo; + ErlDrvPDL q_mtx; /* Mutex for the driver queue, known by the emulator. Also used for + mutual exclusion when accessing field(s) below. */ + size_t write_buffered; +} file_descriptor; + + +static int reply_error(file_descriptor*, Efile_error* errInfo); + +struct erl_drv_entry efile_driver_entry = { + file_init, + file_start, + file_stop, + file_output, + NULL, + NULL, + "efile", + NULL, + NULL, + file_control, + file_timeout, + file_outputv, + file_async_ready, + file_flush, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL +}; + + + +static int thread_short_circuit; + +#define DRIVER_ASYNC(level, desc, f_invoke, data, f_free) \ +if (thread_short_circuit >= (level)) { \ + (*(f_invoke))(data); \ + file_async_ready((ErlDrvData)(desc), (data)); \ +} else { \ + driver_async((desc)->port, KEY(desc), (f_invoke), (data), (f_free)); \ +} + + + +struct t_pbuf_spec { + Sint64 offset; + size_t size; +}; + +struct t_pwritev { + ErlDrvPort port; + ErlDrvPDL q_mtx; + size_t size; + size_t free_size; + unsigned cnt; + unsigned n; + struct t_pbuf_spec specs[1]; +}; + +struct t_preadv { + ErlIOVec eiov; + unsigned n; + unsigned cnt; + size_t size; + Sint64 offsets[1]; +}; + +#define READDIR_BUFSIZE (8*1024) +#if READDIR_BUFSIZE < (2*MAXPATHLEN) +#undef READDIR_BUFSIZE +#define READDIR_BUFSIZE (2*MAXPATHLEN) +#endif + +struct t_readdir_buf { + struct t_readdir_buf *next; + char buf[READDIR_BUFSIZE]; +}; + +struct t_data +{ + struct t_data *next; + int command; + int level; + void (*invoke)(void *); + void (*free)(void *); + int again; + int reply; + int result_ok; + Efile_error errInfo; + int flags; + Sint fd; + /**/ + Efile_info info; + EFILE_DIR_HANDLE dir_handle; /* Handle to open directory. */ + ErlDrvBinary *bin; + int drive; + size_t n; + /*off_t offset;*/ + /*size_t bytesRead; Bytes read from the file. */ + /**/ + union { + struct { + Sint64 offset; + int origin; + Sint64 location; + } lseek; + struct { + ErlDrvPort port; + ErlDrvPDL q_mtx; + size_t size; + size_t free_size; + size_t reply_size; + } writev; + struct t_pwritev pwritev; + struct t_preadv preadv; + struct { + ErlDrvBinary *binp; + size_t bin_offset; + size_t bin_size; + size_t size; + } read; + struct { + ErlDrvBinary *binp; /* in - out */ + size_t read_offset; /* in - out */ + size_t read_size; /* in - out */ + size_t nl_pos; /* out */ + short nl_skip; /* out, 0 or 1 */ +#if !ALWAYS_READ_LINE_AHEAD + short read_ahead; /* in, bool */ +#endif + } read_line; + struct { + ErlDrvBinary *binp; + int size; + int offset; + char name[1]; + } read_file; + struct { + struct t_readdir_buf *first_buf; + struct t_readdir_buf *last_buf; + } read_dir; + } c; + char b[1]; +}; + + +#define EF_ALLOC(S) driver_alloc((S)) +#define EF_REALLOC(P, S) driver_realloc((P), (S)) +#define EF_SAFE_ALLOC(S) ef_safe_alloc((S)) +#define EF_SAFE_REALLOC(P, S) ef_safe_realloc((P), (S)) +#define EF_FREE(P) do { if((P)) driver_free((P)); } while(0) + +static void *ef_safe_alloc(Uint s) +{ + void *p = EF_ALLOC(s); + if (!p) erl_exit(1, "efile drv: Can't allocate %d bytes of memory\n", s); + return p; +} + +#if 0 /* Currently not used */ + +static void *ef_safe_realloc(void *op, Uint s) +{ + void *p = EF_REALLOC(op, s); + if (!p) erl_exit(1, "efile drv: Can't reallocate %d bytes of memory\n", s); + return p; +} + +#endif + +/********************************************************************* + * ErlIOVec manipulation functions. + */ + +/* char EV_CHAR(ErlIOVec *ev, int p, int q) */ +#define EV_CHAR_P(ev, p, q) \ + (((char *)(ev)->iov[(q)].iov_base) + (p)) + +/* int EV_GET_CHAR(ErlIOVec *ev, char *p, int *pp, int *qp) */ +#define EV_GET_CHAR(ev, p, pp, qp) \ + (*(pp)+1 <= (ev)->iov[*(qp)].iov_len \ + ? (*(p) = *EV_CHAR_P(ev, *(pp), *(qp)), \ + *(pp) = ( *(pp)+1 < (ev)->iov[*(qp)].iov_len \ + ? *(pp)+1 \ + : ((*(qp))++, 0)), \ + !0) \ + : 0) + +/* Uint32 EV_UINT32(ErlIOVec *ev, int p, int q)*/ +#define EV_UINT32(ev, p, q) \ + ((Uint32) *(((unsigned char *)(ev)->iov[(q)].iov_base) + (p))) + +/* int EV_GET_UINT32(ErlIOVec *ev, Uint32 *p, int *pp, int *qp) */ +#define EV_GET_UINT32(ev, p, pp, qp) \ + (*(pp)+4 <= (ev)->iov[*(qp)].iov_len \ + ? (*(p) = (EV_UINT32(ev, *(pp), *(qp)) << 24) \ + | (EV_UINT32(ev, *(pp)+1, *(qp)) << 16) \ + | (EV_UINT32(ev, *(pp)+2, *(qp)) << 8) \ + | (EV_UINT32(ev, *(pp)+3, *(qp))), \ + *(pp) = ( *(pp)+4 < (ev)->iov[*(qp)].iov_len \ + ? *(pp)+4 \ + : ((*(qp))++, 0)), \ + !0) \ + : 0) + +/* Uint64 EV_UINT64(ErlIOVec *ev, int p, int q)*/ +#define EV_UINT64(ev, p, q) \ + ((Uint64) *(((unsigned char *)(ev)->iov[(q)].iov_base) + (p))) + +/* int EV_GET_UINT64(ErlIOVec *ev, Uint32 *p, int *pp, int *qp) */ +#define EV_GET_UINT64(ev, p, pp, qp) \ + (*(pp)+8 <= (ev)->iov[*(qp)].iov_len \ + ? (*(p) = (EV_UINT64(ev, *(pp), *(qp)) << 56) \ + | (EV_UINT64(ev, *(pp)+1, *(qp)) << 48) \ + | (EV_UINT64(ev, *(pp)+2, *(qp)) << 40) \ + | (EV_UINT64(ev, *(pp)+3, *(qp)) << 32) \ + | (EV_UINT64(ev, *(pp)+4, *(qp)) << 24) \ + | (EV_UINT64(ev, *(pp)+5, *(qp)) << 16) \ + | (EV_UINT64(ev, *(pp)+6, *(qp)) << 8) \ + | (EV_UINT64(ev, *(pp)+7, *(qp))), \ + *(pp) = ( *(pp)+8 < (ev)->iov[*(qp)].iov_len \ + ? *(pp)+8 \ + : ((*(qp))++, 0)), \ + !0) \ + : 0) + + + +#if 0 + +static void ev_clear(ErlIOVec *ev) { + ASSERT(ev); + ev->size = 0; + ev->vsize = 0; + ev->iov = NULL; + ev->binv = NULL; +} + +/* Assumes that ->iov and ->binv were allocated with sys_alloc(). + */ +static void ev_free(ErlIOVec *ev) { + if (! ev) { + return; + } + if (ev->vsize > 0) { + int i; + ASSERT(ev->iov); + ASSERT(ev->binv); + for (i = 0; i < ev->vsize; i++) { + if (ev->binv[i]) { + driver_free_binary(ev->binv[i]); + } + } + EF_FREE(ev->iov); + EF_FREE(ev->binv); + } +} + +/* Copy the contents from source to dest. + * Data in binaries is not copied, just the pointers; + * and refc is incremented. + */ +static ErlIOVec *ev_copy(ErlIOVec *dest, ErlIOVec *source) { + int *ip; + ASSERT(dest); + ASSERT(source); + if (source->vsize == 0) { + /* Empty source */ + ev_clear(dest); + return dest; + } + /* Allocate ->iov and ->binv */ + dest->iov = EF_ALLOC(sizeof(*dest->iov) * source->vsize); + if (! dest->iov) { + return NULL; + } + dest->binv = EF_ALLOC(sizeof(*dest->binv) * source->vsize); + if (! dest->binv) { + EF_FREE(dest->iov); + return NULL; + } + dest->size = source->size; + /* Copy one vector element at the time. + * Use *ip as an alias for dest->vsize to improve readabiliy. + * Keep dest consistent in every iteration by using + * dest->vsize==*ip as loop variable. + */ + for (ip = &dest->vsize, *ip = 0; *ip < source->vsize; (*ip)++) { + if (source->iov[*ip].iov_len == 0) { + /* Empty vector element */ + dest->iov[*ip].iov_len = 0; + dest->iov[*ip].iov_base = NULL; + dest->binv[*ip] = NULL; + } else { + /* Non empty vector element */ + if (source->binv[*ip]) { + /* Contents in binary - copy pointers and increment refc */ + dest->iov[*ip] = source->iov[*ip]; + dest->binv[*ip] = source->binv[*ip]; + driver_binary_inc_refc(source->binv[*ip]); + } else { + /* Contents not in binary - allocate new binary and copy data */ + if (! (dest->binv[*ip] = + driver_alloc_binary(source->iov[*ip].iov_len))) { + goto failed; + } + sys_memcpy(dest->binv[*ip]->orig_bytes, + source->iov[*ip].iov_base, + source->iov[*ip].iov_len); + dest->iov[*ip].iov_base = dest->binv[*ip]->orig_bytes; + dest->iov[*ip].iov_len = source->iov[*ip].iov_len; + } + } + } + return dest; + failed: + ev_free(dest); + return NULL; +} + +#endif + + + +/********************************************************************* + * Command queue functions + */ + +static void cq_enq(file_descriptor *desc, struct t_data *d) { + ASSERT(d); + if (desc->cq_head) { + ASSERT(desc->cq_tail); + ASSERT(!desc->cq_tail->next); + desc->cq_tail = desc->cq_tail->next = d; + } else { + ASSERT(desc->cq_tail == NULL); + desc->cq_head = desc->cq_tail = d; + } + d->next = NULL; +} + +static struct t_data *cq_deq(file_descriptor *desc) { + struct t_data *d = desc->cq_head; + ASSERT(d || (!d && !desc->cq_tail)); + if (d) { + ASSERT(!d->next || (d->next && desc->cq_tail != d)); + if ((desc->cq_head = d->next) == NULL) { + ASSERT(desc->cq_tail == d); + desc->cq_tail = NULL; + } + } + return d; +} + + + +/********************************************************************* + * Driver entry point -> init + */ +static int +file_init(void) +{ + char buf[21]; /* enough to hold any 64-bit integer */ + size_t bufsz = sizeof(buf); + thread_short_circuit = (erl_drv_getenv("ERL_EFILE_THREAD_SHORT_CIRCUIT", + buf, + &bufsz) == 0 + ? atoi(buf) + : 0); + driver_system_info(&sys_info, sizeof(ErlDrvSysInfo)); + return 0; +} + +/********************************************************************* + * Driver entry point -> start + */ +static ErlDrvData +file_start(ErlDrvPort port, char* command) + +{ + file_descriptor* desc; + + if ((desc = (file_descriptor*) EF_ALLOC(sizeof(file_descriptor))) + == NULL) { + errno = ENOMEM; + return ERL_DRV_ERROR_ERRNO; + } + desc->fd = FILE_FD_INVALID; + desc->port = port; + desc->key = (unsigned) (Uint) port; + desc->flags = 0; + desc->invoke = NULL; + desc->d = NULL; + desc->free = NULL; + desc->cq_head = NULL; + desc->cq_tail = NULL; + desc->timer_state = timer_idle; + desc->read_bufsize = 0; + desc->read_binp = NULL; + desc->read_offset = 0; + desc->read_size = 0; + desc->write_delay = 0L; + desc->write_bufsize = 0; + desc->write_error = 0; + MUTEX_INIT(desc->q_mtx, port); /* Refc is one, referenced by emulator now */ + desc->write_buffered = 0; + return (ErlDrvData) desc; +} + +static void free_data(void *data) +{ + EF_FREE(data); +} + +static void do_close(int flags, Sint fd) { + if (flags & EFILE_COMPRESSED) { + erts_gzclose((gzFile)(fd)); + } else { + efile_closefile((int) fd); + } +} + +static void invoke_close(void *data) +{ + struct t_data *d = (struct t_data *) data; + d->again = 0; + do_close(d->flags, d->fd); +} + +/********************************************************************* + * Driver entry point -> stop + */ +static void +file_stop(ErlDrvData e) +{ + file_descriptor* desc = (file_descriptor*)e; + + TRACE_C('p'); + + if (desc->fd != FILE_FD_INVALID) { + do_close(desc->flags, desc->fd); + desc->fd = FILE_FD_INVALID; + desc->flags = 0; + } + if (desc->read_binp) { + driver_free_binary(desc->read_binp); + } + EF_FREE(desc); +} + + +/* + * Sends back an error reply to Erlang. + */ + +static void reply_posix_error(file_descriptor *desc, int posix_errno) { + char response[256]; /* Response buffer. */ + char* s; + char* t; + + /* + * Contents of buffer sent back: + * + * +-----------------------------------------+ + * | FILE_RESP_ERROR | Posix error id string | + * +-----------------------------------------+ + */ + + TRACE_C('E'); + + response[0] = FILE_RESP_ERROR; + for (s = erl_errno_id(posix_errno), t = response+1; *s; s++, t++) + *t = tolower(*s); + driver_output2(desc->port, response, t-response, NULL, 0); +} + +static void reply_Uint_posix_error(file_descriptor *desc, Uint num, + int posix_errno) { + char response[256]; /* Response buffer. */ + char* s; + char* t; + + /* + * Contents of buffer sent back: + * + * +----------------------------------------------------------------------+ + * | FILE_RESP_NUMERR | 64-bit number (big-endian) | Posix error id string | + * +----------------------------------------------------------------------+ + */ + + TRACE_C('N'); + + response[0] = FILE_RESP_NUMERR; +#if SIZEOF_VOID_P == 4 + put_int32(0, response+1); +#else + put_int32(num>>32, response+1); +#endif + put_int32((Uint32)num, response+1+4); + for (s = erl_errno_id(posix_errno), t = response+1+4+4; *s; s++, t++) + *t = tolower(*s); + driver_output2(desc->port, response, t-response, NULL, 0); +} + + + +static int reply_error(file_descriptor *desc, + Efile_error *errInfo) /* The error codes. */ +{ + reply_posix_error(desc, errInfo->posix_errno); + return 0; +} + +static int reply_Uint_error(file_descriptor *desc, Uint num, + Efile_error *errInfo) /* The error codes. */ +{ + reply_Uint_posix_error(desc, num, errInfo->posix_errno); + return 0; +} + +static int reply_ok(file_descriptor *desc) { + char c = FILE_RESP_OK; + + driver_output2(desc->port, &c, 1, NULL, 0); + return 0; +} + +static int reply(file_descriptor *desc, int ok, Efile_error *errInfo) { + if (!ok) { + reply_error(desc, errInfo); + } else { + TRACE_C('K'); + reply_ok(desc); + } + return 0; +} + +static int reply_Uint(file_descriptor *desc, Uint result) { + char tmp[1+4+4]; + + /* + * Contents of buffer sent back: + * + * +-----------------------------------------------+ + * | FILE_RESP_NUMBER | 64-bit number (big-endian) | + * +-----------------------------------------------+ + */ + + TRACE_C('R'); + + tmp[0] = FILE_RESP_NUMBER; +#if SIZEOF_VOID_P == 4 + put_int32(0, tmp+1); +#else + put_int32(result>>32, tmp+1); +#endif + put_int32((Uint32)result, tmp+1+4); + driver_output2(desc->port, tmp, sizeof(tmp), NULL, 0); + return 0; +} + +static int reply_Sint64(file_descriptor *desc, Sint64 result) { + char tmp[1+4+4]; + + /* + * Contents of buffer sent back: + * + * +-----------------------------------------------+ + * | FILE_RESP_NUMBER | 64-bit number (big-endian) | + * +-----------------------------------------------+ + */ + + TRACE_C('R'); + + tmp[0] = FILE_RESP_NUMBER; + put_int64(result, tmp+1); + driver_output2(desc->port, tmp, sizeof(tmp), NULL, 0); + return 0; +} + +#if 0 +static void reply_again(file_descriptor *desc) { + char tmp[1]; + tmp[0] = FILE_RESP_AGAIN; + driver_output2(desc->port, tmp, sizeof(tmp), NULL, 0); +} +#endif + +static void reply_ev(file_descriptor *desc, char response, ErlIOVec *ev) { + char tmp[1]; + /* Data arriving at the Erlang process: + * [Response, Binary0, Binary1, .... | BinaryN-1] + */ + tmp[0] = response; + driver_outputv(desc->port, tmp, sizeof(tmp), ev, 0); +} + +static void reply_data(file_descriptor *desc, + ErlDrvBinary *binp, size_t offset, size_t len) { + char header[1+4+4]; + /* Data arriving at the Erlang process: + * [?FILE_RESP_DATA, 64-bit length (big-endian) | Data] + */ + header[0] = FILE_RESP_DATA; +#if SIZEOF_SIZE_T == 4 + put_int32(0, header+1); +#else + put_int32(len>>32, header+1); +#endif + put_int32((Uint32)len, header+1+4); + driver_output_binary(desc->port, header, sizeof(header), + binp, offset, len); +} + +static void reply_buf(file_descriptor *desc, char *buf, size_t len) { + char header[1+4+4]; + /* Data arriving at the Erlang process: + * [?FILE_RESP_DATA, 64-bit length (big-endian) | Data] + */ + header[0] = FILE_RESP_DATA; +#if SIZEOF_SIZE_T == 4 + put_int32(0, header+1); +#else + put_int32(len>>32, header+1); +#endif + put_int32((Uint32)len, header+1+4); + driver_output2(desc->port, header, sizeof(header), buf, len); +} + +static int reply_eof(file_descriptor *desc) { + char c = FILE_RESP_EOF; + + driver_output2(desc->port, &c, 1, NULL, 0); + return 0; +} + + + +static void invoke_name(void *data, int (*f)(Efile_error *, char *)) +{ + struct t_data *d = (struct t_data *) data; + char *name = (char *) d->b; + + d->again = 0; + d->result_ok = (*f)(&d->errInfo, name); +} + +static void invoke_mkdir(void *data) +{ + invoke_name(data, efile_mkdir); +} + +static void invoke_rmdir(void *data) +{ + invoke_name(data, efile_rmdir); +} + +static void invoke_delete_file(void *data) +{ + invoke_name(data, efile_delete_file); +} + +static void invoke_chdir(void *data) +{ + invoke_name(data, efile_chdir); +} + +static void invoke_fsync(void *data) +{ + struct t_data *d = (struct t_data *) data; + int fd = (int) d->fd; + + d->again = 0; + d->result_ok = efile_fsync(&d->errInfo, fd); +} + +static void invoke_truncate(void *data) +{ + struct t_data *d = (struct t_data *) data; + int fd = (int) d->fd; + + d->again = 0; + d->result_ok = efile_truncate_file(&d->errInfo, &fd, d->flags); +} + +static void invoke_read(void *data) +{ + struct t_data *d = (struct t_data *) data; + int status, segment; + size_t size, read_size; + + segment = d->again && d->c.read.bin_size >= 2*FILE_SEGMENT_READ; + if (segment) { + size = FILE_SEGMENT_READ; + } else { + size = d->c.read.bin_size; + } + read_size = size; + if (d->flags & EFILE_COMPRESSED) { + read_size = erts_gzread((gzFile)d->fd, + d->c.read.binp->orig_bytes + d->c.read.bin_offset, + size); + status = (read_size != -1); + if (!status) { + d->errInfo.posix_errno = EIO; + } + } else { + status = efile_read(&d->errInfo, d->flags, (int) d->fd, + d->c.read.binp->orig_bytes + d->c.read.bin_offset, + size, + &read_size); + } + if ( (d->result_ok = status)) { + ASSERT(read_size <= size); + d->c.read.bin_offset += read_size; + if (read_size < size || !segment) { + d->c.read.bin_size = 0; + d->again = 0; + } else { + d->c.read.bin_size -= read_size; + } + } else { + d->again = 0; + } +} + +static void free_read(void *data) +{ + struct t_data *d = (struct t_data *) data; + + driver_free_binary(d->c.read.binp); + EF_FREE(d); +} + +static void invoke_read_line(void *data) +{ + struct t_data *d = (struct t_data *) data; + int status; + size_t read_size; + int local_loop = (d->again == 0); + + do { + size_t size = (d->c.read_line.binp)->orig_size - + d->c.read_line.read_offset - d->c.read_line.read_size; + if (size == 0) { + /* Need more place */ + size_t need = (d->c.read_line.read_size >= DEFAULT_LINEBUF_SIZE) ? + d->c.read_line.read_size + DEFAULT_LINEBUF_SIZE : DEFAULT_LINEBUF_SIZE; + ErlDrvBinary *newbin = driver_alloc_binary(need); + if (newbin == NULL) { + d->result_ok = 0; + d->errInfo.posix_errno = ENOMEM; + d->again = 0; + break; + } + memcpy(newbin->orig_bytes, (d->c.read_line.binp)->orig_bytes + d->c.read_line.read_offset, + d->c.read_line.read_size); + driver_free_binary(d->c.read_line.binp); + d->c.read_line.binp = newbin; + d->c.read_line.read_offset = 0; + size = need - d->c.read_line.read_size; + } + if (d->flags & EFILE_COMPRESSED) { + read_size = erts_gzread((gzFile)d->fd, + d->c.read_line.binp->orig_bytes + + d->c.read_line.read_offset + d->c.read_line.read_size, + size); + status = (read_size != -1); + if (!status) { + d->errInfo.posix_errno = EIO; + } + } else { + status = efile_read(&d->errInfo, d->flags, (int) d->fd, + d->c.read_line.binp->orig_bytes + + d->c.read_line.read_offset + d->c.read_line.read_size, + size, + &read_size); + } + if ( (d->result_ok = status)) { + void *nl_ptr = memchr((d->c.read_line.binp)->orig_bytes + + d->c.read_line.read_offset + d->c.read_line.read_size,'\n',read_size); + ASSERT(read_size <= size); + d->c.read_line.read_size += read_size; + if (nl_ptr != NULL) { + /* If found, we're done */ + d->c.read_line.nl_pos = ((char *) nl_ptr) - + ((char *) ((d->c.read_line.binp)->orig_bytes)) + 1; + if (d->c.read_line.nl_pos > 1 && + *(((char *) nl_ptr) - 1) == '\r') { + --d->c.read_line.nl_pos; + *(((char *) nl_ptr) - 1) = '\n'; + d->c.read_line.nl_skip = 1; + } else { + d->c.read_line.nl_skip = 0; + } + d->again = 0; +#if !ALWAYS_READ_LINE_AHEAD + if (!(d->c.read_line.read_ahead)) { + /* Ouch! Undo buffering... */ + size_t too_much = d->c.read_line.read_size - d->c.read_line.nl_skip - + (d->c.read_line.nl_pos - d->c.read_line.read_offset); + d->c.read_line.read_size -= too_much; + ASSERT(d->c.read_line.read_size >= 0); + if (d->flags & EFILE_COMPRESSED) { + Sint64 location = erts_gzseek((gzFile)d->fd, + -((Sint64) too_much), EFILE_SEEK_CUR); + if (location == -1) { + d->result_ok = 0; + d->errInfo.posix_errno = errno; + } + } else { + Sint64 location; + d->result_ok = efile_seek(&d->errInfo, (int) d->fd, + -((Sint64) too_much), EFILE_SEEK_CUR, + &location); + } + } +#endif + break; + } else if (read_size == 0) { + d->c.read_line.nl_pos = + d->c.read_line.read_offset + d->c.read_line.read_size; + d->c.read_line.nl_skip = 0; + d->again = 0; + break; + } + } else { + d->again = 0; + break; + } + } while (local_loop); +} + +static void free_read_line(void *data) +{ + struct t_data *d = (struct t_data *) data; + + driver_free_binary(d->c.read_line.binp); + EF_FREE(d); +} + +static void invoke_read_file(void *data) +{ + struct t_data *d = (struct t_data *) data; + size_t read_size; + int chop; + + if (! d->c.read_file.binp) { /* First invocation only */ + int fd; + Sint64 size; + + if (! (d->result_ok = + efile_openfile(&d->errInfo, d->c.read_file.name, + EFILE_MODE_READ, &fd, &size))) { + goto done; + } + d->fd = fd; + d->c.read_file.size = (int) size; + if (size < 0 || size != d->c.read_file.size || + ! (d->c.read_file.binp = + driver_alloc_binary(d->c.read_file.size))) { + d->result_ok = 0; + d->errInfo.posix_errno = ENOMEM; + goto close; + } + d->c.read_file.offset = 0; + } + /* Invariant: d->c.read_file.size >= d->c.read_file.offset */ + + read_size = (size_t) (d->c.read_file.size - d->c.read_file.offset); + if (! read_size) goto close; + chop = d->again && read_size >= FILE_SEGMENT_READ*2; + if (chop) read_size = FILE_SEGMENT_READ; + d->result_ok = + efile_read(&d->errInfo, + EFILE_MODE_READ, + (int) d->fd, + d->c.read_file.binp->orig_bytes + d->c.read_file.offset, + read_size, + &read_size); + if (d->result_ok) { + d->c.read_file.offset += read_size; + if (chop) return; /* again */ + } + close: + efile_closefile((int) d->fd); + done: + d->again = 0; +} + +static void free_read_file(void *data) +{ + struct t_data *d = (struct t_data *) data; + + if (d->c.read_file.binp) driver_free_binary(d->c.read_file.binp); + EF_FREE(d); +} + + + +static void invoke_preadv(void *data) +{ + struct t_data *d = (struct t_data *) data; + struct t_preadv *c = &d->c.preadv; + ErlIOVec *ev = &c->eiov; + size_t bytes_read_so_far = 0; + unsigned char *p = (unsigned char *)ev->iov[0].iov_base + 4+4+8*c->cnt; + + while (c->cnt < c->n) { + size_t read_size = ev->iov[1 + c->cnt].iov_len - c->size; + size_t bytes_read = 0; + int chop = d->again + && bytes_read_so_far + read_size >= 2*FILE_SEGMENT_READ; + if (chop) { + ASSERT(bytes_read_so_far < FILE_SEGMENT_READ); + read_size = FILE_SEGMENT_READ + FILE_SEGMENT_READ/2 + - bytes_read_so_far; + } + if ( (d->result_ok + = efile_pread(&d->errInfo, + (int) d->fd, + c->offsets[c->cnt] + c->size, + ev->iov[1 + c->cnt].iov_base + c->size, + read_size, + &bytes_read))) { + bytes_read_so_far += bytes_read; + if (chop && bytes_read == read_size) { + c->size += bytes_read; + return; + } + ASSERT(bytes_read <= read_size); + ev->iov[1 + c->cnt].iov_len = bytes_read + c->size; + ev->size += bytes_read + c->size; + put_int64(bytes_read + c->size, p); p += 8; + c->size = 0; + c->cnt++; + if (d->again + && bytes_read_so_far >= FILE_SEGMENT_READ + && c->cnt < c->n) { + return; + } + } else { + /* In case of a read error, ev->size will not be correct, + * which does not matter since no read data is returned + * to Erlang. + */ + break; + } + } + d->again = 0; +} + +static void free_preadv(void *data) { + struct t_data *d = data; + int i; + ErlIOVec *ev = &d->c.preadv.eiov; + + for(i = 0; i < ev->vsize; i++) { + driver_free_binary(ev->binv[i]); + } + EF_FREE(d); +} + +static void invoke_ipread(void *data) +{ + struct t_data *d = data; + struct t_preadv *c = &d->c.preadv; + ErlIOVec *ev = &c->eiov; + size_t bytes_read = 0; + char buf[2*sizeof(Uint32)]; + Uint32 offset, size; + + /* Read indirection header */ + if (! efile_pread(&d->errInfo, (int) d->fd, c->offsets[0], + buf, sizeof(buf), &bytes_read)) { + goto error; + } + if (bytes_read != sizeof(buf)) goto done; /* eof */ + size = get_int32(buf); + offset = get_int32(buf+4); + if (size > c->size) goto done; /* eof */ + c->n = 1; + c->cnt = 0; + c->size = 0; + c->offsets[0] = offset; + if (! (ev->binv[0] = driver_alloc_binary(3*8))) { + d->errInfo.posix_errno = ENOMEM; + goto error; + } + ev->vsize = 1; + ev->iov[0].iov_len = 3*8; + ev->iov[0].iov_base = ev->binv[0]->orig_bytes; + ev->size = ev->iov[0].iov_len; + put_int64(offset, ev->iov[0].iov_base); + put_int64(size, ((char *)ev->iov[0].iov_base) + 2*8); + if (size == 0) { + put_int64(size, ((char *)ev->iov[0].iov_base) + 8); + goto done; + } + if (! (ev->binv[1] = driver_alloc_binary(size))) { + d->errInfo.posix_errno = ENOMEM; + goto error; + } + ev->vsize = 2; + ev->iov[1].iov_len = size; + ev->iov[1].iov_base = ev->binv[1]->orig_bytes; + /* Read data block */ + d->invoke = invoke_preadv; + invoke_preadv(data); + return; + error: + d->result_ok = 0; + d->again = 0; + return; + done: + d->result_ok = !0; + d->again = 0; +} + +/* invoke_writev and invoke_pwritev are the only thread functions that + * access non-thread data i.e the port queue and a mutex in the port + * structure that is used to lock the port queue. + * + * The port will normally not be terminated until the port queue is + * empty, but if the port is killed, i.e., exit(Port, kill) is called, + * it will terminate regardless of the port queue state. When the + * port is invalid driver_peekq() returns NULL and set the size to -1, + * and driver_sizeq() returns -1. + */ + +static void invoke_writev(void *data) { + struct t_data *d = (struct t_data *) data; + SysIOVec *iov0; + SysIOVec *iov; + int iovlen; + int iovcnt; + size_t size; + size_t p; + int segment; + + segment = d->again && d->c.writev.size >= 2*FILE_SEGMENT_WRITE; + if (segment) { + size = FILE_SEGMENT_WRITE; + } else { + size = d->c.writev.size; + } + + /* Copy the io vector to avoid locking the port que while writing */ + MUTEX_LOCK(d->c.writev.q_mtx); /* Lock before accessing the port queue */ + iov0 = driver_peekq(d->c.writev.port, &iovlen); + + /* Calculate iovcnt */ + for (p = 0, iovcnt = 0; + p < size && iovcnt < iovlen; + p += iov0[iovcnt++].iov_len) + ; + iov = EF_ALLOC(sizeof(SysIOVec)*iovcnt); + memcpy(iov,iov0,iovcnt*sizeof(SysIOVec)); + MUTEX_UNLOCK(d->c.writev.q_mtx); + /* Let go of lock until we deque from original vector */ + + if (iovlen > 0) { + ASSERT(iov[iovcnt-1].iov_len > p - size); + iov[iovcnt-1].iov_len -= p - size; + if (d->flags & EFILE_COMPRESSED) { + int i, status = 1; + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_base && iov[i].iov_len > 0) { + /* Just in case, I do not know what gzwrite does + * with errno. + */ + errno = EINVAL; + if (! (status = + erts_gzwrite((gzFile)d->fd, + iov[i].iov_base, + iov[i].iov_len)) == iov[i].iov_len) { + d->errInfo.posix_errno = + d->errInfo.os_errno = errno; /* XXX Correct? */ + break; + } + } + } + d->result_ok = status; + } else { + d->result_ok = efile_writev(&d->errInfo, + d->flags, (int) d->fd, + iov, iovcnt, size); + } + } else if (iovlen == 0) { + d->result_ok = 1; + } + else { /* Port has terminated */ + d->result_ok = 0; + d->errInfo.posix_errno = d->errInfo.os_errno = EINVAL; + } + EF_FREE(iov); + + d->c.writev.free_size = size; + d->c.writev.size -= size; + if (! d->result_ok) { + d->again = 0; + } else { + if (! segment) { + d->again = 0; + } + TRACE_F(("w%lu", (unsigned long)size)); + + } +} + +static void free_writev(void *data) { + struct t_data *d = data; + MUTEX_LOCK(d->c.writev.q_mtx); + driver_deq(d->c.writev.port, d->c.writev.size + d->c.writev.free_size); + MUTEX_UNLOCK(d->c.writev.q_mtx); + EF_FREE(d); +} + +static void invoke_pwd(void *data) +{ + struct t_data *d = (struct t_data *) data; + + d->again = 0; + d->result_ok = efile_getdcwd(&d->errInfo,d->drive, d->b+1, + RESBUFSIZE-1); +} + +static void invoke_readlink(void *data) +{ + struct t_data *d = (struct t_data *) data; + char resbuf[RESBUFSIZE]; /* Result buffer. */ + + d->again = 0; + d->result_ok = efile_readlink(&d->errInfo, d->b, resbuf+1, + RESBUFSIZE-1); + if (d->result_ok != 0) + strcpy((char *) d->b + 1, resbuf+1); +} + +static void invoke_altname(void *data) +{ + struct t_data *d = (struct t_data *) data; + char resbuf[RESBUFSIZE]; /* Result buffer. */ + + d->again = 0; + d->result_ok = efile_altname(&d->errInfo, d->b, resbuf+1, + RESBUFSIZE-1); + if (d->result_ok != 0) + strcpy((char *) d->b + 1, resbuf+1); +} + +static void invoke_pwritev(void *data) { + struct t_data *d = (struct t_data *) data; + SysIOVec *iov0; + SysIOVec *iov; + int iovlen; + int iovcnt; + struct t_pwritev *c = &d->c.pwritev; + size_t p; + int segment; + size_t size, write_size; + + segment = d->again && c->size >= 2*FILE_SEGMENT_WRITE; + if (segment) { + size = FILE_SEGMENT_WRITE; + } else { + size = c->size; + } + d->result_ok = !0; + p = 0; + /* Lock the queue just for a while, we don't want it locked during write */ + MUTEX_LOCK(c->q_mtx); + iov0 = driver_peekq(c->port, &iovlen); + iov = EF_ALLOC(sizeof(SysIOVec)*iovlen); + memcpy(iov,iov0,sizeof(SysIOVec)*iovlen); + MUTEX_UNLOCK(c->q_mtx); + + if (iovlen < 0) + goto error; /* Port terminated */ + for (iovcnt = 0, c->free_size = 0; + c->cnt < c->n && iovcnt < iovlen && c->free_size < size; + c->cnt++) { + int chop; + write_size = c->specs[c->cnt].size; + if (iov[iovcnt].iov_len - p < write_size) { + /* Mismatch between pos/size spec and what is queued */ + d->errInfo.posix_errno = EINVAL; + d->result_ok = 0; + d->again = 0; + goto done; + } + chop = segment && c->free_size + write_size >= 2*FILE_SEGMENT_WRITE; + if (chop) { + ASSERT(c->free_size < FILE_SEGMENT_WRITE); + write_size = FILE_SEGMENT_WRITE + FILE_SEGMENT_WRITE/2 + - c->free_size; + } + d->result_ok = efile_pwrite(&d->errInfo, (int) d->fd, + iov[iovcnt].iov_base + p, + write_size, + c->specs[c->cnt].offset); + if (! d->result_ok) { + d->again = 0; + goto done; + } + c->free_size += write_size; + c->size -= write_size; + if (chop) { + c->specs[c->cnt].offset += write_size; + c->specs[c->cnt].size -= write_size; + /* Schedule out (d->again != 0) */ + goto done; + } + /* Move forward in buffer */ + p += write_size; + ASSERT(iov[iovcnt].iov_len >= p); + if (iov[iovcnt].iov_len == p) { + /* Move to next iov[], we trust that it is not a + * zero length vector, and thereby depend on that + * such are not queued. + */ + iovcnt++; p = 0; + } + } + if (! segment) { + if (c->cnt != c->n) { + /* Mismatch between number of + * pos/size specs vs number of queued buffers . + */ + error: + d->errInfo.posix_errno = EINVAL; + d->result_ok = 0; + d->again = 0; + } else { + ASSERT(c->free_size == size); + d->again = 0; + } + } + done: + EF_FREE(iov); /* Free our copy of the vector, nothing to restore */ +} + +static void free_pwritev(void *data) { + struct t_data *d = data; + + MUTEX_LOCK(d->c.writev.q_mtx); + driver_deq(d->c.pwritev.port, d->c.pwritev.free_size + d->c.pwritev.size); + MUTEX_UNLOCK(d->c.writev.q_mtx); + EF_FREE(d); +} + +static void invoke_flstat(void *data) +{ + struct t_data *d = (struct t_data *) data; + + d->again = 0; + d->result_ok = efile_fileinfo(&d->errInfo, &d->info, + d->b, d->command == FILE_LSTAT); +} + +static void invoke_link(void *data) +{ + struct t_data *d = (struct t_data *) data; + char *name = d->b; + char *new_name; + + d->again = 0; + new_name = name+strlen(name)+1; + d->result_ok = efile_link(&d->errInfo, name, new_name); +} + +static void invoke_symlink(void *data) +{ + struct t_data *d = (struct t_data *) data; + char *name = d->b; + char *new_name; + + d->again = 0; + new_name = name+strlen(name)+1; + d->result_ok = efile_symlink(&d->errInfo, name, new_name); +} + +static void invoke_rename(void *data) +{ + struct t_data *d = (struct t_data *) data; + char *name = d->b; + char *new_name; + + d->again = 0; + new_name = name+strlen(name)+1; + d->result_ok = efile_rename(&d->errInfo, name, new_name); +} + +static void invoke_write_info(void *data) +{ + struct t_data *d = (struct t_data *) data; + + d->again = 0; + d->result_ok = efile_write_info(&d->errInfo, &d->info, d->b); +} + +static void invoke_lseek(void *data) +{ + struct t_data *d = (struct t_data *) data; + int status; + + d->again = 0; + if (d->flags & EFILE_COMPRESSED) { + int offset = (int) d->c.lseek.offset; + + if (offset != d->c.lseek.offset) { + d->errInfo.posix_errno = EINVAL; + status = 0; + } else { + d->c.lseek.location = erts_gzseek((gzFile)d->fd, + offset, d->c.lseek.origin); + if (d->c.lseek.location == -1) { + d->errInfo.posix_errno = errno; + status = 0; + } else { + status = 1; + } + } + } else { + status = efile_seek(&d->errInfo, (int) d->fd, + d->c.lseek.offset, d->c.lseek.origin, + &d->c.lseek.location); + } + d->result_ok = status; +} + +static void invoke_readdir(void *data) +{ + struct t_data *d = (struct t_data *) data; + int s; + char *p = NULL; + int buf_sz = 0; + + d->again = 0; + d->errInfo.posix_errno = 0; + + while (1) { + char *str; + if (buf_sz < (4 /* sz */ + 1 /* cmd */ + MAXPATHLEN + 1 /* '\0' */)) { + struct t_readdir_buf *b; + if (p) { + put_int32(0, p); /* EOB */ + } + b = EF_SAFE_ALLOC(sizeof(struct t_readdir_buf)); + b->next = NULL; + if (d->c.read_dir.last_buf) + d->c.read_dir.last_buf->next = b; + else + d->c.read_dir.first_buf = b; + d->c.read_dir.last_buf = b; + p = &b->buf[0]; + buf_sz = READDIR_BUFSIZE - 4/* EOB */; + } + + p[4] = FILE_RESP_OK; + buf_sz -= 4 + 1; + str = p + 4 + 1; + ASSERT(buf_sz >= MAXPATHLEN + 1); + s = efile_readdir(&d->errInfo, d->b, &d->dir_handle, str, buf_sz); + + if (s) { + int str_sz = strlen(str); + int sz = str_sz + 1; + put_int32(sz, p); + p += 4 + sz; + buf_sz -= str_sz; + } + else { + put_int32(1, p); + p += 4 + 1; + put_int32(0, p); /* EOB */ + d->result_ok = (d->errInfo.posix_errno == 0); + break; + } + } +} + +static void invoke_open(void *data) +{ + struct t_data *d = (struct t_data *) data; + + int status = 1; /* Status of open call. */ + + d->again = 0; + if ((d->flags & EFILE_COMPRESSED) == 0) { + int fd; + status = efile_openfile(&d->errInfo, d->b, d->flags, &fd, NULL); + d->fd = fd; + } else { + char* mode = NULL; + + if (((d->flags & (EFILE_MODE_READ_WRITE)) == EFILE_MODE_READ_WRITE) || + (d->flags & EFILE_MODE_APPEND)) { + status = 0; + d->errInfo.posix_errno = EINVAL; + } else { + status = efile_may_openfile(&d->errInfo, d->b); + if (status || (d->errInfo.posix_errno != EISDIR)) { + mode = (d->flags & EFILE_MODE_READ) ? "rb" : "wb"; + d->fd = (Sint) erts_gzopen(d->b, mode); + if ((gzFile)d->fd) { + status = 1; + } else { + if (errno == 0) { + errno = ENOMEM; + } + d->errInfo.posix_errno = errno; + status = 0; + } + } + } + } + + d->result_ok = status; +} + +static void free_readdir(void *data) +{ + struct t_data *d = (struct t_data *) data; + struct t_readdir_buf *b1 = d->c.read_dir.first_buf; + while (b1) { + struct t_readdir_buf *b2 = b1; + b1 = b1->next; + EF_FREE(b2); + } + EF_FREE(d); +} + + + +static void try_free_read_bin(file_descriptor *desc) { + if ((desc->read_size == 0) + && (desc->read_offset >= desc->read_binp->orig_size)) { + ASSERT(desc->read_offset == desc->read_binp->orig_size); + driver_free_binary(desc->read_binp); + desc->read_binp = NULL; + desc->read_offset = 0; + desc->read_size = 0; + } +} + + + +static int try_again(file_descriptor *desc, struct t_data *d) { + if (! d->again) { + return 0; + } + switch (d->command) { + case FILE_WRITE: + MUTEX_LOCK(d->c.writev.q_mtx); + driver_deq(d->c.writev.port, d->c.writev.free_size); + MUTEX_UNLOCK(d->c.writev.q_mtx); + break; + case FILE_PWRITEV: + MUTEX_LOCK(d->c.writev.q_mtx); + driver_deq(d->c.pwritev.port, d->c.pwritev.free_size); + MUTEX_UNLOCK(d->c.writev.q_mtx); + break; + } + if (desc->timer_state != timer_idle) { + driver_cancel_timer(desc->port); + } + desc->timer_state = timer_again; + desc->invoke = d->invoke; + desc->d = d; + desc->free = d->free; + driver_set_timer(desc->port, 0L); + return !0; +} + + + +static void cq_execute(file_descriptor *desc) { + struct t_data *d; + register void *void_ptr; /* Soft cast variable */ + if (desc->timer_state == timer_again) + return; + if (! (d = cq_deq(desc))) + return; + TRACE_F(("x%i", (int) d->command)); + d->again = sys_info.async_threads == 0; + DRIVER_ASYNC(d->level, desc, d->invoke, void_ptr=d, d->free); +} + +static int async_write(file_descriptor *desc, int *errp, + int reply, Uint32 reply_size) { + struct t_data *d; + if (! (d = EF_ALLOC(sizeof(struct t_data) - 1))) { + if (errp) *errp = ENOMEM; + return -1; + } + TRACE_F(("w%lu", (unsigned long)desc->write_buffered)); + d->command = FILE_WRITE; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.writev.port = desc->port; + d->c.writev.q_mtx = desc->q_mtx; + d->c.writev.size = desc->write_buffered; + d->reply = reply; + d->c.writev.free_size = 0; + d->c.writev.reply_size = reply_size; + d->invoke = invoke_writev; + d->free = free_writev; + d->level = 1; + cq_enq(desc, d); + desc->write_buffered = 0; + return 0; +} + +static int flush_write(file_descriptor *desc, int *errp) { + int result; + MUTEX_LOCK(desc->q_mtx); + if (desc->write_buffered > 0) { + result = async_write(desc, errp, 0, 0); + } else { + result = 0; + } + MUTEX_UNLOCK(desc->q_mtx); + return result; +} + +static int check_write_error(file_descriptor *desc, int *errp) { + if (desc->write_error) { + if (errp) *errp = desc->write_errInfo.posix_errno; + desc->write_error = 0; + return -1; + } + return 0; +} + +static int flush_write_check_error(file_descriptor *desc, int *errp) { + int r; + if ( (r = flush_write(desc, errp)) != 0) { + check_write_error(desc, NULL); + return r; + } else { + return check_write_error(desc, errp); + } +} + +static int async_lseek(file_descriptor *desc, int *errp, int reply, + Sint64 offset, int origin) { + struct t_data *d; + if (! (d = EF_ALLOC(sizeof(struct t_data)))) { + *errp = ENOMEM; + return -1; + } + d->flags = desc->flags; + d->fd = desc->fd; + d->command = FILE_LSEEK; + d->reply = reply; + d->c.lseek.offset = offset; + d->c.lseek.origin = origin; + d->invoke = invoke_lseek; + d->free = free_data; + d->level = 1; + cq_enq(desc, d); + return 0; +} + +static void flush_read(file_descriptor *desc) { + desc->read_offset = 0; + desc->read_size = 0; + if (desc->read_binp) { + driver_free_binary(desc->read_binp); + desc->read_binp = NULL; + } +} + +static int lseek_flush_read(file_descriptor *desc, int *errp) { + int r = 0; + size_t read_size = desc->read_size; + if (read_size != 0) { + flush_read(desc); + if ((r = async_lseek(desc, errp, 0, + -((ssize_t)read_size), EFILE_SEEK_CUR)) + < 0) { + return r; + } + } else { + flush_read(desc); + } + return r; +} + + + +/********************************************************************* + * Driver entry point -> ready_async + */ +static void +file_async_ready(ErlDrvData e, ErlDrvThreadData data) +{ + file_descriptor *desc = (file_descriptor*)e; + struct t_data *d = (struct t_data *) data; + char header[5]; /* result code + count */ + char resbuf[RESBUFSIZE]; /* Result buffer. */ + + + TRACE_C('r'); + + if (try_again(desc, d)) { + return; + } + + switch (d->command) + { + case FILE_READ: + if (!d->result_ok) { + reply_error(desc, &d->errInfo); + } else { + size_t available_bytes = + d->c.read.bin_offset + d->c.read.bin_size - desc->read_offset; + if (available_bytes < d->c.read.size) { + d->c.read.size = available_bytes; + } + TRACE_C('D'); + reply_data(desc, d->c.read.binp, + desc->read_offset, d->c.read.size); + desc->read_offset += d->c.read.size; + desc->read_size = + d->c.read.bin_offset + d->c.read.bin_size - desc->read_offset; + try_free_read_bin(desc); + } + free_read(data); + break; + case FILE_READ_LINE: + /* The read_line stucture differs from the read structure. + The data->read_offset and d->c.read_line.read_offset are copies, as are + data->read_size and d->c.read_line.read_size + The read_line function does not kniow in advance how large the binary has to be, + why new allocation (but not reallocation of the old binary, for obvious reasons) + may happen in the worker thread. */ + if (!d->result_ok) { + reply_error(desc, &d->errInfo); + } else { + size_t len = d->c.read_line.nl_pos - d->c.read_line.read_offset; + TRACE_C('L'); + reply_data(desc, d->c.read_line.binp, + d->c.read_line.read_offset, len); + desc->read_offset = d->c.read_line.read_offset + d->c.read_line.nl_skip + len; + desc->read_size = + d->c.read_line.read_size - d->c.read_line.nl_skip - len; + if (desc->read_binp != d->c.read_line.binp) { /* New binary allocated */ + driver_free_binary(desc->read_binp); + desc->read_binp = d->c.read_line.binp; + driver_binary_inc_refc(desc->read_binp); + } +#if !ALWAYS_READ_LINE_AHEAD + ASSERT(desc->read_bufsize > 0 || desc->read_size == 0); + if (desc->read_bufsize == 0) { + desc->read_offset = desc->read_binp->orig_size; /* triggers cleanup */ + } +#endif + try_free_read_bin(desc); + } + free_read_line(data); + break; + case FILE_READ_FILE: + if (!d->result_ok) + reply_error(desc, &d->errInfo); + else { + header[0] = FILE_RESP_OK; + TRACE_C('R'); + driver_output_binary(desc->port, header, 1, + d->c.read_file.binp, + 0, d->c.read_file.offset); + } + free_read_file(data); + break; + case FILE_WRITE: + if (d->reply) { + if (! d->result_ok) { + reply_error(desc, &d->errInfo); + } else { + reply_Uint(desc, d->c.writev.reply_size); + } + } else { + if (! d->result_ok) { + desc->write_error = !0; + desc->write_errInfo = d->errInfo; + } + } + free_writev(data); + break; + case FILE_LSEEK: + if (d->reply) { + if (d->result_ok) + reply_Sint64(desc, d->c.lseek.location); + else + reply_error(desc, &d->errInfo); + } + free_data(data); + break; + case FILE_MKDIR: + case FILE_RMDIR: + case FILE_CHDIR: + case FILE_DELETE: + case FILE_FSYNC: + case FILE_TRUNCATE: + case FILE_LINK: + case FILE_SYMLINK: + case FILE_RENAME: + case FILE_WRITE_INFO: + reply(desc, d->result_ok, &d->errInfo); + free_data(data); + break; + case FILE_ALTNAME: + case FILE_PWD: + case FILE_READLINK: + { + int length; + char *resbuf = d->b; + + if (!d->result_ok) + reply_error(desc, &d->errInfo); + else { + resbuf[0] = FILE_RESP_OK; + length = 1+strlen((char*) resbuf+1); + TRACE_C('R'); + driver_output2(desc->port, resbuf, length, NULL, 0); + } + free_data(data); + break; + } + case FILE_OPEN: + if (!d->result_ok) { + reply_error(desc, &d->errInfo); + } else { + desc->fd = d->fd; + desc->flags = d->flags; + reply_Uint(desc, d->fd); + } + free_data(data); + break; + case FILE_FSTAT: + case FILE_LSTAT: + { + if (d->result_ok) { + resbuf[0] = FILE_RESP_INFO; + + put_int32(d->info.size_high, &resbuf[1 + (0 * 4)]); + put_int32(d->info.size_low, &resbuf[1 + (1 * 4)]); + put_int32(d->info.type, &resbuf[1 + (2 * 4)]); + + PUT_TIME(d->info.accessTime, resbuf + 1 + 3*4); + PUT_TIME(d->info.modifyTime, resbuf + 1 + 9*4); + PUT_TIME(d->info.cTime, resbuf + 1 + 15*4); + + put_int32(d->info.mode, &resbuf[1 + (21 * 4)]); + put_int32(d->info.links, &resbuf[1 + (22 * 4)]); + put_int32(d->info.major_device, &resbuf[1 + (23 * 4)]); + put_int32(d->info.minor_device, &resbuf[1 + (24 * 4)]); + put_int32(d->info.inode, &resbuf[1 + (25 * 4)]); + put_int32(d->info.uid, &resbuf[1 + (26 * 4)]); + put_int32(d->info.gid, &resbuf[1 + (27 * 4)]); + put_int32(d->info.access, &resbuf[1 + (28 * 4)]); + +#define RESULT_SIZE (1 + (29 * 4)) + TRACE_C('R'); + driver_output2(desc->port, resbuf, RESULT_SIZE, NULL, 0); +#undef RESULT_SIZE + } else + reply_error(desc, &d->errInfo); + } + free_data(data); + break; + case FILE_READDIR: + if (!d->result_ok) + reply_error(desc, &d->errInfo); + else { + struct t_readdir_buf *b1 = d->c.read_dir.first_buf; + TRACE_C('R'); + ASSERT(b1); + while (b1) { + struct t_readdir_buf *b2 = b1; + char *p = &b1->buf[0]; + int sz = get_int32(p); + while (sz) { /* 0 == EOB */ + p += 4; + driver_output2(desc->port, p, sz, NULL, 0); + p += sz; + sz = get_int32(p); + } + b1 = b1->next; + EF_FREE(b2); + } + d->c.read_dir.first_buf = NULL; + d->c.read_dir.last_buf = NULL; + } + free_readdir(data); + break; + /* See file_stop */ + case FILE_CLOSE: + if (d->reply) { + TRACE_C('K'); + reply_ok(desc); + } + free_data(data); + break; + case FILE_PWRITEV: + if (!d->result_ok) { + reply_Uint_error(desc, d->c.pwritev.cnt, &d->errInfo); + } else { + reply_Uint(desc, d->c.pwritev.n); + } + free_pwritev(data); + break; + case FILE_PREADV: + if (!d->result_ok) { + reply_error(desc, &d->errInfo); + } else { + reply_ev(desc, FILE_RESP_LDATA, &d->c.preadv.eiov); + } + free_preadv(data); + break; + case FILE_IPREAD: + if (!d->result_ok) { + reply_error(desc, &d->errInfo); + } else if (!d->c.preadv.eiov.vsize) { + reply_eof(desc); + } else { + reply_ev(desc, FILE_RESP_N2DATA, &d->c.preadv.eiov); + } + free_preadv(data); + break; + default: + abort(); + } + if (desc->write_buffered != 0 && desc->timer_state == timer_idle) { + desc->timer_state = timer_write; + driver_set_timer(desc->port, desc->write_delay); + } + cq_execute(desc); +} + +/********************************************************************* + * Driver entry point -> output + */ +static void +file_output(ErlDrvData e, char* buf, int count) +{ + file_descriptor* desc = (file_descriptor*)e; + Efile_error errInfo; /* The error codes for the last operation. */ + Sint fd; /* The file descriptor for this port, if any, + * -1 if none. + */ + char* name; /* Points to the filename in buf. */ + int command; + struct t_data *d = NULL; + + + TRACE_C('o'); + + fd = desc->fd; + name = buf+1; + command = *(uchar*)buf++; + + switch(command) { + + case FILE_MKDIR: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_mkdir; + d->free = free_data; + d->level = 2; + goto done; + } + case FILE_RMDIR: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_rmdir; + d->free = free_data; + d->level = 2; + goto done; + } + case FILE_DELETE: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_delete_file; + d->free = free_data; + d->level = 2; + goto done; + } + case FILE_RENAME: + { + char* new_name; + + new_name = name+strlen(name)+1; + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + + strlen(name) + 1 + + strlen(new_name) + 1); + + strcpy(d->b, name); + strcpy(d->b + strlen(name) + 1, new_name); + d->flags = desc->flags; + d->fd = fd; + d->command = command; + d->invoke = invoke_rename; + d->free = free_data; + d->level = 2; + goto done; + } + case FILE_CHDIR: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_chdir; + d->free = free_data; + d->level = 2; + goto done; + } + case FILE_PWD: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + RESBUFSIZE + 1); + + d->drive = *(uchar*)buf; + d->command = command; + d->invoke = invoke_pwd; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_READDIR: +#ifdef USE_THREADS + if (sys_info.async_threads > 0) + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->dir_handle = NULL; + d->command = command; + d->invoke = invoke_readdir; + d->free = free_readdir; + d->level = 2; + d->c.read_dir.first_buf = NULL; + d->c.read_dir.last_buf = NULL; + goto done; + } + else +#endif + { + char resbuf[RESBUFSIZE+1]; + EFILE_DIR_HANDLE dir_handle; /* Handle to open directory. */ + + errInfo.posix_errno = 0; + dir_handle = NULL; + resbuf[0] = FILE_RESP_OK; + + while (efile_readdir(&errInfo, name, &dir_handle, + resbuf+1, RESBUFSIZE)) { + int length = 1 + strlen(resbuf+1); + driver_output2(desc->port, resbuf, length, NULL, 0); + } + if (errInfo.posix_errno != 0) { + reply_error(desc, &errInfo); + return; + } + TRACE_C('R'); + driver_output2(desc->port, resbuf, 1, NULL, 0); + return; + } + case FILE_OPEN: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(buf+4) + 1); + + d->flags = get_int32((uchar*)buf); + name = buf+4; + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_open; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_FSYNC: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data)); + + d->fd = fd; + d->command = command; + d->invoke = invoke_fsync; + d->free = free_data; + d->level = 2; + goto done; + } + + + case FILE_FSTAT: + case FILE_LSTAT: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + strlen(name) + 1); + + strcpy(d->b, name); + d->fd = fd; + d->command = command; + d->invoke = invoke_flstat; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_TRUNCATE: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data)); + + d->flags = desc->flags; + d->fd = fd; + d->command = command; + d->invoke = invoke_truncate; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_WRITE_INFO: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + + strlen(buf+21*4) + 1); + + d->info.mode = get_int32(buf + 0 * 4); + d->info.uid = get_int32(buf + 1 * 4); + d->info.gid = get_int32(buf + 2 * 4); + GET_TIME(d->info.accessTime, buf + 3 * 4); + GET_TIME(d->info.modifyTime, buf + 9 * 4); + GET_TIME(d->info.cTime, buf + 15 * 4); + strcpy(d->b, buf+21*4); + d->command = command; + d->invoke = invoke_write_info; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_READLINK: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + RESBUFSIZE + 1); + + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_readlink; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_ALTNAME: + { + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + RESBUFSIZE + 1); + strcpy(d->b, name); + d->command = command; + d->invoke = invoke_altname; + d->free = free_data; + d->level = 2; + goto done; + } + + + case FILE_LINK: + { + char* new_name; + + new_name = name+strlen(name)+1; + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + + strlen(name) + 1 + + strlen(new_name) + 1); + + strcpy(d->b, name); + strcpy(d->b + strlen(name) + 1, new_name); + d->flags = desc->flags; + d->fd = fd; + d->command = command; + d->invoke = invoke_link; + d->free = free_data; + d->level = 2; + goto done; + } + + case FILE_SYMLINK: + { + char* new_name; + + new_name = name+strlen(name)+1; + d = EF_SAFE_ALLOC(sizeof(struct t_data) - 1 + + strlen(name) + 1 + + strlen(new_name) + 1); + + strcpy(d->b, name); + strcpy(d->b + strlen(name) + 1, new_name); + d->flags = desc->flags; + d->fd = fd; + d->command = command; + d->invoke = invoke_symlink; + d->free = free_data; + d->level = 2; + goto done; + } + + } + + /* + * Ignore anything else -- let the caller hang. + */ + + return; + + done: + if (d) { + cq_enq(desc, d); + } +} + +/********************************************************************* + * Driver entry point -> flush + */ +static void +file_flush(ErlDrvData e) { + file_descriptor *desc = (file_descriptor *)e; + int r; + + TRACE_C('f'); + + r = flush_write(desc, NULL); + /* Only possible reason for bad return value is ENOMEM, and + * there is nobody to tell... + */ + ASSERT(r == 0); + r = 0; /* Avoiding warning */ + cq_execute(desc); +} + + + +/********************************************************************* + * Driver entry point -> control + */ +static int +file_control(ErlDrvData e, unsigned int command, + char* buf, int len, char **rbuf, int rlen) { + file_descriptor *desc = (file_descriptor *)e; + switch (command) { + default: + return 0; + } /* switch (command) */ + ASSERT(0); + desc = NULL; /* XXX Avoid warning while empty switch */ + return 0; +} + +/********************************************************************* + * Driver entry point -> timeout + */ +static void +file_timeout(ErlDrvData e) { + file_descriptor *desc = (file_descriptor *)e; + enum e_timer timer_state = desc->timer_state; + + TRACE_C('t'); + + desc->timer_state = timer_idle; + switch (timer_state) { + case timer_idle: + ASSERT(0); + break; + case timer_again: + ASSERT(desc->invoke); + ASSERT(desc->free); + driver_async(desc->port, KEY(desc), desc->invoke, desc->d, desc->free); + break; + case timer_write: { + int r = flush_write(desc, NULL); + /* Only possible reason for bad return value is ENOMEM, and + * there is nobody to tell... + */ + ASSERT(r == 0); + r = 0; /* Avoiding warning */ + cq_execute(desc); + } break; + } /* case */ +} + + + +/********************************************************************* + * Driver entry point -> outputv + */ +static void +file_outputv(ErlDrvData e, ErlIOVec *ev) { + file_descriptor* desc = (file_descriptor*)e; + char command; + int p, q; + int err; + + TRACE_C('v'); + + p = 0; q = 1; + if (! EV_GET_CHAR(ev, &command, &p, &q)) { + /* Empty command */ + reply_posix_error(desc, EINVAL); + goto done; + } + /* 'command' contains the decoded command number, + * 'p' and 'q' point out the next byte in the command: + * ((char *)ev->iov[q].iov_base) + p; + */ + + TRACE_F(("%i", (int) command)); + + switch (command) { + + case FILE_CLOSE: { + flush_read(desc); + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (ev->size != 1) { + /* Wrong command length */ + reply_posix_error(desc, EINVAL); + goto done; + } + if (desc->fd != FILE_FD_INVALID) { + struct t_data *d; + if (! (d = EF_ALLOC(sizeof(struct t_data)))) { + reply_posix_error(desc, ENOMEM); + } else { + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->invoke = invoke_close; + d->free = free_data; + d->level = 2; + cq_enq(desc, d); + desc->fd = FILE_FD_INVALID; + desc->flags = 0; + } + } else { + reply_posix_error(desc, EBADF); + } + } goto done; + + case FILE_READ: { + Uint32 sizeH, sizeL; + size_t size, alloc_size; + struct t_data *d; + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } +#if ALWAYS_READ_LINE_AHEAD + if (desc->read_bufsize == 0 && desc->read_binp != NULL && desc->read_size > 0) { + /* We have allocated a buffer for line mode but should not really have a + read-ahead buffer... */ + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + } +#endif + if (ev->size != 1+8 + || !EV_GET_UINT32(ev, &sizeH, &p, &q) + || !EV_GET_UINT32(ev, &sizeL, &p, &q)) { + /* Wrong buffer length to contain the read count */ + reply_posix_error(desc, EINVAL); + goto done; + } +#if SIZEOF_SIZE_T == 4 + if (sizeH != 0) { + reply_posix_error(desc, EINVAL); + goto done; + } + size = sizeL; +#else + size = ((size_t)sizeH << 32) | sizeL; +#endif + if ((desc->fd == FILE_FD_INVALID) + || (! (desc->flags & EFILE_MODE_READ)) ) { + reply_posix_error(desc, EBADF); + goto done; + } + if (size == 0) { + reply_buf(desc, &command, 0); + goto done; + } + if (desc->read_size >= size) { + /* We already have all data */ + TRACE_C('D'); + reply_data(desc, desc->read_binp, desc->read_offset, size); + desc->read_offset += size; + desc->read_size -= size; + try_free_read_bin(desc); + goto done; + } + /* We may have some of the data + */ + /* Justification for the following strange formula: + * If the read request is for such a large block as more than + * half the buffer size it may lead to a lot of unnecessary copying, + * since the tail of the old buffer is copied to the head of the + * new, and if the tail is almost half the buffer it is a lot + * to copy. Therefore allocate the exact amount needed in + * this case, giving no lingering tail. */ + alloc_size = + size > (desc->read_bufsize>>1) ? + size : desc->read_bufsize; + if (! desc->read_binp) { + /* Need to allocate a new binary for the result */ + if (! (desc->read_binp = driver_alloc_binary(alloc_size))) { + reply_posix_error(desc, ENOMEM); + goto done; + } + } else { + /* We already have a buffer */ + if (desc->read_binp->orig_size - desc->read_offset < size) { + /* Need to allocate a new binary for the result */ + ErlDrvBinary *binp; + if (! (binp = driver_alloc_binary(alloc_size))) { + reply_posix_error(desc, ENOMEM); + goto done; + } + /* Move data we already have to the new binary */ + sys_memcpy(binp->orig_bytes, + desc->read_binp->orig_bytes + desc->read_offset, + desc->read_size); + driver_free_binary(desc->read_binp); + desc->read_offset = 0; + desc->read_binp = binp; + } + } + if (! (d = EF_ALLOC(sizeof(struct t_data)))) { + reply_posix_error(desc, ENOMEM); + goto done; + } + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.read.binp = desc->read_binp; + d->c.read.bin_offset = desc->read_offset + desc->read_size; + d->c.read.bin_size = desc->read_binp->orig_size - d->c.read.bin_offset; + d->c.read.size = size; + driver_binary_inc_refc(d->c.read.binp); + d->invoke = invoke_read; + d->free = free_read; + d->level = 1; + cq_enq(desc, d); + } goto done; /* case FILE_READ: */ + + case FILE_READ_LINE: { + /* + * Icky little creature... We do mostly as ordinary file read, but with a few differences. + * 1) We have to scan for proper newline sequence if there is a buffer already, we cannot know + * in advance if the buffer contains a whole line without scanning. + * 2) We do not know how large the buffer needs to be in advance. We give a default buffer, + * but the worker may need to allocate a new one. Freeing the old and rereferencing a newly + * allocated binary + dealing with offsets and lengts are done in file_async ready + * for this OP. + */ + struct t_data *d; + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (ev->size != 1) { + /* Wrong command length */ + reply_posix_error(desc, EINVAL); + goto done; + } + if ((desc->fd == FILE_FD_INVALID) + || (! (desc->flags & EFILE_MODE_READ)) ) { + reply_posix_error(desc, EBADF); + goto done; + } + if (desc->read_size > 0) { + /* look for '\n' in what we'we already got */ + void *nl_ptr = memchr(desc->read_binp->orig_bytes + desc->read_offset,'\n',desc->read_size); + if (nl_ptr != NULL) { + /* If found, we're done */ + int skip = 0; + size_t size = ((char *) nl_ptr) - + ((char *) (desc->read_binp->orig_bytes + desc->read_offset)) + 1; + if (size > 1 && + *(((char *) nl_ptr) - 1) == '\r') { + *(((char *) nl_ptr) - 1) = '\n'; + skip = 1; + --size; + } + reply_data(desc, desc->read_binp, desc->read_offset, size); + desc->read_offset += (size + skip); + desc->read_size -= (size + skip); + try_free_read_bin(desc); + goto done; + } + } + /* Now, it's up to the thread to work out the need for more buffers and such, it's + no use doing it in this thread as we do not have the information required anyway. + Even a NULL buffer could be handled by the thread, but code is simplified by us + allocating it */ + if (! desc->read_binp) { + int alloc_size = (desc->read_bufsize > DEFAULT_LINEBUF_SIZE) ? desc->read_bufsize : + DEFAULT_LINEBUF_SIZE; + /* Allocate a new binary for the result */ + if (! (desc->read_binp = driver_alloc_binary(alloc_size))) { + reply_posix_error(desc, ENOMEM); + goto done; + } + } + if (! (d = EF_ALLOC(sizeof(struct t_data)))) { + reply_posix_error(desc, ENOMEM); + goto done; + } + + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.read_line.binp = desc->read_binp; + d->c.read_line.read_offset = desc->read_offset; + d->c.read_line.read_size = desc->read_size; +#if !ALWAYS_READ_LINE_AHEAD + d->c.read_line.read_ahead = (desc->read_bufsize > 0); +#endif + driver_binary_inc_refc(d->c.read.binp); + d->invoke = invoke_read_line; + d->free = free_read_line; + d->level = 1; + cq_enq(desc, d); + } goto done; + case FILE_WRITE: { + int skip = 1; + int size = ev->size - skip; + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (! (desc->flags & EFILE_MODE_WRITE)) { + reply_posix_error(desc, EBADF); + goto done; + } + if (size <= 0) { + reply_Uint(desc, size); + goto done; + } + MUTEX_LOCK(desc->q_mtx); + if (driver_enqv(desc->port, ev, skip)) { + MUTEX_UNLOCK(desc->q_mtx); + reply_posix_error(desc, ENOMEM); + goto done; + } + desc->write_buffered += size; + if (desc->write_buffered < desc->write_bufsize) { + MUTEX_UNLOCK(desc->q_mtx); + reply_Uint(desc, size); + if (desc->timer_state == timer_idle) { + desc->timer_state = timer_write; + driver_set_timer(desc->port, desc->write_delay); + } + } else { + if (async_write(desc, &err, !0, size) != 0) { + MUTEX_UNLOCK(desc->q_mtx); + reply_posix_error(desc, err); + goto done; + } else { + MUTEX_UNLOCK(desc->q_mtx); + } + } + } goto done; /* case FILE_WRITE */ + + case FILE_PWRITEV: { + Uint32 i, j, n; + size_t total; + struct t_data *d; + if (lseek_flush_read(desc, &err) < 0) { + reply_Uint_posix_error(desc, 0, err); + goto done; + } + if (flush_write_check_error(desc, &err) < 0) { + reply_Uint_posix_error(desc, 0, err); + goto done; + } + if (ev->size < 1+4 + || !EV_GET_UINT32(ev, &n, &p, &q)) { + /* Buffer too short to contain even the number of pos/size specs */ + reply_Uint_posix_error(desc, 0, EINVAL); + goto done; + } + if (n == 0) { + /* Trivial case - nothing to write */ + if (ev->size != 1+4) { + reply_posix_error(desc, err); + } else { + reply_Uint(desc, 0); + } + goto done; + } + if (ev->size < 1+4+8*(2*n)) { + /* Buffer too short to contain even the pos/size specs */ + reply_Uint_posix_error(desc, 0, EINVAL); + goto done; + } + d = EF_ALLOC(sizeof(struct t_data) + + (n * sizeof(struct t_pbuf_spec))); + if (! d) { + reply_Uint_posix_error(desc, 0, ENOMEM); + goto done; + } + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.pwritev.port = desc->port; + d->c.pwritev.q_mtx = desc->q_mtx; + d->c.pwritev.n = n; + d->c.pwritev.cnt = 0; + total = 0; + j = 0; + /* Create pos/size specs in the thread data structure + * for all non-zero size binaries. Calculate total size. + */ + for(i = 0; i < n; i++) { + Uint32 sizeH, sizeL; + size_t size; + if ( !EV_GET_UINT64(ev, &d->c.pwritev.specs[i].offset, &p, &q) + || !EV_GET_UINT32(ev, &sizeH, &p, &q) + || !EV_GET_UINT32(ev, &sizeL, &p, &q)) { + /* Misalignment in buffer */ + reply_Uint_posix_error(desc, 0, EINVAL); + EF_FREE(d); + goto done; + } +#if SIZEOF_SIZE_T == 4 + if (sizeH != 0) { + reply_Uint_posix_error(desc, 0, EINVAL); + EF_FREE(d); + goto done; + } + size = sizeL; +#else + size = ((size_t)sizeH<<32) | sizeL; +#endif + if (size > 0) { + total += size; + d->c.pwritev.specs[j].size = size; + j++; + } + } + d->c.pwritev.size = total; + d->c.pwritev.free_size = 0; + if (j == 0) { + /* Trivial case - nothing to write */ + EF_FREE(d); + reply_Uint(desc, 0); + } else { + size_t skip = 1 + 4 + 8*(2*n); + if (skip + total != ev->size) { + /* Actual amount of data does not match + * total of all pos/size specs + */ + EF_FREE(d); + reply_Uint_posix_error(desc, 0, EINVAL); + } else { + /* Enqueue the data */ + MUTEX_LOCK(desc->q_mtx); + driver_enqv(desc->port, ev, skip); + MUTEX_UNLOCK(desc->q_mtx); + /* Execute the command */ + d->invoke = invoke_pwritev; + d->free = free_pwritev; + d->level = 1; + cq_enq(desc, d); + } + } + } goto done; /* case FILE_PWRITEV: */ + + case FILE_PREADV: { + register void * void_ptr; + Uint32 i, n; + struct t_data *d; + ErlIOVec *res_ev; + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (ev->size < 1+8 + || !EV_GET_UINT32(ev, &n, &p, &q) + || !EV_GET_UINT32(ev, &n, &p, &q)) { + /* Buffer too short to contain even the number of pos/size specs */ + reply_posix_error(desc, EINVAL); + goto done; + } + if (ev->size != 1+8+8*(2*n)) { + /* Buffer wrong length to contain the pos/size specs */ + reply_posix_error(desc, EINVAL); + goto done; + } + /* Create the thread data structure with the contained ErlIOVec + * and corresponding binaries for the response + */ + d = EF_ALLOC(sizeof(*d) + + (n * sizeof(*d->c.preadv.offsets)) + + ((1+n) * (sizeof(*res_ev->iov) + + sizeof(*res_ev->binv)))); + if (! d) { + reply_posix_error(desc, ENOMEM); + goto done; + } + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.preadv.n = n; + d->c.preadv.cnt = 0; + d->c.preadv.size = 0; + res_ev = &d->c.preadv.eiov; + /* XXX possible alignment problems here for weird machines */ + res_ev->vsize = 1+d->c.preadv.n; + res_ev->iov = void_ptr = &d->c.preadv.offsets[d->c.preadv.n]; + res_ev->binv = void_ptr = &res_ev->iov[res_ev->vsize]; + /* Read in the pos/size specs and allocate binaries for the results */ + for (i = 1; i < 1+n; i++) { + Uint32 sizeH, sizeL; + size_t size; + if ( !EV_GET_UINT64(ev, &d->c.preadv.offsets[i-1], &p, &q) + || !EV_GET_UINT32(ev, &sizeH, &p, &q) + || !EV_GET_UINT32(ev, &sizeL, &p, &q)) { + reply_posix_error(desc, EINVAL); + break; + } +#if SIZEOF_SIZE_T == 4 + if (sizeH != 0) { + reply_posix_error(desc, EINVAL); + break; + } + size = sizeL; +#else + size = ((size_t)sizeH<<32) | sizeL; +#endif + if (! (res_ev->binv[i] = driver_alloc_binary(size))) { + reply_posix_error(desc, ENOMEM); + break; + } else { + res_ev->iov[i].iov_len = size; + res_ev->iov[i].iov_base = res_ev->binv[i]->orig_bytes; + } + } + if (i < 1+n) { + for (i--; i > 0; i--) { + driver_free_binary(res_ev->binv[i]); + } + EF_FREE(d); + goto done; + } + /* Allocate the header binary (index 0) */ + res_ev->binv[0] = driver_alloc_binary(4+4+8*n); + if (! res_ev->binv[0]) { + reply_posix_error(desc, ENOMEM); + for (i = 1; i < 1+n; i++) { + driver_free_binary(res_ev->binv[i]); + } + EF_FREE(d); + goto done; + } + res_ev->iov[0].iov_len = 4+4+8*n; + res_ev->iov[0].iov_base = res_ev->binv[0]->orig_bytes; + /* Fill in the number of buffers in the header */ + put_int32(0, res_ev->iov[0].iov_base); + put_int32(n, res_ev->iov[0].iov_base+4); + /**/ + res_ev->size = res_ev->iov[0].iov_len; + if (n == 0) { + /* Trivial case - nothing to read */ + reply_ev(desc, FILE_RESP_LDATA, res_ev); + free_preadv(d); + goto done; + } else { + d->invoke = invoke_preadv; + d->free = free_preadv; + d->level = 1; + cq_enq(desc, d); + } + } goto done; /* case FILE_PREADV: */ + + case FILE_LSEEK: { + Sint64 offset; /* Offset for seek */ + Uint32 origin; /* Origin of seek. */ + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (ev->size != 1+8+4 + || !EV_GET_UINT64(ev, &offset, &p, &q) + || !EV_GET_UINT32(ev, &origin, &p, &q)) { + /* Wrong length of buffer to contain offset and origin */ + reply_posix_error(desc, EINVAL); + goto done; + } + if (async_lseek(desc, &err, !0, offset, origin) < 0) { + reply_posix_error(desc, err); + goto done; + } + } goto done; + + case FILE_READ_FILE: { + struct t_data *d; + if (ev->size < 1+1) { + /* Buffer contains empty name */ + reply_posix_error(desc, ENOENT); + goto done; + } + if (ev->size-1 != ev->iov[q].iov_len-p) { + /* Name not in one single buffer */ + reply_posix_error(desc, EINVAL); + goto done; + } + d = EF_ALLOC(sizeof(struct t_data) + ev->size); + if (! d) { + reply_posix_error(desc, ENOMEM); + goto done; + } + d->command = command; + d->reply = !0; + /* Copy name */ + memcpy(d->c.read_file.name, EV_CHAR_P(ev, p, q), ev->size-1); + d->c.read_file.name[ev->size-1] = '\0'; + d->c.read_file.binp = NULL; + d->invoke = invoke_read_file; + d->free = free_read_file; + d->level = 2; + cq_enq(desc, d); + } goto done; + + case FILE_IPREAD: { + /* This operation cheets by using invoke_preadv() and free_preadv() + * plus its own invoke_ipread. Therefore the result format is + * a bit awkward - the header binary contains one extra 64 bit + * field that invoke_preadv() fortunately ignores, + * and the first 64 bit field does not contain the number of + * data binaries which invoke_preadv() also ignores. + */ + register void * void_ptr; + char mode; + Sint64 hdr_offset; + Uint32 max_size; + struct t_data *d; + ErlIOVec *res_ev; + int vsize; + if (! EV_GET_CHAR(ev, &mode, &p, &q)) { + /* Empty command */ + reply_posix_error(desc, EINVAL); + goto done; + } + if (mode != IPREAD_S32BU_P32BU) { + reply_posix_error(desc, EINVAL); + goto done; + } + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (ev->size < 1+1+8+4 + || !EV_GET_UINT64(ev, &hdr_offset, &p, &q) + || !EV_GET_UINT32(ev, &max_size, &p, &q)) { + /* Buffer too short to contain + * the header offset and max size spec */ + reply_posix_error(desc, EINVAL); + goto done; + } + /* Create the thread data structure with the contained ErlIOVec + * and corresponding binaries for the response + */ + vsize = 2; + d = EF_ALLOC(sizeof(*d) + + vsize*(sizeof(*res_ev->iov) + sizeof(*res_ev->binv))); + if (! d) { + reply_posix_error(desc, ENOMEM); + goto done; + } + d->command = command; + d->reply = !0; + d->fd = desc->fd; + d->flags = desc->flags; + d->c.preadv.offsets[0] = hdr_offset; + d->c.preadv.size = max_size; + res_ev = &d->c.preadv.eiov; + /* XXX possible alignment problems here for weird machines */ + res_ev->iov = void_ptr = d + 1; + res_ev->binv = void_ptr = res_ev->iov + vsize; + res_ev->size = 0; + res_ev->vsize = 0; + d->invoke = invoke_ipread; + d->free = free_preadv; + d->level = 1; + cq_enq(desc, d); + } goto done; /* case FILE_IPREAD: */ + + case FILE_SETOPT: { + char opt; + if (ev->size < 1+1 + || !EV_GET_CHAR(ev, &opt, &p, &q)) { + /* Buffer too short to contain even the option type */ + reply_posix_error(desc, EINVAL); + goto done; + } + switch (opt) { + case FILE_OPT_DELAYED_WRITE: { + Uint32 sizeH, sizeL, delayH, delayL; + if (ev->size != 1+1+4*sizeof(Uint32) + || !EV_GET_UINT32(ev, &sizeH, &p, &q) + || !EV_GET_UINT32(ev, &sizeL, &p, &q) + || !EV_GET_UINT32(ev, &delayH, &p, &q) + || !EV_GET_UINT32(ev, &delayL, &p, &q)) { + /* Buffer has wrong length to contain the option values */ + reply_posix_error(desc, EINVAL); + goto done; + } +#if SIZEOF_SIZE_T == 4 + if (sizeH != 0) { + reply_posix_error(desc, EINVAL); + goto done; + } + desc->write_bufsize = sizeL; +#else + desc->write_bufsize = ((size_t)sizeH << 32) | sizeL; +#endif +#if SIZEOF_LONG == 4 + if (delayH != 0) { + reply_posix_error(desc, EINVAL); + goto done; + } + desc->write_delay = delayL; +#else + desc->write_delay = ((unsigned long)delayH << 32) | delayL; +#endif + TRACE_C('K'); + reply_ok(desc); + } goto done; + case FILE_OPT_READ_AHEAD: { + Uint32 sizeH, sizeL; + if (ev->size != 1+1+2*sizeof(Uint32) + || !EV_GET_UINT32(ev, &sizeH, &p, &q) + || !EV_GET_UINT32(ev, &sizeL, &p, &q)) { + /* Buffer has wrong length to contain the option values */ + reply_posix_error(desc, EINVAL); + goto done; + } +#if SIZEOF_SIZE_T == 4 + if (sizeH != 0) { + reply_posix_error(desc, EINVAL); + goto done; + } + desc->read_bufsize = sizeL; +#else + desc->read_bufsize = ((size_t)sizeH << 32) | sizeL; +#endif + TRACE_C('K'); + reply_ok(desc); + } goto done; + default: + reply_posix_error(desc, EINVAL); + goto done; + } /* case FILE_OPT_DELAYED_WRITE: */ + } ASSERT(0); goto done; /* case FILE_SETOPT: */ + + } /* switch(command) */ + + if (lseek_flush_read(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } + if (flush_write_check_error(desc, &err) < 0) { + reply_posix_error(desc, err); + goto done; + } else { + /* Flatten buffer and send it to file_output(desc, buf, len) */ + int len = ev->size; + char *buf = EF_ALLOC(len); + if (! buf) { + reply_posix_error(desc, ENOMEM); + goto done; + } + driver_vec_to_buf(ev, buf, len); + file_output((ErlDrvData) desc, buf, len); + EF_FREE(buf); + goto done; + } + + done: + cq_execute(desc); +} diff --git a/erts/emulator/drivers/common/erl_efile.h b/erts/emulator/drivers/common/erl_efile.h new file mode 100644 index 0000000000..9aa941e550 --- /dev/null +++ b/erts/emulator/drivers/common/erl_efile.h @@ -0,0 +1,152 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Defines the interfaces between the generic efile driver and its + * operating-system dependent helpers. + */ + +#include "sys.h" +#include "erl_driver.h" + +/* + * Open modes for efile_openfile(). + */ +#define EFILE_MODE_READ 1 +#define EFILE_MODE_WRITE 2 /* Implies truncating file when used alone. */ +#define EFILE_MODE_READ_WRITE 3 +#define EFILE_MODE_APPEND 4 +#define EFILE_COMPRESSED 8 +#define EFILE_NO_TRUNCATE 16 /* Special for reopening on VxWorks */ + +/* + * Seek modes for efile_seek(). + */ +#define EFILE_SEEK_SET 0 +#define EFILE_SEEK_CUR 1 +#define EFILE_SEEK_END 2 + +/* + * File types returned by efile_fileinfo(). + */ +#define FT_DEVICE 1 +#define FT_DIRECTORY 2 +#define FT_REGULAR 3 +#define FT_SYMLINK 4 +#define FT_OTHER 5 + +/* + * Access attributes returned by efile_fileinfo() (the bits can be ORed + * together). + */ +#define FA_NONE 0 +#define FA_WRITE 1 +#define FA_READ 2 + +/* + * An handle to an open directory. To be cast to the correct type + * in the system-dependent directory functions. + */ + +typedef struct _Efile_Dir_Handle* EFILE_DIR_HANDLE; + +/* + * Error information from the last call. + */ +typedef struct _Efile_error { + int posix_errno; /* Posix error number, as in <errno.h>. */ + int os_errno; /* Os-dependent error number (not used). */ +} Efile_error; + +/* + * This structure contains date and time. + */ +typedef struct _Efile_time { + unsigned year; /* (4 digits). */ + unsigned month; /* (1..12). */ + unsigned day; /* (1..31). */ + unsigned hour; /* (0..23). */ + unsigned minute; /* (0..59). */ + unsigned second; /* (0..59). */ +} Efile_time; + + +/* + * Describes what is returned by file:file_info/1. + */ + +typedef struct _Efile_info { + Uint32 size_low; /* Size of file, lower 32 bits.. */ + Uint32 size_high; /* Size of file, higher 32 bits. */ + Uint32 type; /* Type of file -- one of FT_*. */ + Uint32 access; /* Access to file -- one of FA_*. */ + Uint32 mode; /* Access permissions -- bit field. */ + Uint32 links; /* Number of links to file. */ + Uint32 major_device; /* Major device or file system. */ + Uint32 minor_device; /* Minor device (for devices). */ + Uint32 inode; /* Inode number. */ + Uint32 uid; /* User id of owner. */ + Uint32 gid; /* Group id of owner. */ + Efile_time accessTime; /* Last time the file was accessed. */ + Efile_time modifyTime; /* Last time the file was modified. */ + Efile_time cTime; /* Creation time (Windows) or last + * inode change (Unix). + */ +} Efile_info; + +/* + * Functions. + */ + +int efile_mkdir(Efile_error* errInfo, char* name); +int efile_rmdir(Efile_error* errInfo, char* name); +int efile_delete_file(Efile_error* errInfo, char* name); +int efile_rename(Efile_error* errInfo, char* src, char* dst); +int efile_chdir(Efile_error* errInfo, char* name); +int efile_getdcwd(Efile_error* errInfo, int drive, + char* buffer, size_t size); +int efile_readdir(Efile_error* errInfo, char* name, + EFILE_DIR_HANDLE* dir_handle, + char* buffer, size_t size); +int efile_openfile(Efile_error* errInfo, char* name, int flags, + int* pfd, Sint64* pSize); +void efile_closefile(int fd); +int efile_fsync(Efile_error* errInfo, int fd); +int efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char *name, int info_for_link); +int efile_write_info(Efile_error* errInfo, Efile_info* pInfo, char *name); +int efile_write(Efile_error* errInfo, int flags, int fd, + char* buf, size_t count); +int efile_writev(Efile_error* errInfo, int flags, int fd, + SysIOVec* iov, int iovcnt, size_t size); +int efile_read(Efile_error* errInfo, int flags, int fd, + char* buf, size_t count, size_t* pBytesRead); +int efile_seek(Efile_error* errInfo, int fd, + Sint64 offset, int origin, Sint64* new_location); +int efile_truncate_file(Efile_error* errInfo, int *fd, int flags); +int efile_pwrite(Efile_error* errInfo, int fd, + char* buf, size_t count, Sint64 offset); +int efile_pread(Efile_error* errInfo, int fd, + Sint64 offset, char* buf, size_t count, size_t* pBytesRead); +int efile_readlink(Efile_error* errInfo, char *name, + char* buffer, size_t size); +int efile_altname(Efile_error* errInfo, char *name, + char* buffer, size_t size); +int efile_link(Efile_error* errInfo, char* old, char* new); +int efile_symlink(Efile_error* errInfo, char* old, char* new); +int efile_may_openfile(Efile_error* errInfo, char *name); diff --git a/erts/emulator/drivers/common/gzio.c b/erts/emulator/drivers/common/gzio.c new file mode 100644 index 0000000000..801bc61d4d --- /dev/null +++ b/erts/emulator/drivers/common/gzio.c @@ -0,0 +1,822 @@ +/* + * Original version by Jean-loup Gailly. Modified for use by the + * Erlang run-time system and efile_driver; names of all external + * functions changed to avoid conflicts with the official gzio.c file. + * + * gzio.c -- IO on .gz files + * Copyright (C) 1995-1996 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ +/* %ExternalCopyright% */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdio.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <ctype.h> +#include "erl_driver.h" +#include "sys.h" + +#ifdef VXWORKS +/* pull in FOPEN from zutil.h instead */ +#undef F_OPEN +#endif + +#ifdef __WIN32__ +#define HAVE_CONFLICTING_FREAD_DECLARATION +#endif + +#ifdef STDC +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#include "gzio_zutil.h" +#include "erl_zlib.h" +#include "gzio.h" + +/********struct internal_state {int dummy;}; / * for buggy compilers */ + +#define Z_BUFSIZE 4096 + +#define ALLOC(size) driver_alloc(size) +#define TRYFREE(p) {if (p) driver_free(p);} + +static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +typedef struct gz_stream { + z_stream stream; + int z_err; /* error code for last stream operation */ + int z_eof; /* set if end of input file */ +#ifdef UNIX + int file; /* .gz file descriptor */ +#else + FILE *file; /* .gz file */ +#endif + Byte *inbuf; /* input buffer */ + Byte *outbuf; /* output buffer */ + uLong crc; /* crc32 of uncompressed data */ + char *msg; /* error message */ + char *path; /* path name for debugging only */ + int transparent; /* 1 if input file is not a .gz file */ + char mode; /* 'w' or 'r' */ + int position; /* Position (for seek) */ + int (*destroy)OF((struct gz_stream*)); /* Function to destroy + * this structure. */ +} gz_stream; + +local gzFile gz_open OF((const char *path, const char *mode)); +local int get_byte OF((gz_stream *s)); +local void check_header OF((gz_stream *s)); +local int destroy OF((gz_stream *s)); +local uLong getLong OF((gz_stream *s)); + +#ifdef UNIX +/* + * In Solaris 8 and earlier, fopen() and its friends cannot handle + * file descriptors larger than 255. Therefore, we use read()/write() + * on all Unix systems. + */ +# define ERTS_GZWRITE(File, Buf, Count) write((File), (Buf), (Count)) +# define ERTS_GZREAD(File, Buf, Count) read((File), (Buf), (Count)) +#else +/* + * On all other operating systems, using fopen(), fread()/fwrite(), since + * there is not guaranteed to exist any read()/write() (not part of + * ANSI/ISO-C). + */ +# define ERTS_GZWRITE(File, Buf, Count) fwrite((Buf), 1, (Count), (File)) +# define ERTS_GZREAD(File, Buf, Count) fread((Buf), 1, (Count), (File)) +#endif + +/* =========================================================================== + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb"). The file is given either by file descriptor + or path name (if fd == -1). + gz_open return NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). +*/ +local gzFile gz_open (path, mode) + const char *path; + const char *mode; +{ + int err; + int level = Z_DEFAULT_COMPRESSION; /* compression level */ + char *p = (char*)mode; + gz_stream *s; + char fmode[80]; /* copy of mode, without the compression level */ + char *m = fmode; + + if (!path || !mode) return Z_NULL; + + s = (gz_stream *)ALLOC(sizeof(gz_stream)); + if (!s) return Z_NULL; + + erl_zlib_alloc_init(&s->stream); + s->stream.next_in = s->inbuf = Z_NULL; + s->stream.next_out = s->outbuf = Z_NULL; + s->stream.avail_in = s->stream.avail_out = 0; +#ifdef UNIX + s->file = -1; +#else + s->file = NULL; +#endif + s->z_err = Z_OK; + s->z_eof = 0; + s->crc = crc32(0L, Z_NULL, 0); + s->msg = NULL; + s->transparent = 0; + s->position = 0; + s->destroy = destroy; + + s->path = (char*)ALLOC(strlen(path)+1); + if (s->path == NULL) { + return s->destroy(s), (gzFile)Z_NULL; + } + strcpy(s->path, path); /* do this early for debugging */ + + s->mode = '\0'; + do { + if (*p == 'r') + s->mode = 'r'; + if (*p == 'w' || *p == 'a') + s->mode = 'w'; + if (isdigit((int)*p)) { + level = *p - '0'; + } else { + *m++ = *p; /* Copy the mode */ + } + } while (*p++ && m < fmode + sizeof(fmode) - 1); + *m = '\0'; + if (s->mode == '\0') + return s->destroy(s), (gzFile)Z_NULL; + + if (s->mode == 'w') { + err = deflateInit2(&(s->stream), level, + Z_DEFLATED, MAX_WBITS+16, DEF_MEM_LEVEL, 0); + /* windowBits is passed < 0 to suppress zlib header */ + + s->stream.next_out = s->outbuf = (Byte*)ALLOC(Z_BUFSIZE); + + if (err != Z_OK || s->outbuf == Z_NULL) { + return s->destroy(s), (gzFile)Z_NULL; + } + } else { + /* + * It is tempting to use the built-in support in zlib + * for handling GZIP headers, but unfortunately it + * cannot handle multiple GZIP headers (which occur when + * several GZIP files have been concatenated). + */ + + err = inflateInit2(&(s->stream), -MAX_WBITS); + s->stream.next_in = s->inbuf = (Byte*)ALLOC(Z_BUFSIZE); + + if (err != Z_OK || s->inbuf == Z_NULL) { + return s->destroy(s), (gzFile)Z_NULL; + } + } + s->stream.avail_out = Z_BUFSIZE; + + errno = 0; +#ifdef UNIX + if (s->mode == 'r') { + s->file = open(path, O_RDONLY); + } else { + s->file = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + } + if (s->file == -1) { + return s->destroy(s), (gzFile)Z_NULL; + } +#else + s->file = F_OPEN(path, fmode); + if (s->file == NULL) { + return s->destroy(s), (gzFile)Z_NULL; + } +#endif + if (s->mode == 'r') { + check_header(s); /* skip the .gz header */ + } + return (gzFile)s; +} + +/* =========================================================================== + Rewind a gzfile back to the beginning. +*/ + +local int gz_rewind (gz_stream *s) +{ + TRYFREE(s->msg); + +#ifdef UNIX + lseek(s->file, 0L, SEEK_SET); +#else + fseek(s->file, 0L, SEEK_SET); +#endif + inflateReset(&(s->stream)); + s->stream.next_in = Z_NULL; + s->stream.next_out = Z_NULL; + s->stream.avail_in = s->stream.avail_out = 0; + s->z_err = Z_OK; + s->z_eof = 0; + s->crc = crc32(0L, Z_NULL, 0); + s->msg = NULL; + s->position = 0; + s->stream.next_in = s->inbuf; + + s->stream.avail_out = Z_BUFSIZE; + + check_header(s); /* skip the .gz header */ + return 1; +} + +/* =========================================================================== + Opens a gzip (.gz) file for reading or writing. +*/ +gzFile erts_gzopen (path, mode) + const char *path; + const char *mode; +{ + return gz_open (path, mode); +} + + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ +local int get_byte(s) + gz_stream *s; +{ + if (s->z_eof) return EOF; + if (s->stream.avail_in == 0) { +#ifdef UNIX + size_t res; + errno = 0; + res = ERTS_GZREAD(s->file, s->inbuf, Z_BUFSIZE); + if (res == 0) { + s->stream.avail_in = 0; + s->z_eof = 1; + return EOF; + } else if (res < 0) { + s->stream.avail_in = 0; + s->z_eof = 1; + s->z_err = Z_ERRNO; + return EOF; + } else { + s->stream.avail_in = (uInt) res; + } +#else + errno = 0; + s->stream.avail_in = ERTS_GZREAD(s->file, s->inbuf, Z_BUFSIZE); + if (s->stream.avail_in == 0) { + s->z_eof = 1; + if (s->file && ferror(s->file)) + s->z_err = Z_ERRNO; + return EOF; + } +#endif + s->stream.next_in = s->inbuf; + } + s->stream.avail_in--; + return *(s->stream.next_in)++; +} + +/* =========================================================================== + Check the gzip header of a gz_stream opened for reading. Set the stream + mode to transparent if the gzip magic header is not present; set s->err + to Z_DATA_ERROR if the magic header is present but the rest of the header + is incorrect. + IN assertion: the stream s has already been created sucessfully; + s->stream.avail_in is zero for the first time, but may be non-zero + for concatenated .gz files. +*/ +local void check_header(s) + gz_stream *s; +{ + int method; /* method byte */ + int flags; /* flags byte */ + uInt len; + int c; + + /* Check the gzip magic header */ + for (len = 0; len < 2; len++) { + c = get_byte(s); + if (c != gz_magic[len]) { + if (len != 0) s->stream.avail_in++, s->stream.next_in--; + if (c != EOF) { + s->stream.avail_in++, s->stream.next_in--; + s->transparent = 1; + } + s->z_err = s->stream.avail_in != 0 ? Z_OK : Z_STREAM_END; + return; + } + } + method = get_byte(s); + flags = get_byte(s); + if (method != Z_DEFLATED || (flags & RESERVED) != 0) { + s->z_err = Z_DATA_ERROR; + return; + } + + /* Discard time, xflags and OS code: */ + for (len = 0; len < 6; len++) (void)get_byte(s); + + if ((flags & EXTRA_FIELD) != 0) { /* skip the extra field */ + len = (uInt)get_byte(s); + len += ((uInt)get_byte(s))<<8; + /* len is garbage if EOF but the loop below will quit anyway */ + while (len-- != 0 && get_byte(s) != EOF) ; + } + if ((flags & ORIG_NAME) != 0) { /* skip the original file name */ + while ((c = get_byte(s)) != 0 && c != EOF) ; + } + if ((flags & COMMENT) != 0) { /* skip the .gz file comment */ + while ((c = get_byte(s)) != 0 && c != EOF) ; + } + if ((flags & HEAD_CRC) != 0) { /* skip the header crc */ + for (len = 0; len < 2; len++) (void)get_byte(s); + } + s->z_err = s->z_eof ? Z_DATA_ERROR : Z_OK; +} + + /* =========================================================================== + * Cleanup then free the given gz_stream. Return a zlib error code. + Try freeing in the reverse order of allocations. + */ +local int destroy (s) + gz_stream *s; +{ + int err = Z_OK; + + if (!s) return Z_STREAM_ERROR; + + TRYFREE(s->msg); + + if (s->stream.state != NULL) { + if (s->mode == 'w') { + err = deflateEnd(&(s->stream)); + } else if (s->mode == 'r') { + err = inflateEnd(&(s->stream)); + } + } +#ifdef UNIX + if (s->file != -1 && close(s->file)) { + err = Z_ERRNO; + } +#else + if (s->file != NULL && fclose(s->file)) { + err = Z_ERRNO; + } +#endif + if (s->z_err < 0) err = s->z_err; + + TRYFREE(s->inbuf); + TRYFREE(s->outbuf); + TRYFREE(s->path); + TRYFREE(s); + return err; +} + +/* =========================================================================== + Reads the given number of uncompressed bytes from the compressed file. + gzread returns the number of bytes actually read (0 for end of file). +*/ +int +erts_gzread(gzFile file, voidp buf, unsigned len) +{ + gz_stream *s = (gz_stream*)file; + Bytef *start = buf; /* starting point for crc computation */ + Byte *next_out; /* == stream.next_out but not forced far (for MSDOS) */ + + if (s == NULL || s->mode != 'r') return Z_STREAM_ERROR; + + if (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO) return -1; + if (s->z_err == Z_STREAM_END) return 0; /* EOF */ + + s->stream.next_out = next_out = buf; + s->stream.avail_out = len; + + while (s->stream.avail_out != 0) { + + if (s->transparent) { + /* Copy first the lookahead bytes: */ + uInt n = s->stream.avail_in; + if (n > s->stream.avail_out) n = s->stream.avail_out; + if (n > 0) { + zmemcpy(s->stream.next_out, s->stream.next_in, n); + next_out += n; + s->stream.next_out = next_out; + s->stream.next_in += n; + s->stream.avail_out -= n; + s->stream.avail_in -= n; + } + if (s->stream.avail_out > 0) { + s->stream.avail_out -= ERTS_GZREAD(s->file, next_out, + s->stream.avail_out); + } + len -= s->stream.avail_out; + s->stream.total_in += (uLong)len; + s->stream.total_out += (uLong)len; + if (len == 0) s->z_eof = 1; + s->position += (int)len; + return (int)len; + } + if (s->stream.avail_in == 0 && !s->z_eof) { +#ifdef UNIX + size_t res; + errno = 0; + res = ERTS_GZREAD(s->file, s->inbuf, Z_BUFSIZE); + if (res == 0) { + s->stream.avail_in = 0; + s->z_eof = 1; + return EOF; + } else if (res < 0) { + s->stream.avail_in = 0; + s->z_eof = 1; + s->z_err = Z_ERRNO; + return EOF; + } else { + s->stream.avail_in = (uInt) res; + } +#else + errno = 0; + s->stream.avail_in = ERTS_GZREAD(s->file, s->inbuf, Z_BUFSIZE); + if (s->stream.avail_in == 0) { + s->z_eof = 1; + if (s->file && ferror(s->file)) { + s->z_err = Z_ERRNO; + break; + } + } +#endif + s->stream.next_in = s->inbuf; + } + s->z_err = inflate(&(s->stream), Z_NO_FLUSH); + + if (s->z_err == Z_STREAM_END) { + /* Check CRC and original size */ + s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start)); + start = s->stream.next_out; + + if (getLong(s) != s->crc) { + s->z_err = Z_DATA_ERROR; + } else { + (void)getLong(s); + /* The uncompressed length returned by above getlong() may + * be different from s->stream.total_out) in case of + * concatenated .gz files. Check for such files: + */ + check_header(s); + if (s->z_err == Z_OK) { + uLong total_in = s->stream.total_in; + uLong total_out = s->stream.total_out; + + inflateReset(&(s->stream)); + s->stream.total_in = total_in; + s->stream.total_out = total_out; + s->crc = crc32(0L, Z_NULL, 0); + } + } + } + if (s->z_err != Z_OK || s->z_eof) break; + } + s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start)); + + s->position += (int)(len - s->stream.avail_out); + + return (int)(len - s->stream.avail_out); +} + +/* =========================================================================== + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of bytes actually written (0 in case of error). +*/ +int +erts_gzwrite(gzFile file, voidp buf, unsigned len) +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; + + s->stream.next_in = buf; + s->stream.avail_in = len; + + while (s->stream.avail_in != 0) { + + if (s->stream.avail_out == 0) { + + s->stream.next_out = s->outbuf; + if (ERTS_GZWRITE(s->file, s->outbuf, Z_BUFSIZE) != Z_BUFSIZE) { + s->z_err = Z_ERRNO; + break; + } + s->stream.avail_out = Z_BUFSIZE; + } + s->z_err = deflate(&(s->stream), Z_NO_FLUSH); + if (s->z_err != Z_OK) break; + } + s->position += (int)(len - s->stream.avail_in); + return (int)(len - s->stream.avail_in); +} + +/* + * For use by Erlang file driver. + * + * XXX Limitations: + * - SEEK_END is not allowed (length of file is not known). + * - When writing, only forward seek is supported. + */ + +int +erts_gzseek(gzFile file, int offset, int whence) +{ + int pos; + gz_stream* s = (gz_stream *) file; + + if (s == NULL) { + errno = EINVAL; + return -1; + } + if (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO) { + errno = EIO; + return -1; + } + + switch (whence) { + case SEEK_SET: pos = offset; break; + case SEEK_CUR: pos = s->position+offset; break; + case SEEK_END: + default: + errno = EINVAL; return -1; + } + + if (pos == s->position) { + return pos; + } + + if (pos < s->position) { + if (s->mode == 'w') { + errno = EINVAL; + return -1; + } + gz_rewind(s); + } + + while (s->position < pos) { + char buf[512]; + int n; + + n = pos - s->position; + if (n > sizeof(buf)) + n = sizeof(buf); + + if (s->mode == 'r') { + erts_gzread(file, buf, n); + } else { + memset(buf, '\0', n); + erts_gzwrite(file, buf, n); + } + } + + return s->position; +} + +/* =========================================================================== + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ +int +erts_gzflush(gzFile file, int flush) +{ + uInt len; + int done = 0; + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; + + s->stream.avail_in = 0; /* should be zero already anyway */ + + for (;;) { + len = Z_BUFSIZE - s->stream.avail_out; + + if (len != 0) { + if ((uInt)ERTS_GZWRITE(s->file, s->outbuf, len) != len) { + s->z_err = Z_ERRNO; + return Z_ERRNO; + } + s->stream.next_out = s->outbuf; + s->stream.avail_out = Z_BUFSIZE; + } + if (done) break; + s->z_err = deflate(&(s->stream), flush); + + /* deflate has finished flushing only when it hasn't used up + * all the available space in the output buffer: + */ + done = (s->stream.avail_out != 0 || s->z_err == Z_STREAM_END); + + if (s->z_err != Z_OK && s->z_err != Z_STREAM_END) break; + } +#ifndef UNIX + fflush(s->file); +#endif + return s->z_err == Z_STREAM_END ? Z_OK : s->z_err; +} + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local uLong getLong (s) + gz_stream *s; +{ + uLong x = (uLong)get_byte(s); + int c; + + x += ((uLong)get_byte(s))<<8; + x += ((uLong)get_byte(s))<<16; + c = get_byte(s); + if (c == EOF) s->z_err = Z_DATA_ERROR; + x += ((uLong)c)<<24; + return x; +} + +/* =========================================================================== + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. +*/ +int +erts_gzclose(gzFile file) +{ + int err; + gz_stream *s = (gz_stream*)file; + + if (s == NULL) return Z_STREAM_ERROR; + + if (s->mode == 'w') { + err = erts_gzflush (file, Z_FINISH); + if (err != Z_OK) return s->destroy(file); + } + return s->destroy(file); +} + + +/* =========================================================================== + Uncompresses the buffer given and returns a pointer to a binary. + If the buffer was not compressed with gzip, the buffer contents + will be copied unchanged into the binary. + + If a `gzip' header was found, but there were subsequent errors, + a NULL pointer is returned. +*/ + +ErlDrvBinary* +erts_gzinflate_buffer(char* start, uLong size) +{ + ErlDrvBinary* bin; + ErlDrvBinary* bin2; + z_stream zstr; + unsigned char* bptr; + + /* + * Check for the magic bytes beginning a GZIP header. + */ + bptr = (unsigned char *) start; + if (size < 2 || bptr[0] != gz_magic[0] || bptr[1] != gz_magic[1]) { + /* No GZIP header -- just copy the data into a new binary */ + if ((bin = driver_alloc_binary(size)) == NULL) { + return NULL; + } + memcpy(bin->orig_bytes, start, size); + return bin; + } + + /* + * The magic bytes for a GZIP header are there. Now try to decompress. + * It is an error if the GZIP header is not correct. + */ + + zstr.next_in = (unsigned char*) start; + zstr.avail_in = size; + erl_zlib_alloc_init(&zstr); + size *= 2; + if ((bin = driver_alloc_binary(size)) == NULL) { + return NULL; + } + if (inflateInit2(&zstr, 15+16) != Z_OK) { /* Decode GZIP format */ + driver_free(bin); + return NULL; + } + for (;;) { + int status; + + zstr.next_out = (unsigned char *) bin->orig_bytes + zstr.total_out; + zstr.avail_out = size - zstr.total_out; + status = inflate(&zstr, Z_NO_FLUSH); + if (status == Z_OK) { + size *= 2; + if ((bin2 = driver_realloc_binary(bin, size)) == NULL) { + error: + driver_free_binary(bin); + inflateEnd(&zstr); + return NULL; + } + bin = bin2; + } else if (status == Z_STREAM_END) { + if ((bin2 = driver_realloc_binary(bin, zstr.total_out)) == NULL) { + goto error; + } + inflateEnd(&zstr); + return bin2; + } else { + goto error; + } + } +} + +/* =========================================================================== + Compresses the buffer given and returns a pointer to a binary. + A NULL pointer is returned if any error occurs. + Writes a gzip header as well. +*/ + +#define GZIP_HD_SIZE 10 +#define GZIP_TL_SIZE 8 + +#define GZIP_X_SIZE (GZIP_HD_SIZE+GZIP_TL_SIZE) + +ErlDrvBinary* +erts_gzdeflate_buffer(char* start, uLong size) +{ + z_stream c_stream; /* compression stream */ + ErlDrvBinary* bin; + ErlDrvBinary* bin2; + uLong crc; /* crc32 of uncompressed data */ + uLong szIn; + Byte* ptr; + int comprLen = size + (size/1000) + 1 + 12; /* see zlib.h */ + + crc = crc32(0L, Z_NULL, 0); + erl_zlib_alloc_init(&c_stream); + + if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, 0) != Z_OK) + return NULL; + + if ((bin = driver_alloc_binary(comprLen+GZIP_X_SIZE)) == NULL) + return NULL; + sprintf(bin->orig_bytes, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1], + Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE); + + c_stream.next_out = ((Byte*) bin->orig_bytes)+GZIP_HD_SIZE; + c_stream.avail_out = (uInt) bin->orig_size - GZIP_HD_SIZE; + c_stream.next_in = (Byte*) start; + c_stream.avail_in = (uInt) size; + + if (deflate(&c_stream, Z_FINISH) != Z_STREAM_END) { + driver_free_binary(bin); + return NULL; + } + crc = crc32(crc, (unsigned char*)start, size); + ptr = c_stream.next_out; + szIn = c_stream.total_in; + + *ptr++ = (crc & 0xff); crc >>= 8; + *ptr++ = (crc & 0xff); crc >>= 8; + *ptr++ = (crc & 0xff); crc >>= 8; + *ptr++ = (crc & 0xff); crc >>= 8; + + *ptr++ = (szIn & 0xff); szIn >>= 8; + *ptr++ = (szIn & 0xff); szIn >>= 8; + *ptr++ = (szIn & 0xff); szIn >>= 8; + *ptr++ = (szIn & 0xff); szIn >>= 8; + + if (deflateEnd(&c_stream) != Z_OK) { + driver_free_binary(bin); + return NULL; + } + size = ptr - (Byte*)bin->orig_bytes; + + if ((bin2 = driver_realloc_binary(bin, size)) == NULL) + driver_free_binary(bin); + return bin2; +} + diff --git a/erts/emulator/drivers/common/gzio.h b/erts/emulator/drivers/common/gzio.h new file mode 100644 index 0000000000..3f1e546140 --- /dev/null +++ b/erts/emulator/drivers/common/gzio.h @@ -0,0 +1,27 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1999-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +gzFile erts_gzopen (const char *path, const char *mode); +int erts_gzread(gzFile file, voidp buf, unsigned len); +int erts_gzwrite(gzFile file, voidp buf, unsigned len); +int erts_gzseek(gzFile, int, int); +int erts_gzflush(gzFile file, int flush); +int erts_gzclose(gzFile file); +ErlDrvBinary* erts_gzinflate_buffer(char*, uLong); +ErlDrvBinary* erts_gzdeflate_buffer(char*, uLong); diff --git a/erts/emulator/drivers/common/gzio_zutil.h b/erts/emulator/drivers/common/gzio_zutil.h new file mode 100644 index 0000000000..00eccc80fc --- /dev/null +++ b/erts/emulator/drivers/common/gzio_zutil.h @@ -0,0 +1,82 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* A replacement for zlib internal header file zutil.h. + * Just the minimal things that our gzio.c seems to need. + * We don't want to be dependant on some internal header file + * that may change or not exist at all. + */ + +#ifndef HAVE_LIBZ +/* Use our "real" copy of zutil.h if we don't use shared zlib */ +#include "zutil.h" + +#else /* HAVE_LIBZ: Shared zlib is used */ + +#define local static +#define DEF_MEM_LEVEL 8 +#define zmemcpy sys_memcpy + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + + +#endif /* HAVE_LIBZ */ + diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c new file mode 100644 index 0000000000..b7b577da5b --- /dev/null +++ b/erts/emulator/drivers/common/inet_drv.c @@ -0,0 +1,9949 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* If we HAVE_SCTP_H and Solaris, we need to define the following in + order to get SCTP working: +*/ +#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4)) +#define SOLARIS10 1 +/* WARNING: This is not quite correct, it may also be Solaris 11! */ +#define _XPG4_2 +#define __EXTENSIONS__ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/types.h> +#include <errno.h> + +#define IDENTITY(c) c +#define STRINGIFY_1(b) IDENTITY(#b) +#define STRINGIFY(a) STRINGIFY_1(a) + +#ifndef _OSE_ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif +#endif + + +/* All platforms fail on malloc errors. */ +#define FATAL_MALLOC + + +#include "erl_driver.h" + +#ifdef __WIN32__ +#define STRNCASECMP strncasecmp + +#define INCL_WINSOCK_API_TYPEDEFS 1 + +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include <winsock2.h> +#endif +#include <windows.h> + +#include <Ws2tcpip.h> /* NEED VC 6.0 !!! */ + +#undef WANT_NONBLOCKING +#include "sys.h" + +#undef EWOULDBLOCK +#undef ETIMEDOUT + +#define HAVE_MULTICAST_SUPPORT + +#define ERRNO_BLOCK WSAEWOULDBLOCK + +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ENOTSOCK WSAENOTSOCK +#define EDESTADDRREQ WSAEDESTADDRREQ +#define EMSGSIZE WSAEMSGSIZE +#define EPROTOTYPE WSAEPROTOTYPE +#define ENOPROTOOPT WSAENOPROTOOPT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define EADDRINUSE WSAEADDRINUSE +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define ENETDOWN WSAENETDOWN +#define ENETUNREACH WSAENETUNREACH +#define ENETRESET WSAENETRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET +#define ENOBUFS WSAENOBUFS +#define EISCONN WSAEISCONN +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define ELOOP WSAELOOP +#undef ENAMETOOLONG +#define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY +#define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE + +#define INVALID_EVENT WSA_INVALID_EVENT + +static BOOL (WINAPI *fpSetHandleInformation)(HANDLE,DWORD,DWORD); + +#define sock_open(af, type, proto) \ + make_noninheritable_handle(socket((af), (type), (proto))) +#define sock_close(s) closesocket((s)) +#define sock_shutdown(s, how) shutdown((s), (how)) + +#define sock_accept(s, addr, len) \ + make_noninheritable_handle(accept((s), (addr), (len))) +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_listen(s, b) listen((s), (b)) +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) +#define sock_setopt(s,t,n,v,l) setsockopt((s),(t),(n),(v),(l)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) +#define sock_ntohl(x) ntohl((x)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) +#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) +#define sock_sendv(s, vec, size, np, flag) \ + WSASend((s),(WSABUF*)(vec),\ + (size),(np),(flag),NULL,NULL) +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) + +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_hostname(buf, len) gethostname((buf), (len)) + +#define sock_getservbyname(name,proto) getservbyname((name),(proto)) +#define sock_getservbyport(port,proto) getservbyport((port),(proto)) + +#define sock_errno() WSAGetLastError() +#define sock_create_event(d) WSACreateEvent() +#define sock_close_event(e) WSACloseEvent(e) + +#define sock_select(D, Flags, OnOff) winsock_event_select(D, Flags, OnOff) + +#define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) +#define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value) + + +static unsigned long zero_value = 0; +static unsigned long one_value = 1; + +#else + +#ifdef VXWORKS +#include <sockLib.h> +#include <sys/times.h> +#include <iosLib.h> +#include <taskLib.h> +#include <selectLib.h> +#include <ioLib.h> +#else +#include <sys/time.h> +#ifdef NETDB_H_NEEDS_IN_H +#include <netinet/in.h> +#endif +#include <netdb.h> +#endif + +#ifndef _OSE_ +#include <sys/socket.h> +#include <netinet/in.h> +#else +/* datatypes and macros from Solaris socket.h */ +struct linger { + int l_onoff; /* option on/off */ + int l_linger; /* linger time */ +}; +#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */ +#define SO_LINGER 0x0080 /* linger on close if data present */ +#endif + +#ifdef VXWORKS +#include <rpc/rpctypes.h> +#endif +#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +#include <rpc/types.h> +#endif + +#ifndef _OSE_ +#include <netinet/tcp.h> +#include <arpa/inet.h> +#endif + +#if (!defined(VXWORKS) && !defined(_OSE_)) +#include <sys/param.h> +#ifdef HAVE_ARPA_NAMESER_H +#include <arpa/nameser.h> +#endif +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifndef _OSE_ +#include <net/if.h> +#else +#define IFF_MULTICAST 0x00000800 +#endif + +#ifdef _OSE_ +#include "inet.h" +#include "ineterr.h" +#include "ose_inet_drv.h" +#include "nameser.h" +#include "resolv.h" +#define SET_ASYNC(s) setsockopt((s), SOL_SOCKET, SO_OSEEVENT, (&(s)), sizeof(int)) + +extern void select_release(void); + +#endif /* _OSE_ */ + +/* Solaris headers, only to be used with SFK */ +#ifdef _OSE_SFK_ +#include <ctype.h> +#include <string.h> +#endif + +/* SCTP support -- currently for UNIX platforms only: */ +#undef HAVE_SCTP +#if (!defined(VXWORKS) && !defined(_OSE_) && !defined(__WIN32__) && defined(HAVE_SCTP_H)) + +#include <netinet/sctp.h> + +/* SCTP Socket API Draft from version 11 on specifies that netinet/sctp.h must + explicitly define HAVE_SCTP in case when SCTP is supported, but Solaris 10 + still apparently uses Draft 10, and does not define that symbol, so we have + to define it explicitly: +*/ +#ifndef HAVE_SCTP +# define HAVE_SCTP +#endif + +/* These changed in draft 11, so SOLARIS10 uses the old MSG_* */ +#if ! HAVE_DECL_SCTP_UNORDERED +# define SCTP_UNORDERED MSG_UNORDERED +#endif +#if ! HAVE_DECL_SCTP_ADDR_OVER +# define SCTP_ADDR_OVER MSG_ADDR_OVER +#endif +#if ! HAVE_DECL_SCTP_ABORT +# define SCTP_ABORT MSG_ABORT +#endif +#if ! HAVE_DECL_SCTP_EOF +# define SCTP_EOF MSG_EOF +#endif + +/* New spelling in lksctp 2.6.22 or maybe even earlier: + * adaption -> adaptation + */ +#if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER) +# define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER +# define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION +# define sctp_adaptation_event sctp_adaption_event +# define sctp_setadaptation sctp_setadaption +# define sn_adaptation_event sn_adaption_event +# define sai_adaptation_ind sai_adaption_ind +# define ssb_adaptation_ind ssb_adaption_ind +# define sctp_adaptation_layer_event sctp_adaption_layer_event +#endif + +static void *h_libsctp = NULL; +#ifdef __GNUC__ +static typeof(sctp_bindx) *p_sctp_bindx = NULL; +#else +static int (*p_sctp_bindx)(int sd, struct sockaddr *addrs, + int addrcnt, int flags) = NULL; +#endif + +#endif /* SCTP supported */ + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" + +/* #define INET_DRV_DEBUG 1 */ +#ifdef INET_DRV_DEBUG +#define DEBUG 1 +#undef DEBUGF +#define DEBUGF(X) printf X +#endif + +#if !defined(__WIN32__) && !defined(HAVE_STRNCASECMP) +#define STRNCASECMP my_strncasecmp + +static int my_strncasecmp(const char *s1, const char *s2, size_t n) +{ + int i; + + for (i=0;i<n-1 && s1[i] && s2[i] && toupper(s1[i]) == toupper(s2[i]);++i) + ; + return (toupper(s1[i]) - toupper(s2[i])); +} + + +#else +#define STRNCASECMP strncasecmp +#endif + +#define INVALID_SOCKET -1 +#define INVALID_EVENT -1 +#define SOCKET_ERROR -1 +#define SOCKET int +#define HANDLE long int +#define FD_READ ERL_DRV_READ +#define FD_WRITE ERL_DRV_WRITE +#define FD_CLOSE 0 +#define FD_CONNECT ERL_DRV_WRITE +#define FD_ACCEPT ERL_DRV_READ + +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_listen(s, b) listen((s), (b)) +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#ifdef VXWORKS +#define sock_getopt(s,t,n,v,l) wrap_sockopt(&getsockopt,\ + s,t,n,v,(unsigned int)(l)) +#define sock_setopt(s,t,n,v,l) wrap_sockopt(&setsockopt,\ + s,t,n,v,(unsigned int)(l)) +#else +#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) +#define sock_setopt(s,t,n,v,l) setsockopt((s),(t),(n),(v),(l)) +#endif +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) +#define sock_ntohl(x) ntohl((x)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) + +#ifdef _OSE_ +#define sock_accept(s, addr, len) ose_inet_accept((s), (addr), (len)) +#define sock_send(s,buf,len,flag) ose_inet_send((s),(buf),(len),(flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + ose_inet_sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_sendv(s, vec, size, np, flag) \ + (*(np) = ose_inet_sendv((s), (SysIOVec*)(vec), (size))) +#define sock_open(af, type, proto) ose_inet_socket((af), (type), (proto)) +#define sock_close(s) ose_inet_close((s)) +#define sock_hostname(buf, len) ose_gethostname((buf), (len)) +#define sock_getservbyname(name,proto) ose_getservbyname((name), (proto)) +#define sock_getservbyport(port,proto) ose_getservbyport((port), (proto)) + +#else +#define sock_accept(s, addr, len) accept((s), (addr), (len)) +#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_sendv(s, vec, size, np, flag) \ + (*(np) = writev((s), (struct iovec*)(vec), (size))) +#define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag)) + +#define sock_open(af, type, proto) socket((af), (type), (proto)) +#define sock_close(s) close((s)) +#define sock_shutdown(s, how) shutdown((s), (how)) + +#define sock_hostname(buf, len) gethostname((buf), (len)) +#define sock_getservbyname(name,proto) getservbyname((name), (proto)) +#define sock_getservbyport(port,proto) getservbyport((port), (proto)) +#endif /* _OSE_ */ + +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag)) + +#define sock_errno() errno +#define sock_create_event(d) ((d)->s) /* return file descriptor */ +#define sock_close_event(e) /* do nothing */ + +#ifdef _OSE_ +#define inet_driver_select(port, e, mode, on) \ + ose_inet_select(port, e, mode, on) +#else +#define inet_driver_select(port, e, mode, on) \ + driver_select(port, e, mode | (on?ERL_DRV_USE:0), on) +#endif /* _OSE_ */ + +#define sock_select(d, flags, onoff) do { \ + (d)->event_mask = (onoff) ? \ + ((d)->event_mask | (flags)) : \ + ((d)->event_mask & ~(flags)); \ + DEBUGF(("sock_select(%ld): flags=%02X, onoff=%d, event_mask=%02lX\r\n", \ + (long) (d)->port, (flags), (onoff), (unsigned long) (d)->event_mask)); \ + inet_driver_select((d)->port, (ErlDrvEvent)(long)(d)->event, (flags), (onoff)); \ + } while(0) + + +#endif /* __WIN32__ */ + +#include "packet_parser.h" + +#define get_int24(s) ((((unsigned char*) (s))[0] << 16) | \ + (((unsigned char*) (s))[1] << 8) | \ + (((unsigned char*) (s))[2])) + +#define get_little_int32(s) ((((unsigned char*) (s))[3] << 24) | \ + (((unsigned char*) (s))[2] << 16) | \ + (((unsigned char*) (s))[1] << 8) | \ + (((unsigned char*) (s))[0])) + +/*---------------------------------------------------------------------------- +** Interface constants. +** +** This section must be "identical" to the corresponding inet_int.hrl +*/ + +/* general address encode/decode tag */ +#define INET_AF_INET 1 +#define INET_AF_INET6 2 +#define INET_AF_ANY 3 /* INADDR_ANY or IN6ADDR_ANY_INIT */ +#define INET_AF_LOOPBACK 4 /* INADDR_LOOPBACK or IN6ADDR_LOOPBACK_INIT */ + +/* INET_REQ_GETTYPE enumeration */ +#define INET_TYPE_STREAM 1 +#define INET_TYPE_DGRAM 2 +#define INET_TYPE_SEQPACKET 3 + +/* INET_LOPT_MODE options */ +#define INET_MODE_LIST 0 +#define INET_MODE_BINARY 1 + +/* INET_LOPT_DELIVER options */ +#define INET_DELIVER_PORT 0 +#define INET_DELIVER_TERM 1 + +/* INET_LOPT_ACTIVE options */ +#define INET_PASSIVE 0 /* false */ +#define INET_ACTIVE 1 /* true */ +#define INET_ONCE 2 /* true; active once then passive */ + +/* INET_REQ_GETSTATUS enumeration */ +#define INET_F_OPEN 0x0001 +#define INET_F_BOUND 0x0002 +#define INET_F_ACTIVE 0x0004 +#define INET_F_LISTEN 0x0008 +#define INET_F_CON 0x0010 +#define INET_F_ACC 0x0020 +#define INET_F_LST 0x0040 +#define INET_F_BUSY 0x0080 +#define INET_F_MULTI_CLIENT 0x0100 /* Multiple clients for one descriptor, i.e. multi-accept */ + +/* One numberspace for *_REC_* so if an e.g UDP request is issued +** for a TCP socket, the driver can protest. +*/ +#define INET_REQ_OPEN 1 +#define INET_REQ_CLOSE 2 +#define INET_REQ_CONNECT 3 +#define INET_REQ_PEER 4 +#define INET_REQ_NAME 5 +#define INET_REQ_BIND 6 +#define INET_REQ_SETOPTS 7 +#define INET_REQ_GETOPTS 8 +/* #define INET_REQ_GETIX 9 NOT USED ANY MORE */ +/* #define INET_REQ_GETIF 10 REPLACE BY NEW STUFF */ +#define INET_REQ_GETSTAT 11 +#define INET_REQ_GETHOSTNAME 12 +#define INET_REQ_FDOPEN 13 +#define INET_REQ_GETFD 14 +#define INET_REQ_GETTYPE 15 +#define INET_REQ_GETSTATUS 16 +#define INET_REQ_GETSERVBYNAME 17 +#define INET_REQ_GETSERVBYPORT 18 +#define INET_REQ_SETNAME 19 +#define INET_REQ_SETPEER 20 +#define INET_REQ_GETIFLIST 21 +#define INET_REQ_IFGET 22 +#define INET_REQ_IFSET 23 +#define INET_REQ_SUBSCRIBE 24 +/* TCP requests */ +#define TCP_REQ_ACCEPT 40 +#define TCP_REQ_LISTEN 41 +#define TCP_REQ_RECV 42 +#define TCP_REQ_UNRECV 43 +#define TCP_REQ_SHUTDOWN 44 +#define TCP_REQ_MULTI_OP 45 +/* UDP and SCTP requests */ +#define PACKET_REQ_RECV 60 /* Common for UDP and SCTP */ +#define SCTP_REQ_LISTEN 61 /* Different from TCP; not for UDP */ +#define SCTP_REQ_BINDX 62 /* Multi-home SCTP bind */ + +/* INET_REQ_SUBSCRIBE sub-requests */ +#define INET_SUBS_EMPTY_OUT_Q 1 + +/* TCP additional flags */ +#define TCP_ADDF_DELAY_SEND 1 +#define TCP_ADDF_CLOSE_SENT 2 /* Close sent (active mode only) */ +#define TCP_ADDF_DELAYED_CLOSE_RECV 4 /* If receive fails, report {error,closed} (passive mode) */ +#define TCP_ADDF_DELAYED_CLOSE_SEND 8 /* If send fails, report {error,closed} (passive mode) */ + +/* *_REQ_* replies */ +#define INET_REP_ERROR 0 +#define INET_REP_OK 1 +#define INET_REP_SCTP 2 + +/* INET_REQ_SETOPTS and INET_REQ_GETOPTS options */ +#define INET_OPT_REUSEADDR 0 /* enable/disable local address reuse */ +#define INET_OPT_KEEPALIVE 1 /* enable/disable keep connections alive */ +#define INET_OPT_DONTROUTE 2 /* enable/disable routing for messages */ +#define INET_OPT_LINGER 3 /* linger on close if data is present */ +#define INET_OPT_BROADCAST 4 /* enable/disable transmission of broadcast */ +#define INET_OPT_OOBINLINE 5 /* enable/disable out-of-band data in band */ +#define INET_OPT_SNDBUF 6 /* set send buffer size */ +#define INET_OPT_RCVBUF 7 /* set receive buffer size */ +#define INET_OPT_PRIORITY 8 /* set priority */ +#define INET_OPT_TOS 9 /* Set type of service */ +#define TCP_OPT_NODELAY 10 /* don't delay send to coalesce packets */ +#define UDP_OPT_MULTICAST_IF 11 /* set/get IP multicast interface */ +#define UDP_OPT_MULTICAST_TTL 12 /* set/get IP multicast timetolive */ +#define UDP_OPT_MULTICAST_LOOP 13 /* set/get IP multicast loopback */ +#define UDP_OPT_ADD_MEMBERSHIP 14 /* add an IP group membership */ +#define UDP_OPT_DROP_MEMBERSHIP 15 /* drop an IP group membership */ +/* LOPT is local options */ +#define INET_LOPT_BUFFER 20 /* min buffer size hint */ +#define INET_LOPT_HEADER 21 /* list header size */ +#define INET_LOPT_ACTIVE 22 /* enable/disable active receive */ +#define INET_LOPT_PACKET 23 /* packet header type (TCP) */ +#define INET_LOPT_MODE 24 /* list or binary mode */ +#define INET_LOPT_DELIVER 25 /* port or term delivery */ +#define INET_LOPT_EXITONCLOSE 26 /* exit port on active close or not ! */ +#define INET_LOPT_TCP_HIWTRMRK 27 /* set local high watermark */ +#define INET_LOPT_TCP_LOWTRMRK 28 /* set local low watermark */ +#define INET_LOPT_BIT8 29 /* set 8 bit detection */ +#define INET_LOPT_TCP_SEND_TIMEOUT 30 /* set send timeout */ +#define INET_LOPT_TCP_DELAY_SEND 31 /* Delay sends until next poll */ +#define INET_LOPT_PACKET_SIZE 32 /* Max packet size */ +#define INET_LOPT_UDP_READ_PACKETS 33 /* Number of packets to read */ +#define INET_OPT_RAW 34 /* Raw socket options */ +#define INET_LOPT_TCP_SEND_TIMEOUT_CLOSE 35 /* auto-close on send timeout or not */ +/* SCTP options: a separate range, from 100: */ +#define SCTP_OPT_RTOINFO 100 +#define SCTP_OPT_ASSOCINFO 101 +#define SCTP_OPT_INITMSG 102 +#define SCTP_OPT_AUTOCLOSE 103 +#define SCTP_OPT_NODELAY 104 +#define SCTP_OPT_DISABLE_FRAGMENTS 105 +#define SCTP_OPT_I_WANT_MAPPED_V4_ADDR 106 +#define SCTP_OPT_MAXSEG 107 +#define SCTP_OPT_SET_PEER_PRIMARY_ADDR 108 +#define SCTP_OPT_PRIMARY_ADDR 109 +#define SCTP_OPT_ADAPTATION_LAYER 110 +#define SCTP_OPT_PEER_ADDR_PARAMS 111 +#define SCTP_OPT_DEFAULT_SEND_PARAM 112 +#define SCTP_OPT_EVENTS 113 +#define SCTP_OPT_DELAYED_ACK_TIME 114 +#define SCTP_OPT_STATUS 115 +#define SCTP_OPT_GET_PEER_ADDR_INFO 116 + +/* INET_REQ_IFGET and INET_REQ_IFSET options */ +#define INET_IFOPT_ADDR 1 +#define INET_IFOPT_BROADADDR 2 +#define INET_IFOPT_DSTADDR 3 +#define INET_IFOPT_MTU 4 +#define INET_IFOPT_NETMASK 5 +#define INET_IFOPT_FLAGS 6 +#define INET_IFOPT_HWADDR 7 + +/* INET_LOPT_BIT8 options */ +#define INET_BIT8_CLEAR 0 +#define INET_BIT8_SET 1 +#define INET_BIT8_ON 2 +#define INET_BIT8_OFF 3 + +/* INET_REQ_GETSTAT enumeration */ +#define INET_STAT_RECV_CNT 1 +#define INET_STAT_RECV_MAX 2 +#define INET_STAT_RECV_AVG 3 +#define INET_STAT_RECV_DVI 4 +#define INET_STAT_SEND_CNT 5 +#define INET_STAT_SEND_MAX 6 +#define INET_STAT_SEND_AVG 7 +#define INET_STAT_SEND_PND 8 +#define INET_STAT_RECV_OCT 9 /* received octets */ +#define INET_STAT_SEND_OCT 10 /* sent octets */ + +/* INET_IFOPT_FLAGS enumeration */ +#define INET_IFF_UP 0x0001 +#define INET_IFF_BROADCAST 0x0002 +#define INET_IFF_LOOPBACK 0x0004 +#define INET_IFF_POINTTOPOINT 0x0008 +#define INET_IFF_RUNNING 0x0010 +#define INET_IFF_MULTICAST 0x0020 +/* Complement flags for turning them off */ +#define INET_IFF_DOWN 0x0100 +#define INET_IFF_NBROADCAST 0x0200 +/* #define INET_IFF_NLOOPBACK 0x0400 */ +#define INET_IFF_NPOINTTOPOINT 0x0800 +/* #define INET_IFF_NRUNNING 0x1000 */ +/* #define INET_IFF_NMULTICAST 0x2000 */ + +/* Flags for "sctp_sndrcvinfo". Used in a bitmask -- must be powers of 2: +** INET_REQ_SETOPTS:SCTP_OPT_DEFAULT_SEND_PARAM +*/ +#define SCTP_FLAG_UNORDERED (1 /* am_unordered */) +#define SCTP_FLAG_ADDR_OVER (2 /* am_addr_over */) +#define SCTP_FLAG_ABORT (4 /* am_abort */) +#define SCTP_FLAG_EOF (8 /* am_eof */) +#define SCTP_FLAG_SNDALL (16 /* am_sndall, NOT YET IMPLEMENTED */) + +/* Flags for "sctp_set_opts" (actually for SCTP_OPT_PEER_ADDR_PARAMS). +** These flags are also used in a bitmask, so they must be powers of 2: +*/ +#define SCTP_FLAG_HB_ENABLE (1 /* am_hb_enable */) +#define SCTP_FLAG_HB_DISABLE (2 /* am_hb_disable */) +#define SCTP_FLAG_HB_DEMAND (4 /* am_hb_demand */) +#define SCTP_FLAG_PMTUD_ENABLE (8 /* am_pmtud_enable */) +#define SCTP_FLAG_PMTUD_DISABLE (16 /* am_pmtud_disable */) +#define SCTP_FLAG_SACDELAY_ENABLE (32 /* am_sackdelay_enable */) +#define SCTP_FLAG_SACDELAY_DISABLE (64 /* am_sackdelay_disable */) + +/* +** End of interface constants. +**--------------------------------------------------------------------------*/ + +#define INET_STATE_CLOSED 0 +#define INET_STATE_OPEN (INET_F_OPEN) +#define INET_STATE_BOUND (INET_STATE_OPEN | INET_F_BOUND) +#define INET_STATE_CONNECTED (INET_STATE_BOUND | INET_F_ACTIVE) + +#define IS_OPEN(d) \ + (((d)->state & INET_F_OPEN) == INET_F_OPEN) + +#define IS_BOUND(d) \ + (((d)->state & INET_F_BOUND) == INET_F_BOUND) + +#define IS_CONNECTED(d) \ + (((d)->state & INET_STATE_CONNECTED) == INET_STATE_CONNECTED) + +#define IS_CONNECTING(d) \ + (((d)->state & INET_F_CON) == INET_F_CON) + +#define IS_BUSY(d) \ + (((d)->state & INET_F_BUSY) == INET_F_BUSY) + +#define INET_DEF_BUFFER 1460 /* default buffer size */ +#define INET_MIN_BUFFER 1 /* internal min buffer */ +#define INET_MAX_BUFFER (1024*64) /* internal max buffer */ + +/* Note: INET_HIGH_WATERMARK MUST be less than 2*INET_MAX_BUFFER */ +#define INET_HIGH_WATERMARK (1024*8) /* 8k pending high => busy */ +/* Note: INET_LOW_WATERMARK MUST be less than INET_MAX_BUFFER and +** less than INET_HIGH_WATERMARK +*/ +#define INET_LOW_WATERMARK (1024*4) /* 4k pending => allow more */ + +#define INET_INFINITY 0xffffffff /* infinity value */ + +#define INET_MAX_ASYNC 1 /* max number of async queue ops */ + +/* INET_LOPT_UDP_PACKETS */ +#define INET_PACKET_POLL 5 /* maximum number of packets to poll */ + +/* Max interface name */ +#define INET_IFNAMSIZ 16 + +/* Max length of Erlang Term Buffer (for outputting structured terms): */ +#ifdef HAVE_SCTP +#define PACKET_ERL_DRV_TERM_DATA_LEN 512 +#else +#define PACKET_ERL_DRV_TERM_DATA_LEN 32 +#endif + + +#define BIN_REALLOC_LIMIT(x) (((x)*3)/4) /* 75% */ + +/* The general purpose sockaddr */ +typedef union { + struct sockaddr sa; + struct sockaddr_in sai; +#ifdef HAVE_IN6 + struct sockaddr_in6 sai6; +#endif +} inet_address; + + +/* for AF_INET & AF_INET6 */ +#define inet_address_port(x) ((x)->sai.sin_port) + +#if defined(HAVE_IN6) && defined(AF_INET6) +#define addrlen(family) \ + ((family == AF_INET) ? sizeof(struct in_addr) : \ + ((family == AF_INET6) ? sizeof(struct in6_addr) : 0)) +#else +#define addrlen(family) \ + ((family == AF_INET) ? sizeof(struct in_addr) : 0) +#endif + +typedef struct _multi_timer_data { + ErlDrvNowData when; + ErlDrvTermData caller; + void (*timeout_function)(ErlDrvData drv_data, ErlDrvTermData caller); + struct _multi_timer_data *next; + struct _multi_timer_data *prev; +} MultiTimerData; + +static MultiTimerData *add_multi_timer(MultiTimerData **first, ErlDrvPort port, + ErlDrvTermData caller, unsigned timeout, + void (*timeout_fun)(ErlDrvData drv_data, + ErlDrvTermData caller)); +static void fire_multi_timers(MultiTimerData **first, ErlDrvPort port, + ErlDrvData data); +static void remove_multi_timer(MultiTimerData **first, ErlDrvPort port, MultiTimerData *p); + +static void tcp_inet_multi_timeout(ErlDrvData e, ErlDrvTermData caller); +static void clean_multi_timers(MultiTimerData **first, ErlDrvPort port); + +typedef struct { + int id; /* id used to identify reply */ + ErlDrvTermData caller; /* recipient of async reply */ + int req; /* Request id (CONNECT/ACCEPT/RECV) */ + union { + unsigned value; /* Request timeout (since op issued,not started) */ + MultiTimerData *mtd; + } tmo; + ErlDrvMonitor monitor; +} inet_async_op; + +typedef struct inet_async_multi_op_ { + inet_async_op op; + struct inet_async_multi_op_ *next; +} inet_async_multi_op; + + +typedef struct subs_list_ { + ErlDrvTermData subscriber; + struct subs_list_ *next; +} subs_list; + +#define NO_PROCESS 0 +#define NO_SUBSCRIBERS(SLP) ((SLP)->subscriber == NO_PROCESS) +static void send_to_subscribers(ErlDrvPort, subs_list *, int, + ErlDrvTermData [], int); +static void free_subscribers(subs_list*); +static int save_subscriber(subs_list *, ErlDrvTermData); + +typedef struct { + SOCKET s; /* the socket or INVALID_SOCKET if not open */ + HANDLE event; /* Event handle (same as s in unix) */ + long event_mask; /* current FD events */ +#ifdef __WIN32__ + long forced_events; /* Mask of events that are forcefully signalled + on windows see winsock_event_select + for details */ + int send_would_block; /* Last send attempt failed with "WOULDBLOCK" */ +#endif + ErlDrvPort port; /* the port identifier */ + ErlDrvTermData dport; /* the port identifier as DriverTermData */ + int state; /* status */ + int prebound; /* only set when opened with inet_fdopen */ + int mode; /* BINARY | LIST + (affect how to interpret hsz) */ + int exitf; /* exit port on close or not */ + int bit8f; /* check if data has bit number 7 set */ + int deliver; /* Delivery mode, TERM or PORT */ + + ErlDrvTermData caller; /* recipient of sync reply */ + ErlDrvTermData busy_caller; /* recipient of sync reply when caller busy. + * Only valid while INET_F_BUSY. */ + + inet_async_op* oph; /* queue head or NULL */ + inet_async_op* opt; /* queue tail or NULL */ + inet_async_op op_queue[INET_MAX_ASYNC]; /* call queue */ + + int active; /* 0 = passive, 1 = active, 2 = active once */ + int stype; /* socket type: + SOCK_STREAM/SOCK_DGRAM/SOCK_SEQPACKET */ + int sprotocol; /* socket protocol: + IPPROTO_TCP|IPPROTO_UDP|IPPROTO_SCTP */ + int sfamily; /* address family */ + enum PacketParseType htype; /* header type (TCP only?) */ + unsigned int psize; /* max packet size (TCP only?) */ + int bit8; /* set if bit8f==true and data some data + seen had the 7th bit set */ + inet_address remote; /* remote address for connected sockets */ + inet_address peer_addr; /* fake peer address */ + inet_address name_addr; /* fake local address */ + + inet_address* peer_ptr; /* fake peername or NULL */ + inet_address* name_ptr; /* fake sockname or NULL */ + + int bufsz; /* minimum buffer constraint */ + unsigned int hsz; /* the list header size, -1 is large !!! */ + /* statistics */ + unsigned long recv_oct[2]; /* number of received octets >= 64 bits */ + unsigned long recv_cnt; /* number of packets received */ + unsigned long recv_max; /* maximum packet size received */ + double recv_avg; /* average packet size received */ + double recv_dvi; /* avarage deviation from avg_size */ + unsigned long send_oct[2]; /* number of octets sent >= 64 bits */ + unsigned long send_cnt; /* number of packets sent */ + unsigned long send_max; /* maximum packet send */ + double send_avg; /* average packet size sent */ + + subs_list empty_out_q_subs; /* Empty out queue subscribers */ +} inet_descriptor; + + + +#define TCP_STATE_CLOSED INET_STATE_CLOSED +#define TCP_STATE_OPEN (INET_F_OPEN) +#define TCP_STATE_BOUND (TCP_STATE_OPEN | INET_F_BOUND) +#define TCP_STATE_CONNECTED (TCP_STATE_BOUND | INET_F_ACTIVE) +#define TCP_STATE_LISTEN (TCP_STATE_BOUND | INET_F_LISTEN) +#define TCP_STATE_CONNECTING (TCP_STATE_BOUND | INET_F_CON) +#define TCP_STATE_ACCEPTING (TCP_STATE_LISTEN | INET_F_ACC) +#define TCP_STATE_MULTI_ACCEPTING (TCP_STATE_ACCEPTING | INET_F_MULTI_CLIENT) + + +#define TCP_MAX_PACKET_SIZE 0x4000000 /* 64 M */ + +#define MAX_VSIZE 16 /* Max number of entries allowed in an I/O + * vector sock_sendv(). + */ + +static int tcp_inet_init(void); +static void tcp_inet_stop(ErlDrvData); +static void tcp_inet_command(ErlDrvData, char*, int); +static void tcp_inet_commandv(ErlDrvData, ErlIOVec*); +static void tcp_inet_flush(ErlDrvData drv_data); +static void tcp_inet_drv_input(ErlDrvData, ErlDrvEvent); +static void tcp_inet_drv_output(ErlDrvData data, ErlDrvEvent event); +static ErlDrvData tcp_inet_start(ErlDrvPort, char* command); +static int tcp_inet_ctl(ErlDrvData, unsigned int, char*, int, char**, int); +static void tcp_inet_timeout(ErlDrvData); +static void tcp_inet_process_exit(ErlDrvData, ErlDrvMonitor *); +static void inet_stop_select(ErlDrvEvent, void*); +#ifdef __WIN32__ +static void tcp_inet_event(ErlDrvData, ErlDrvEvent); +static void find_dynamic_functions(void); +#endif + +static struct erl_drv_entry tcp_inet_driver_entry = +{ + tcp_inet_init, /* inet_init will add this driver !! */ + tcp_inet_start, + tcp_inet_stop, + tcp_inet_command, +#ifdef __WIN32__ + tcp_inet_event, + NULL, +#else + tcp_inet_drv_input, + tcp_inet_drv_output, +#endif + "tcp_inet", + NULL, + NULL, + tcp_inet_ctl, + tcp_inet_timeout, + tcp_inet_commandv, + NULL, + tcp_inet_flush, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING|ERL_DRV_FLAG_SOFT_BUSY, + NULL, + tcp_inet_process_exit, + inet_stop_select +}; + +#define PACKET_STATE_CLOSED INET_STATE_CLOSED +#define PACKET_STATE_OPEN (INET_F_OPEN) +#define PACKET_STATE_BOUND (PACKET_STATE_OPEN | INET_F_BOUND) +#define SCTP_STATE_LISTEN (PACKET_STATE_BOUND | INET_F_LISTEN) +#define SCTP_STATE_CONNECTING (PACKET_STATE_BOUND | INET_F_CON) +#define PACKET_STATE_CONNECTED (PACKET_STATE_BOUND | INET_F_ACTIVE) + + +static int packet_inet_init(void); +static void packet_inet_stop(ErlDrvData); +static void packet_inet_command(ErlDrvData, char*, int); +static void packet_inet_drv_input(ErlDrvData data, ErlDrvEvent event); +static void packet_inet_drv_output(ErlDrvData data, ErlDrvEvent event); +static ErlDrvData udp_inet_start(ErlDrvPort, char* command); +#ifdef HAVE_SCTP +static ErlDrvData sctp_inet_start(ErlDrvPort, char* command); +#endif +static int packet_inet_ctl(ErlDrvData, unsigned int, char*, + int, char**, int); +static void packet_inet_timeout(ErlDrvData); +#ifdef __WIN32__ +static void packet_inet_event(ErlDrvData, ErlDrvEvent); +static SOCKET make_noninheritable_handle(SOCKET s); +static int winsock_event_select(inet_descriptor *, int, int); +#endif + +static struct erl_drv_entry udp_inet_driver_entry = +{ + packet_inet_init, /* inet_init will add this driver !! */ + udp_inet_start, + packet_inet_stop, + packet_inet_command, +#ifdef __WIN32__ + packet_inet_event, + NULL, +#else + packet_inet_drv_input, + packet_inet_drv_output, +#endif + "udp_inet", + NULL, + NULL, + packet_inet_ctl, + packet_inet_timeout, + NULL, + NULL, + NULL, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL, + NULL, + inet_stop_select +}; + +#ifdef HAVE_SCTP +static struct erl_drv_entry sctp_inet_driver_entry = +{ + packet_inet_init, /* inet_init will add this driver !! */ + sctp_inet_start, + packet_inet_stop, + packet_inet_command, +#ifdef __WIN32__ + packet_inet_event, + NULL, +#else + packet_inet_drv_input, + packet_inet_drv_output, +#endif + "sctp_inet", + NULL, + NULL, + packet_inet_ctl, + packet_inet_timeout, + NULL, + NULL, + NULL, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL, + NULL, /* process_exit */ + inet_stop_select +}; +#endif + +typedef struct { + inet_descriptor inet; /* common data structure (DON'T MOVE) */ + int high; /* high watermark */ + int low; /* low watermark */ + int send_timeout; /* timeout to use in send */ + int send_timeout_close; /* auto-close socket on send_timeout */ + int busy_on_send; /* busy on send with timeout! */ + int i_bufsz; /* current input buffer size (<= bufsz) */ + ErlDrvBinary* i_buf; /* current binary buffer */ + char* i_ptr; /* current pos in buf */ + char* i_ptr_start; /* packet start pos in buf */ + int i_remain; /* remaining chars to read */ + int tcp_add_flags;/* Additional TCP descriptor flags */ + int http_state; /* 0 = response|request 1=headers fields */ + inet_async_multi_op *multi_first;/* NULL == no multi-accept-queue, op is in ordinary queue */ + inet_async_multi_op *multi_last; + MultiTimerData *mtd; /* Timer structures for multiple accept */ +} tcp_descriptor; + +/* send function */ +static int tcp_send(tcp_descriptor* desc, char* ptr, int len); +static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev); +static int tcp_recv(tcp_descriptor* desc, int request_len); +static int tcp_deliver(tcp_descriptor* desc, int len); + +static int tcp_inet_output(tcp_descriptor* desc, HANDLE event); +static int tcp_inet_input(tcp_descriptor* desc, HANDLE event); + +typedef struct { + inet_descriptor inet; /* common data structure (DON'T MOVE) */ + int read_packets; /* Number of packets to read per invocation */ +} udp_descriptor; + + +static int packet_inet_input(udp_descriptor* udesc, HANDLE event); +static int packet_inet_output(udp_descriptor* udesc, HANDLE event); + +/* convert descriptor poiner to inet_descriptor pointer */ +#define INETP(d) (&(d)->inet) + +static int async_ref = 0; /* async reference id generator */ +#define NEW_ASYNC_ID() ((async_ref++) & 0xffff) + + +static ErlDrvTermData am_ok; +static ErlDrvTermData am_tcp; +static ErlDrvTermData am_udp; +static ErlDrvTermData am_error; +static ErlDrvTermData am_inet_async; +static ErlDrvTermData am_inet_reply; +static ErlDrvTermData am_timeout; +static ErlDrvTermData am_closed; +static ErlDrvTermData am_tcp_closed; +static ErlDrvTermData am_tcp_error; +static ErlDrvTermData am_udp_error; +static ErlDrvTermData am_empty_out_q; +static ErlDrvTermData am_ssl_tls; +#ifdef HAVE_SCTP +static ErlDrvTermData am_sctp; +static ErlDrvTermData am_sctp_error; +static ErlDrvTermData am_true; +static ErlDrvTermData am_false; +static ErlDrvTermData am_buffer; +static ErlDrvTermData am_mode; +static ErlDrvTermData am_list; +static ErlDrvTermData am_binary; +static ErlDrvTermData am_active; +static ErlDrvTermData am_once; +static ErlDrvTermData am_buffer; +static ErlDrvTermData am_linger; +static ErlDrvTermData am_recbuf; +static ErlDrvTermData am_sndbuf; +static ErlDrvTermData am_reuseaddr; +static ErlDrvTermData am_dontroute; +static ErlDrvTermData am_priority; +static ErlDrvTermData am_tos; +#endif + +/* speical errors for bad ports and sequences */ +#define EXBADPORT "exbadport" +#define EXBADSEQ "exbadseq" + + +static int inet_init(void); +static int ctl_reply(int, char*, int, char**, int); + +struct erl_drv_entry inet_driver_entry = +{ + inet_init, /* inet_init will add TCP, UDP and SCTP drivers */ + NULL, /* start */ + NULL, /* stop */ + NULL, /* output */ + NULL, /* ready_input */ + NULL, /* ready_output */ + "inet" +}; + +/* XXX: is this a driver interface function ??? */ +extern void erl_exit(int n, char*, _DOTS_); + +/* + * Malloc wrapper, + * we would like to change the behaviour for different + * systems here. + */ + +#ifdef FATAL_MALLOC + +static void *alloc_wrapper(size_t size){ + void *ret = driver_alloc(size); + if(ret == NULL) + erl_exit(1,"Out of virtual memory in malloc (%s)", __FILE__); + return ret; +} +#define ALLOC(X) alloc_wrapper(X) + +static void *realloc_wrapper(void *current, size_t size){ + void *ret = driver_realloc(current,size); + if(ret == NULL) + erl_exit(1,"Out of virtual memory in realloc (%s)", __FILE__); + return ret; +} +#define REALLOC(X,Y) realloc_wrapper(X,Y) +#define FREE(P) driver_free((P)) +#else /* FATAL_MALLOC */ + +#define ALLOC(X) driver_alloc((X)) +#define REALLOC(X,Y) driver_realloc((X), (Y)) +#define FREE(P) driver_free((P)) + +#endif /* FATAL_MALLOC */ + +#define INIT_ATOM(NAME) am_ ## NAME = driver_mk_atom(#NAME) + +#define LOAD_ATOM_CNT 2 +#define LOAD_ATOM(vec, i, atom) \ + (((vec)[(i)] = ERL_DRV_ATOM), \ + ((vec)[(i)+1] = (atom)), \ + ((i)+LOAD_ATOM_CNT)) + +#define LOAD_INT_CNT 2 +#define LOAD_INT(vec, i, val) \ + (((vec)[(i)] = ERL_DRV_INT), \ + ((vec)[(i)+1] = (ErlDrvTermData)(val)), \ + ((i)+LOAD_INT_CNT)) + +#define LOAD_UINT_CNT 2 +#define LOAD_UINT(vec, i, val) \ + (((vec)[(i)] = ERL_DRV_UINT), \ + ((vec)[(i)+1] = (ErlDrvTermData)(val)), \ + ((i)+LOAD_UINT_CNT)) + +#define LOAD_PORT_CNT 2 +#define LOAD_PORT(vec, i, port) \ + (((vec)[(i)] = ERL_DRV_PORT), \ + ((vec)[(i)+1] = (port)), \ + ((i)+LOAD_PORT_CNT)) + +#define LOAD_PID_CNT 2 +#define LOAD_PID(vec, i, pid) \ + (((vec)[(i)] = ERL_DRV_PID), \ + ((vec)[(i)+1] = (pid)), \ + ((i)+LOAD_PID_CNT)) + +#define LOAD_BINARY_CNT 4 +#define LOAD_BINARY(vec, i, bin, offs, len) \ + (((vec)[(i)] = ERL_DRV_BINARY), \ + ((vec)[(i)+1] = (ErlDrvTermData)(bin)), \ + ((vec)[(i)+2] = (len)), \ + ((vec)[(i)+3] = (offs)), \ + ((i)+LOAD_BINARY_CNT)) + +#define LOAD_BUF2BINARY_CNT 3 +#define LOAD_BUF2BINARY(vec, i, buf, len) \ + (((vec)[(i)] = ERL_DRV_BUF2BINARY), \ + ((vec)[(i)+1] = (ErlDrvTermData)(buf)), \ + ((vec)[(i)+2] = (len)), \ + ((i)+LOAD_BUF2BINARY_CNT)) + +#define LOAD_STRING_CNT 3 +#define LOAD_STRING(vec, i, str, len) \ + (((vec)[(i)] = ERL_DRV_STRING), \ + ((vec)[(i)+1] = (ErlDrvTermData)(str)), \ + ((vec)[(i)+2] = (len)), \ + ((i)+LOAD_STRING_CNT)) + +#define LOAD_STRING_CONS_CNT 3 +#define LOAD_STRING_CONS(vec, i, str, len) \ + (((vec)[(i)] = ERL_DRV_STRING_CONS), \ + ((vec)[(i)+1] = (ErlDrvTermData)(str)), \ + ((vec)[(i)+2] = (len)), \ + ((i)+LOAD_STRING_CONS_CNT)) + +#define LOAD_TUPLE_CNT 2 +#define LOAD_TUPLE(vec, i, size) \ + (((vec)[(i)] = ERL_DRV_TUPLE), \ + ((vec)[(i)+1] = (size)), \ + ((i)+LOAD_TUPLE_CNT)) + +#define LOAD_NIL_CNT 1 +#define LOAD_NIL(vec, i) \ + (((vec)[(i)] = ERL_DRV_NIL), \ + ((i)+LOAD_NIL_CNT)) + +#define LOAD_LIST_CNT 2 +#define LOAD_LIST(vec, i, size) \ + (((vec)[(i)] = ERL_DRV_LIST), \ + ((vec)[(i)+1] = (size)), \ + ((i)+LOAD_LIST_CNT)) + + +#ifdef HAVE_SCTP + /* "IS_SCTP": tells the difference between a UDP and an SCTP socket: */ +# define IS_SCTP(desc)((desc)->sprotocol==IPPROTO_SCTP) + + /* For AssocID, 4 bytes should be enough -- checked by "init": */ +# define GET_ASSOC_ID get_int32 +# define ASSOC_ID_LEN 4 +# define LOAD_ASSOC_ID LOAD_INT +# define LOAD_ASSOC_ID_CNT LOAD_INT_CNT +# define SCTP_ANC_BUFF_SIZE INET_DEF_BUFFER/2 /* XXX: not very good... */ +#endif + +static int load_ip_port(ErlDrvTermData* spec, int i, char* buf) +{ + spec[i++] = ERL_DRV_INT; + spec[i++] = (ErlDrvTermData) get_int16(buf); + return i; +} + +static int load_ip_address(ErlDrvTermData* spec, int i, int family, char* buf) +{ + int n; + if (family == AF_INET) { + for (n = 0; n < 4; n++) { + spec[i++] = ERL_DRV_INT; + spec[i++] = (ErlDrvTermData) ((unsigned char)buf[n]); + } + spec[i++] = ERL_DRV_TUPLE; + spec[i++] = 4; + } +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (family == AF_INET6) { + for (n = 0; n < 16; n += 2) { + spec[i++] = ERL_DRV_INT; + spec[i++] = (ErlDrvTermData) get_int16(buf+n); + } + spec[i++] = ERL_DRV_TUPLE; + spec[i++] = 8; + } +#endif + else { + spec[i++] = ERL_DRV_TUPLE; + spec[i++] = 0; + } + return i; +} + + +#ifdef HAVE_SCTP +/* For SCTP, we often need to return {IP, Port} tuples: */ +static int inet_get_address + (int family, char* dst, inet_address* src, unsigned int* len); + +#define LOAD_IP_AND_PORT_CNT \ + (8*LOAD_INT_CNT + LOAD_TUPLE_CNT + LOAD_INT_CNT + LOAD_TUPLE_CNT) + +static int load_ip_and_port + (ErlDrvTermData* spec, int i, inet_descriptor* desc, + struct sockaddr_storage* addr) +{ + /* The size of the buffer used to stringify the addr is the same as + that of "sockaddr_storage" itself: only their layout is different: + */ + unsigned int len = sizeof(struct sockaddr_storage); + unsigned int alen = len; + char abuf [len]; + int res = + inet_get_address(desc->sfamily, abuf, (inet_address*) addr, &alen); + ASSERT(res==0); + res = 0; + /* Now "abuf" contains: Family(1b), Port(2b), IP(4|16b) */ + + /* NB: the following functions are safe to use, as they create tuples + of copied Ints on the "spec", and do not install any String pts -- + a ptr to "abuf" would be dangling upon exiting this function: */ + i = load_ip_address(spec, i, desc->sfamily, abuf+3); + i = load_ip_port (spec, i, abuf+1); + i = LOAD_TUPLE (spec, i, 2); + return i; +} + +/* Loading Boolean flags as Atoms: */ +#define LOAD_BOOL_CNT LOAD_ATOM_CNT +#define LOAD_BOOL(spec, i, flag) \ + LOAD_ATOM((spec), (i), (flag) ? am_true : am_false); +#endif /* HAVE_SCTP */ + +/* +** Binary Buffer Managment +** We keep a stack of usable buffers +*/ +#define BUFFER_STACK_SIZE 16 + +static erts_smp_spinlock_t inet_buffer_stack_lock; +static ErlDrvBinary* buffer_stack[BUFFER_STACK_SIZE]; +static int buffer_stack_pos = 0; + + +/* + * XXX + * The erts_smp_spin_* functions should not be used by drivers (but this + * driver is special). Replace when driver locking api has been implemented. + * /rickard + */ +#define BUFSTK_LOCK erts_smp_spin_lock(&inet_buffer_stack_lock); +#define BUFSTK_UNLOCK erts_smp_spin_unlock(&inet_buffer_stack_lock); + +#ifdef DEBUG +static int tot_buf_allocated = 0; /* memory in use for i_buf */ +static int tot_buf_stacked = 0; /* memory on stack */ +static int max_buf_allocated = 0; /* max allocated */ + +#define COUNT_BUF_ALLOC(sz) do { \ + BUFSTK_LOCK; \ + tot_buf_allocated += (sz); \ + if (tot_buf_allocated > max_buf_allocated) \ + max_buf_allocated = tot_buf_allocated; \ + BUFSTK_UNLOCK; \ +} while(0) + +#define COUNT_BUF_FREE(sz) do { \ + BUFSTK_LOCK; \ + tot_buf_allocated -= (sz); \ + BUFSTK_UNLOCK; \ + } while(0) + +#define COUNT_BUF_STACK(sz) do { \ + BUFSTK_LOCK; \ + tot_buf_stacked += (sz); \ + BUFSTK_UNLOCK; \ + } while(0) + +#else + +#define COUNT_BUF_ALLOC(sz) +#define COUNT_BUF_FREE(sz) +#define COUNT_BUF_STACK(sz) + +#endif + +static ErlDrvBinary* alloc_buffer(long minsz) +{ + ErlDrvBinary* buf = NULL; + + BUFSTK_LOCK; + + DEBUGF(("alloc_buffer: sz = %ld, tot = %d, max = %d\r\n", + minsz, tot_buf_allocated, max_buf_allocated)); + + if (buffer_stack_pos > 0) { + int origsz; + + buf = buffer_stack[--buffer_stack_pos]; + origsz = buf->orig_size; + BUFSTK_UNLOCK; + COUNT_BUF_STACK(-origsz); + if (origsz < minsz) { + if ((buf = driver_realloc_binary(buf, minsz)) == NULL) + return NULL; + COUNT_BUF_ALLOC(buf->orig_size - origsz); + } + } + else { + BUFSTK_UNLOCK; + if ((buf = driver_alloc_binary(minsz)) == NULL) + return NULL; + COUNT_BUF_ALLOC(buf->orig_size); + } + return buf; +} + +/* +** Max buffer memory "cached" BUFFER_STACK_SIZE * INET_MAX_BUFFER +** (16 * 64k ~ 1M) +*/ +/*#define CHECK_DOUBLE_RELEASE 1*/ +static void release_buffer(ErlDrvBinary* buf) +{ + DEBUGF(("release_buffer: %ld\r\n", (buf==NULL) ? 0 : buf->orig_size)); + if (buf == NULL) + return; + BUFSTK_LOCK; + if ((buf->orig_size > INET_MAX_BUFFER) || + (buffer_stack_pos >= BUFFER_STACK_SIZE)) { + BUFSTK_UNLOCK; + COUNT_BUF_FREE(buf->orig_size); + driver_free_binary(buf); + } + else { +#ifdef CHECK_DOUBLE_RELEASE +#ifdef __GNUC__ +#warning CHECK_DOUBLE_RELEASE is enabled, this is a custom build emulator +#endif + int i; + for (i = 0; i < buffer_stack_pos; ++i) { + if (buffer_stack[i] == buf) { + erl_exit(1,"Multiple buffer release in inet_drv, this is a " + "bug, save the core and send it to " + "[email protected]!"); + } + } +#endif + buffer_stack[buffer_stack_pos++] = buf; + BUFSTK_UNLOCK; + COUNT_BUF_STACK(buf->orig_size); + } +} + +static ErlDrvBinary* realloc_buffer(ErlDrvBinary* buf, long newsz) +{ + ErlDrvBinary* bin; +#ifdef DEBUG + long orig_size = buf->orig_size; +#endif + + if ((bin = driver_realloc_binary(buf,newsz)) != NULL) { + COUNT_BUF_ALLOC(newsz - orig_size); + ; + } + return bin; +} + +/* use a TRICK, access the refc field to see if any one else has + * a ref to this buffer then call driver_free_binary else + * release_buffer instead + */ +static void free_buffer(ErlDrvBinary* buf) +{ + DEBUGF(("free_buffer: %ld\r\n", (buf==NULL) ? 0 : buf->orig_size)); + + if (buf != NULL) { + if (driver_binary_get_refc(buf) == 1) + release_buffer(buf); + else { + COUNT_BUF_FREE(buf->orig_size); + driver_free_binary(buf); + } + } +} + + +#ifdef __WIN32__ + +static ErlDrvData dummy_start(ErlDrvPort port, char* command) +{ + return (ErlDrvData)port; +} + +static int dummy_ctl(ErlDrvData data, unsigned int cmd, char* buf, int len, + char** rbuf, int rsize) +{ + static char error[] = "no_winsock2"; + + driver_failure_atom((ErlDrvPort)data, error); + return ctl_reply(INET_REP_ERROR, error, sizeof(error), rbuf, rsize); +} + +static void dummy_command(ErlDrvData data, char* buf, int len) +{ +} + +static struct erl_drv_entry dummy_tcp_driver_entry = +{ + NULL, /* init */ + dummy_start, /* start */ + NULL, /* stop */ + dummy_command, /* command */ + NULL, /* input */ + NULL, /* output */ + "tcp_inet", /* name */ + NULL, + NULL, + dummy_ctl, + NULL, + NULL +}; + +static struct erl_drv_entry dummy_udp_driver_entry = +{ + NULL, /* init */ + dummy_start, /* start */ + NULL, /* stop */ + dummy_command, /* command */ + NULL, /* input */ + NULL, /* output */ + "udp_inet", /* name */ + NULL, + NULL, + dummy_ctl, + NULL, + NULL +}; + +#ifdef HAVE_SCTP +static struct erl_drv_entry dummy_sctp_driver_entry = +{ /* Though there is no SCTP for Win32 yet... */ + NULL, /* init */ + dummy_start, /* start */ + NULL, /* stop */ + dummy_command, /* command */ + NULL, /* input */ + NULL, /* output */ + "sctp_inet", /* name */ + NULL, + NULL, + dummy_ctl, + NULL, + NULL +}; +#endif + +#endif + +/* general control reply function */ +static int ctl_reply(int rep, char* buf, int len, char** rbuf, int rsize) +{ + char* ptr; + + if ((len+1) > rsize) { + ptr = ALLOC(len+1); + *rbuf = ptr; + } + else + ptr = *rbuf; + *ptr++ = rep; + memcpy(ptr, buf, len); + return len+1; +} + +/* general control error reply function */ +static int ctl_error(int err, char** rbuf, int rsize) +{ + char response[256]; /* Response buffer. */ + char* s; + char* t; + + for (s = erl_errno_id(err), t = response; *s; s++, t++) + *t = tolower(*s); + return ctl_reply(INET_REP_ERROR, response, t-response, rbuf, rsize); +} + +static int ctl_xerror(char* xerr, char** rbuf, int rsize) +{ + int n = strlen(xerr); + return ctl_reply(INET_REP_ERROR, xerr, n, rbuf, rsize); +} + + +static ErlDrvTermData error_atom(int err) +{ + char errstr[256]; + char* s; + char* t; + + for (s = erl_errno_id(err), t = errstr; *s; s++, t++) + *t = tolower(*s); + *t = '\0'; + return driver_mk_atom(errstr); +} + + +static void enq_old_multi_op(tcp_descriptor *desc, int id, int req, + ErlDrvTermData caller, MultiTimerData *timeout, + ErlDrvMonitor *monitorp) +{ + inet_async_multi_op *opp; + + opp = ALLOC(sizeof(inet_async_multi_op)); + + opp->op.id = id; + opp->op.caller = caller; + opp->op.req = req; + opp->op.tmo.mtd = timeout; + memcpy(&(opp->op.monitor), monitorp, sizeof(ErlDrvMonitor)); + opp->next = NULL; + + if (desc->multi_first == NULL) { + desc->multi_first = opp; + } else { + desc->multi_last->next = opp; + } + desc->multi_last = opp; +} + +static void enq_multi_op(tcp_descriptor *desc, char *buf, int req, + ErlDrvTermData caller, MultiTimerData *timeout, + ErlDrvMonitor *monitorp) +{ + int id = NEW_ASYNC_ID(); + enq_old_multi_op(desc,id,req,caller,timeout,monitorp); + if (buf != NULL) + put_int16(id, buf); +} + +static int deq_multi_op(tcp_descriptor *desc, int *id_p, int *req_p, + ErlDrvTermData *caller_p, MultiTimerData **timeout_p, + ErlDrvMonitor *monitorp) +{ + inet_async_multi_op *opp; + opp = desc->multi_first; + if (!opp) { + return -1; + } + desc->multi_first = opp->next; + if (desc->multi_first == NULL) { + desc->multi_last = NULL; + } + *id_p = opp->op.id; + *req_p = opp->op.req; + *caller_p = opp->op.caller; + if (timeout_p != NULL) { + *timeout_p = opp->op.tmo.mtd; + } + if (monitorp != NULL) { + memcpy(monitorp,&(opp->op.monitor),sizeof(ErlDrvMonitor)); + } + FREE(opp); + return 0; +} + +static int remove_multi_op(tcp_descriptor *desc, int *id_p, int *req_p, + ErlDrvTermData caller, MultiTimerData **timeout_p, + ErlDrvMonitor *monitorp) +{ + inet_async_multi_op *opp, *slap; + for (opp = desc->multi_first, slap = NULL; + opp != NULL && opp->op.caller != caller; + slap = opp, opp = opp->next) + ; + if (!opp) { + return -1; + } + if (slap == NULL) { + desc->multi_first = opp->next; + } else { + slap->next = opp->next; + } + if (desc->multi_last == opp) { + desc->multi_last = slap; + } + *id_p = opp->op.id; + *req_p = opp->op.req; + if (timeout_p != NULL) { + *timeout_p = opp->op.tmo.mtd; + } + if (monitorp != NULL) { + memcpy(monitorp,&(opp->op.monitor),sizeof(ErlDrvMonitor)); + } + FREE(opp); + return 0; +} + +/* setup a new async id + caller (format async_id into buf) */ + +static int enq_async_w_tmo(inet_descriptor* desc, char* buf, int req, unsigned timeout, + ErlDrvMonitor *monitorp) +{ + int id = NEW_ASYNC_ID(); + inet_async_op* opp; + + if ((opp = desc->oph) == NULL) /* queue empty */ + opp = desc->oph = desc->opt = desc->op_queue; + else if (desc->oph == desc->opt) { /* queue full */ + DEBUGF(("enq(%ld): queue full\r\n", (long)desc->port)); + return -1; + } + + opp->id = id; + opp->caller = driver_caller(desc->port); + opp->req = req; + opp->tmo.value = timeout; + if (monitorp != NULL) { + memcpy(&(opp->monitor),monitorp,sizeof(ErlDrvMonitor)); + } + + DEBUGF(("enq(%ld): %d %ld %d\r\n", + (long) desc->port, opp->id, opp->caller, opp->req)); + + opp++; + if (opp >= desc->op_queue + INET_MAX_ASYNC) + desc->oph = desc->op_queue; + else + desc->oph = opp; + + if (buf != NULL) + put_int16(id, buf); + return 0; +} + +static int enq_async(inet_descriptor* desc, char* buf, int req) +{ + return enq_async_w_tmo(desc,buf,req,INET_INFINITY, NULL); +} + +static int deq_async_w_tmo(inet_descriptor* desc, int* ap, ErlDrvTermData* cp, + int* rp, unsigned *tp, ErlDrvMonitor *monitorp) +{ + inet_async_op* opp; + + if ((opp = desc->opt) == NULL) { /* queue empty */ + DEBUGF(("deq(%ld): queue empty\r\n", (long)desc->port)); + return -1; + } + *ap = opp->id; + *cp = opp->caller; + *rp = opp->req; + if (tp != NULL) { + *tp = opp->tmo.value; + } + if (monitorp != NULL) { + memcpy(monitorp,&(opp->monitor),sizeof(ErlDrvMonitor)); + } + + DEBUGF(("deq(%ld): %d %ld %d\r\n", + (long)desc->port, opp->id, opp->caller, opp->req)); + + opp++; + if (opp >= desc->op_queue + INET_MAX_ASYNC) + desc->opt = desc->op_queue; + else + desc->opt = opp; + + if (desc->opt == desc->oph) + desc->opt = desc->oph = NULL; + return 0; +} + +static int deq_async(inet_descriptor* desc, int* ap, ErlDrvTermData* cp, int* rp) +{ + return deq_async_w_tmo(desc,ap,cp,rp,NULL,NULL); +} +/* send message: +** {inet_async, Port, Ref, ok} +*/ +static int +send_async_ok(ErlDrvPort port, ErlDrvTermData Port, int Ref, + ErlDrvTermData recipient) +{ + ErlDrvTermData spec[2*LOAD_ATOM_CNT + LOAD_PORT_CNT + + LOAD_INT_CNT + LOAD_TUPLE_CNT]; + int i = 0; + + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, Port); + i = LOAD_INT(spec, i, Ref); + i = LOAD_ATOM(spec, i, am_ok); + i = LOAD_TUPLE(spec, i, 4); + + ASSERT(i == sizeof(spec)/sizeof(*spec)); + + return driver_send_term(port, recipient, spec, i); +} + +/* send message: +** {inet_async, Port, Ref, {ok,Port2}} +*/ +static int +send_async_ok_port(ErlDrvPort port, ErlDrvTermData Port, int Ref, + ErlDrvTermData recipient, ErlDrvTermData Port2) +{ + ErlDrvTermData spec[2*LOAD_ATOM_CNT + 2*LOAD_PORT_CNT + + LOAD_INT_CNT + 2*LOAD_TUPLE_CNT]; + int i = 0; + + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, Port); + i = LOAD_INT(spec, i, Ref); + { + i = LOAD_ATOM(spec, i, am_ok); + i = LOAD_PORT(spec, i, Port2); + i = LOAD_TUPLE(spec, i, 2); + } + i = LOAD_TUPLE(spec, i, 4); + + ASSERT(i == sizeof(spec)/sizeof(*spec)); + + return driver_send_term(port, recipient, spec, i); +} + +/* send message: +** {inet_async, Port, Ref, {error,Reason}} +*/ +static int +send_async_error(ErlDrvPort port, ErlDrvTermData Port, int Ref, + ErlDrvTermData recipient, ErlDrvTermData Reason) +{ + ErlDrvTermData spec[3*LOAD_ATOM_CNT + LOAD_PORT_CNT + + LOAD_INT_CNT + 2*LOAD_TUPLE_CNT]; + int i = 0; + + i = 0; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, Port); + i = LOAD_INT(spec, i, Ref); + { + i = LOAD_ATOM(spec, i, am_error); + i = LOAD_ATOM(spec, i, Reason); + i = LOAD_TUPLE(spec, i, 2); + } + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + DEBUGF(("send_async_error %ld %ld\r\n", recipient, Reason)); + return driver_send_term(port, recipient, spec, i); +} + + +static int async_ok(inet_descriptor* desc) +{ + int req; + int aid; + ErlDrvTermData caller; + + if (deq_async(desc, &aid, &caller, &req) < 0) + return -1; + return send_async_ok(desc->port, desc->dport, aid, caller); +} + +static int async_ok_port(inet_descriptor* desc, ErlDrvTermData Port2) +{ + int req; + int aid; + ErlDrvTermData caller; + + if (deq_async(desc, &aid, &caller, &req) < 0) + return -1; + return send_async_ok_port(desc->port, desc->dport, aid, caller, Port2); +} + +static int async_error_am(inet_descriptor* desc, ErlDrvTermData reason) +{ + int req; + int aid; + ErlDrvTermData caller; + + if (deq_async(desc, &aid, &caller, &req) < 0) + return -1; + return send_async_error(desc->port, desc->dport, aid, caller, + reason); +} + +/* dequeue all operations */ +static int async_error_am_all(inet_descriptor* desc, ErlDrvTermData reason) +{ + int req; + int aid; + ErlDrvTermData caller; + + while (deq_async(desc, &aid, &caller, &req) == 0) { + send_async_error(desc->port, desc->dport, aid, caller, + reason); + } + return 0; +} + + +static int async_error(inet_descriptor* desc, int err) +{ + return async_error_am(desc, error_atom(err)); +} + +/* send: +** {inet_reply, S, ok} +*/ + +static int inet_reply_ok(inet_descriptor* desc) +{ + ErlDrvTermData spec[2*LOAD_ATOM_CNT + LOAD_PORT_CNT + LOAD_TUPLE_CNT]; + ErlDrvTermData caller = desc->caller; + int i = 0; + + i = LOAD_ATOM(spec, i, am_inet_reply); + i = LOAD_PORT(spec, i, desc->dport); + i = LOAD_ATOM(spec, i, am_ok); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + + desc->caller = 0; + return driver_send_term(desc->port, caller, spec, i); +} + +/* send: +** {inet_reply, S, {error, Reason}} +*/ +static int inet_reply_error_am(inet_descriptor* desc, ErlDrvTermData reason) +{ + ErlDrvTermData spec[3*LOAD_ATOM_CNT + LOAD_PORT_CNT + 2*LOAD_TUPLE_CNT]; + ErlDrvTermData caller = desc->caller; + int i = 0; + + i = LOAD_ATOM(spec, i, am_inet_reply); + i = LOAD_PORT(spec, i, desc->dport); + i = LOAD_ATOM(spec, i, am_error); + i = LOAD_ATOM(spec, i, reason); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + desc->caller = 0; + + DEBUGF(("inet_reply_error_am %ld %ld\r\n", caller, reason)); + return driver_send_term(desc->port, caller, spec, i); +} + +/* send: +** {inet_reply, S, {error, Reason}} +*/ +static int inet_reply_error(inet_descriptor* desc, int err) +{ + return inet_reply_error_am(desc, error_atom(err)); +} + +/* +** Deliver port data from buffer +*/ +static int inet_port_data(inet_descriptor* desc, const char* buf, int len) +{ + unsigned int hsz = desc->hsz; + + DEBUGF(("inet_port_data(%ld): len = %d\r\n", (long)desc->port, len)); + + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) + return driver_output2(desc->port, (char*)buf, len, NULL, 0); + else if (hsz > 0) + return driver_output2(desc->port, (char*)buf, hsz, (char*)buf+hsz, len-hsz); + else + return driver_output(desc->port, (char*)buf, len); +} + +/* +** Deliver port data from binary (for an active mode socket) +*/ +static int +inet_port_binary_data(inet_descriptor* desc, ErlDrvBinary* bin, int offs, int len) +{ + unsigned int hsz = desc->hsz; + + DEBUGF(("inet_port_binary_data(%ld): offs=%d, len = %d\r\n", + (long)desc->port, offs, len)); + + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) + return driver_output2(desc->port, bin->orig_bytes+offs, len, NULL, 0); + else + return driver_output_binary(desc->port, bin->orig_bytes+offs, hsz, + bin, offs+hsz, len-hsz); +} + +static ErlDrvTermData am_http_eoh; +static ErlDrvTermData am_http_header; +static ErlDrvTermData am_http_request; +static ErlDrvTermData am_http_response; +static ErlDrvTermData am_http_error; +static ErlDrvTermData am_abs_path; +static ErlDrvTermData am_absoluteURI; +static ErlDrvTermData am_star; +static ErlDrvTermData am_undefined; +static ErlDrvTermData am_http; +static ErlDrvTermData am_https; +static ErlDrvTermData am_scheme; + +static int http_load_string(tcp_descriptor* desc, ErlDrvTermData* spec, int i, + const char* str, int len) +{ + if (desc->inet.htype >= TCP_PB_HTTP_BIN) { + ASSERT(desc->inet.htype == TCP_PB_HTTP_BIN || + desc->inet.htype == TCP_PB_HTTPH_BIN); + i = LOAD_BUF2BINARY(spec, i, str, len); + } else { + i = LOAD_STRING(spec, i, str, len); + } + return i; +} + +static int http_response_inetdrv(void *arg, int major, int minor, + int status, const char* phrase, int phrase_len) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[27]; + ErlDrvTermData caller; + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async,S,Ref,{ok,{http_response,Version,Status,Phrase}}} */ + int req; + int aid; + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) + return -1; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_ok); + } + else { + /* {http, S, {http_response,Version,Status,Phrase}} */ + i = LOAD_ATOM(spec, i, am_http); + i = LOAD_PORT(spec, i, desc->inet.dport); + } + i = LOAD_ATOM(spec, i, am_http_response); + i = LOAD_INT(spec, i, major); + i = LOAD_INT(spec, i, minor); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_INT(spec, i, status); + i = http_load_string(desc, spec, i, phrase, phrase_len); + i = LOAD_TUPLE(spec, i, 4); + + if (desc->inet.active == INET_PASSIVE) { + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i<=27); + return driver_send_term(desc->inet.port, caller, spec, i); + } + else { + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i<=27); + return driver_output_term(desc->inet.port, spec, i); + } +} + +static int http_load_uri(tcp_descriptor* desc, ErlDrvTermData* spec, int i, + const PacketHttpURI* uri) +{ + ErlDrvTermData scheme; + + switch (uri->type) { + case URI_STAR: + i = LOAD_ATOM(spec, i, am_star); + break; + case URI_ABS_PATH: + i = LOAD_ATOM(spec, i, am_abs_path); + i = http_load_string(desc, spec, i, uri->s1_ptr, uri->s1_len); + i = LOAD_TUPLE(spec, i, 2); + break; + case URI_HTTP: + scheme = am_http; + goto http_common; + case URI_HTTPS: + scheme = am_https; + http_common: + i = LOAD_ATOM(spec, i, am_absoluteURI); + i = LOAD_ATOM(spec, i, scheme); + i = http_load_string(desc, spec, i, uri->s1_ptr, uri->s1_len); + if (uri->port == 0) { + i = LOAD_ATOM(spec, i, am_undefined); + } else { + i = LOAD_INT(spec, i, uri->port); + } + i = http_load_string(desc, spec, i, uri->s2_ptr, uri->s2_len); + i = LOAD_TUPLE(spec, i, 5); + break; + + case URI_STRING: + i = http_load_string(desc, spec, i, uri->s1_ptr, uri->s1_len); + break; + case URI_SCHEME: + i = LOAD_ATOM(spec, i, am_scheme); + i = http_load_string(desc, spec, i, uri->s1_ptr, uri->s1_len); + i = http_load_string(desc, spec, i, uri->s2_ptr, uri->s2_len); + i = LOAD_TUPLE(spec, i, 3); + } + return i; +} + + +static int +http_request_inetdrv(void* arg, const http_atom_t* meth, const char* meth_ptr, + int meth_len, const PacketHttpURI* uri, + int major, int minor) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[43]; + ErlDrvTermData caller; + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async, S, Ref, {ok,{http_request,Meth,Uri,Version}}} */ + int req; + int aid; + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) + return -1; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_ok); + } + else { + /* {http, S, {http_request,Meth,Uri,Version}}} */ + i = LOAD_ATOM(spec, i, am_http); + i = LOAD_PORT(spec, i, desc->inet.dport); + } + + i = LOAD_ATOM(spec, i, am_http_request); + if (meth != NULL) + i = LOAD_ATOM(spec, i, meth->atom); + else + i = http_load_string(desc, spec, i, meth_ptr, meth_len); + i = http_load_uri(desc, spec, i, uri); + i = LOAD_INT(spec, i, major); + i = LOAD_INT(spec, i, minor); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + + if (desc->inet.active == INET_PASSIVE) { + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 43); + return driver_send_term(desc->inet.port, caller, spec, i); + } + else { + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 43); + return driver_output_term(desc->inet.port, spec, i); + } +} + +static int +http_header_inetdrv(void* arg, const http_atom_t* name, const char* name_ptr, + int name_len, const char* value_ptr, int value_len) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[26]; + ErlDrvTermData caller; + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async,S,Ref,{ok,{http_header,Bit,Name,IValue,Value}} */ + int req; + int aid; + + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) + return -1; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_ok); + } + else { + /* {http, S, {http_header,Bit,Name,IValue,Value}} */ + i = LOAD_ATOM(spec, i, am_http); + i = LOAD_PORT(spec, i, desc->inet.dport); + } + + i = LOAD_ATOM(spec, i, am_http_header); + if (name != NULL) { + i = LOAD_INT(spec, i, name->index+1); + i = LOAD_ATOM(spec, i, name->atom); + } + else { + i = LOAD_INT(spec, i, 0); + i = http_load_string(desc, spec, i, name_ptr, name_len); + } + i = LOAD_ATOM(spec, i, am_undefined); + i = http_load_string(desc, spec, i, value_ptr, value_len); + i = LOAD_TUPLE(spec, i, 5); + + if (desc->inet.active == INET_PASSIVE) { + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 26); + return driver_send_term(desc->inet.port, caller, spec, i); + } + else { + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 26); + return driver_output_term(desc->inet.port, spec, i); + } +} + +static int http_eoh_inetdrv(void* arg) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[14]; + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async,S,Ref,{ok,http_eoh}} */ + int req; + int aid; + ErlDrvTermData caller; + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) + return -1; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_ok); + i = LOAD_ATOM(spec, i, am_http_eoh); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 14); + return driver_send_term(desc->inet.port, caller, spec, i); + } + else { + /* {http, S, http_eoh} */ + i = LOAD_ATOM(spec, i, am_http); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_ATOM(spec, i, am_http_eoh); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 14); + return driver_output_term(desc->inet.port, spec, i); + } +} + +static int http_error_inetdrv(void* arg, const char* buf, int len) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[19]; + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async,S,Ref,{error,{http_error,Line}}} */ + int req; + int aid; + ErlDrvTermData caller; + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) + return -1; + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_error); + i = LOAD_ATOM(spec, i, am_http_error); + i = http_load_string(desc, spec, i, buf, len); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 19); + return driver_send_term(desc->inet.port, caller, spec, i); + } + else { + /* {http, S, {http_error,Line} */ + i = LOAD_ATOM(spec, i, am_http); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_ATOM(spec, i, am_http_error); + i = http_load_string(desc, spec, i, buf, len); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 19); + return driver_output_term(desc->inet.port, spec, i); + } +} + + +static +int ssl_tls_inetdrv(void* arg, unsigned type, unsigned major, unsigned minor, + const char* buf, int len, const char* prefix, int plen) +{ + tcp_descriptor* desc = (tcp_descriptor*) arg; + int i = 0; + ErlDrvTermData spec[28]; + ErlDrvTermData caller; + ErlDrvBinary* bin; + int ret; + + if ((bin = driver_alloc_binary(plen+len)) == NULL) + return async_error(&desc->inet, ENOMEM); + memcpy(bin->orig_bytes+plen, buf, len); + if (plen) { + memcpy(bin->orig_bytes, prefix, plen); + len += plen; + } + + if (desc->inet.active == INET_PASSIVE) { + /* {inet_async,S,Ref,{ok,{ssl_tls,...}}} */ + int req; + int aid; + + if (deq_async(INETP(desc), &aid, &caller, &req) < 0) { + ret = -1; + goto done; + } + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, aid); + i = LOAD_ATOM(spec, i, am_ok); + } + + /* {ssl_tls,S,ContentType,{Major,Minor},Bin} */ + i = LOAD_ATOM(spec, i, am_ssl_tls); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_INT(spec, i, type); + i = LOAD_INT(spec, i, major); + i = LOAD_INT(spec, i, minor); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_BINARY(spec, i, bin, 0, len); + i = LOAD_TUPLE(spec, i, 5); + + if (desc->inet.active == INET_PASSIVE) { + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 28); + ret = driver_send_term(desc->inet.port, caller, spec, i); + } + else { + ASSERT(i <= 28); + ret = driver_output_term(desc->inet.port, spec, i); + } +done: + driver_free_binary(bin); + return ret; +} + + +static PacketCallbacks packet_callbacks = +{ + http_response_inetdrv, + http_request_inetdrv, + http_eoh_inetdrv, + http_header_inetdrv, + http_error_inetdrv, + ssl_tls_inetdrv +}; + + +/* +** passive mode reply: +** {inet_async, S, Ref, {ok,[H1,...Hsz | Data]}} +** NB: this is for TCP only; +** UDP and SCTP use inet_async_binary_data . +*/ +static int inet_async_data(inet_descriptor* desc, const char* buf, int len) +{ + unsigned int hsz = desc->hsz; + ErlDrvTermData spec[20]; + ErlDrvTermData caller; + int req; + int aid; + int i = 0; + + DEBUGF(("inet_async_data(%ld): len = %d\r\n", (long)desc->port, len)); + + if (deq_async(desc, &aid, &caller, &req) < 0) + return -1; + + i = LOAD_ATOM(spec, i, am_inet_async); + i = LOAD_PORT(spec, i, desc->dport); + i = LOAD_INT(spec, i, aid); + + i = LOAD_ATOM(spec, i, am_ok); + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) { + i = LOAD_STRING(spec, i, buf, len); /* => [H1,H2,...Hn] */ + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i == 15); + desc->caller = 0; + return driver_send_term(desc->port, caller, spec, i); + } + else { + /* INET_MODE_BINARY => [H1,H2,...HSz | Binary] */ + int sz = len - hsz; + int code; + + i = LOAD_BUF2BINARY(spec, i, buf+hsz, sz); + if (hsz > 0) + i = LOAD_STRING_CONS(spec, i, buf, hsz); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i <= 20); + desc->caller = 0; + code = driver_send_term(desc->port, caller, spec, i); + return code; + } +} + +#ifdef HAVE_SCTP +/* +** SCTP-related atoms: +*/ +static ErlDrvTermData am_sctp_rtoinfo, /* Option names */ + am_sctp_associnfo, am_sctp_initmsg, + am_sctp_autoclose, am_sctp_nodelay, + am_sctp_disable_fragments, am_sctp_i_want_mapped_v4_addr, + am_sctp_maxseg, am_sctp_set_peer_primary_addr, + am_sctp_primary_addr, am_sctp_adaptation_layer, + am_sctp_peer_addr_params, am_sctp_default_send_param, + am_sctp_events, am_sctp_delayed_ack_time, + am_sctp_status, am_sctp_get_peer_addr_info, + + /* Record names */ + am_sctp_sndrcvinfo, am_sctp_assoc_change, + am_sctp_paddr_change, am_sctp_remote_error, + am_sctp_send_failed, am_sctp_shutdown_event, + am_sctp_adaptation_event, am_sctp_pdapi_event, + am_sctp_assocparams, am_sctp_prim, + am_sctp_setpeerprim, am_sctp_setadaptation, + am_sctp_paddrparams, am_sctp_event_subscribe, + am_sctp_assoc_value, am_sctp_paddrinfo, + + /* For #sctp_sndrcvinfo{}: */ + am_unordered, am_addr_over, + am_abort, am_eof, + + /* For #sctp_assoc_change{}: */ + am_comm_up, am_comm_lost, + am_restart, am_shutdown_comp, + am_cant_assoc, + + /* For #sctp_paddr_change{}: */ + am_addr_available, am_addr_unreachable, + am_addr_removed, am_addr_added, + am_addr_made_prim, am_addr_confirmed, + + /* For #sctp_remote_error{}: */ + am_short_recv, am_wrong_anc_data, + + /* For #sctp_pdap_event{}: */ + am_partial_delivery_aborted, + + /* For #sctp_paddrparams{}: */ + am_hb_enable, am_hb_disable, + am_hb_demand, am_pmtud_enable, + am_pmtud_disable, am_sackdelay_enable, + am_sackdelay_disable, + + /* For #sctp_paddrinfo{}: */ + am_active, am_inactive, + + /* For #sctp_status{}: */ + am_empty, am_closed, + am_cookie_wait, am_cookie_echoed, + am_established, am_shutdown_pending, + am_shutdown_sent, am_shutdown_received, + am_shutdown_ack_sent; + /* Not yet implemented in the Linux kernel: + ** am_bound, am_listen; + */ + +/* +** Parsing of "sctp_sndrcvinfo": ancillary data coming with received msgs. +** This function is mainly used by "sctp_parse_ancillary_data", but also +** by "sctp_parse_async_event" in case of SCTP_SEND_FAILED: +*/ +#define SCTP_PARSE_SNDRCVINFO_CNT \ + (5*LOAD_ATOM_CNT + 5*LOAD_INT_CNT + 2*LOAD_UINT_CNT + \ + LOAD_NIL_CNT + LOAD_LIST_CNT + LOAD_ASSOC_ID_CNT + LOAD_TUPLE_CNT) +static int sctp_parse_sndrcvinfo + (ErlDrvTermData * spec, int i, struct sctp_sndrcvinfo * sri) +{ + int n; + + i = LOAD_ATOM (spec, i, am_sctp_sndrcvinfo); + i = LOAD_INT (spec, i, sri->sinfo_stream); + i = LOAD_INT (spec, i, sri->sinfo_ssn); + /* Now Flags, as a list: */ + n = 0; + if (sri->sinfo_flags & SCTP_UNORDERED) + { i = LOAD_ATOM (spec, i, am_unordered); n++; } + + if (sri->sinfo_flags & SCTP_ADDR_OVER) + { i = LOAD_ATOM (spec, i, am_addr_over); n++; } + + if (sri->sinfo_flags & SCTP_ABORT) + { i = LOAD_ATOM (spec, i, am_abort); n++; } + + if (sri->sinfo_flags & SCTP_EOF) + { i = LOAD_ATOM (spec, i, am_eof); n++; } + + /* SCTP_SENDALL is not yet supported by the Linux kernel */ + i = LOAD_NIL (spec, i); + i = LOAD_LIST (spec, i, n+1); + + /* Continue with other top-level fields: */ + i = LOAD_INT (spec, i, sock_ntohl(sri->sinfo_ppid)); + i = LOAD_INT (spec, i, sri->sinfo_context); + i = LOAD_INT (spec, i, sri->sinfo_timetolive); + i = LOAD_UINT (spec, i, sri->sinfo_tsn); + i = LOAD_UINT (spec, i, sri->sinfo_cumtsn); + i = LOAD_ASSOC_ID (spec, i, sri->sinfo_assoc_id); + + /* Close up the record: */ + i = LOAD_TUPLE (spec, i, 10); + return i; +} + +/* +** This function skips non-SCTP ancillary data, returns SCTP-specific anc.data +** (currently "sctp_sndrcvinfo" only) as a list of records: +*/ +static int sctp_parse_ancillary_data + (ErlDrvTermData * spec, int i, struct msghdr * mptr) +{ + /* First of all, check for ancillary data: */ + struct cmsghdr * cmsg, * frst_msg = CMSG_FIRSTHDR(mptr); + int s = 0; + for (cmsg = frst_msg; cmsg != NULL; cmsg = CMSG_NXTHDR(mptr,cmsg)) + { + struct sctp_sndrcvinfo * sri; + + /* Skip other possible ancillary data, e.g. from IPv6: */ + if (cmsg->cmsg_level != IPPROTO_SCTP || + cmsg->cmsg_type != SCTP_SNDRCV) + continue; + + if (((char*)cmsg + cmsg->cmsg_len) - (char*)frst_msg > + mptr->msg_controllen) + /* MUST check this in Linux -- the returned "cmsg" may actually + go too far! */ + break; + + /* The ONLY kind of ancillary SCTP data which can occur on receiving + is "sctp_sndrcvinfo" (on sending, "sctp_initmsg" can be specified + by the user). So parse this type: + */ + sri = (struct sctp_sndrcvinfo*) CMSG_DATA(cmsg); + i = sctp_parse_sndrcvinfo (spec, i, sri); + s ++; + } + /* Now make the list of tuples created above. Normally, it will be [] or + a singleton list. The list must first be closed with NIL, otherwise + traversing it in Erlang would be problematic: + */ + i = LOAD_NIL (spec, i); + i = LOAD_LIST(spec, i, s+1); + return i; +} + +/* +** Parsing of ERROR and ABORT SCTP chunks. The function returns a list of error +** causes (as atoms). The chunks also contain some extended cause info, but it +** is not very detailed anyway, and of no interest at the user level (it only +** concerns the protocol implementation), so we omit it: +*/ +static int sctp_parse_error_chunk + (ErlDrvTermData * spec, int i, char * chunk, int chlen) +{ + /* The "chunk" itself contains its length, which must not be greater than + the "chlen" derived from the over-all msg size: + */ + char *causes, *cause; + int coff, /* Cause offset */ + ccode, /* Cause code */ + clen, /* cause length */ + s; + int len = sock_ntohs (*((uint16_t*)(chunk+2))); + ASSERT(len >= 4 && len <= chlen); + + causes = chunk + 4; + coff = 0; + len -= 4; /* Total length of the "causes" fields */ + cause = causes; + s = 0; + + while (coff < len) + { + ccode = sock_ntohs (*((uint16_t*)(cause))); + clen = sock_ntohs (*((uint16_t*)(cause + 2))); + if (clen <= 0) + /* Strange, but must guard against that! */ + break; + + /* Install the corresp atom for this "ccode": */ + i = LOAD_INT (spec, i, ccode); + cause += clen; + coff += clen; + s ++; + } + i = LOAD_NIL (spec, i); + i = LOAD_LIST(spec, i, s+1); + return i; +} + +/* +** Parsing of SCTP notification events. NB: they are NOT ancillary data: they +** are sent IN PLACE OF, not in conjunction with, the normal data: +*/ +static int sctp_parse_async_event + (ErlDrvTermData * spec, int i, int ok_pos, + ErlDrvTermData error_atom, inet_descriptor* desc, + ErlDrvBinary * bin, int offs, int sz) +{ + char* body = bin->orig_bytes + offs; + union sctp_notification * nptr = (union sctp_notification *) body; + + switch (nptr->sn_header.sn_type) + { + case SCTP_ASSOC_CHANGE: + { /* {sctp_assoc_change, + State : Atom(), + Error : Atom(), + OutBoundStreams : Int(), + InBoundStreams : Int(), + AssocID : Int(), + // AbortCauses : [Atom()] // NOT YET IMPLEMENTED + } + */ + struct sctp_assoc_change* sptr = &(nptr->sn_assoc_change); + ASSERT(sptr->sac_length <= sz); /* No buffer overrun */ + + i = LOAD_ATOM (spec, i, am_sctp_assoc_change); + + switch (sptr->sac_state) + { + case SCTP_COMM_UP: + i = LOAD_ATOM (spec, i, am_comm_up); + break; + case SCTP_COMM_LOST: + i = LOAD_ATOM (spec, i, am_comm_lost); + break; + case SCTP_RESTART: + i = LOAD_ATOM (spec, i, am_restart); + break; + case SCTP_SHUTDOWN_COMP: + i = LOAD_ATOM (spec, i, am_shutdown_comp); + break; + case SCTP_CANT_STR_ASSOC: + i = LOAD_ATOM (spec, i, am_cant_assoc); + break; + default: + ASSERT(0); + } + i = LOAD_INT (spec, i, sptr->sac_error); + i = LOAD_INT (spec, i, sptr->sac_outbound_streams); + i = LOAD_INT (spec, i, sptr->sac_inbound_streams); + i = LOAD_INT (spec, i, sptr->sac_assoc_id); + + /* The ABORT chunk may or may not be present at the end, depending + on whether there was really an ABORT. In the Linux Kernel SCTP + implementation, this chunk is not delivered anyway, so we leave + it out. Just close up the tuple: + */ + i = LOAD_TUPLE (spec, i, 6); + break; + } + + case SCTP_PEER_ADDR_CHANGE: + { /* {sctp_paddr_change, + AffectedAddr : String(), + State : Atom(), + Error : Atom(), + AssocID : Int() + } + */ + struct sctp_paddr_change* sptr = &(nptr->sn_paddr_change); + ASSERT(sptr->spc_length <= sz); /* No buffer overrun */ + + i = LOAD_ATOM (spec, i, am_sctp_paddr_change); + i = load_ip_and_port(spec, i, desc, &sptr->spc_aaddr); + + switch (sptr->spc_state) + { + case SCTP_ADDR_AVAILABLE: + i = LOAD_ATOM (spec, i, am_addr_available); + break; + case SCTP_ADDR_UNREACHABLE: + i = LOAD_ATOM (spec, i, am_addr_unreachable); + break; + case SCTP_ADDR_REMOVED: + i = LOAD_ATOM (spec, i, am_addr_removed); + break; + case SCTP_ADDR_ADDED: + i = LOAD_ATOM (spec, i, am_addr_added); + break; + case SCTP_ADDR_MADE_PRIM: + i = LOAD_ATOM (spec, i, am_addr_made_prim); + break; +#if HAVE_DECL_SCTP_ADDR_CONFIRMED + case SCTP_ADDR_CONFIRMED: + i = LOAD_ATOM (spec, i, am_addr_confirmed); + break; +#endif + default: + ASSERT(0); + } + i = LOAD_INT (spec, i, sptr->spc_error); + i = LOAD_INT (spec, i, sptr->spc_assoc_id); + i = LOAD_TUPLE (spec, i, 5); + break; + } + + case SCTP_REMOTE_ERROR: + { /* This is an error condition, so we return an error term + {sctp_remote_error, + Error : Int(), + AssocID : Int(), + RemoteCauses : [Atom()] // Remote Error flags + } + */ + char *chunk; + int chlen; + struct sctp_remote_error * sptr = &(nptr->sn_remote_error); + ASSERT(sptr->sre_length <= sz); /* No buffer overrun */ + + /* Over-write the prev part of the response with an error: */ + (void)LOAD_ATOM(spec, ok_pos, error_atom); + + /* Continue from the curr pos: */ + i = LOAD_ATOM (spec, i, am_sctp_remote_error); + + i = LOAD_INT (spec, i, sock_ntohs(sptr->sre_error)); + i = LOAD_INT (spec, i, sptr->sre_assoc_id); + +# ifdef HAVE_STRUCT_SCTP_REMOTE_ERROR_SRE_DATA + chunk = (char*) (&(sptr->sre_data)); +# else + chunk = ((char*)sptr) + sizeof(*sptr); +# endif + chlen = sptr->sre_length - (chunk - (char *)sptr); + i = sctp_parse_error_chunk(spec, i, chunk, chlen); + + i = LOAD_TUPLE (spec, i, 4); + /* The {error, {...}} will be closed by the caller */ + break; + } + + case SCTP_SEND_FAILED: + { /* {sctp_send_failed, + DataSent : Atom() // true or false + Error : Atom(), + OrigInfo : Tuple(), + AssocID : Int(), + OrigData : Binary() + } + This is also an ERROR condition -- overwrite the 'ok': + */ + char *chunk; + int chlen, choff; + struct sctp_send_failed * sptr = &(nptr->sn_send_failed); + ASSERT(sptr->ssf_length <= sz); /* No buffer overrun */ + + /* Over-write 'ok' with 'error', continue from curr "i": */ + (void)LOAD_ATOM(spec, ok_pos, error_atom); + + i = LOAD_ATOM (spec, i, am_sctp_send_failed); + switch (sptr->ssf_flags) { + case SCTP_DATA_SENT: + i = LOAD_ATOM (spec, i, am_true); + break; + case SCTP_DATA_UNSENT: + i = LOAD_ATOM (spec, i, am_false); + break; + default: + ASSERT(0); + } + i = LOAD_INT (spec, i, sptr->ssf_error); + /* Now parse the orig SCTP_SNDRCV info */ + i = sctp_parse_sndrcvinfo (spec, i, &sptr->ssf_info); + i = LOAD_ASSOC_ID (spec, i, sptr->ssf_assoc_id); + + /* Load the orig data chunk, as an unparsed binary. Note that + in LOAD_BINARY below, we must specify the offset wrt bin-> + orig_bytes. In Solaris 10, we don't have ssf_data: + */ +# ifdef HAVE_STRUCT_SCTP_SEND_FAILED_SSF_DATA + chunk = (char*) (&(sptr->ssf_data)); +# else + chunk = ((char*)sptr) + sizeof(*sptr); +# endif + chlen = sptr->ssf_length - (chunk - (char*) sptr); + choff = chunk - bin->orig_bytes; + + i = LOAD_BINARY(spec, i, bin, choff, chlen); + i = LOAD_TUPLE (spec, i, 6); + /* The {error, {...}} tuple is not yet closed */ + break; + } + + case SCTP_SHUTDOWN_EVENT: + { /* {sctp_shutdown_event, + AssocID : Int() + } + */ + struct sctp_shutdown_event * sptr = &(nptr->sn_shutdown_event); + + ASSERT (sptr->sse_length == sizeof(struct sctp_shutdown_event) && + sptr->sse_length <= sz); /* No buffer overrun */ + + i = LOAD_ATOM (spec, i, am_sctp_shutdown_event); + i = LOAD_INT (spec, i, sptr->sse_assoc_id); + i = LOAD_TUPLE (spec, i, 2); + break; + } + + case SCTP_ADAPTATION_INDICATION: + { /* {sctp_adaptation_event, + Indication : Atom(), + AssocID : Int() + } + */ + struct sctp_adaptation_event * sptr = + &(nptr->sn_adaptation_event); + ASSERT (sptr->sai_length == sizeof(struct sctp_adaptation_event) + && sptr->sai_length <= sz); /* No buffer overrun */ + + i = LOAD_ATOM (spec, i, am_sctp_adaptation_event); + i = LOAD_INT (spec, i, sock_ntohl(sptr->sai_adaptation_ind)); + i = LOAD_INT (spec, i, sptr->sai_assoc_id); + i = LOAD_TUPLE (spec, i, 3); + break; + } + + case SCTP_PARTIAL_DELIVERY_EVENT: + { /* It is not clear whether this event is sent to the sender + (when the receiver gets only a part of a message), or to + the receiver itself. In any case, we do not support partial + delivery of msgs in this implementation, so this is an error + condition: + {sctp_pdapi_event, sctp_partial_delivery_aborted, AssocID}: + */ + struct sctp_pdapi_event * sptr; + (void) LOAD_ATOM (spec, ok_pos, error_atom); + + sptr = &(nptr->sn_pdapi_event); + ASSERT (sptr->pdapi_length == sizeof(struct sctp_pdapi_event) && + sptr->pdapi_length <= sz); /* No buffer overrun */ + + i = LOAD_ATOM (spec, i, am_sctp_pdapi_event); + + /* Currently, there is only one indication possible: */ + ASSERT (sptr->pdapi_indication == SCTP_PARTIAL_DELIVERY_ABORTED); + + i = LOAD_ATOM (spec, i, am_partial_delivery_aborted); + i = LOAD_INT (spec, i, sptr->pdapi_assoc_id); + i = LOAD_TUPLE (spec, i, 3); + /* The {error, {...}} tuple is not yet closed */ + break; + } + + /* XXX: No more supported SCTP Event types. The standard also provides + SCTP_AUTHENTICATION_EVENT, but it is not implemented in the Linux + kernel, hence not supported here either. It is not possible to + request delivery of such events in this implementation, so they + cannot occur: + */ + default: ASSERT(0); + } + return i; +} +#endif /* HAVE_SCTP */ + +/* +** passive mode reply: +** for UDP: +** {inet_async, S, Ref, {ok, Data=[H1,...,Hsz | BinData]}} +** or (in the list mode) +** {inet_async, S, Ref, {ok, Data=[H1,...,Hsz]}} +** +** for SCTP: +** {inet_async, S, Ref, {ok, {[H1,...,HSz], [AncilData], Data_OR_Event}}} +** where each AncilDatum:Tuple(); +** Data:List() or Binary(), but if List(), then without the Addr part, +** which is moved in front; +** Event:Tuple(); +** or +** {inet_async, S, Ref, {error, {[H1,...,HSz], [AncilData], ErrorTerm}}} +** +** Cf: the output of send_async_error() is +** {inet_async, S, Ref, {error, Cause:Atom()}} +*/ +static int +inet_async_binary_data + (inet_descriptor* desc, unsigned int phsz, + ErlDrvBinary * bin, int offs, int len, void * extra) +{ + unsigned int hsz = desc->hsz + phsz; + ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN]; + ErlDrvTermData caller = desc->caller; + int aid; + int req; + int i = 0; +#ifdef HAVE_SCTP + int ok_pos; +#endif + + DEBUGF(("inet_async_binary_data(%ld): offs=%d, len=%d\r\n", + (long)desc->port, offs, len)); + + if (deq_async(desc, &aid, &caller, &req) < 0) + return -1; + + i = LOAD_ATOM(spec, i, am_inet_async); /* 'inet_async' */ + i = LOAD_PORT(spec, i, desc->dport); /* S */ + i = LOAD_INT (spec, i, aid); /* Ref */ + +#ifdef HAVE_SCTP + /* Need to memoise the position of the 'ok' atom written, as it may + later be overridden by an 'error': */ + ok_pos = i; +#endif + i = LOAD_ATOM(spec, i, am_ok); + +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) + { /* For SCTP we always have desc->hsz==0 (i.e., no application-level + headers are used), so hsz==phsz (see above): */ + struct msghdr* mptr; + int sz; + + ASSERT (hsz == phsz && hsz != 0); + sz = len - hsz; /* Size of the msg data proper, w/o the addr */ + + /* We always put the Addr as a list in front */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs, hsz); + + /* Put in the list (possibly empty) of Ancillary Data: */ + mptr = (struct msghdr *) extra; + i = sctp_parse_ancillary_data (spec, i, mptr); + + /* Then: Data or Event (Notification)? */ + if (mptr->msg_flags & MSG_NOTIFICATION) + /* This is an Event, parse it. It may indicate a normal or an error + condition; in the latter case, the 'ok' above is overridden by + an 'error', and the Event we receive contains the error term: */ + i = sctp_parse_async_event + (spec, i, ok_pos, am_error, desc, bin, offs+hsz, sz); + else + /* This is SCTP data, not a notification event. The data can be + returned as a List or as a Binary, similar to the generic case: + */ + if (desc->mode == INET_MODE_LIST) + /* INET_MODE_LIST => [H1,H2,...Hn], addr and data together, + butthe Addr has already been parsed, so start at offs+hsz: + */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs+hsz, sz); + else + /* INET_MODE_BINARY => Binary */ + i = LOAD_BINARY(spec, i, bin, offs+hsz, sz); + + /* Close up the {[H1,...,HSz], [AncilData], Event_OR_Data} tuple. This + is valid even in the case when Event is a error notification: */ + i = LOAD_TUPLE (spec, i, 3); + } + else +#endif /* HAVE_SCTP */ + /* Generic case. Both Addr and Data (or a single list of them together) are + returned: */ + + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) { + /* INET_MODE_LIST => [H1,H2,...Hn] */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs, len); + } + else { + /* INET_MODE_BINARY => [H1,H2,...HSz | Binary] or [Binary]: */ + int sz = len - hsz; + i = LOAD_BINARY(spec, i, bin, offs+hsz, sz); + if (hsz > 0) + i = LOAD_STRING_CONS(spec, i, bin->orig_bytes+offs, hsz); + } + /* Close up the {ok, ...} or {error, ...} tuple: */ + i = LOAD_TUPLE(spec, i, 2); + + /* Close up the outer {inet_async, S, Ref, {ok|error, ...}} tuple: */ + i = LOAD_TUPLE(spec, i, 4); + + ASSERT(i <= PACKET_ERL_DRV_TERM_DATA_LEN); + desc->caller = 0; + return driver_send_term(desc->port, caller, spec, i); +} + +/* +** active mode message: +** {tcp, S, [H1,...Hsz | Data]} +*/ +static int tcp_message(inet_descriptor* desc, const char* buf, int len) +{ + unsigned int hsz = desc->hsz; + ErlDrvTermData spec[20]; + int i = 0; + + DEBUGF(("tcp_message(%ld): len = %d\r\n", (long)desc->port, len)); + + i = LOAD_ATOM(spec, i, am_tcp); + i = LOAD_PORT(spec, i, desc->dport); + + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) { + i = LOAD_STRING(spec, i, buf, len); /* => [H1,H2,...Hn] */ + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 20); + return driver_output_term(desc->port, spec, i); + } + else { + /* INET_MODE_BINARY => [H1,H2,...HSz | Binary] */ + int sz = len - hsz; + int code; + + i = LOAD_BUF2BINARY(spec, i, buf+hsz, sz); + if (hsz > 0) + i = LOAD_STRING_CONS(spec, i, buf, hsz); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 20); + code = driver_output_term(desc->port, spec, i); + return code; + } +} + +/* +** active mode message: +** {tcp, S, [H1,...Hsz | Data]} +*/ +static int +tcp_binary_message(inet_descriptor* desc, ErlDrvBinary* bin, int offs, int len) +{ + unsigned int hsz = desc->hsz; + ErlDrvTermData spec[20]; + int i = 0; + + DEBUGF(("tcp_binary_message(%ld): len = %d\r\n", (long)desc->port, len)); + + i = LOAD_ATOM(spec, i, am_tcp); + i = LOAD_PORT(spec, i, desc->dport); + + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) { + /* INET_MODE_LIST => [H1,H2,...Hn] */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs, len); + } + else { + /* INET_MODE_BINARY => [H1,H2,...HSz | Binary] */ + int sz = len - hsz; + + i = LOAD_BINARY(spec, i, bin, offs+hsz, sz); + if (hsz > 0) + i = LOAD_STRING_CONS(spec, i, bin->orig_bytes+offs, hsz); + } + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 20); + return driver_output_term(desc->port, spec, i); +} + +/* +** send: active mode {tcp_closed, S} +*/ +static int tcp_closed_message(tcp_descriptor* desc) +{ + ErlDrvTermData spec[6]; + int i = 0; + + DEBUGF(("tcp_closed_message(%ld):\r\n", (long)desc->inet.port)); + if (!(desc->tcp_add_flags & TCP_ADDF_CLOSE_SENT)) { + desc->tcp_add_flags |= TCP_ADDF_CLOSE_SENT; + + i = LOAD_ATOM(spec, i, am_tcp_closed); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_TUPLE(spec, i, 2); + ASSERT(i <= 6); + return driver_output_term(desc->inet.port, spec, i); + } + return 0; +} + +/* +** send active message {tcp_error, S, Error} +*/ +static int tcp_error_message(tcp_descriptor* desc, int err) +{ + ErlDrvTermData spec[8]; + ErlDrvTermData am_err = error_atom(err); + int i = 0; + + DEBUGF(("tcp_error_message(%ld): %d\r\n", (long)desc->inet.port, err)); + + i = LOAD_ATOM(spec, i, am_tcp_error); + i = LOAD_PORT(spec, i, desc->inet.dport); + i = LOAD_ATOM(spec, i, am_err); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i <= 8); + return driver_output_term(desc->inet.port, spec, i); +} + +/* +** active mode message: +** {udp, S, IP, Port, [H1,...Hsz | Data]} or +** {sctp, S, IP, Port, {[AncilData], Event_or_Data}} +** where +** [H1,...,HSz] are msg headers (without IP/Port, UDP only), +** Data : List() | Binary() +*/ +static int packet_binary_message + (inet_descriptor* desc, ErlDrvBinary* bin, int offs, int len, void* extra) +{ + unsigned int hsz = desc->hsz; + ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN]; + int i = 0; + int alen; + + DEBUGF(("packet_binary_message(%ld): len = %d\r\n", + (long)desc->port, len)); +# ifdef HAVE_SCTP + i = LOAD_ATOM(spec, i, IS_SCTP(desc) ? am_sctp : am_udp); /* UDP|SCTP */ +# else + i = LOAD_ATOM(spec, i, am_udp ); /* UDP only */ +# endif + i = LOAD_PORT(spec, i, desc->dport); /* S */ + + alen = addrlen(desc->sfamily); + i = load_ip_address(spec, i, desc->sfamily, bin->orig_bytes+offs+3); + i = load_ip_port(spec, i, bin->orig_bytes+offs+1); /* IP, Port */ + + offs += (alen + 3); + len -= (alen + 3); + +# ifdef HAVE_SCTP + if (!IS_SCTP(desc)) + { +# endif + if ((desc->mode == INET_MODE_LIST) || (hsz > len)) + /* INET_MODE_LIST, or only headers => [H1,H2,...Hn] */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs, len); + else { + /* INET_MODE_BINARY => [H1,H2,...HSz | Binary] */ + int sz = len - hsz; + + i = LOAD_BINARY(spec, i, bin, offs+hsz, sz); + if (hsz > 0) + i = LOAD_STRING_CONS(spec, i, bin->orig_bytes+offs, hsz); + } +# ifdef HAVE_SCTP + } + else + { /* For SCTP we always have desc->hsz==0 (i.e., no application-level + headers are used): */ + struct msghdr* mptr; + ASSERT(hsz == 0); + + /* Put in the list (possibly empty) of Ancillary Data: */ + mptr = (struct msghdr *) extra; + i = sctp_parse_ancillary_data (spec, i, mptr); + + /* Then: Data or Event (Notification)? */ + if (mptr->msg_flags & MSG_NOTIFICATION) + /* This is an Event, parse it. It may indicate a normal or an error + condition; in the latter case, the initial 'sctp' atom is over- + ridden by 'sctp_error', and the Event we receive contains the + error term: */ + i = sctp_parse_async_event + (spec, i, 0, am_sctp_error, desc, bin, offs, len); + else + /* This is SCTP data, not a notification event. The data can be + returned as a List or as a Binary, similar to the generic case: + */ + if (desc->mode == INET_MODE_LIST) + /* INET_MODE_LIST => [H1,H2,...Hn], addr and data together, + but the Addr has already been parsed, so start at offs: + */ + i = LOAD_STRING(spec, i, bin->orig_bytes+offs, len); + else + /* INET_MODE_BINARY => Binary */ + i = LOAD_BINARY(spec, i, bin, offs, len); + + /* Close up the {[AncilData], Event_OR_Data} tuple: */ + i = LOAD_TUPLE (spec, i, 2); + } +# endif /* HAVE_SCTP */ + + /* Close up the outer 5-tuple: */ + i = LOAD_TUPLE(spec, i, 5); + ASSERT(i <= PACKET_ERL_DRV_TERM_DATA_LEN); + return driver_output_term(desc->port, spec, i); +} + +/* +** send active message {udp_error|sctp_error, S, Error} +*/ +static int packet_error_message(udp_descriptor* udesc, int err) +{ + inet_descriptor* desc = INETP(udesc); + ErlDrvTermData spec[2*LOAD_ATOM_CNT + LOAD_PORT_CNT + LOAD_TUPLE_CNT]; + ErlDrvTermData am_err = error_atom(err); + int i = 0; + + DEBUGF(("packet_error_message(%ld): %d\r\n", + (long)desc->port, err)); + +# ifdef HAVE_SCTP + if (IS_SCTP(desc) ) + i = LOAD_ATOM(spec, i, am_sctp_error); + else +# endif + i = LOAD_ATOM(spec, i, am_udp_error); + + i = LOAD_PORT(spec, i, desc->dport); + i = LOAD_ATOM(spec, i, am_err); + i = LOAD_TUPLE(spec, i, 3); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + return driver_output_term(desc->port, spec, i); +} + + +/* scan buffer for bit 7 */ +static void scanbit8(inet_descriptor* desc, const char* buf, int len) +{ + int c; + + if (!desc->bit8f || desc->bit8) return; + c = 0; + while(len--) c |= *buf++; + desc->bit8 = ((c & 0x80) != 0); +} + +/* +** active=TRUE: +** (NOTE! distribution MUST use active=TRUE, deliver=PORT) +** deliver=PORT {S, {data, [H1,..Hsz | Data]}} +** deliver=TERM {tcp, S, [H1..Hsz | Data]} +** +** active=FALSE: +** {async, S, Ref, {ok,[H1,...Hsz | Data]}} +*/ +static int tcp_reply_data(tcp_descriptor* desc, char* buf, int len) +{ + int code; + const char* body = buf; + int bodylen = len; + + packet_get_body(desc->inet.htype, &body, &bodylen); + + scanbit8(INETP(desc), body, bodylen); + + if (desc->inet.deliver == INET_DELIVER_PORT) { + code = inet_port_data(INETP(desc), body, bodylen); + } + else if ((code=packet_parse(desc->inet.htype, buf, len, + &desc->http_state, &packet_callbacks, + desc)) == 0) { + /* No body parsing, return raw binary */ + if (desc->inet.active == INET_PASSIVE) + return inet_async_data(INETP(desc), body, bodylen); + else + code = tcp_message(INETP(desc), body, bodylen); + } + + if (code < 0) + return code; + if (desc->inet.active == INET_ONCE) + desc->inet.active = INET_PASSIVE; + return code; +} + +static int +tcp_reply_binary_data(tcp_descriptor* desc, ErlDrvBinary* bin, int offs, int len) +{ + int code; + const char* buf = bin->orig_bytes + offs; + const char* body = buf; + int bodylen = len; + + packet_get_body(desc->inet.htype, &body, &bodylen); + offs = body - bin->orig_bytes; /* body offset now */ + + scanbit8(INETP(desc), body, bodylen); + + if (desc->inet.deliver == INET_DELIVER_PORT) + code = inet_port_binary_data(INETP(desc), bin, offs, bodylen); + else if ((code=packet_parse(desc->inet.htype, buf, len, &desc->http_state, + &packet_callbacks,desc)) == 0) { + /* No body parsing, return raw data */ + if (desc->inet.active == INET_PASSIVE) + return inet_async_binary_data(INETP(desc), 0, bin, offs, bodylen, NULL); + else + code = tcp_binary_message(INETP(desc), bin, offs, bodylen); + } + if (code < 0) + return code; + if (desc->inet.active == INET_ONCE) + desc->inet.active = INET_PASSIVE; + return code; +} + + +static int +packet_reply_binary_data(inet_descriptor* desc, unsigned int hsz, + ErlDrvBinary * bin, int offs, int len, + void * extra) +{ + int code; + + scanbit8(desc, bin->orig_bytes+offs, len); + + if (desc->active == INET_PASSIVE) + /* "inet" is actually for both UDP and SCTP, as well as TCP! */ + return inet_async_binary_data(desc, hsz, bin, offs, len, extra); + else + { /* INET_ACTIVE or INET_ONCE: */ + if (desc->deliver == INET_DELIVER_PORT) + code = inet_port_binary_data(desc, bin, offs, len); + else + code = packet_binary_message(desc, bin, offs, len, extra); + if (code < 0) + return code; + if (desc->active == INET_ONCE) + desc->active = INET_PASSIVE; + return code; + } +} + +/* ---------------------------------------------------------------------------- + + INET + +---------------------------------------------------------------------------- */ + +static int +sock_init(void) /* May be called multiple times. */ +{ +#ifdef __WIN32__ + WORD wVersionRequested; + WSADATA wsaData; + static int res = -1; /* res < 0 == initialization never attempted */ + + if (res >= 0) + return res; + + wVersionRequested = MAKEWORD(2,0); + if (WSAStartup(wVersionRequested, &wsaData) != 0) + goto error; + + if ((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 0)) + goto error; + + find_dynamic_functions(); + + return res = 1; + + error: + + WSACleanup(); + return res = 0; +#else + return 1; +#endif +} + +#ifdef HAVE_SCTP +static void inet_init_sctp(void) { + INIT_ATOM(sctp); + INIT_ATOM(sctp_error); + INIT_ATOM(true); + INIT_ATOM(false); + INIT_ATOM(buffer); + INIT_ATOM(mode); + INIT_ATOM(list); + INIT_ATOM(binary); + INIT_ATOM(active); + INIT_ATOM(once); + INIT_ATOM(buffer); + INIT_ATOM(linger); + INIT_ATOM(recbuf); + INIT_ATOM(sndbuf); + INIT_ATOM(reuseaddr); + INIT_ATOM(dontroute); + INIT_ATOM(priority); + INIT_ATOM(tos); + + /* Option names */ + INIT_ATOM(sctp_rtoinfo); + INIT_ATOM(sctp_associnfo); + INIT_ATOM(sctp_initmsg); + INIT_ATOM(sctp_autoclose); + INIT_ATOM(sctp_nodelay); + INIT_ATOM(sctp_disable_fragments); + INIT_ATOM(sctp_i_want_mapped_v4_addr); + INIT_ATOM(sctp_maxseg); + INIT_ATOM(sctp_set_peer_primary_addr); + INIT_ATOM(sctp_primary_addr); + INIT_ATOM(sctp_adaptation_layer); + INIT_ATOM(sctp_peer_addr_params); + INIT_ATOM(sctp_default_send_param); + INIT_ATOM(sctp_events); + INIT_ATOM(sctp_delayed_ack_time); + INIT_ATOM(sctp_status); + INIT_ATOM(sctp_get_peer_addr_info); + + /* Record names */ + INIT_ATOM(sctp_sndrcvinfo); + INIT_ATOM(sctp_assoc_change); + INIT_ATOM(sctp_paddr_change); + INIT_ATOM(sctp_remote_error); + INIT_ATOM(sctp_send_failed); + INIT_ATOM(sctp_shutdown_event); + INIT_ATOM(sctp_adaptation_event); + INIT_ATOM(sctp_pdapi_event); + INIT_ATOM(sctp_assocparams); + INIT_ATOM(sctp_prim); + INIT_ATOM(sctp_setpeerprim); + INIT_ATOM(sctp_setadaptation); + INIT_ATOM(sctp_paddrparams); + INIT_ATOM(sctp_event_subscribe); + INIT_ATOM(sctp_assoc_value); + INIT_ATOM(sctp_paddrinfo); + + /* For #sctp_sndrcvinfo{}: */ + INIT_ATOM(unordered); + INIT_ATOM(addr_over); + INIT_ATOM(abort); + INIT_ATOM(eof); + + /* For #sctp_assoc_change{}: */ + INIT_ATOM(comm_up); + INIT_ATOM(comm_lost); + INIT_ATOM(restart); + INIT_ATOM(shutdown_comp); + INIT_ATOM(cant_assoc); + + /* For #sctp_paddr_change{}: */ + INIT_ATOM(addr_available); + INIT_ATOM(addr_unreachable); + INIT_ATOM(addr_removed); + INIT_ATOM(addr_added); + INIT_ATOM(addr_made_prim); + INIT_ATOM(addr_confirmed); + + INIT_ATOM(short_recv); + INIT_ATOM(wrong_anc_data); + + /* For #sctp_pdap_event{}: */ + INIT_ATOM(partial_delivery_aborted); + + /* For #sctp_paddrparams{}: */ + INIT_ATOM(hb_enable); + INIT_ATOM(hb_disable); + INIT_ATOM(hb_demand); + INIT_ATOM(pmtud_enable); + INIT_ATOM(pmtud_disable); + INIT_ATOM(sackdelay_enable); + INIT_ATOM(sackdelay_disable); + + /* For #sctp_paddrinfo{}: */ + INIT_ATOM(active); + INIT_ATOM(inactive); + + /* For #sctp_status{}: */ + INIT_ATOM(empty); + INIT_ATOM(closed); + INIT_ATOM(cookie_wait); + INIT_ATOM(cookie_echoed); + INIT_ATOM(established); + INIT_ATOM(shutdown_pending); + INIT_ATOM(shutdown_sent); + INIT_ATOM(shutdown_received); + INIT_ATOM(shutdown_ack_sent); + /* Not yet implemented in the Linux kernel: + ** INIT_ATOM(bound); + ** INIT_ATOM(listen); + */ +} +#endif /* HAVE_SCTP */ + +static int inet_init() +{ + if (!sock_init()) + goto error; + + buffer_stack_pos = 0; + + erts_smp_spinlock_init(&inet_buffer_stack_lock, "inet_buffer_stack_lock"); + + ASSERT(sizeof(struct in_addr) == 4); +# if defined(HAVE_IN6) && defined(AF_INET6) + ASSERT(sizeof(struct in6_addr) == 16); +# endif + +#ifdef DEBUG + tot_buf_allocated = 0; + max_buf_allocated = 0; + tot_buf_stacked = 0; +#endif + INIT_ATOM(ok); + INIT_ATOM(tcp); + INIT_ATOM(udp); + INIT_ATOM(error); + INIT_ATOM(inet_async); + INIT_ATOM(inet_reply); + INIT_ATOM(timeout); + INIT_ATOM(closed); + INIT_ATOM(tcp_closed); + INIT_ATOM(tcp_error); + INIT_ATOM(udp_error); + INIT_ATOM(empty_out_q); + INIT_ATOM(ssl_tls); + + INIT_ATOM(http_eoh); + INIT_ATOM(http_header); + INIT_ATOM(http_request); + INIT_ATOM(http_response); + INIT_ATOM(http_error); + INIT_ATOM(abs_path); + INIT_ATOM(absoluteURI); + am_star = driver_mk_atom("*"); + INIT_ATOM(undefined); + INIT_ATOM(http); + INIT_ATOM(https); + INIT_ATOM(scheme); + + /* add TCP, UDP and SCTP drivers */ +#ifdef _OSE_ + add_ose_tcp_drv_entry(&tcp_inet_driver_entry); + add_ose_udp_drv_entry(&udp_inet_driver_entry); +#else + add_driver_entry(&tcp_inet_driver_entry); + add_driver_entry(&udp_inet_driver_entry); +# ifdef HAVE_SCTP + /* Check the size of SCTP AssocID -- currently both this driver and the + Erlang part require 32 bit: */ + ASSERT(sizeof(sctp_assoc_t)==ASSOC_ID_LEN); +# ifndef LIBSCTP +# error LIBSCTP not defined +# endif + if (erts_sys_ddll_open_noext(STRINGIFY(LIBSCTP), &h_libsctp, NULL) == 0) { + void *ptr; + if (erts_sys_ddll_sym(h_libsctp, "sctp_bindx", &ptr) == 0) { + p_sctp_bindx = ptr; + inet_init_sctp(); + add_driver_entry(&sctp_inet_driver_entry); + } + } +# endif +#endif /* _OSE_ */ + /* remove the dummy inet driver */ + remove_driver_entry(&inet_driver_entry); + return 0; + + error: + remove_driver_entry(&inet_driver_entry); + return -1; +} + + +/* +** Set a inaddr structure: +** src = [P1,P0,X1,X2,.....] +** dst points to a structure large enugh to keep any kind +** of inaddr. +** *len is set to length of src on call +** and is set to actual length of dst on return +** return NULL on error and ptr after port address on success +*/ +static char* inet_set_address(int family, inet_address* dst, char* src, int* len) +{ + short port; + + if ((family == AF_INET) && (*len >= 2+4)) { + sys_memzero((char*)dst, sizeof(struct sockaddr_in)); + port = get_int16(src); + dst->sai.sin_family = family; + dst->sai.sin_port = sock_htons(port); + sys_memcpy(&dst->sai.sin_addr, src+2, 4); + *len = sizeof(struct sockaddr_in); + return src + 2+4; + } +#if defined(HAVE_IN6) && defined(AF_INET6) + else if ((family == AF_INET6) && (*len >= 2+16)) { + sys_memzero((char*)dst, sizeof(struct sockaddr_in6)); + port = get_int16(src); + dst->sai6.sin6_family = family; + dst->sai6.sin6_port = sock_htons(port); + dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */ + sys_memcpy(&dst->sai6.sin6_addr, src+2, 16); + *len = sizeof(struct sockaddr_in6); + return src + 2+16; + } +#endif + return NULL; +} +#ifdef HAVE_SCTP +/* +** Set an inaddr structure, address family comes from source data, +** or from argument if source data specifies constant address. +** +** src = [TAG,P1,P0] when TAG = INET_AF_ANY | INET_AF_LOOPBACK +** src = [TAG,P1,P0,X1,X2,...] when TAG = INET_AF_INET | INET_AF_INET6 +*/ +static char *inet_set_faddress(int family, inet_address* dst, + char *src, int* len) { + int tag; + + if (*len < 1) return NULL; + (*len) --; + tag = *(src ++); + switch (tag) { + case INET_AF_INET: + family = AF_INET; + break; +# if defined(HAVE_IN6) && defined(AF_INET6) + case INET_AF_INET6: + family = AF_INET6; + break; +# endif + case INET_AF_ANY: + case INET_AF_LOOPBACK: { + int port; + + if (*len < 2) return NULL; + port = get_int16(src); + switch (family) { + case AF_INET: { + struct in_addr addr; + switch (tag) { + case INET_AF_ANY: + addr.s_addr = sock_htonl(INADDR_ANY); + break; + case INET_AF_LOOPBACK: + addr.s_addr = sock_htonl(INADDR_LOOPBACK); + break; + default: + return NULL; + } + sys_memzero((char*)dst, sizeof(struct sockaddr_in)); + dst->sai.sin_family = family; + dst->sai.sin_port = sock_htons(port); + dst->sai.sin_addr.s_addr = addr.s_addr; + *len = sizeof(struct sockaddr_in); + } break; +# if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: { + const struct in6_addr* paddr; + switch (tag) { + case INET_AF_ANY: + paddr = &in6addr_any; + break; + case INET_AF_LOOPBACK: + paddr = &in6addr_loopback; + break; + default: + return NULL; + } + sys_memzero((char*)dst, sizeof(struct sockaddr_in6)); + dst->sai6.sin6_family = family; + dst->sai6.sin6_port = sock_htons(port); + dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */ + dst->sai6.sin6_addr = *paddr; + *len = sizeof(struct sockaddr_in6); + } break; +# endif + default: + return NULL; + } + return src + 2; + } break; + default: + return NULL; + } + return inet_set_address(family, dst, src, len); +} +#endif /* HAVE_SCTP */ + +/* Get a inaddr structure +** src = inaddr structure +** *len is the lenght of structure +** dst is filled with [F,P1,P0,X1,....] +** where F is the family code (coded) +** and *len is the length of dst on return +** (suitable to deliver to erlang) +*/ +static int inet_get_address(int family, char* dst, inet_address* src, unsigned int* len) +{ + short port; + + if ((family == AF_INET) && (*len >= sizeof(struct sockaddr_in))) { + dst[0] = INET_AF_INET; + port = sock_ntohs(src->sai.sin_port); + put_int16(port, dst+1); + sys_memcpy(dst+3, (char*)&src->sai.sin_addr, sizeof(struct in_addr)); + *len = 3 + sizeof(struct in_addr); + return 0; + } +#if defined(HAVE_IN6) && defined(AF_INET6) + else if ((family == AF_INET6) && (*len >= sizeof(struct sockaddr_in6))) { + dst[0] = INET_AF_INET6; + port = sock_ntohs(src->sai6.sin6_port); + put_int16(port, dst+1); + sys_memcpy(dst+3, (char*)&src->sai6.sin6_addr,sizeof(struct in6_addr)); + *len = 3 + sizeof(struct in6_addr); + return 0; + } +#endif + return -1; +} + +static void desc_close(inet_descriptor* desc) +{ + if (desc->s != INVALID_SOCKET) { +#ifdef __WIN32__ + winsock_event_select(desc, FD_READ|FD_WRITE|FD_CLOSE, 0); + sock_close(desc->s); + desc->forced_events = 0; + desc->send_would_block = 0; +#endif + driver_select(desc->port, (ErlDrvEvent)(long)desc->event, ERL_DRV_USE, 0); + desc->event = INVALID_EVENT; /* closed by stop_select callback */ + desc->s = INVALID_SOCKET; + desc->event_mask = 0; + } +} + +static void desc_close_read(inet_descriptor* desc) +{ + if (desc->s != INVALID_SOCKET) { +#ifdef __WIN32__ + /* This call can not be right??? + * We want to turn off read events but keep any write events. + * But on windows driver_select(...,READ,1) is only used as a + * way to hook into the pollset. sock_select is used to control + * which events to wait for. + * It seems we used to disabled all events for the socket here. + * + driver_select(desc->port, desc->event, DO_READ, 0); REMOVED */ +#endif + sock_select(desc, FD_READ | FD_CLOSE, 0); + } +} + + +static int erl_inet_close(inet_descriptor* desc) +{ + free_subscribers(&desc->empty_out_q_subs); + if ((desc->prebound == 0) && (desc->state & INET_F_OPEN)) { + desc_close(desc); + desc->state = INET_STATE_CLOSED; + } else if (desc->prebound && (desc->s != INVALID_SOCKET)) { + sock_select(desc, FD_READ | FD_WRITE | FD_CLOSE, 0); + desc->event_mask = 0; +#ifdef __WIN32__ + desc->forced_events = 0; + desc->send_would_block = 0; +#endif + } + return 0; +} + + +static int inet_ctl_open(inet_descriptor* desc, int domain, int type, + char** rbuf, int rsize) +{ + if (desc->state != INET_STATE_CLOSED) + return ctl_xerror(EXBADSEQ, rbuf, rsize); + if ((desc->s = sock_open(domain, type, desc->sprotocol)) == INVALID_SOCKET) + return ctl_error(sock_errno(), rbuf, rsize); + if ((desc->event = sock_create_event(desc)) == INVALID_EVENT) + return ctl_error(sock_errno(), rbuf, rsize); + SET_NONBLOCKING(desc->s); +#ifdef __WIN32__ + driver_select(desc->port, desc->event, ERL_DRV_READ, 1); +#endif + desc->state = INET_STATE_OPEN; + desc->stype = type; + desc->sfamily = domain; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + + +/* as inet_open but pass in an open socket (MUST BE OF RIGHT TYPE) */ +static int inet_ctl_fdopen(inet_descriptor* desc, int domain, int type, + SOCKET s, char** rbuf, int rsize) +{ + inet_address name; + unsigned int sz = sizeof(name); + + /* check that it is a socket and that the socket is bound */ + if (sock_name(s, (struct sockaddr*) &name, &sz) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + desc->s = s; + if ((desc->event = sock_create_event(desc)) == INVALID_EVENT) + return ctl_error(sock_errno(), rbuf, rsize); + SET_NONBLOCKING(desc->s); +#ifdef __WIN32__ + driver_select(desc->port, desc->event, ERL_DRV_READ, 1); +#endif + desc->state = INET_STATE_BOUND; /* assume bound */ + if (type == SOCK_STREAM) { /* check if connected */ + sz = sizeof(name); + if (sock_peer(s, (struct sockaddr*) &name, &sz) != SOCKET_ERROR) + desc->state = INET_STATE_CONNECTED; + } + + desc->prebound = 1; /* used to prevent a real close since + * the fd probably comes from an + * external wrapper program, so it is + * not certain that we can open it again */ + desc->stype = type; + desc->sfamily = domain; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + +/* +** store interface info as: (bytes) +** [Len] Name(Len) Flags(1) addr(4) baddr(4) mask(4) bw(4) +*/ +struct addr_if { + char name[INET_IFNAMSIZ]; + long flags; /* coded flags */ + struct in_addr addr; /* interface address */ + struct in_addr baddr; /* broadcast address */ + struct in_addr mask; /* netmask */ +}; + + +#ifndef SIOCGIFNETMASK +static struct in_addr net_mask(in) +struct in_addr in; +{ + register u_long i = sock_ntohl(in.s_addr); + + if (IN_CLASSA(i)) + in.s_addr = sock_htonl(IN_CLASSA_NET); + else if (IN_CLASSB(i)) + in.s_addr = sock_htonl(IN_CLASSB_NET); + else + in.s_addr = sock_htonl(IN_CLASSC_NET); + return in; +} +#endif + +#if defined(__WIN32__) && defined(SIO_GET_INTERFACE_LIST) + +/* format address in dot notation */ +static char* fmt_addr(unsigned long x, char* ptr) +{ + int i; + for (i = 0; i < 4; i++) { + int nb[3]; + int y = (x >> 24) & 0xff; + x <<= 8; + nb[0] = y % 10; y /= 10; + nb[1] = y % 10; y /= 10; + nb[2] = y % 10; y /= 10; + switch((nb[2] ? 3 : (nb[1] ? 2 : 1))) { + case 3: *ptr++ = nb[2] + '0'; + case 2: *ptr++ = nb[1] + '0'; + case 1: *ptr++ = nb[0] + '0'; + } + *ptr++ = '.'; + } + *(ptr-1) = '\0'; + return ptr; +} + +static int parse_addr(char* ptr, int n, long* x) +{ + long addr = 0; + int dots = 0; + int digs = 0; + int v = 0; + + while(n--) { + switch(*ptr) { + case '0': case '1': case '2':case '3':case '4':case '5': + case '6': case '7': case '8':case '9': + v = v*10 + *ptr - '0'; + if (++digs > 3) return -1; + break; + case '.': + if ((dots>2) || (digs==0) || (digs > 3) || (v > 0xff)) return -1; + dots++; + digs = 0; + addr = (addr << 8) | v; + v = 0; + break; + default: + return -1; + } + ptr++; + } + if ((dots!=3) || (digs==0) || (digs > 3) || (v > 0xff)) return -1; + addr = (addr << 8) | v; + *x = addr; + return 0; +} + +#endif + +#define buf_check(ptr, end, n) \ +do { if ((end)-(ptr) < (n)) goto error; } while(0) + +static char* sockaddr_to_buf(struct sockaddr* addr, char* ptr, char* end) +{ + if (addr->sa_family == AF_INET || addr->sa_family == 0) { + struct in_addr a; + buf_check(ptr,end,sizeof(struct in_addr)); + a = ((struct sockaddr_in*) addr)->sin_addr; + sys_memcpy(ptr, (char*)&a, sizeof(struct in_addr)); + return ptr + sizeof(struct in_addr); + } +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (addr->sa_family == AF_INET6) { + struct in6_addr a; + buf_check(ptr,end,sizeof(struct in6_addr)); + a = ((struct sockaddr_in6*) addr)->sin6_addr; + sys_memcpy(ptr, (char*)&a, sizeof(struct in6_addr)); + return ptr + sizeof(struct in6_addr); + } +#endif + error: + return NULL; + +} + +static char* buf_to_sockaddr(char* ptr, char* end, struct sockaddr* addr) +{ + buf_check(ptr,end,sizeof(struct in_addr)); + sys_memcpy((char*) &((struct sockaddr_in*)addr)->sin_addr, ptr, + sizeof(struct in_addr)); + addr->sa_family = AF_INET; + return ptr + sizeof(struct in_addr); + + error: + return NULL; +} + + + +#if defined(__WIN32__) && defined(SIO_GET_INTERFACE_LIST) + +static int inet_ctl_getiflist(inet_descriptor* desc, char** rbuf, int rsize) +{ + char ifbuf[BUFSIZ]; + char sbuf[BUFSIZ]; + char* sptr; + INTERFACE_INFO* ifp; + DWORD len; + int n; + int err; + + ifp = (INTERFACE_INFO*) ifbuf; + len = 0; + err = WSAIoctl(desc->s, SIO_GET_INTERFACE_LIST, NULL, 0, + (LPVOID) ifp, BUFSIZ, (LPDWORD) &len, + NULL, NULL); + + if (err == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + + n = (len + sizeof(INTERFACE_INFO) - 1) / sizeof(INTERFACE_INFO); + sptr = sbuf; + + while(n--) { + if (((struct sockaddr*)&ifp->iiAddress)->sa_family == desc->sfamily) { + struct in_addr sina = ((struct sockaddr_in*)&ifp->iiAddress)->sin_addr; + /* discard INADDR_ANY interface address */ + if (sina.s_addr != INADDR_ANY) + sptr = fmt_addr(sock_ntohl(sina.s_addr), sptr); + } + ifp++; + } + return ctl_reply(INET_REP_OK, sbuf, sptr - sbuf, rbuf, rsize); +} + + +/* input is an ip-address in string format i.e A.B.C.D +** scan the INTERFACE_LIST to get the options +*/ +static int inet_ctl_ifget(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + char ifbuf[BUFSIZ]; + int n; + char sbuf[BUFSIZ]; + char* sptr; + char* s_end = sbuf + BUFSIZ; + int namlen; + int err; + INTERFACE_INFO* ifp; + long namaddr; + + if ((len == 0) || ((namlen = buf[0]) > len)) + goto error; + if (parse_addr(buf+1, namlen, &namaddr) < 0) + goto error; + namaddr = sock_ntohl(namaddr); + buf += (namlen+1); + len -= (namlen+1); + + ifp = (INTERFACE_INFO*) ifbuf; + err = WSAIoctl(desc->s, SIO_GET_INTERFACE_LIST, NULL, 0, + (LPVOID) ifp, BUFSIZ, (LPDWORD) &n, + NULL, NULL); + if (err == SOCKET_ERROR) { + return ctl_error(sock_errno(), rbuf, rsize); + } + + n = (n + sizeof(INTERFACE_INFO) - 1) / sizeof(INTERFACE_INFO); + + /* find interface */ + while(n) { + if (((struct sockaddr_in*)&ifp->iiAddress)->sin_addr.s_addr == namaddr) + break; + ifp++; + n--; + } + if (n == 0) + goto error; + + sptr = sbuf; + + while (len--) { + switch(*buf++) { + case INET_IFOPT_ADDR: + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_ADDR; + if ((sptr = sockaddr_to_buf((struct sockaddr *)&ifp->iiAddress, + sptr, s_end)) == NULL) + goto error; + break; + + case INET_IFOPT_HWADDR: + break; + + case INET_IFOPT_BROADADDR: +#ifdef SIOCGIFBRDADDR + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_BROADADDR; + if ((sptr=sockaddr_to_buf((struct sockaddr *) + &ifp->iiBroadcastAddress,sptr,s_end)) + == NULL) + goto error; +#endif + break; + + case INET_IFOPT_DSTADDR: + break; + + case INET_IFOPT_NETMASK: + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_NETMASK; + if ((sptr = sockaddr_to_buf((struct sockaddr *) + &ifp->iiNetmask,sptr,s_end)) == NULL) + goto error; + break; + + case INET_IFOPT_MTU: + break; + + case INET_IFOPT_FLAGS: { + long eflags = 0; + int flags = ifp->iiFlags; + /* just enumerate the interfaces (no names) */ + + /* translate flags */ + if (flags & IFF_UP) + eflags |= INET_IFF_UP; + if (flags & IFF_BROADCAST) + eflags |= INET_IFF_BROADCAST; + if (flags & IFF_LOOPBACK) + eflags |= INET_IFF_LOOPBACK; + if (flags & IFF_POINTTOPOINT) + eflags |= INET_IFF_POINTTOPOINT; + if (flags & IFF_UP) /* emulate runnign ? */ + eflags |= INET_IFF_RUNNING; + if (flags & IFF_MULTICAST) + eflags |= INET_IFF_MULTICAST; + + buf_check(sptr, s_end, 5); + *sptr++ = INET_IFOPT_FLAGS; + put_int32(eflags, sptr); + sptr += 4; + break; + } + default: + goto error; + } + } + return ctl_reply(INET_REP_OK, sbuf, sptr - sbuf, rbuf, rsize); + + error: + return ctl_error(EINVAL, rbuf, rsize); +} + +/* not supported */ +static int inet_ctl_ifset(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + + +#elif defined(SIOCGIFCONF) && defined(SIOCSIFFLAGS) +/* cygwin has SIOCGIFCONF but not SIOCSIFFLAGS (Nov 2002) */ + +#define VOIDP(x) ((void*)(x)) +#if defined(AF_LINK) && !defined(NO_SA_LEN) +#define SIZEA(p) (((p).sa_len > sizeof(p)) ? (p).sa_len : sizeof(p)) +#else +#define SIZEA(p) (sizeof (p)) +#endif + + +static int inet_ctl_getiflist(inet_descriptor* desc, char** rbuf, int rsize) +{ + struct ifconf ifc; + struct ifreq *ifr; + char *buf; + int buflen, ifc_len, i; + char *sbuf, *sp; + + /* Courtesy of Per Bergqvist and W. Richard Stevens */ + + ifc_len = 0; + buflen = 100 * sizeof(struct ifreq); + buf = ALLOC(buflen); + + for (;;) { + ifc.ifc_len = buflen; + ifc.ifc_buf = buf; + if (ioctl(desc->s, SIOCGIFCONF, (char *)&ifc) < 0) { + int res = sock_errno(); + if (res != EINVAL || ifc_len) { + FREE(buf); + return ctl_error(res, rbuf, rsize); + } + } else { + if (ifc.ifc_len == ifc_len) break; /* buf large enough */ + ifc_len = ifc.ifc_len; + } + buflen += 10 * sizeof(struct ifreq); + buf = (char *)REALLOC(buf, buflen); + } + + sp = sbuf = ALLOC(ifc_len+1); + *sp++ = INET_REP_OK; + i = 0; + for (;;) { + int n; + + ifr = (struct ifreq *) VOIDP(buf + i); + n = sizeof(ifr->ifr_name) + SIZEA(ifr->ifr_addr); + if (n < sizeof(*ifr)) n = sizeof(*ifr); + if (i+n > ifc_len) break; + i += n; + + switch (ifr->ifr_addr.sa_family) { +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: +#endif + case AF_INET: + ASSERT(sp+IFNAMSIZ+1 < sbuf+buflen+1) + strncpy(sp, ifr->ifr_name, IFNAMSIZ); + sp[IFNAMSIZ] = '\0'; + sp += strlen(sp), ++sp; + } + + if (i >= ifc_len) break; + } + FREE(buf); + *rbuf = sbuf; + return sp - sbuf; +} + + + +static int inet_ctl_ifget(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + char sbuf[BUFSIZ]; + char* sptr; + char* s_end = sbuf + BUFSIZ; + struct ifreq ifreq; + int namlen; + + if ((len == 0) || ((namlen = buf[0]) > len)) + goto error; + sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); + sys_memcpy(ifreq.ifr_name, buf+1, + (namlen > IFNAMSIZ) ? IFNAMSIZ : namlen); + buf += (namlen+1); + len -= (namlen+1); + sptr = sbuf; + + while (len--) { + switch(*buf++) { + case INET_IFOPT_ADDR: + if (ioctl(desc->s, SIOCGIFADDR, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_ADDR; + if ((sptr = sockaddr_to_buf(&ifreq.ifr_addr, sptr, s_end)) == NULL) + goto error; + break; + + case INET_IFOPT_HWADDR: { +#ifdef SIOCGIFHWADDR + if (ioctl(desc->s, SIOCGIFHWADDR, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 1+IFHWADDRLEN); + *sptr++ = INET_IFOPT_HWADDR; + /* raw memcpy (fix include autoconf later) */ + sys_memcpy(sptr, (char*)(&ifreq.ifr_hwaddr.sa_data), IFHWADDRLEN); + sptr += IFHWADDRLEN; +#endif + break; + } + + + case INET_IFOPT_BROADADDR: +#ifdef SIOCGIFBRDADDR + if (ioctl(desc->s, SIOCGIFBRDADDR, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_BROADADDR; + if ((sptr=sockaddr_to_buf(&ifreq.ifr_broadaddr,sptr,s_end)) == NULL) + goto error; +#endif + break; + + case INET_IFOPT_DSTADDR: +#ifdef SIOCGIFDSTADDR + if (ioctl(desc->s, SIOCGIFDSTADDR, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_DSTADDR; + if ((sptr = sockaddr_to_buf(&ifreq.ifr_dstaddr,sptr,s_end)) == NULL) + goto error; +#endif + break; + + case INET_IFOPT_NETMASK: +#if defined(SIOCGIFNETMASK) + if (ioctl(desc->s, SIOCGIFNETMASK, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_NETMASK; +#if defined(ifr_netmask) + sptr = sockaddr_to_buf(&ifreq.ifr_netmask,sptr,s_end); +#else + /* SIOCGNETMASK exist but not macro ??? */ + sptr = sockaddr_to_buf(&ifreq.ifr_addr,sptr,s_end); +#endif + if (sptr == NULL) + goto error; +#else + if (ioctl(desc->s, SIOCGIFADDR, (char *)&ifreq) < 0) + break; + else { + struct sockadd_in* ap; + /* emulate netmask, + * (wasted stuff since noone uses classes) + */ + buf_check(sptr, s_end, 1); + *sptr++ = INET_IFOPT_NETMASK; + ap = (struct sockaddr_in*) VOIDP(&ifreq.ifr_addr); + ap->sin_addr = net_mask(ap->sin_addr); + if ((sptr = sockaddr_to_buf(&ifreq.ifr_addr,sptr,s_end)) == NULL) + goto error; + } +#endif + break; + + case INET_IFOPT_MTU: { +#if defined(SIOCGIFMTU) && defined(ifr_mtu) + int n; + + if (ioctl(desc->s, SIOCGIFMTU, (char *)&ifreq) < 0) + break; + buf_check(sptr, s_end, 5); + *sptr++ = INET_IFOPT_MTU; + n = ifreq.ifr_mtu; + put_int32(n, sptr); + sptr += 4; +#endif + break; + } + + case INET_IFOPT_FLAGS: { + int flags; + int eflags = 0; + + if (ioctl(desc->s, SIOCGIFFLAGS, (char*)&ifreq) < 0) + flags = 0; + else + flags = ifreq.ifr_flags; + /* translate flags */ + if (flags & IFF_UP) + eflags |= INET_IFF_UP; + if (flags & IFF_BROADCAST) + eflags |= INET_IFF_BROADCAST; + if (flags & IFF_LOOPBACK) + eflags |= INET_IFF_LOOPBACK; + if (flags & IFF_POINTOPOINT) + eflags |= INET_IFF_POINTTOPOINT; + if (flags & IFF_RUNNING) + eflags |= INET_IFF_RUNNING; + if (flags & IFF_MULTICAST) + eflags |= INET_IFF_MULTICAST; + + buf_check(sptr, s_end, 5); + *sptr++ = INET_IFOPT_FLAGS; + put_int32(eflags, sptr); + sptr += 4; + break; + } + default: + goto error; + } + } + return ctl_reply(INET_REP_OK, sbuf, sptr - sbuf, rbuf, rsize); + + error: + return ctl_error(EINVAL, rbuf, rsize); +} + +/* FIXME: temporary hack */ +#ifndef IFHWADDRLEN +#define IFHWADDRLEN 6 +#endif + +static int inet_ctl_ifset(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + struct ifreq ifreq; + int namlen; + char* b_end = buf + len; + + if ((len == 0) || ((namlen = buf[0]) > len)) + goto error; + sys_memset(ifreq.ifr_name, '\0', IFNAMSIZ); + sys_memcpy(ifreq.ifr_name, buf+1, + (namlen > IFNAMSIZ) ? IFNAMSIZ : namlen); + buf += (namlen+1); + len -= (namlen+1); + + while(buf < b_end) { + switch(*buf++) { + case INET_IFOPT_ADDR: + if ((buf = buf_to_sockaddr(buf, b_end, &ifreq.ifr_addr)) == NULL) + goto error; + (void) ioctl(desc->s, SIOCSIFADDR, (char*)&ifreq); + break; + + case INET_IFOPT_HWADDR: + buf_check(buf, b_end, IFHWADDRLEN); +#ifdef SIOCSIFHWADDR + /* raw memcpy (fix include autoconf later) */ + sys_memcpy((char*)(&ifreq.ifr_hwaddr.sa_data), buf, IFHWADDRLEN); + + (void) ioctl(desc->s, SIOCSIFHWADDR, (char *)&ifreq); +#endif + buf += IFHWADDRLEN; + break; + + + case INET_IFOPT_BROADADDR: +#ifdef SIOCSIFBRDADDR + if ((buf = buf_to_sockaddr(buf, b_end, &ifreq.ifr_broadaddr)) == NULL) + goto error; + (void) ioctl(desc->s, SIOCSIFBRDADDR, (char *)&ifreq); +#endif + break; + + case INET_IFOPT_DSTADDR: +#ifdef SIOCSIFDSTADDR + if ((buf = buf_to_sockaddr(buf, b_end, &ifreq.ifr_dstaddr)) == NULL) + goto error; + (void) ioctl(desc->s, SIOCSIFDSTADDR, (char *)&ifreq); +#endif + break; + + case INET_IFOPT_NETMASK: +#ifdef SIOCSIFNETMASK + +#if defined(ifr_netmask) + buf = buf_to_sockaddr(buf,b_end, &ifreq.ifr_netmask); +#else + buf = buf_to_sockaddr(buf,b_end, &ifreq.ifr_addr); +#endif + if (buf == NULL) + goto error; + (void) ioctl(desc->s, SIOCSIFNETMASK, (char *)&ifreq); +#endif + break; + + case INET_IFOPT_MTU: + buf_check(buf, b_end, 4); +#if defined(SIOCSIFMTU) && defined(ifr_mtu) + ifreq.ifr_mtu = get_int32(buf); + (void) ioctl(desc->s, SIOCSIFMTU, (char *)&ifreq); +#endif + buf += 4; + break; + + case INET_IFOPT_FLAGS: { + int flags0; + int flags; + int eflags; + + buf_check(buf, b_end, 4); + eflags = get_int32(buf); + + /* read current flags */ + if (ioctl(desc->s, SIOCGIFFLAGS, (char*)&ifreq) < 0) + flags0 = flags = 0; + else + flags0 = flags = ifreq.ifr_flags; + + /* update flags */ + if (eflags & INET_IFF_UP) flags |= IFF_UP; + if (eflags & INET_IFF_DOWN) flags &= ~IFF_UP; + if (eflags & INET_IFF_BROADCAST) flags |= IFF_BROADCAST; + if (eflags & INET_IFF_NBROADCAST) flags &= ~IFF_BROADCAST; + if (eflags & INET_IFF_POINTTOPOINT) flags |= IFF_POINTOPOINT; + if (eflags & INET_IFF_NPOINTTOPOINT) flags &= ~IFF_POINTOPOINT; + + if (flags != flags0) { + ifreq.ifr_flags = flags; + (void) ioctl(desc->s, SIOCSIFFLAGS, (char*)&ifreq); + } + buf += 4; + break; + } + + default: + goto error; + } + } + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + + error: + return ctl_error(EINVAL, rbuf, rsize); +} + +#else + + +static int inet_ctl_getiflist(inet_descriptor* desc, char** rbuf, int rsize) +{ + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + + +static int inet_ctl_ifget(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + + +static int inet_ctl_ifset(inet_descriptor* desc, char* buf, int len, + char** rbuf, int rsize) +{ + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); +} + +#endif + +#ifdef VXWORKS +/* +** THIS is a terrible creature, a bug in the TCP part +** of the old VxWorks stack (non SENS) created a race. +** If (and only if?) a socket got closed from the other +** end and we tried a set/getsockopt on the TCP level, +** the task would generate a bus error... +*/ +static STATUS wrap_sockopt(STATUS (*function)() /* Yep, no parameter + check */, + int s, int level, int optname, + char *optval, unsigned int optlen + /* optlen is a pointer if function + is getsockopt... */) +{ + fd_set rs; + struct timeval timeout; + int to_read; + int ret; + + FD_ZERO(&rs); + FD_SET(s,&rs); + memset(&timeout,0,sizeof(timeout)); + if (level == IPPROTO_TCP) { + taskLock(); + if (select(s+1,&rs,NULL,NULL,&timeout)) { + if (ioctl(s,FIONREAD,(int)&to_read) == ERROR || + to_read == 0) { /* End of file, other end closed? */ + sock_errno() = EBADF; + taskUnlock(); + return ERROR; + } + } + ret = (*function)(s,level,optname,optval,optlen); + taskUnlock(); + } else { + ret = (*function)(s,level,optname,optval,optlen); + } + return ret; +} +#endif + +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) +static int setopt_prio_tos_trick + (int fd, int proto, int type, char* arg_ptr, int arg_sz) +{ + /* The relations between SO_PRIORITY, TOS and other options + is not what you (or at least I) would expect...: + If TOS is set after priority, priority is zeroed. + If any other option is set after tos, tos might be zeroed. + Therefore, save tos and priority. If something else is set, + restore both after setting, if tos is set, restore only + prio and if prio is set restore none... All to keep the + user feeling socket options are independent. /PaN */ + int tmp_ival_prio; + int tmp_ival_tos; + int res; +#ifdef HAVE_SOCKLEN_T + socklen_t +#else + int +#endif + tmp_arg_sz_prio = sizeof(tmp_ival_prio), + tmp_arg_sz_tos = sizeof(tmp_ival_tos); + + res = sock_getopt(fd, SOL_SOCKET, SO_PRIORITY, + (char *) &tmp_ival_prio, &tmp_arg_sz_prio); + if (res == 0) { + res = sock_getopt(fd, SOL_IP, IP_TOS, + (char *) &tmp_ival_tos, &tmp_arg_sz_tos); + if (res == 0) { + res = sock_setopt(fd, proto, type, arg_ptr, arg_sz); + if (res == 0) { + if (type != SO_PRIORITY) { + if (type != IP_TOS) { + res = sock_setopt(fd, + SOL_IP, + IP_TOS, + (char *) &tmp_ival_tos, + tmp_arg_sz_tos); + } + if (res == 0) { + res = sock_setopt(fd, + SOL_SOCKET, + SO_PRIORITY, + (char *) &tmp_ival_prio, + tmp_arg_sz_prio); + } + } + } + } + } + return (res); +} +#endif + +/* set socket options: +** return -1 on error +** 0 if ok +** 1 if ok force deliver of queued data +*/ +#ifdef HAVE_SCTP +static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len); +#endif + +static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) +{ + int type; + int proto; + int opt; + struct linger li_val; +#ifdef HAVE_MULTICAST_SUPPORT + struct ip_mreq mreq_val; +#endif + int ival; + char* arg_ptr; + int arg_sz; + enum PacketParseType old_htype = desc->htype; + int old_active = desc->active; + int propagate = 0; /* Set to 1 if failure to set this option + should be propagated to erlang (not all + errors can be propagated for BC reasons) */ + int res; +#ifdef HAVE_SCTP + /* SCTP sockets are treated completely separately: */ + if (IS_SCTP(desc)) + return sctp_set_opts(desc, ptr, len); +#endif + + while(len >= 5) { + opt = *ptr++; + ival = get_int32(ptr); + ptr += 4; + len -= 5; + arg_ptr = (char*) &ival; + arg_sz = sizeof(ival); + proto = SOL_SOCKET; + + switch(opt) { + case INET_LOPT_HEADER: + DEBUGF(("inet_set_opts(%ld): s=%d, HEADER=%d\r\n", + (long)desc->port, desc->s,ival)); + desc->hsz = ival; + continue; + + case INET_LOPT_MODE: + /* List or Binary: */ + DEBUGF(("inet_set_opts(%ld): s=%d, MODE=%d\r\n", + (long)desc->port, desc->s, ival)); + desc->mode = ival; + continue; + + case INET_LOPT_DELIVER: + DEBUGF(("inet_set_opts(%ld): s=%d, DELIVER=%d\r\n", + (long)desc->port, desc->s, ival)); + desc->deliver = ival; + continue; + + case INET_LOPT_BUFFER: + DEBUGF(("inet_set_opts(%ld): s=%d, BUFFER=%d\r\n", + (long)desc->port, desc->s, ival)); + if (ival > INET_MAX_BUFFER) ival = INET_MAX_BUFFER; + else if (ival < INET_MIN_BUFFER) ival = INET_MIN_BUFFER; + desc->bufsz = ival; + continue; + + case INET_LOPT_ACTIVE: + DEBUGF(("inet_set_opts(%ld): s=%d, ACTIVE=%d\r\n", + (long)desc->port, desc->s,ival)); + desc->active = ival; + if ((desc->stype == SOCK_STREAM) && (desc->active != INET_PASSIVE) && + (desc->state == INET_STATE_CLOSED)) { + tcp_closed_message((tcp_descriptor *) desc); + if (desc->exitf) { + driver_exit(desc->port, 0); + return 0; /* Give up on this socket, descriptor lost */ + } else { + desc_close_read(desc); + } + } + continue; + + case INET_LOPT_PACKET: + DEBUGF(("inet_set_opts(%ld): s=%d, PACKET=%d\r\n", + (long)desc->port, desc->s, ival)); + desc->htype = ival; + continue; + + case INET_LOPT_PACKET_SIZE: + DEBUGF(("inet_set_opts(%ld): s=%d, PACKET_SIZE=%d\r\n", + (long)desc->port, desc->s, ival)); + desc->psize = (unsigned int)ival; + continue; + + case INET_LOPT_EXITONCLOSE: + DEBUGF(("inet_set_opts(%ld): s=%d, EXITONCLOSE=%d\r\n", + (long)desc->port, desc->s, ival)); + desc->exitf = ival; + continue; + + case INET_LOPT_BIT8: + DEBUGF(("inet_set_opts(%ld): s=%d, BIT8=%d\r\n", + (long)desc->port, desc->s, ival)); + switch(ival) { + case INET_BIT8_ON: + desc->bit8f = 1; + desc->bit8 = 0; + break; + case INET_BIT8_OFF: + desc->bit8f = 0; + desc->bit8 = 0; + break; + case INET_BIT8_CLEAR: + desc->bit8f = 1; + desc->bit8 = 0; + break; + case INET_BIT8_SET: + desc->bit8f = 1; + desc->bit8 = 1; + break; + } + continue; + + case INET_LOPT_TCP_HIWTRMRK: + if (desc->stype == SOCK_STREAM) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (ival < 0) ival = 0; + else if (ival > INET_MAX_BUFFER*2) ival = INET_MAX_BUFFER*2; + if (tdesc->low > ival) + tdesc->low = ival; + tdesc->high = ival; + } + continue; + + case INET_LOPT_TCP_LOWTRMRK: + if (desc->stype == SOCK_STREAM) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (ival < 0) ival = 0; + else if (ival > INET_MAX_BUFFER) ival = INET_MAX_BUFFER; + if (tdesc->high < ival) + tdesc->high = ival; + tdesc->low = ival; + } + continue; + + case INET_LOPT_TCP_SEND_TIMEOUT: + if (desc->stype == SOCK_STREAM) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + tdesc->send_timeout = ival; + } + continue; + + case INET_LOPT_TCP_SEND_TIMEOUT_CLOSE: + if (desc->stype == SOCK_STREAM) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + tdesc->send_timeout_close = ival; + } + continue; + + + case INET_LOPT_TCP_DELAY_SEND: + if (desc->stype == SOCK_STREAM) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (ival) + tdesc->tcp_add_flags |= TCP_ADDF_DELAY_SEND; + else + tdesc->tcp_add_flags &= ~TCP_ADDF_DELAY_SEND; + } + continue; + + case INET_LOPT_UDP_READ_PACKETS: + if (desc->stype == SOCK_DGRAM) { + udp_descriptor* udesc = (udp_descriptor*) desc; + if (ival <= 0) return -1; + udesc->read_packets = ival; + } + continue; + + case INET_OPT_REUSEADDR: +#ifdef __WIN32__ + continue; /* Bjorn says */ +#else + type = SO_REUSEADDR; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_REUSEADDR=%d\r\n", + (long)desc->port, desc->s,ival)); + break; +#endif + case INET_OPT_KEEPALIVE: type = SO_KEEPALIVE; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_KEEPALIVE=%d\r\n", + (long)desc->port, desc->s, ival)); + break; + case INET_OPT_DONTROUTE: type = SO_DONTROUTE; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_DONTROUTE=%d\r\n", + (long)desc->port, desc->s, ival)); + break; + case INET_OPT_BROADCAST: type = SO_BROADCAST; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_BROADCAST=%d\r\n", + (long)desc->port, desc->s,ival)); + break; + case INET_OPT_OOBINLINE: type = SO_OOBINLINE; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_OOBINLINE=%d\r\n", + (long)desc->port, desc->s, ival)); + break; + case INET_OPT_SNDBUF: type = SO_SNDBUF; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_SNDBUF=%d\r\n", + (long)desc->port, desc->s, ival)); + /* + * Setting buffer sizes in VxWorks gives unexpected results + * our workaround is to leave it at default. + */ +#ifdef VXWORKS + goto skip_os_setopt; +#else + break; +#endif + case INET_OPT_RCVBUF: type = SO_RCVBUF; + DEBUGF(("inet_set_opts(%ld): s=%d, SO_RCVBUF=%d\r\n", + (long)desc->port, desc->s, ival)); +#ifdef VXWORKS + goto skip_os_setopt; +#else + break; +#endif + case INET_OPT_LINGER: type = SO_LINGER; + if (len < 4) + return -1; + li_val.l_onoff = ival; + li_val.l_linger = get_int32(ptr); + ptr += 4; + len -= 4; + arg_ptr = (char*) &li_val; + arg_sz = sizeof(li_val); + DEBUGF(("inet_set_opts(%ld): s=%d, SO_LINGER=%d,%d", + (long)desc->port, desc->s, li_val.l_onoff,li_val.l_linger)); + break; + + case INET_OPT_PRIORITY: +#ifdef SO_PRIORITY + type = SO_PRIORITY; + propagate = 1; /* We do want to know if this fails */ + DEBUGF(("inet_set_opts(%ld): s=%d, SO_PRIORITY=%d\r\n", + (long)desc->port, desc->s, ival)); + break; +#else + continue; +#endif + case INET_OPT_TOS: +#if defined(IP_TOS) && defined(SOL_IP) + proto = SOL_IP; + type = IP_TOS; + propagate = 1; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_TOS=%d\r\n", + (long)desc->port, desc->s, ival)); + break; +#else + continue; +#endif + + case TCP_OPT_NODELAY: + proto = IPPROTO_TCP; + type = TCP_NODELAY; + DEBUGF(("inet_set_opts(%ld): s=%d, TCP_NODELAY=%d\r\n", + (long)desc->port, desc->s, ival)); + break; + +#ifdef HAVE_MULTICAST_SUPPORT + + case UDP_OPT_MULTICAST_TTL: + proto = IPPROTO_IP; + type = IP_MULTICAST_TTL; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_TTL=%d\r\n", + (long)desc->port,desc->s,ival)); + break; + + case UDP_OPT_MULTICAST_LOOP: + proto = IPPROTO_IP; + type = IP_MULTICAST_LOOP; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_LOOP=%d\r\n", + (long)desc->port,desc->s,ival)); + break; + + case UDP_OPT_MULTICAST_IF: + proto = IPPROTO_IP; + type = IP_MULTICAST_IF; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_IF=%x\r\n", + (long)desc->port, desc->s, ival)); + ival = sock_htonl(ival); + break; + + case UDP_OPT_ADD_MEMBERSHIP: + proto = IPPROTO_IP; + type = IP_ADD_MEMBERSHIP; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_ADD_MEMBERSHIP=%d\r\n", + (long)desc->port, desc->s,ival)); + goto L_set_mreq; + + case UDP_OPT_DROP_MEMBERSHIP: + proto = IPPROTO_IP; + type = IP_DROP_MEMBERSHIP; + DEBUGF(("inet_set_opts(%ld): s=%d, IP_DROP_MEMBERSHIP=%x\r\n", + (long)desc->port, desc->s, ival)); + L_set_mreq: + mreq_val.imr_multiaddr.s_addr = sock_htonl(ival); + ival = get_int32(ptr); + mreq_val.imr_interface.s_addr = sock_htonl(ival); + ptr += 4; + len -= 4; + arg_ptr = (char*)&mreq_val; + arg_sz = sizeof(mreq_val); + break; + +#endif /* HAVE_MULTICAST_SUPPORT */ + + case INET_OPT_RAW: + if (len < 8) { + return -1; + } + proto = ival; + type = get_int32(ptr); + ptr += 4; + arg_sz = get_int32(ptr); + ptr += 4; + len -= 8; + if (len < arg_sz) { + return -1; + } + arg_ptr = ptr; + ptr += arg_sz; + len -= arg_sz; + break; + + default: + return -1; + } +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) + res = setopt_prio_tos_trick (desc->s, proto, type, arg_ptr, arg_sz); +#else + res = sock_setopt (desc->s, proto, type, arg_ptr, arg_sz); +#endif + if (propagate && res != 0) { + return -1; + } + DEBUGF(("inet_set_opts(%ld): s=%d returned %d\r\n", + (long)desc->port, desc->s, res)); +#ifdef VXWORKS +skip_os_setopt: +#endif + if (type == SO_RCVBUF) { + /* make sure we have desc->bufsz >= SO_RCVBUF */ + if (ival > desc->bufsz) + desc->bufsz = ival; + } + } + + if ( ((desc->stype == SOCK_STREAM) && IS_CONNECTED(desc)) || + ((desc->stype == SOCK_DGRAM) && IS_OPEN(desc))) { + + if (desc->active != old_active) + sock_select(desc, (FD_READ|FD_CLOSE), (desc->active>0)); + + if ((desc->stype==SOCK_STREAM) && desc->active) { + if (!old_active || (desc->htype != old_htype)) { + /* passive => active change OR header type change in active mode */ + return 1; + } + return 0; + } + } + return 0; +} + +#ifdef HAVE_SCTP + +/* "sctp_get_initmsg": +** Used by both "send*" and "setsockopt". Gets the 4 fields of "sctp_initmsg" +** from the input buffer: +*/ +#define SCTP_GET_INITMSG_LEN (4*2) +static char* sctp_get_initmsg(struct sctp_initmsg* ini, char* curr) +{ + ini->sinit_num_ostreams = get_int16 (curr); curr += 2; + ini->sinit_max_instreams = get_int16 (curr); curr += 2; + ini->sinit_max_attempts = get_int16 (curr); curr += 2; + ini->sinit_max_init_timeo = get_int16 (curr); curr += 2; + return curr; +} + +/* "sctp_get_sendparams": +** Parses (from the command buffer) the 6 user-sprcified parms of +** "sctp_sndrcvinfo": +** stream(u16), flags(u16), ppid(u32), context(u32), +** timetoleave(u32), assoc_id +** Is used by both "send*" and "setsockopt": +*/ +#define SCTP_GET_SENDPARAMS_LEN (2*2 + 3*4 + ASSOC_ID_LEN) +static char* sctp_get_sendparams (struct sctp_sndrcvinfo* sri, char* curr) +{ + int eflags; + int cflags; + + sri->sinfo_stream = get_int16(curr); curr += 2; + sri->sinfo_ssn = 0; + + /* The "flags" are already ORed at the Erlang side, here we + reconstruct the real SCTP flags: + */ + eflags = get_int16(curr); curr += 2; + cflags = 0; + if (eflags & SCTP_FLAG_UNORDERED) cflags |= SCTP_UNORDERED; + if (eflags & SCTP_FLAG_ADDR_OVER) cflags |= SCTP_ADDR_OVER; + if (eflags & SCTP_FLAG_ABORT) cflags |= SCTP_ABORT; + if (eflags & SCTP_FLAG_EOF) cflags |= SCTP_EOF; + + sri->sinfo_flags = cflags; + sri->sinfo_ppid = sock_htonl(get_int32(curr)); + curr += 4; + sri->sinfo_context = get_int32(curr); curr += 4; + sri->sinfo_timetolive = get_int32(curr); curr += 4; + sri->sinfo_tsn = 0; + sri->sinfo_cumtsn = 0; + sri->sinfo_assoc_id = GET_ASSOC_ID (curr); curr += ASSOC_ID_LEN; + + return curr; +} + +/* Set SCTP options: +** return -1 on error +** 0 if ok +** NB: unlike inet_set_opts(), we don't have an active mode here, so there is no +** mode change which could force data delivery on setting an option. +** Arg: "ptr": [(erlang_encoded_opt(u8), value(...)), ...]; thus, multiple opts +** can be set at a time. +*/ +static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len) +{ +# define CHKLEN(Ptr, Len) \ + do { \ + if ((Ptr) + (Len) > ptr + len) return -1; \ + } while (0) + + char * curr = ptr; + int proto, type, res; + + /* The following union is used to hold any arg to "setsockopt": */ + union opts_union + { + int ival; + struct sctp_rtoinfo rtoi; + struct sctp_assocparams ap; + struct sctp_initmsg im; + struct linger lin; + struct sctp_setpeerprim prim; + struct sctp_setadaptation ad; + struct sctp_paddrparams pap; + struct sctp_sndrcvinfo sri; + struct sctp_event_subscribe es; +# ifdef SCTP_DELAYED_ACK_TIME + struct sctp_assoc_value av; /* Not in SOLARIS10 */ +# endif + } + arg; + + char * arg_ptr = NULL; + int arg_sz = 0; + int old_active = desc->active; + + while (curr < ptr + len) + { + /* Get the Erlang-encoded option type -- always 1 byte: */ + int eopt = *curr; + curr++; + + /* Get the option value. XXX: The condition (curr < ptr + len) + does not preclude us from reading from beyond the buffer end, + if the Erlang part of the driver specifies its input wrongly! + */ + CHKLEN(curr, 4); /* All options need at least 4 bytes */ + switch(eopt) + { + /* Local INET options: */ + + case INET_LOPT_BUFFER: + desc->bufsz = get_int32(curr); curr += 4; + + if (desc->bufsz > INET_MAX_BUFFER) + desc->bufsz = INET_MAX_BUFFER; + else + if (desc->bufsz < INET_MIN_BUFFER) + desc->bufsz = INET_MIN_BUFFER; + res = 0; /* This does not affect the kernel buffer size */ + continue; + + case INET_LOPT_MODE: + desc->mode = get_int32(curr); curr += 4; + res = 0; + continue; + + case INET_LOPT_ACTIVE: + desc->active = get_int32(curr); curr += 4; + res = 0; + continue; + + /* SCTP options and applicable generic INET options: */ + + case SCTP_OPT_RTOINFO: + { + CHKLEN(curr, ASSOC_ID_LEN + 3*4); + arg.rtoi.srto_assoc_id = GET_ASSOC_ID(curr); curr += ASSOC_ID_LEN; + arg.rtoi.srto_initial = get_int32 (curr); curr += 4; + arg.rtoi.srto_max = get_int32 (curr); curr += 4; + arg.rtoi.srto_min = get_int32 (curr); curr += 4; + + proto = IPPROTO_SCTP; + type = SCTP_RTOINFO; + arg_ptr = (char*) (&arg.rtoi); + arg_sz = sizeof ( arg.rtoi); + break; + } + case SCTP_OPT_ASSOCINFO: + { + CHKLEN(curr, ASSOC_ID_LEN + 2*2 + 3*4); + + arg.ap.sasoc_assoc_id = GET_ASSOC_ID(curr); curr += ASSOC_ID_LEN; + arg.ap.sasoc_asocmaxrxt = get_int16 (curr); curr += 2; + arg.ap.sasoc_number_peer_destinations = + get_int16 (curr); curr += 2; + arg.ap.sasoc_peer_rwnd = get_int32 (curr); curr += 4; + arg.ap.sasoc_local_rwnd = get_int32 (curr); curr += 4; + arg.ap.sasoc_cookie_life = get_int32 (curr); curr += 4; + + proto = IPPROTO_SCTP; + type = SCTP_ASSOCINFO; + arg_ptr = (char*) (&arg.ap); + arg_sz = sizeof ( arg.ap); + break; + } + case SCTP_OPT_INITMSG: + { + CHKLEN(curr, SCTP_GET_INITMSG_LEN); + curr = sctp_get_initmsg (&arg.im, curr); + + proto = IPPROTO_SCTP; + type = SCTP_INITMSG; + arg_ptr = (char*) (&arg.im); + arg_sz = sizeof ( arg.im); + break; + } + case INET_OPT_LINGER: + { + CHKLEN(curr, ASSOC_ID_LEN + 2 + 4); + arg.lin.l_onoff = get_int16 (curr); curr += 2; + arg.lin.l_linger = get_int32 (curr); curr += 4; + + proto = SOL_SOCKET; + type = SO_LINGER; + arg_ptr = (char*) (&arg.lin); + arg_sz = sizeof ( arg.lin); + break; + } + case SCTP_OPT_NODELAY: + { + arg.ival= get_int32 (curr); curr += 4; + proto = IPPROTO_SCTP; + type = SCTP_NODELAY; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case INET_OPT_RCVBUF: + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_SOCKET; + type = SO_RCVBUF; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + + /* Adjust the size of the user-level recv buffer, so it's not + smaller than the kernel one: */ + if (desc->bufsz <= arg.ival) + desc->bufsz = arg.ival; + break; + } + case INET_OPT_SNDBUF: + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_SOCKET; + type = SO_SNDBUF; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + + /* Adjust the size of the user-level recv buffer, so it's not + smaller than the kernel one: */ + if (desc->bufsz <= arg.ival) + desc->bufsz = arg.ival; + break; + } + case INET_OPT_REUSEADDR: + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_SOCKET; + type = SO_REUSEADDR; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case INET_OPT_DONTROUTE: + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_SOCKET; + type = SO_DONTROUTE; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case INET_OPT_PRIORITY: +# ifdef SO_PRIORITY + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_SOCKET; + type = SO_PRIORITY; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } +# else + continue; /* Option not supported -- ignore it */ +# endif + + case INET_OPT_TOS: +# if defined(IP_TOS) && defined(SOL_IP) + { + arg.ival= get_int32 (curr); curr += 4; + proto = SOL_IP; + type = IP_TOS; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } +# else + continue; /* Option not supported -- ignore it */ +# endif + + case SCTP_OPT_AUTOCLOSE: + { + arg.ival= get_int32 (curr); curr += 4; + proto = IPPROTO_SCTP; + type = SCTP_AUTOCLOSE; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case SCTP_OPT_DISABLE_FRAGMENTS: + { + arg.ival= get_int32 (curr); curr += 4; + proto = IPPROTO_SCTP; + type = SCTP_DISABLE_FRAGMENTS; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case SCTP_OPT_I_WANT_MAPPED_V4_ADDR: + { + arg.ival= get_int32 (curr); curr += 4; + proto = IPPROTO_SCTP; + type = SCTP_I_WANT_MAPPED_V4_ADDR; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case SCTP_OPT_MAXSEG: + { + arg.ival= get_int32 (curr); curr += 4; + proto = IPPROTO_SCTP; + type = SCTP_MAXSEG; + arg_ptr = (char*) (&arg.ival); + arg_sz = sizeof ( arg.ival); + break; + } + case SCTP_OPT_PRIMARY_ADDR: + case SCTP_OPT_SET_PEER_PRIMARY_ADDR: + { + int alen; + char *after; + + CHKLEN(curr, ASSOC_ID_LEN); + /* XXX: These 2 opts have isomorphic value data structures, + "sctp_setpeerprim" and "sctp_prim" (in Solaris 10, the latter + is called "sctp_setprim"), so we grouped them together: + */ + arg.prim.sspp_assoc_id = GET_ASSOC_ID(curr); curr += ASSOC_ID_LEN; + + /* Fill in "arg.prim.sspp_addr": */ + alen = ptr + len - curr; + after = inet_set_faddress(desc->sfamily, + (inet_address*) (&arg.prim.sspp_addr), + curr, &alen); + if (after == NULL) + return -1; + curr = after; + + proto = IPPROTO_SCTP; + if (eopt == SCTP_OPT_PRIMARY_ADDR) + type = SCTP_PRIMARY_ADDR; + else + type = SCTP_SET_PEER_PRIMARY_ADDR; + + arg_ptr = (char*) (&arg.prim); + arg_sz = sizeof ( arg.prim); + break; + } + case SCTP_OPT_ADAPTATION_LAYER: + { + /* XXX: do we need to convert the Ind into network byte order??? */ + arg.ad.ssb_adaptation_ind = sock_htonl (get_int32(curr)); curr += 4; + + proto = IPPROTO_SCTP; + type = SCTP_ADAPTATION_LAYER; + arg_ptr = (char*) (&arg.ad); + arg_sz = sizeof ( arg.ad); + break; + } + case SCTP_OPT_PEER_ADDR_PARAMS: + { + int alen; + char *after; +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS + int eflags, cflags, hb_enable, hb_disable, + pmtud_enable, pmtud_disable, + sackdelay_enable, sackdelay_disable; +# endif + + CHKLEN(curr, ASSOC_ID_LEN); + arg.pap.spp_assoc_id = GET_ASSOC_ID(curr); curr += ASSOC_ID_LEN; + + /* Fill in "pap.spp_address": */ + alen = ptr + len - curr; + after = inet_set_faddress(desc->sfamily, + (inet_address*) (&arg.pap.spp_address), + curr, &alen); + if (after == NULL) + return -1; + curr = after; + + CHKLEN(curr, 4 + 2 + 3*4); + + arg.pap.spp_hbinterval = get_int32(curr); curr += 4; + arg.pap.spp_pathmaxrxt = get_int16(curr); curr += 2; + + /* The following are missing in Solaris 10: */ +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU + arg.pap.spp_pathmtu = get_int32(curr); +# endif + curr += 4; +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY + arg.pap.spp_sackdelay = get_int32(curr); +# endif + curr += 4; + +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS + /* Now re-construct the flags: */ + eflags = get_int32(curr); + cflags = 0; + + hb_enable = eflags & SCTP_FLAG_HB_ENABLE; + hb_disable = eflags & SCTP_FLAG_HB_DISABLE; + if (hb_enable && hb_disable) + return -1; + if (hb_enable) cflags |= SPP_HB_ENABLE; + if (hb_disable) cflags |= SPP_HB_DISABLE; + if (eflags & SCTP_FLAG_HB_DEMAND) cflags |= SPP_HB_DEMAND; + + pmtud_enable = eflags & SCTP_FLAG_PMTUD_ENABLE; + pmtud_disable = eflags & SCTP_FLAG_PMTUD_DISABLE; + if (pmtud_enable && pmtud_disable) + return -1; + if (pmtud_enable) cflags |= SPP_PMTUD_ENABLE; + if (pmtud_disable) cflags |= SPP_PMTUD_DISABLE; + + sackdelay_enable =eflags& SCTP_FLAG_SACDELAY_ENABLE; + sackdelay_disable=eflags& SCTP_FLAG_SACDELAY_DISABLE; + if (sackdelay_enable && sackdelay_disable) + return -1; + if (sackdelay_enable) cflags |= SPP_SACKDELAY_ENABLE; + if (sackdelay_disable) cflags |= SPP_SACKDELAY_DISABLE; + + arg.pap.spp_flags = cflags; +# endif + curr += 4; + + proto = IPPROTO_SCTP; + type = SCTP_PEER_ADDR_PARAMS; + arg_ptr = (char*) (&arg.pap); + arg_sz = sizeof ( arg.pap); + break; + } + case SCTP_OPT_DEFAULT_SEND_PARAM: + { + CHKLEN(curr, SCTP_GET_SENDPARAMS_LEN); + curr = sctp_get_sendparams (&arg.sri, curr); + + proto = IPPROTO_SCTP; + type = SCTP_DEFAULT_SEND_PARAM; + arg_ptr = (char*) (&arg.sri); + arg_sz = sizeof ( arg.sri); + break; + } + case SCTP_OPT_EVENTS: + { + CHKLEN(curr, 9); + /* We do not support "sctp_authentication_event" -- it is not + implemented in Linux Kernel SCTP anyway. Just in case if + the above structure has more fields than we support, zero + it out -- the extraneous events will NOT be used: + */ + memset (&arg.es, 0, sizeof(arg.es)); + + /* The input "buf" must contain the full definition of all the + supported event fields, 1 byte per each, as each event is + either explicitly subscribed or cleared: + */ + arg.es.sctp_data_io_event = get_int8(curr); curr++; + arg.es.sctp_association_event = get_int8(curr); curr++; + arg.es.sctp_address_event = get_int8(curr); curr++; + arg.es.sctp_send_failure_event = get_int8(curr); curr++; + arg.es.sctp_peer_error_event = get_int8(curr); curr++; + arg.es.sctp_shutdown_event = get_int8(curr); curr++; + arg.es.sctp_partial_delivery_event = get_int8(curr); curr++; + arg.es.sctp_adaptation_layer_event = get_int8(curr); curr++; + /* sctp_authentication_event not implemented */ curr++; + + proto = IPPROTO_SCTP; + type = SCTP_EVENTS; + arg_ptr = (char*) (&arg.es); + arg_sz = sizeof ( arg.es); + break; + } + /* The following is not available on Solaris 10: */ +# ifdef SCTP_DELAYED_ACK_TIME + case SCTP_OPT_DELAYED_ACK_TIME: + { + CHKLEN(curr, ASSOC_ID_LEN + 4); + arg.av.assoc_id = GET_ASSOC_ID(curr); curr += ASSOC_ID_LEN; + arg.av.assoc_value = get_int32(curr); curr += 4; + + proto = IPPROTO_SCTP; + type = SCTP_DELAYED_ACK_TIME; + arg_ptr = (char*) (&arg.av); + arg_sz = sizeof ( arg.es); + break; + } +# endif + default: + /* XXX: No more supported SCTP options. In particular, authentica- + tion options (SCTP_AUTH_CHUNK, SCTP_AUTH_KEY, SCTP_PEER_AUTH_ + CHUNKS, SCTP_LOCAL_AUTH_CHUNKS, SCTP_AUTH_SETKEY_ACTIVE) are + not yet implemented in the Linux kernel, hence not supported + here. Also not supported are SCTP_HMAC_IDENT, as well as any + "generic" options except "INET_LOPT_MODE". Raise an error: + */ + return -1; + } +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) + res = setopt_prio_tos_trick (desc->s, proto, type, arg_ptr, arg_sz); +#else + res = sock_setopt (desc->s, proto, type, arg_ptr, arg_sz); +#endif + /* The return values of "sock_setopt" can only be 0 or -1: */ + ASSERT(res == 0 || res == -1); + if (res == -1) + { /* Got an error, DO NOT continue with other options. However, on + Solaris 10, we DO allow SO_SNDBUF and SO_RCVBUF to fail, assu- + min that the default kernel versions are good enough: + */ +# ifdef SOLARIS10 + if (type != SO_SNDBUF && type != SO_RCVBUF) +# endif + return res; + } + } + /* If we got here, all "sock_setopt"s above were successful: */ + if (IS_OPEN(desc) && desc->active != old_active) { + sock_select(desc, (FD_READ|FD_CLOSE), (desc->active > 0)); + } + return 0; +# undef CHKLEN +} +#endif /* HAVE_SCTP */ + +/* load all option values into the buf and reply +** return total length of reply filled into ptr +** ptr should point to a buffer with 9*len +1 to be safe!! +*/ + +static int inet_fill_opts(inet_descriptor* desc, + char* buf, int len, char** dest, int destlen) +{ + int type; + int proto; + int opt; + struct linger li_val; + int ival; + char* arg_ptr; + unsigned int arg_sz; + char *ptr = NULL; + int dest_used = 0; + int dest_allocated = destlen; + char *orig_dest = *dest; + + /* Ptr is a name parameter */ +#define RETURN_ERROR() \ + do { \ + if (dest_allocated > destlen) { \ + FREE(*dest); \ + *dest = orig_dest; \ + } \ + return -1; \ + } while(0) + +#define PLACE_FOR(Size,Ptr) \ + do { \ + int need = dest_used + (Size); \ + if (need > INET_MAX_BUFFER) { \ + RETURN_ERROR(); \ + } \ + if (need > dest_allocated) { \ + char *new_buffer; \ + if (dest_allocated == destlen) { \ + new_buffer = ALLOC((dest_allocated = need + 10)); \ + memcpy(new_buffer,*dest,dest_used); \ + } else { \ + new_buffer = REALLOC(*dest, (dest_allocated = need + 10)); \ + } \ + *dest = new_buffer; \ + } \ + (Ptr) = (*dest) + dest_used; \ + dest_used = need; \ + } while (0) + + /* Ptr is a name parameter */ +#define TRUNCATE_TO(Size,Ptr) \ + do { \ + int new_need = ((Ptr) - (*dest)) + (Size); \ + if (new_need > dest_used) { \ + erl_exit(1,"Internal error in inet_drv, " \ + "miscalculated buffer size"); \ + } \ + dest_used = new_need; \ + } while(0) + + + PLACE_FOR(1,ptr); + *ptr = INET_REP_OK; + + while(len--) { + opt = *buf++; + proto = SOL_SOCKET; + ival = 0; /* Windows Vista needs this (only writes part of it) */ + arg_sz = sizeof(ival); + arg_ptr = (char*) &ival; + + PLACE_FOR(5,ptr); + + switch(opt) { + case INET_LOPT_BUFFER: + *ptr++ = opt; + put_int32(desc->bufsz, ptr); + continue; + case INET_LOPT_HEADER: + *ptr++ = opt; + put_int32(desc->hsz, ptr); + continue; + case INET_LOPT_MODE: + *ptr++ = opt; + put_int32(desc->mode, ptr); + continue; + case INET_LOPT_DELIVER: + *ptr++ = opt; + put_int32(desc->deliver, ptr); + continue; + case INET_LOPT_ACTIVE: + *ptr++ = opt; + put_int32(desc->active, ptr); + continue; + case INET_LOPT_PACKET: + *ptr++ = opt; + put_int32(desc->htype, ptr); + continue; + case INET_LOPT_PACKET_SIZE: + *ptr++ = opt; + put_int32(desc->psize, ptr); + continue; + case INET_LOPT_EXITONCLOSE: + *ptr++ = opt; + put_int32(desc->exitf, ptr); + continue; + + case INET_LOPT_BIT8: + *ptr++ = opt; + if (desc->bit8f) { + put_int32(desc->bit8, ptr); + } else { + put_int32(INET_BIT8_OFF, ptr); + } + continue; + + case INET_LOPT_TCP_HIWTRMRK: + if (desc->stype == SOCK_STREAM) { + *ptr++ = opt; + ival = ((tcp_descriptor*)desc)->high; + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_LOPT_TCP_LOWTRMRK: + if (desc->stype == SOCK_STREAM) { + *ptr++ = opt; + ival = ((tcp_descriptor*)desc)->low; + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_LOPT_TCP_SEND_TIMEOUT: + if (desc->stype == SOCK_STREAM) { + *ptr++ = opt; + ival = ((tcp_descriptor*)desc)->send_timeout; + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_LOPT_TCP_SEND_TIMEOUT_CLOSE: + if (desc->stype == SOCK_STREAM) { + *ptr++ = opt; + ival = ((tcp_descriptor*)desc)->send_timeout_close; + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_LOPT_TCP_DELAY_SEND: + if (desc->stype == SOCK_STREAM) { + *ptr++ = opt; + ival = !!(((tcp_descriptor*)desc)->tcp_add_flags & TCP_ADDF_DELAY_SEND); + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_LOPT_UDP_READ_PACKETS: + if (desc->stype == SOCK_DGRAM) { + *ptr++ = opt; + ival = ((udp_descriptor*)desc)->read_packets; + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + + case INET_OPT_PRIORITY: +#ifdef SO_PRIORITY + type = SO_PRIORITY; + break; +#else + *ptr++ = opt; + put_int32(0, ptr); + continue; +#endif + case INET_OPT_TOS: +#if defined(IP_TOS) && defined(SOL_IP) + proto = SOL_IP; + type = IP_TOS; + break; +#else + *ptr++ = opt; + put_int32(0, ptr); + continue; +#endif + case INET_OPT_REUSEADDR: + type = SO_REUSEADDR; + break; + case INET_OPT_KEEPALIVE: + type = SO_KEEPALIVE; + break; + case INET_OPT_DONTROUTE: + type = SO_DONTROUTE; + break; + case INET_OPT_BROADCAST: + type = SO_BROADCAST; + break; + case INET_OPT_OOBINLINE: + type = SO_OOBINLINE; + break; + case INET_OPT_SNDBUF: + type = SO_SNDBUF; + break; + case INET_OPT_RCVBUF: + type = SO_RCVBUF; + break; + case TCP_OPT_NODELAY: + proto = IPPROTO_TCP; + type = TCP_NODELAY; + break; + +#ifdef HAVE_MULTICAST_SUPPORT + case UDP_OPT_MULTICAST_TTL: + proto = IPPROTO_IP; + type = IP_MULTICAST_TTL; + break; + case UDP_OPT_MULTICAST_LOOP: + proto = IPPROTO_IP; + type = IP_MULTICAST_LOOP; + break; + case UDP_OPT_MULTICAST_IF: + proto = IPPROTO_IP; + type = IP_MULTICAST_IF; + break; + case INET_OPT_LINGER: + arg_sz = sizeof(li_val); + sys_memzero((void *) &li_val, sizeof(li_val)); + arg_ptr = (char*) &li_val; + type = SO_LINGER; + break; +#endif /* HAVE_MULTICAST_SUPPORT */ + + case INET_OPT_RAW: + { + int data_provided; + /* Raw options are icky, handle directly... */ + if (len < 13) { + RETURN_ERROR(); + } + len -= 13; + proto = get_int32(buf); + buf += 4; + type = get_int32(buf); + buf += 4; + data_provided = (int) *buf++; + arg_sz = get_int32(buf); + if (arg_sz > INET_MAX_BUFFER) { + RETURN_ERROR(); + } + buf += 4; + TRUNCATE_TO(0,ptr); + PLACE_FOR(13 + arg_sz,ptr); + arg_ptr = ptr + 13; + if (data_provided) { + if (len < arg_sz) { + RETURN_ERROR(); + } + memcpy(arg_ptr,buf,arg_sz); + buf += arg_sz; + len -= arg_sz; + } + if (sock_getopt(desc->s,proto,type,arg_ptr,&arg_sz) == + SOCKET_ERROR) { + TRUNCATE_TO(0,ptr); + continue; + } + TRUNCATE_TO(arg_sz + 13,ptr); + *ptr++ = opt; + put_int32(proto,ptr); + ptr += 4; + put_int32(type,ptr); + ptr += 4; + put_int32(arg_sz,ptr); + continue; + } + default: + RETURN_ERROR(); + } + /* We have 5 bytes allocated to ptr */ + if (sock_getopt(desc->s,proto,type,arg_ptr,&arg_sz) == SOCKET_ERROR) { + TRUNCATE_TO(0,ptr); + continue; + } + *ptr++ = opt; + if (arg_ptr == (char*)&ival) { + put_int32(ival, ptr); + } + else { + put_int32(((Uint32) li_val.l_onoff), ptr); + PLACE_FOR(4,ptr); + put_int32(((Uint32) li_val.l_linger), ptr); + } + } + return (dest_used); +#undef PLACE_FOR +#undef TRUNCATE_TO +#undef RETURN_ERROR +} + +#ifdef HAVE_SCTP +#define LOAD_PADDRINFO_CNT \ + (2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + LOAD_IP_AND_PORT_CNT + \ + 4*LOAD_INT_CNT + LOAD_TUPLE_CNT) +static int load_paddrinfo (ErlDrvTermData * spec, int i, + inet_descriptor* desc, struct sctp_paddrinfo* pai) +{ + i = LOAD_ATOM (spec, i, am_sctp_paddrinfo); + i = LOAD_ASSOC_ID (spec, i, pai->spinfo_assoc_id); + i = load_ip_and_port(spec, i, desc, &pai->spinfo_address); + switch(pai->spinfo_state) + { + case SCTP_ACTIVE: + i = LOAD_ATOM (spec, i, am_active); + break; + case SCTP_INACTIVE: + i = LOAD_ATOM (spec, i, am_inactive); + break; + default: + ASSERT(0); /* NB: SCTP_UNCONFIRMED modifier not yet supported */ + } + i = LOAD_INT (spec, i, pai->spinfo_cwnd); + i = LOAD_INT (spec, i, pai->spinfo_srtt); + i = LOAD_INT (spec, i, pai->spinfo_rto ); + i = LOAD_INT (spec, i, pai->spinfo_mtu ); + /* Close up the record: */ + i = LOAD_TUPLE (spec, i, 8); + return i; +} + +/* +** "sctp_fill_opts": Returns {ok, Results}, or an error: +*/ +static int sctp_fill_opts(inet_descriptor* desc, char* buf, int buflen, + char** dest, int destlen) +{ + /* In contrast to the generic "inet_fill_opts", the output here is + represented by tuples/records, which are formed in the "spec": + */ + ErlDrvTermData *spec; + int i = 0; + int length = 0; /* Number of result list entries */ + + int spec_allocated = PACKET_ERL_DRV_TERM_DATA_LEN; + spec = ALLOC(sizeof(* spec) * spec_allocated); + +# define RETURN_ERROR(Spec, Errno) \ + do { \ + FREE(Spec); \ + return (Errno); \ + } while(0) + + /* Spec is a name parmeter */ +# define PLACE_FOR(Spec, Index, N) \ + do { \ + int need; \ + if ((Index) > spec_allocated) { \ + erl_exit(1,"Internal error in inet_drv, " \ + "miscalculated buffer size"); \ + } \ + need = (Index) + (N); \ + if (need > INET_MAX_BUFFER/sizeof(ErlDrvTermData)) { \ + RETURN_ERROR((Spec), -ENOMEM); \ + } \ + if (need > spec_allocated) { \ + (Spec) = REALLOC((Spec), \ + sizeof(* (Spec)) \ + * (spec_allocated = need + 20)); \ + } \ + } while (0) + + PLACE_FOR(spec, i, 2*LOAD_ATOM_CNT + LOAD_PORT_CNT); + i = LOAD_ATOM (spec, i, am_inet_reply); + i = LOAD_PORT (spec, i, desc->dport); + i = LOAD_ATOM (spec, i, am_ok); + + while (buflen > 0) { + int eopt = *buf; /* "eopt" is 1-byte encoded */ + buf ++; buflen --; + + switch(eopt) + { + /* Local options allowed for SCTP. For TCP and UDP, the values of + these options are returned via "res" using integer encoding, + but here, we encode them as proper terms the same way as we do + it for all other SCTP options: + */ + case INET_LOPT_BUFFER: + { + PLACE_FOR(spec, i, LOAD_ATOM_CNT + LOAD_INT_CNT + LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_buffer); + i = LOAD_INT (spec, i, desc->bufsz); + i = LOAD_TUPLE(spec, i, 2); + break; + } + case INET_LOPT_MODE: + { + PLACE_FOR(spec, i, 2*LOAD_ATOM_CNT + LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_mode); + switch (desc->mode) + { + case INET_MODE_LIST : + { i = LOAD_ATOM (spec, i, am_list); break; } + + case INET_MODE_BINARY: + { i = LOAD_ATOM (spec, i, am_binary); break; } + + default: ASSERT (0); + } + i = LOAD_TUPLE (spec, i, 2); + break; + } + case INET_LOPT_ACTIVE: + { + PLACE_FOR(spec, i, 2*LOAD_ATOM_CNT + LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_active); + switch (desc->active) + { + case INET_ACTIVE : + { i = LOAD_ATOM (spec, i, am_true); break; } + + case INET_PASSIVE: + { i = LOAD_ATOM (spec, i, am_false); break; } + + case INET_ONCE : + { i = LOAD_ATOM (spec, i, am_once); break; } + + default: ASSERT (0); + } + i = LOAD_TUPLE (spec, i, 2); + break; + } + + /* SCTP and generic INET options: */ + + case SCTP_OPT_RTOINFO: + { + struct sctp_rtoinfo rti; + unsigned int sz = sizeof(rti); + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + rti.srto_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_RTOINFO, + &rti, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + 3*LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_rtoinfo); + i = LOAD_ATOM (spec, i, am_sctp_rtoinfo); + i = LOAD_ASSOC_ID (spec, i, rti.srto_assoc_id); + i = LOAD_INT (spec, i, rti.srto_initial); + i = LOAD_INT (spec, i, rti.srto_max); + i = LOAD_INT (spec, i, rti.srto_min); + i = LOAD_TUPLE (spec, i, 5); + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_ASSOCINFO: + { + struct sctp_assocparams ap; + unsigned int sz = sizeof(ap); + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + ap.sasoc_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_ASSOCINFO, + &ap, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + 5*LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_associnfo); + i = LOAD_ATOM (spec, i, am_sctp_assocparams); + i = LOAD_ASSOC_ID (spec, i, ap.sasoc_assoc_id); + i = LOAD_INT (spec, i, ap.sasoc_asocmaxrxt); + i = LOAD_INT (spec, i, ap.sasoc_number_peer_destinations); + i = LOAD_INT (spec, i, ap.sasoc_peer_rwnd); + i = LOAD_INT (spec, i, ap.sasoc_local_rwnd); + i = LOAD_INT (spec, i, ap.sasoc_cookie_life); + i = LOAD_TUPLE (spec, i, 7); + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_INITMSG: + { + struct sctp_initmsg im; + unsigned int sz = sizeof(im); + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_INITMSG, + &im, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + + 4*LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_initmsg); + i = LOAD_ATOM (spec, i, am_sctp_initmsg); + i = LOAD_INT (spec, i, im.sinit_num_ostreams); + i = LOAD_INT (spec, i, im.sinit_max_instreams); + i = LOAD_INT (spec, i, im.sinit_max_attempts); + i = LOAD_INT (spec, i, im.sinit_max_init_timeo); + i = LOAD_TUPLE (spec, i, 5); + i = LOAD_TUPLE (spec, i, 2); + break; + } + /* The following option returns a tuple {bool, int}: */ + case INET_OPT_LINGER: + { + struct linger lg; + unsigned int sz = sizeof(lg); + + if (sock_getopt(desc->s, IPPROTO_SCTP, SO_LINGER, + &lg, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + LOAD_ATOM_CNT + LOAD_BOOL_CNT + + LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_linger); + i = LOAD_BOOL (spec, i, lg.l_onoff); + i = LOAD_INT (spec, i, lg.l_linger); + i = LOAD_TUPLE (spec, i, 2); + i = LOAD_TUPLE (spec, i, 2); + break; + } + /* The following options just return an integer value: */ + case INET_OPT_RCVBUF : + case INET_OPT_SNDBUF : + case INET_OPT_REUSEADDR: + case INET_OPT_DONTROUTE: + case INET_OPT_PRIORITY : + case INET_OPT_TOS : + case SCTP_OPT_AUTOCLOSE: + case SCTP_OPT_MAXSEG : + /* The following options return true or false: */ + case SCTP_OPT_NODELAY : + case SCTP_OPT_DISABLE_FRAGMENTS: + case SCTP_OPT_I_WANT_MAPPED_V4_ADDR: + { + int res = 0; + unsigned int sz = sizeof(res); + int proto = 0, type = 0, is_int = 0; + ErlDrvTermData tag = am_sctp_error; + + switch(eopt) + { + case INET_OPT_RCVBUF : + { + proto = IPPROTO_SCTP; + type = SO_RCVBUF; + is_int = 1; + tag = am_recbuf; + break; + } + case INET_OPT_SNDBUF : + { + proto = IPPROTO_SCTP; + type = SO_SNDBUF; + is_int = 1; + tag = am_sndbuf; + break; + } + case INET_OPT_REUSEADDR: + { + proto = SOL_SOCKET; + type = SO_REUSEADDR; + is_int = 0; + tag = am_reuseaddr; + break; + } + case INET_OPT_DONTROUTE: + { + proto = SOL_SOCKET; + type = SO_DONTROUTE; + is_int = 0; + tag = am_dontroute; + break; + } + case INET_OPT_PRIORITY: + { +# if defined(SO_PRIORITY) + proto = SOL_SOCKET; + type = SO_PRIORITY; + is_int = 1; + tag = am_priority; + break; +# else + /* Not supported -- ignore */ + continue; +# endif + } + case INET_OPT_TOS: + { +# if defined(IP_TOS) && defined(SOL_IP) + proto = SOL_IP; + type = IP_TOS; + is_int = 1; + tag = am_tos; + break; +# else + /* Not supported -- ignore */ + continue; +# endif + } + case SCTP_OPT_AUTOCLOSE: + { + proto = IPPROTO_SCTP; + type = SCTP_AUTOCLOSE; + is_int = 1; + tag = am_sctp_autoclose; + break; + } + case SCTP_OPT_MAXSEG : + { + proto = IPPROTO_SCTP; + type = SCTP_MAXSEG; + is_int = 1; + tag = am_sctp_maxseg; + break; + } + case SCTP_OPT_NODELAY : + { + proto = IPPROTO_SCTP; + type = SCTP_NODELAY; + is_int = 0; + tag = am_sctp_nodelay; + break; + } + case SCTP_OPT_DISABLE_FRAGMENTS: + { + proto = IPPROTO_SCTP; + type = SCTP_DISABLE_FRAGMENTS; + is_int = 0; + tag = am_sctp_disable_fragments; + break; + } + case SCTP_OPT_I_WANT_MAPPED_V4_ADDR: + { + proto = IPPROTO_SCTP; + type = SCTP_I_WANT_MAPPED_V4_ADDR; + is_int = 0; + tag = am_sctp_i_want_mapped_v4_addr; + break; + } + default: ASSERT(0); + } + if (sock_getopt (desc->s, proto, type, &res, &sz) < 0) continue; + /* Form the result: */ + PLACE_FOR(spec, i, LOAD_ATOM_CNT + + (is_int ? LOAD_INT_CNT : LOAD_BOOL_CNT) + + LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, tag); + if (is_int) + i = LOAD_INT (spec, i, res); + else + i = LOAD_BOOL (spec, i, res); + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_PRIMARY_ADDR: + case SCTP_OPT_SET_PEER_PRIMARY_ADDR: + { + /* These 2 options use completely isomorphic data structures: */ + struct sctp_setpeerprim sp; + unsigned int sz = sizeof(sp); + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + sp.sspp_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + + if (sock_getopt(desc->s, IPPROTO_SCTP, + (eopt == SCTP_OPT_PRIMARY_ADDR) ? + SCTP_PRIMARY_ADDR : SCTP_SET_PEER_PRIMARY_ADDR, + &sp, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + LOAD_IP_AND_PORT_CNT + 2*LOAD_TUPLE_CNT); + switch (eopt) { + case SCTP_OPT_PRIMARY_ADDR: + i = LOAD_ATOM(spec, i, am_sctp_primary_addr); + i = LOAD_ATOM(spec, i, am_sctp_prim); + break; + case SCTP_OPT_SET_PEER_PRIMARY_ADDR: + i = LOAD_ATOM(spec, i, am_sctp_set_peer_primary_addr); + i = LOAD_ATOM(spec, i, am_sctp_setpeerprim); + break; + default: + ASSERT(0); + } + i = LOAD_ASSOC_ID (spec, i, sp.sspp_assoc_id); + i = load_ip_and_port(spec, i, desc, &sp.sspp_addr); + i = LOAD_TUPLE (spec, i, 3); + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_ADAPTATION_LAYER: + { + struct sctp_setadaptation ad; + unsigned int sz = sizeof (ad); + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_ADAPTATION_LAYER, + &ad, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_adaptation_layer); + i = LOAD_ATOM (spec, i, am_sctp_setadaptation); + i = LOAD_INT (spec, i, ad.ssb_adaptation_ind); + i = LOAD_TUPLE (spec, i, 2); + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_PEER_ADDR_PARAMS: + { + struct sctp_paddrparams ap; + unsigned int sz = sizeof(ap); + int n; + char *after; + int alen; + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + ap.spp_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + alen = buflen; + after = inet_set_faddress(desc->sfamily, + (inet_address*) (&ap.spp_address), + buf, &alen); + if (after == NULL) RETURN_ERROR(spec, -EINVAL); + buflen -= after - buf; + buf = after; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, + &ap, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + LOAD_IP_AND_PORT_CNT + 4*LOAD_INT_CNT); + i = LOAD_ATOM (spec, i, am_sctp_peer_addr_params); + i = LOAD_ATOM (spec, i, am_sctp_paddrparams); + i = LOAD_ASSOC_ID (spec, i, ap.spp_assoc_id); + i = load_ip_and_port(spec, i, desc, &ap.spp_address); + i = LOAD_INT (spec, i, ap.spp_hbinterval); + i = LOAD_INT (spec, i, ap.spp_pathmaxrxt); + + /* The following fields are not suported in SOLARIS10, + ** so put 0s for "spp_pathmtu", "spp_sackdelay", + ** and empty list for "spp_flags": + */ + +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU + i = LOAD_INT (spec, i, ap.spp_pathmtu); +# else + i = LOAD_INT (spec, i, 0); +# endif + +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY + i = LOAD_INT (spec, i, ap.spp_sackdelay); +# else + i = LOAD_INT (spec, i, 0); +# endif + + n = 0; +# ifdef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS + PLACE_FOR(spec, i, 7*LOAD_ATOM_CNT); + /* Now Flags, as a list: */ + if (ap.spp_flags & SPP_HB_ENABLE) + { i = LOAD_ATOM (spec, i, am_hb_enable); n++; } + + if (ap.spp_flags & SPP_HB_DISABLE) + { i = LOAD_ATOM (spec, i, am_hb_disable); n++; } + + if (ap.spp_flags & SPP_HB_DEMAND) + { i = LOAD_ATOM (spec, i, am_hb_demand); n++; } + + if (ap.spp_flags & SPP_PMTUD_ENABLE) + { i = LOAD_ATOM (spec, i, am_pmtud_enable); n++; } + + if (ap.spp_flags & SPP_PMTUD_DISABLE) + { i = LOAD_ATOM (spec, i, am_pmtud_disable); n++; } + + if (ap.spp_flags & SPP_SACKDELAY_ENABLE) + { i = LOAD_ATOM (spec, i, am_sackdelay_enable); n++; } + + if (ap.spp_flags & SPP_SACKDELAY_DISABLE) + { i = LOAD_ATOM (spec, i, am_sackdelay_disable); n++; } +# endif + + PLACE_FOR(spec, i, + LOAD_NIL_CNT + LOAD_LIST_CNT + 2*LOAD_TUPLE_CNT); + + /* Close up the Flags list: */ + i = LOAD_NIL (spec, i); + i = LOAD_LIST (spec, i, n+1); + + /* Close up the record: */ + i = LOAD_TUPLE (spec, i, 8); + /* Close up the result tuple: */ + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_DEFAULT_SEND_PARAM: + { + struct sctp_sndrcvinfo sri; + unsigned int sz = sizeof(sri); + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_DEFAULT_SEND_PARAM, + &sri, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, LOAD_ATOM_CNT + + SCTP_PARSE_SNDRCVINFO_CNT + LOAD_TUPLE_CNT); + i = LOAD_ATOM(spec, i, am_sctp_default_send_param); + i = sctp_parse_sndrcvinfo(spec, i, &sri); + i = LOAD_TUPLE(spec, i, 2); + break; + } + case SCTP_OPT_EVENTS: + { + struct sctp_event_subscribe evs; + unsigned int sz = sizeof(evs); + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_EVENTS, + &evs, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + 2*LOAD_ATOM_CNT + 9*LOAD_BOOL_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_events); + i = LOAD_ATOM (spec, i, am_sctp_event_subscribe); + i = LOAD_BOOL (spec, i, evs.sctp_data_io_event); + i = LOAD_BOOL (spec, i, evs.sctp_association_event); + i = LOAD_BOOL (spec, i, evs.sctp_address_event); + i = LOAD_BOOL (spec, i, evs.sctp_send_failure_event); + i = LOAD_BOOL (spec, i, evs.sctp_peer_error_event); + i = LOAD_BOOL (spec, i, evs.sctp_shutdown_event); + i = LOAD_BOOL (spec, i, evs.sctp_partial_delivery_event); + i = LOAD_BOOL (spec, i, evs.sctp_adaptation_layer_event); + i = LOAD_BOOL (spec, i, 0);/* NB: sctp_authentication_event + * is not yet supported in Linux + */ + i = LOAD_TUPLE (spec, i, 10); + i = LOAD_TUPLE (spec, i, 2); + break; + } + /* The following option is not available in Solaris 10: */ +# ifdef SCTP_DELAYED_ACK_TIME + case SCTP_OPT_DELAYED_ACK_TIME: + { + struct sctp_assoc_value av; + unsigned int sz = sizeof(av); + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + av.assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_DELAYED_ACK_TIME, + &av, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, 2*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + LOAD_INT_CNT + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_delayed_ack_time); + i = LOAD_ATOM (spec, i, am_sctp_assoc_value); + i = LOAD_ASSOC_ID (spec, i, av.assoc_id); + i = LOAD_INT (spec, i, av.assoc_value); + i = LOAD_TUPLE (spec, i, 3); + i = LOAD_TUPLE (spec, i, 2); + break; + } +# endif + case SCTP_OPT_STATUS: + { + struct sctp_status st; + unsigned int sz = sizeof(st); + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + st.sstat_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_STATUS, + &st, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, 3*LOAD_ATOM_CNT + LOAD_ASSOC_ID_CNT + + 6*LOAD_INT_CNT + LOAD_PADDRINFO_CNT + + 2*LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_status); + i = LOAD_ATOM (spec, i, am_sctp_status); + i = LOAD_ASSOC_ID (spec, i, st.sstat_assoc_id); + switch(st.sstat_state) + { + /* SCTP_EMPTY is not supported on SOLARIS10: */ +# ifdef SCTP_EMPTY + case SCTP_EMPTY: + i = LOAD_ATOM (spec, i, am_empty); + break; +# endif + case SCTP_CLOSED: + i = LOAD_ATOM (spec, i, am_closed); + break; + /* The following states are not supported by Linux Kernel SCTP yet: + case SCTP_BOUND: + i = LOAD_ATOM (spec, i, am_bound); + break; + case SCTP_LISTEN: + i = LOAD_ATOM (spec, i, am_listen); + break; + */ + case SCTP_COOKIE_WAIT: + i = LOAD_ATOM (spec, i, am_cookie_wait); + break; + case SCTP_COOKIE_ECHOED: + i = LOAD_ATOM (spec, i, am_cookie_echoed); + break; + case SCTP_ESTABLISHED: + i = LOAD_ATOM (spec, i, am_established); + break; + case SCTP_SHUTDOWN_PENDING: + i = LOAD_ATOM (spec, i, am_shutdown_pending); + break; + case SCTP_SHUTDOWN_SENT: + i = LOAD_ATOM (spec, i, am_shutdown_sent); + break; + case SCTP_SHUTDOWN_RECEIVED: + i = LOAD_ATOM (spec, i, am_shutdown_received); + break; + case SCTP_SHUTDOWN_ACK_SENT: + i = LOAD_ATOM (spec, i, am_shutdown_ack_sent); + break; + default: + i = LOAD_ATOM (spec, i, am_undefined); + break; + } + i = LOAD_INT (spec, i, st.sstat_rwnd); + i = LOAD_INT (spec, i, st.sstat_unackdata); + i = LOAD_INT (spec, i, st.sstat_penddata); + i = LOAD_INT (spec, i, st.sstat_instrms); + i = LOAD_INT (spec, i, st.sstat_outstrms); + i = LOAD_INT (spec, i, st.sstat_fragmentation_point); + i = load_paddrinfo (spec, i, desc, &st.sstat_primary); + /* Close up the record: */ + i = LOAD_TUPLE (spec, i, 10); + /* Close up the result tuple: */ + i = LOAD_TUPLE (spec, i, 2); + break; + } + case SCTP_OPT_GET_PEER_ADDR_INFO: + { + struct sctp_paddrinfo pai; + unsigned int sz = sizeof(pai); + char *after; + int alen; + + if (buflen < ASSOC_ID_LEN) RETURN_ERROR(spec, -EINVAL); + pai.spinfo_assoc_id = GET_ASSOC_ID(buf); + buf += ASSOC_ID_LEN; + buflen -= ASSOC_ID_LEN; + alen = buflen; + after = inet_set_faddress(desc->sfamily, + (inet_address*) (&pai.spinfo_address), + buf, &alen); + if (after == NULL) RETURN_ERROR(spec, -EINVAL); + buflen -= after - buf; + buf = after; + + if (sock_getopt(desc->s, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, + &pai, &sz) < 0) continue; + /* Fill in the response: */ + PLACE_FOR(spec, i, + LOAD_ATOM_CNT + LOAD_PADDRINFO_CNT + LOAD_TUPLE_CNT); + i = LOAD_ATOM (spec, i, am_sctp_get_peer_addr_info); + i = load_paddrinfo (spec, i, desc, &pai); + i = LOAD_TUPLE (spec, i, 2); + break; + } + default: + RETURN_ERROR(spec, -EINVAL); /* No more valid options */ + } + /* If we get here one result has been succesfully loaded */ + length ++; + } + if (buflen != 0) RETURN_ERROR(spec, -EINVAL); /* Optparam mismatch */ + + PLACE_FOR(spec, i, LOAD_NIL_CNT + LOAD_LIST_CNT + 2*LOAD_TUPLE_CNT); + + /* If we get here, we have "length" options: */ + i = LOAD_NIL (spec, i); + i = LOAD_LIST (spec, i, length+1); + + /* Close up the {ok, List} response: */ + i = LOAD_TUPLE(spec, i, 2); + /* Close up the {inet_reply, S, {ok, List}} response: */ + i = LOAD_TUPLE(spec, i, 3); + + /* Now, convert "spec" into the returnable term: */ + /* desc->caller = 0; What does it mean? */ + driver_output_term(desc->port, spec, i); + FREE(spec); + + (*dest)[0] = INET_REP_SCTP; + return 1; /* Response length */ +# undef PLACE_FOR +# undef RETURN_ERROR +} +#endif + +/* fill statistics reply, op codes from src and result in dest +** dst area must be a least 5*len + 1 bytes +*/ +static int inet_fill_stat(inet_descriptor* desc, char* src, int len, char* dst) +{ + unsigned long val; + int op; + char* dst_start = dst; + + *dst++ = INET_REP_OK; /* put reply code */ + while (len--) { + op = *src++; + *dst++ = op; /* copy op code */ + switch(op) { + case INET_STAT_RECV_CNT: + val = desc->recv_cnt; + break; + case INET_STAT_RECV_MAX: + val = (unsigned long) desc->recv_max; + break; + case INET_STAT_RECV_AVG: + val = (unsigned long) desc->recv_avg; + break; + case INET_STAT_RECV_DVI: + val = (unsigned long) fabs(desc->recv_dvi); + break; + case INET_STAT_SEND_CNT: + val = desc->send_cnt; + break; + case INET_STAT_SEND_MAX: + val = desc->send_max; + break; + case INET_STAT_SEND_AVG: + val = (unsigned long) desc->send_avg; + break; + case INET_STAT_SEND_PND: + val = driver_sizeq(desc->port); + break; + case INET_STAT_RECV_OCT: + put_int32(desc->recv_oct[1], dst); /* write high 32bit */ + put_int32(desc->recv_oct[0], dst+4); /* write low 32bit */ + dst += 8; + continue; + case INET_STAT_SEND_OCT: + put_int32(desc->send_oct[1], dst); /* write high 32bit */ + put_int32(desc->send_oct[0], dst+4); /* write low 32bit */ + dst += 8; + continue; + default: return -1; /* invalid argument */ + } + put_int32(val, dst); /* write 32bit value */ + dst += 4; + } + return dst - dst_start; /* actual length */ +} + +static void +send_empty_out_q_msgs(inet_descriptor* desc) +{ + ErlDrvTermData msg[6]; + int msg_len = 0; + + if(NO_SUBSCRIBERS(&desc->empty_out_q_subs)) + return; + + msg_len = LOAD_ATOM(msg, msg_len, am_empty_out_q); + msg_len = LOAD_PORT(msg, msg_len, desc->dport); + msg_len = LOAD_TUPLE(msg, msg_len, 2); + + ASSERT(msg_len == sizeof(msg)/sizeof(*msg)); + + send_to_subscribers(desc->port, + &desc->empty_out_q_subs, + 1, + msg, + msg_len); +} + +/* subscribe and fill subscription reply, op codes from src and +** result in dest dst area must be a least 5*len + 1 bytes +*/ +static int inet_subscribe(inet_descriptor* desc, char* src, int len, char* dst) +{ + unsigned long val; + int op; + char* dst_start = dst; + + *dst++ = INET_REP_OK; /* put reply code */ + while (len--) { + op = *src++; + *dst++ = op; /* copy op code */ + switch(op) { + case INET_SUBS_EMPTY_OUT_Q: + val = driver_sizeq(desc->port); + if(val > 0) + if(!save_subscriber(&desc->empty_out_q_subs, + driver_caller(desc->port))) + return 0; + break; + default: return -1; /* invalid argument */ + } + put_int32(val, dst); /* write 32bit value */ + dst += 4; + } + return dst - dst_start; /* actual length */ +} + +/* Terminate socket */ +static void inet_stop(inet_descriptor* desc) +{ + erl_inet_close(desc); + FREE(desc); +} + + +/* Allocate descriptor */ +static ErlDrvData inet_start(ErlDrvPort port, int size, int protocol) +{ + inet_descriptor* desc; + + if ((desc = (inet_descriptor*) ALLOC(size)) == NULL) + return NULL; + + desc->s = INVALID_SOCKET; + desc->event = INVALID_EVENT; + desc->event_mask = 0; +#ifdef __WIN32__ + desc->forced_events = 0; + desc->send_would_block = 0; +#endif + desc->port = port; + desc->dport = driver_mk_port(port); + desc->state = INET_STATE_CLOSED; + desc->prebound = 0; + desc->bufsz = INET_DEF_BUFFER; + desc->hsz = 0; /* list header size */ + desc->htype = TCP_PB_RAW; /* default packet type */ + desc->psize = 0; /* no size check */ + desc->stype = -1; /* bad stype */ + desc->sfamily = -1; + desc->sprotocol = protocol; + desc->mode = INET_MODE_LIST; /* list mode */ + desc->exitf = 1; /* exit port when close on active + socket */ + desc->bit8f = 0; + desc->bit8 = 0; + desc->deliver = INET_DELIVER_TERM; /* standard term format */ + desc->active = INET_PASSIVE; /* start passive */ + desc->oph = NULL; + desc->opt = NULL; + + desc->peer_ptr = NULL; + desc->name_ptr = NULL; + + desc->recv_oct[0] = desc->recv_oct[1] = 0; + desc->recv_cnt = 0; + desc->recv_max = 0; + desc->recv_avg = 0.0; + desc->recv_dvi = 0.0; + desc->send_oct[0] = desc->send_oct[1] = 0; + desc->send_cnt = 0; + desc->send_max = 0; + desc->send_avg = 0.0; + desc->empty_out_q_subs.subscriber = NO_PROCESS; + desc->empty_out_q_subs.next = NULL; + + sys_memzero((char *)&desc->remote,sizeof(desc->remote)); + + return (ErlDrvData)desc; +} + + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* +** common TCP/UDP/SCTP control command +*/ +static int inet_ctl(inet_descriptor* desc, int cmd, char* buf, int len, + char** rbuf, int rsize) +{ + switch (cmd) { + + case INET_REQ_GETSTAT: { + char* dst; + int i; + int dstlen = 1; /* Reply code */ + + for (i = 0; i < len; i++) { + switch(buf[i]) { + case INET_STAT_SEND_OCT: dstlen += 9; break; + case INET_STAT_RECV_OCT: dstlen += 9; break; + default: dstlen += 5; break; + } + } + DEBUGF(("inet_ctl(%ld): GETSTAT\r\n", (long) desc->port)); + if (dstlen > INET_MAX_BUFFER) /* sanity check */ + return 0; + if (dstlen > rsize) { + if ((dst = (char*) ALLOC(dstlen)) == NULL) + return 0; + *rbuf = dst; /* call will free this buffer */ + } + else + dst = *rbuf; /* ok we fit in buffer given */ + return inet_fill_stat(desc, buf, len, dst); + } + + case INET_REQ_SUBSCRIBE: { + char* dst; + int dstlen = 1 /* Reply code */ + len*5; + DEBUGF(("inet_ctl(%ld): INET_REQ_SUBSCRIBE\r\n", (long) desc->port)); + if (dstlen > INET_MAX_BUFFER) /* sanity check */ + return 0; + if (dstlen > rsize) { + if ((dst = (char*) ALLOC(dstlen)) == NULL) + return 0; + *rbuf = dst; /* call will free this buffer */ + } + else + dst = *rbuf; /* ok we fit in buffer given */ + return inet_subscribe(desc, buf, len, dst); + } + + case INET_REQ_GETOPTS: { /* get options */ + int replen; + DEBUGF(("inet_ctl(%ld): GETOPTS\r\n", (long)desc->port)); +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) + { + if ((replen = sctp_fill_opts(desc, buf, len, rbuf, rsize)) < 0) + return ctl_error(-replen, rbuf, rsize); + } else +#endif + if ((replen = inet_fill_opts(desc, buf, len, rbuf, rsize)) < 0) { + return ctl_error(EINVAL, rbuf, rsize); + } + return replen; + } + + case INET_REQ_GETIFLIST: { + DEBUGF(("inet_ctl(%ld): GETIFLIST\r\n", (long)desc->port)); + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + return inet_ctl_getiflist(desc, rbuf, rsize); + } + + case INET_REQ_IFGET: { + DEBUGF(("inet_ctl(%ld): IFGET\r\n", (long)desc->port)); + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + return inet_ctl_ifget(desc, buf, len, rbuf, rsize); + } + + case INET_REQ_IFSET: { + DEBUGF(("inet_ctl(%ld): IFSET\r\n", (long)desc->port)); + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + return inet_ctl_ifset(desc, buf, len, rbuf, rsize); + } + + case INET_REQ_SETOPTS: { /* set options */ + DEBUGF(("inet_ctl(%ld): SETOPTS\r\n", (long)desc->port)); + switch(inet_set_opts(desc, buf, len)) { + case -1: + return ctl_error(EINVAL, rbuf, rsize); + case 0: + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + default: /* active/passive change!! */ + /* + * Let's hope that the descriptor really is a tcp_descriptor here. + */ + tcp_deliver((tcp_descriptor *) desc, 0); + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + } + + case INET_REQ_GETSTATUS: { + char tbuf[4]; + + DEBUGF(("inet_ctl(%ld): GETSTATUS\r\n", (long)desc->port)); + put_int32(desc->state, tbuf); + return ctl_reply(INET_REP_OK, tbuf, 4, rbuf, rsize); + } + + case INET_REQ_GETTYPE: { + char tbuf[8]; + + DEBUGF(("inet_ctl(%ld): GETTYPE\r\n", (long)desc->port)); + if (desc->sfamily == AF_INET) { + put_int32(INET_AF_INET, &tbuf[0]); + } +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (desc->sfamily == AF_INET6) { + put_int32(INET_AF_INET6, &tbuf[0]); + } +#endif + else + return ctl_error(EINVAL, rbuf, rsize); + + if (desc->stype == SOCK_STREAM) { + put_int32(INET_TYPE_STREAM, &tbuf[4]); + } + else if (desc->stype == SOCK_DGRAM) { + put_int32(INET_TYPE_DGRAM, &tbuf[4]); + } +#ifdef HAVE_SCTP + else if (desc->stype == SOCK_SEQPACKET) { + put_int32(INET_TYPE_SEQPACKET, &tbuf[4]); + } +#endif + else + return ctl_error(EINVAL, rbuf, rsize); + return ctl_reply(INET_REP_OK, tbuf, 8, rbuf, rsize); + } + + + case INET_REQ_GETFD: { + char tbuf[4]; + + DEBUGF(("inet_ctl(%ld): GETFD\r\n", (long)desc->port)); + if (!IS_OPEN(desc)) + return ctl_error(EINVAL, rbuf, rsize); + put_int32((long)desc->s, tbuf); + return ctl_reply(INET_REP_OK, tbuf, 4, rbuf, rsize); + } + + case INET_REQ_GETHOSTNAME: { /* get host name */ + char tbuf[MAXHOSTNAMELEN]; + + DEBUGF(("inet_ctl(%ld): GETHOSTNAME\r\n", (long)desc->port)); + if (len != 0) + return ctl_error(EINVAL, rbuf, rsize); + + if (sock_hostname(tbuf, MAXHOSTNAMELEN) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + return ctl_reply(INET_REP_OK, tbuf, strlen(tbuf), rbuf, rsize); + } + + case INET_REQ_PEER: { /* get peername */ + char tbuf[sizeof(inet_address)]; + inet_address peer; + inet_address* ptr; + unsigned int sz = sizeof(peer); + + DEBUGF(("inet_ctl(%ld): PEER\r\n", (long)desc->port)); + + if (!(desc->state & INET_F_ACTIVE)) + return ctl_error(ENOTCONN, rbuf, rsize); + if ((ptr = desc->peer_ptr) == NULL) { + ptr = &peer; + if (sock_peer(desc->s, (struct sockaddr*)ptr,&sz) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + } + if (inet_get_address(desc->sfamily, tbuf, ptr, &sz) < 0) + return ctl_error(EINVAL, rbuf, rsize); + return ctl_reply(INET_REP_OK, tbuf, sz, rbuf, rsize); + } + + case INET_REQ_SETPEER: { /* set fake peername Port Address */ + if (len == 0) { + desc->peer_ptr = NULL; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + else if (len < 2) + return ctl_error(EINVAL, rbuf, rsize); + else if (inet_set_address(desc->sfamily, &desc->peer_addr, + buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + else { + desc->peer_ptr = &desc->peer_addr; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + } + + case INET_REQ_NAME: { /* get sockname */ + char tbuf[sizeof(inet_address)]; + inet_address name; + inet_address* ptr; + unsigned int sz = sizeof(name); + + DEBUGF(("inet_ctl(%ld): NAME\r\n", (long)desc->port)); + + if (!IS_BOUND(desc)) + return ctl_error(EINVAL, rbuf, rsize); /* address is not valid */ + + if ((ptr = desc->name_ptr) == NULL) { + ptr = &name; + if (sock_name(desc->s, (struct sockaddr*)ptr, &sz) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + } + if (inet_get_address(desc->sfamily, tbuf, ptr, &sz) < 0) + return ctl_error(EINVAL, rbuf, rsize); + return ctl_reply(INET_REP_OK, tbuf, sz, rbuf, rsize); + } + + case INET_REQ_SETNAME: { /* set fake peername Port Address */ + if (len == 0) { + desc->name_ptr = NULL; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + else if (len < 2) + return ctl_error(EINVAL, rbuf, rsize); + else if (inet_set_address(desc->sfamily, &desc->name_addr, + buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + else { + desc->name_ptr = &desc->name_addr; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + } + + case INET_REQ_BIND: { /* bind socket */ + char tbuf[2]; + inet_address local; + short port; + + DEBUGF(("inet_ctl(%ld): BIND\r\n", (long)desc->port)); + + if (len < 2) + return ctl_error(EINVAL, rbuf, rsize); + if (desc->state != INET_STATE_OPEN) + return ctl_xerror(EXBADPORT, rbuf, rsize); + + if (inet_set_address(desc->sfamily, &local, buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + + if (sock_bind(desc->s,(struct sockaddr*) &local, len) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + + desc->state = INET_STATE_BOUND; + + if ((port = inet_address_port(&local)) == 0) { + len = sizeof(local); + sock_name(desc->s, (struct sockaddr*) &local, (unsigned int*)&len); + port = inet_address_port(&local); + } + port = sock_ntohs(port); + put_int16(port, tbuf); + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + +#ifndef VXWORKS + + case INET_REQ_GETSERVBYNAME: { /* L1 Name-String L2 Proto-String */ + char namebuf[256]; + char protobuf[256]; + char tbuf[2]; + struct servent* srv; + short port; + int n; + + if (len < 2) + return ctl_error(EINVAL, rbuf, rsize); + n = buf[0]; buf++; len--; + if (n >= len) /* the = sign makes the test inklude next length byte */ + return ctl_error(EINVAL, rbuf, rsize); + memcpy(namebuf, buf, n); + namebuf[n] = '\0'; + len -= n; buf += n; + n = buf[0]; buf++; len--; + if (n > len) + return ctl_error(EINVAL, rbuf, rsize); + memcpy(protobuf, buf, n); + protobuf[n] = '\0'; + if ((srv = sock_getservbyname(namebuf, protobuf)) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + port = sock_ntohs(srv->s_port); + put_int16(port, tbuf); + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + + case INET_REQ_GETSERVBYPORT: { /* P1 P0 L1 Proto-String */ + char protobuf[256]; + unsigned short port; + int n; + struct servent* srv; + + if (len < 3) + return ctl_error(EINVAL, rbuf, rsize); + port = get_int16(buf); + port = sock_htons(port); + buf += 2; + n = buf[0]; buf++; len -= 3; + if (n > len) + return ctl_error(EINVAL, rbuf, rsize); + memcpy(protobuf, buf, n); + protobuf[n] = '\0'; + if ((srv = sock_getservbyport(port, protobuf)) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + len = strlen(srv->s_name); + return ctl_reply(INET_REP_OK, srv->s_name, len, rbuf, rsize); + } + +#endif /* !VXWORKS */ + + default: + return ctl_xerror(EXBADPORT, rbuf, rsize); + } +} + +/* update statistics on output packets */ +static void inet_output_count(inet_descriptor* desc, int len) +{ + unsigned long n = desc->send_cnt + 1; + unsigned long t = desc->send_oct[0] + len; + int c = (t < desc->send_oct[0]); + double avg = desc->send_avg; + + /* at least 64 bit octet count */ + desc->send_oct[0] = t; + desc->send_oct[1] += c; + + if (n == 0) /* WRAP, use old avg as input to a new sequence */ + n = 1; + desc->send_avg += (len - avg) / n; + if (len > desc->send_max) + desc->send_max = len; + desc->send_cnt = n; +} + +/* update statistics on input packets */ +static void inet_input_count(inet_descriptor* desc, int len) +{ + unsigned long n = desc->recv_cnt + 1; + unsigned long t = desc->recv_oct[0] + len; + int c = (t < desc->recv_oct[0]); + double avg = desc->recv_avg; + double dvi; + + /* at least 64 bit octet count */ + desc->recv_oct[0] = t; + desc->recv_oct[1] += c; + + if (n == 0) /* WRAP */ + n = 1; + + /* average packet length */ + avg = avg + (len - avg) / n; + desc->recv_avg = avg; + + if (len > desc->recv_max) + desc->recv_max = len; + + /* average deviation from average packet length */ + dvi = desc->recv_dvi; + desc->recv_dvi = dvi + ((len - avg) - dvi) / n; + desc->recv_cnt = n; +} + +/*---------------------------------------------------------------------------- + + TCP + +-----------------------------------------------------------------------------*/ + +/* +** Set new size on buffer, used when packet size is determined +** and the buffer is to small. +** buffer must have a size of at least len bytes (counting from ptr_start!) +*/ +static int tcp_expand_buffer(tcp_descriptor* desc, int len) +{ + ErlDrvBinary* bin; + int offs1; + int offs2; + int used = desc->i_ptr_start - desc->i_buf->orig_bytes; + int ulen = used + len; + + if (desc->i_bufsz >= ulen) /* packet will fit */ + return 0; + else if (desc->i_buf->orig_size >= ulen) { /* buffer is large enough */ + desc->i_bufsz = ulen; /* set "virtual" size */ + return 0; + } + + DEBUGF(("tcp_expand_buffer(%ld): s=%d, from %ld to %d\r\n", + (long)desc->inet.port, desc->inet.s, desc->i_buf->orig_size, ulen)); + + offs1 = desc->i_ptr_start - desc->i_buf->orig_bytes; + offs2 = desc->i_ptr - desc->i_ptr_start; + + if ((bin = driver_realloc_binary(desc->i_buf, ulen)) == NULL) + return -1; + + desc->i_buf = bin; + desc->i_ptr_start = bin->orig_bytes + offs1; + desc->i_ptr = desc->i_ptr_start + offs2; + desc->i_bufsz = ulen; + return 0; +} + +/* push data into i_buf */ +static int tcp_push_buffer(tcp_descriptor* desc, char* buf, int len) +{ + ErlDrvBinary* bin; + + if (desc->i_buf == NULL) { + bin = alloc_buffer(len); + sys_memcpy(bin->orig_bytes, buf, len); + desc->i_buf = bin; + desc->i_bufsz = len; + desc->i_ptr_start = desc->i_buf->orig_bytes; + desc->i_ptr = desc->i_ptr_start + len; + } + else { + char* start = desc->i_buf->orig_bytes; + int sz_before = desc->i_ptr_start - start; + int sz_filled = desc->i_ptr - desc->i_ptr_start; + + if (len <= sz_before) { + sys_memcpy(desc->i_ptr_start - len, buf, len); + desc->i_ptr_start -= len; + } + else { + bin = alloc_buffer(desc->i_bufsz+len); + sys_memcpy(bin->orig_bytes, buf, len); + sys_memcpy(bin->orig_bytes+len, desc->i_ptr_start, sz_filled); + free_buffer(desc->i_buf); + desc->i_bufsz += len; + desc->i_buf = bin; + desc->i_ptr_start = bin->orig_bytes; + desc->i_ptr = desc->i_ptr_start + sz_filled + len; + } + } + desc->i_remain = 0; + return 0; +} + +/* clear CURRENT input buffer */ +static void tcp_clear_input(tcp_descriptor* desc) +{ + if (desc->i_buf != NULL) + free_buffer(desc->i_buf); + desc->i_buf = NULL; + desc->i_remain = 0; + desc->i_ptr = NULL; + desc->i_ptr_start = NULL; + desc->i_bufsz = 0; +} + +/* clear QUEUED output */ +static void tcp_clear_output(tcp_descriptor* desc) +{ + ErlDrvPort ix = desc->inet.port; + int qsz = driver_sizeq(ix); + + driver_deq(ix, qsz); + send_empty_out_q_msgs(INETP(desc)); +} + + +/* Move data so that ptr_start point at buf->orig_bytes */ +static void tcp_restart_input(tcp_descriptor* desc) +{ + if (desc->i_ptr_start != desc->i_buf->orig_bytes) { + int n = desc->i_ptr - desc->i_ptr_start; + + DEBUGF(("tcp_restart_input: move %d bytes\r\n", n)); + sys_memmove(desc->i_buf->orig_bytes, desc->i_ptr_start, n); + desc->i_ptr_start = desc->i_buf->orig_bytes; + desc->i_ptr = desc->i_ptr_start + n; + } +} + + +static int tcp_inet_init(void) +{ + DEBUGF(("tcp_inet_init() {}\r\n")); + return 0; +} + +/* initialize the TCP descriptor */ + +static ErlDrvData tcp_inet_start(ErlDrvPort port, char* args) +{ + tcp_descriptor* desc; + DEBUGF(("tcp_inet_start(%ld) {\r\n", (long)port)); + + desc = (tcp_descriptor*) + inet_start(port, sizeof(tcp_descriptor), IPPROTO_TCP); + if (desc == NULL) + return ERL_DRV_ERROR_ERRNO; + desc->high = INET_HIGH_WATERMARK; + desc->low = INET_LOW_WATERMARK; + desc->send_timeout = INET_INFINITY; + desc->send_timeout_close = 0; + desc->busy_on_send = 0; + desc->i_buf = NULL; + desc->i_ptr = NULL; + desc->i_ptr_start = NULL; + desc->i_remain = 0; + desc->i_bufsz = 0; + desc->tcp_add_flags = 0; + desc->http_state = 0; + desc->mtd = NULL; + desc->multi_first = desc->multi_last = NULL; + DEBUGF(("tcp_inet_start(%ld) }\r\n", (long)port)); + return (ErlDrvData) desc; +} + +/* Copy a descriptor, by creating a new port with same settings + * as the descriptor desc. + * return NULL on error (ENFILE no ports avail) + */ +static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s, + ErlDrvTermData owner, int* err) +{ + ErlDrvPort port = desc->inet.port; + tcp_descriptor* copy_desc; + + copy_desc = (tcp_descriptor*) tcp_inet_start(port, NULL); + + /* Setup event if needed */ + if ((copy_desc->inet.s = s) != INVALID_SOCKET) { + if ((copy_desc->inet.event = sock_create_event(INETP(copy_desc))) == + INVALID_EVENT) { + *err = sock_errno(); + FREE(copy_desc); + return NULL; + } + } + + /* Some flags must be inherited at this point */ + copy_desc->inet.mode = desc->inet.mode; + copy_desc->inet.exitf = desc->inet.exitf; + copy_desc->inet.bit8f = desc->inet.bit8f; + copy_desc->inet.deliver = desc->inet.deliver; + copy_desc->inet.htype = desc->inet.htype; + copy_desc->inet.psize = desc->inet.psize; + copy_desc->inet.stype = desc->inet.stype; + copy_desc->inet.sfamily = desc->inet.sfamily; + copy_desc->inet.hsz = desc->inet.hsz; + copy_desc->inet.bufsz = desc->inet.bufsz; + copy_desc->high = desc->high; + copy_desc->low = desc->low; + copy_desc->send_timeout = desc->send_timeout; + copy_desc->send_timeout_close = desc->send_timeout_close; + + /* The new port will be linked and connected to the original caller */ + port = driver_create_port(port, owner, "tcp_inet", (ErlDrvData) copy_desc); + if ((long)port == -1) { + *err = ENFILE; + FREE(copy_desc); + return NULL; + } + copy_desc->inet.port = port; + copy_desc->inet.dport = driver_mk_port(port); + *err = 0; + return copy_desc; +} + +/* +** Check Special cases: +** 1. we are a listener doing nb accept -> report error on accept ! +** 2. we are doing accept -> restore listener state +*/ +static void tcp_close_check(tcp_descriptor* desc) +{ + /* XXX:PaN - multiple clients to handle! */ + if (desc->inet.state == TCP_STATE_ACCEPTING) { + inet_async_op *this_op = desc->inet.opt; + sock_select(INETP(desc), FD_ACCEPT, 0); + desc->inet.state = TCP_STATE_LISTEN; + if (this_op != NULL) { + driver_demonitor_process(desc->inet.port, &(this_op->monitor)); + } + async_error_am(INETP(desc), am_closed); + } + else if (desc->inet.state == TCP_STATE_MULTI_ACCEPTING) { + int id,req; + ErlDrvTermData caller; + ErlDrvMonitor monitor; + + sock_select(INETP(desc), FD_ACCEPT, 0); + desc->inet.state = TCP_STATE_LISTEN; + while (deq_multi_op(desc,&id,&req,&caller,NULL,&monitor) == 0) { + driver_demonitor_process(desc->inet.port, &monitor); + send_async_error(desc->inet.port, desc->inet.dport, id, caller, am_closed); + } + clean_multi_timers(&(desc->mtd), desc->inet.port); + } + + else if (desc->inet.state == TCP_STATE_CONNECTING) { + async_error_am(INETP(desc), am_closed); + } + else if (desc->inet.state == TCP_STATE_CONNECTED) { + async_error_am_all(INETP(desc), am_closed); + } +} + +/* +** Cleanup & Free +*/ +static void tcp_inet_stop(ErlDrvData e) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + DEBUGF(("tcp_inet_stop(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + tcp_close_check(desc); + /* free input buffer & output buffer */ + if (desc->i_buf != NULL) + release_buffer(desc->i_buf); + desc->i_buf = NULL; /* net_mess2 may call this function recursively when + faulty messages arrive on dist ports*/ + DEBUGF(("tcp_inet_stop(%ld) }\r\n", (long)desc->inet.port)); + inet_stop(INETP(desc)); +} + + + + +/* TCP requests from Erlang */ +static int tcp_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len, + char** rbuf, int rsize) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + switch(cmd) { + case INET_REQ_OPEN: /* open socket and return internal index */ + DEBUGF(("tcp_inet_ctl(%ld): OPEN\r\n", (long)desc->inet.port)); + if ((len == 1) && (buf[0] == INET_AF_INET)) + return + inet_ctl_open(INETP(desc), AF_INET, SOCK_STREAM, rbuf, rsize); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if ((len == 1) && (buf[0] == INET_AF_INET6)) + return + inet_ctl_open(INETP(desc), AF_INET6, SOCK_STREAM, rbuf, rsize); +#else + else if ((len == 1) && (buf[0] == INET_AF_INET6)) + return ctl_xerror("eafnosupport",rbuf,rsize); +#endif + else + return ctl_error(EINVAL, rbuf, rsize); + + case INET_REQ_FDOPEN: /* pass in an open socket */ + DEBUGF(("tcp_inet_ctl(%ld): FDOPEN\r\n", (long)desc->inet.port)); + if ((len == 5) && (buf[0] == INET_AF_INET)) + return inet_ctl_fdopen(INETP(desc), AF_INET, SOCK_STREAM, + (SOCKET) get_int32(buf+1), rbuf, rsize); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if ((len == 5) && (buf[0] == INET_AF_INET6)) + return inet_ctl_fdopen(INETP(desc), AF_INET6, SOCK_STREAM, + (SOCKET) get_int32(buf+1), rbuf, rsize); +#endif + else + return ctl_error(EINVAL, rbuf, rsize); + + case TCP_REQ_LISTEN: { /* argument backlog */ + + int backlog; + DEBUGF(("tcp_inet_ctl(%ld): LISTEN\r\n", (long)desc->inet.port)); + if (desc->inet.state == TCP_STATE_CLOSED) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (!IS_OPEN(INETP(desc))) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (!IS_BOUND(INETP(desc))) + return ctl_xerror(EXBADSEQ, rbuf, rsize); + if (len != 2) + return ctl_error(EINVAL, rbuf, rsize); + backlog = get_int16(buf); + if (sock_listen(desc->inet.s, backlog) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + desc->inet.state = TCP_STATE_LISTEN; + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + + + case INET_REQ_CONNECT: { /* do async connect */ + int code; + char tbuf[2]; + unsigned timeout; + + DEBUGF(("tcp_inet_ctl(%ld): CONNECT\r\n", (long)desc->inet.port)); + /* INPUT: Timeout(4), Port(2), Address(N) */ + + if (!IS_OPEN(INETP(desc))) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (IS_CONNECTED(INETP(desc))) + return ctl_error(EISCONN, rbuf, rsize); + if (!IS_BOUND(INETP(desc))) + return ctl_xerror(EXBADSEQ, rbuf, rsize); + if (IS_CONNECTING(INETP(desc))) + return ctl_error(EINVAL, rbuf, rsize); + if (len < 6) + return ctl_error(EINVAL, rbuf, rsize); + timeout = get_int32(buf); + buf += 4; + len -= 4; + if (inet_set_address(desc->inet.sfamily, &desc->inet.remote, + buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + + code = sock_connect(desc->inet.s, + (struct sockaddr*) &desc->inet.remote, len); + if ((code == SOCKET_ERROR) && + ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ + (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ + sock_select(INETP(desc), FD_CONNECT, 1); + desc->inet.state = TCP_STATE_CONNECTING; + if (timeout != INET_INFINITY) + driver_set_timer(desc->inet.port, timeout); + enq_async(INETP(desc), tbuf, INET_REQ_CONNECT); + } + else if (code == 0) { /* ok we are connected */ + desc->inet.state = TCP_STATE_CONNECTED; + if (desc->inet.active) + sock_select(INETP(desc), (FD_READ|FD_CLOSE), 1); + enq_async(INETP(desc), tbuf, INET_REQ_CONNECT); + async_ok(INETP(desc)); + } + else { + return ctl_error(sock_errno(), rbuf, rsize); + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + + case TCP_REQ_ACCEPT: { /* do async accept */ + char tbuf[2]; + unsigned timeout; + inet_address remote; + unsigned int n; + SOCKET s; + + DEBUGF(("tcp_inet_ctl(%ld): ACCEPT\r\n", (long)desc->inet.port)); + /* INPUT: Timeout(4) */ + + if ((desc->inet.state != TCP_STATE_LISTEN && desc->inet.state != TCP_STATE_ACCEPTING && + desc->inet.state != TCP_STATE_MULTI_ACCEPTING) || len != 4) { + return ctl_error(EINVAL, rbuf, rsize); + } + + timeout = get_int32(buf); + + if (desc->inet.state == TCP_STATE_ACCEPTING) { + unsigned long time_left; + int oid; + ErlDrvTermData ocaller; + int oreq; + unsigned otimeout; + ErlDrvTermData caller = driver_caller(desc->inet.port); + MultiTimerData *mtd = NULL,*omtd = NULL; + ErlDrvMonitor monitor, omonitor; + + + if (driver_monitor_process(desc->inet.port, caller ,&monitor) != 0) { + return ctl_xerror("noproc", rbuf, rsize); + } + deq_async_w_tmo(INETP(desc),&oid,&ocaller,&oreq,&otimeout,&omonitor); + if (otimeout != INET_INFINITY) { + driver_read_timer(desc->inet.port, &time_left); + driver_cancel_timer(desc->inet.port); + if (time_left <= 0) { + time_left = 1; + } + omtd = add_multi_timer(&(desc->mtd), desc->inet.port, ocaller, + time_left, &tcp_inet_multi_timeout); + } + enq_old_multi_op(desc, oid, oreq, ocaller, omtd, &omonitor); + if (timeout != INET_INFINITY) { + mtd = add_multi_timer(&(desc->mtd), desc->inet.port, caller, + timeout, &tcp_inet_multi_timeout); + } + enq_multi_op(desc, tbuf, TCP_REQ_ACCEPT, caller, mtd, &monitor); + desc->inet.state = TCP_STATE_MULTI_ACCEPTING; + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } else if (desc->inet.state == TCP_STATE_MULTI_ACCEPTING) { + ErlDrvTermData caller = driver_caller(desc->inet.port); + MultiTimerData *mtd = NULL; + ErlDrvMonitor monitor; + + if (driver_monitor_process(desc->inet.port, caller ,&monitor) != 0) { + return ctl_xerror("noproc", rbuf, rsize); + } + if (timeout != INET_INFINITY) { + mtd = add_multi_timer(&(desc->mtd), desc->inet.port, caller, + timeout, &tcp_inet_multi_timeout); + } + enq_multi_op(desc, tbuf, TCP_REQ_ACCEPT, caller, mtd, &monitor); + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } else { + n = sizeof(desc->inet.remote); + s = sock_accept(desc->inet.s, (struct sockaddr*) &remote, &n); + if (s == INVALID_SOCKET) { + if (sock_errno() == ERRNO_BLOCK) { + ErlDrvMonitor monitor; + if (driver_monitor_process(desc->inet.port, driver_caller(desc->inet.port), + &monitor) != 0) { + return ctl_xerror("noproc", rbuf, rsize); + } + enq_async_w_tmo(INETP(desc), tbuf, TCP_REQ_ACCEPT, timeout, &monitor); + desc->inet.state = TCP_STATE_ACCEPTING; + sock_select(INETP(desc),FD_ACCEPT,1); + if (timeout != INET_INFINITY) { + driver_set_timer(desc->inet.port, timeout); + } + } else { + return ctl_error(sock_errno(), rbuf, rsize); + } + } else { + ErlDrvTermData caller = driver_caller(desc->inet.port); + tcp_descriptor* accept_desc; + int err; + + if ((accept_desc = tcp_inet_copy(desc,s,caller,&err)) == NULL) { + sock_close(s); + return ctl_error(err, rbuf, rsize); + } + /* FIXME: may MUST lock access_port + * 1 - Port is accessible via the erlang:ports() + * 2 - Port is accessible via callers process_info(links) + */ + accept_desc->inet.remote = remote; + SET_NONBLOCKING(accept_desc->inet.s); +#ifdef __WIN32__ + driver_select(accept_desc->inet.port, accept_desc->inet.event, + ERL_DRV_READ, 1); +#endif + accept_desc->inet.state = TCP_STATE_CONNECTED; + enq_async(INETP(desc), tbuf, TCP_REQ_ACCEPT); + async_ok_port(INETP(desc), accept_desc->inet.dport); + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + } + case INET_REQ_CLOSE: + DEBUGF(("tcp_inet_ctl(%ld): CLOSE\r\n", (long)desc->inet.port)); + tcp_close_check(desc); + erl_inet_close(INETP(desc)); + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + + + case TCP_REQ_RECV: { + unsigned timeout; + char tbuf[2]; + int n; + + DEBUGF(("tcp_inet_ctl(%ld): RECV\r\n", (long)desc->inet.port)); + /* INPUT: Timeout(4), Length(4) */ + if (!IS_CONNECTED(INETP(desc))) { + if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_RECV) { + desc->tcp_add_flags &= ~(TCP_ADDF_DELAYED_CLOSE_RECV| + TCP_ADDF_DELAYED_CLOSE_SEND); + return ctl_reply(INET_REP_ERROR, "closed", 6, rbuf, rsize); + } + return ctl_error(ENOTCONN, rbuf, rsize); + } + if (desc->inet.active || (len != 8)) + return ctl_error(EINVAL, rbuf, rsize); + timeout = get_int32(buf); + buf += 4; + n = get_int32(buf); + DEBUGF(("tcp_inet_ctl(%ld) timeout = %d, n = %d\r\n", + (long)desc->inet.port,timeout,n)); + if ((desc->inet.htype != TCP_PB_RAW) && (n != 0)) + return ctl_error(EINVAL, rbuf, rsize); + if (n > TCP_MAX_PACKET_SIZE) + return ctl_error(ENOMEM, rbuf, rsize); + if (enq_async(INETP(desc), tbuf, TCP_REQ_RECV) < 0) + return ctl_error(EALREADY, rbuf, rsize); + + if (tcp_recv(desc, n) == 0) { + if (timeout == 0) + async_error_am(INETP(desc), am_timeout); + else { + if (timeout != INET_INFINITY) + driver_set_timer(desc->inet.port, timeout); + sock_select(INETP(desc),(FD_READ|FD_CLOSE),1); + } + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + + case TCP_REQ_UNRECV: { + DEBUGF(("tcp_inet_ctl(%ld): UNRECV\r\n", (long)desc->inet.port)); + if (!IS_CONNECTED(INETP(desc))) + return ctl_error(ENOTCONN, rbuf, rsize); + tcp_push_buffer(desc, buf, len); + if (desc->inet.active) + tcp_deliver(desc, 0); + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } +#ifndef _OSE_ + case TCP_REQ_SHUTDOWN: { + int how; + DEBUGF(("tcp_inet_ctl(%ld): FDOPEN\r\n", (long)desc->inet.port)); + if (!IS_CONNECTED(INETP(desc))) { + return ctl_error(ENOTCONN, rbuf, rsize); + } + if (len != 1) { + return ctl_error(EINVAL, rbuf, rsize); + } + how = buf[0]; + if (sock_shutdown(INETP(desc)->s, how) == 0) { + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } else { + return ctl_error(sock_errno(), rbuf, rsize); + } + } +#endif + default: + DEBUGF(("tcp_inet_ctl(%ld): %u\r\n", (long)desc->inet.port, cmd)); + return inet_ctl(INETP(desc), cmd, buf, len, rbuf, rsize); + } + +} + +/* +** tcp_inet_timeout: +** called when timer expire: +** TCP socket may be: +** +** a) receiving -- deselect +** b) connecting -- close socket +** c) accepting -- reset listener +** +*/ + +static void tcp_inet_timeout(ErlDrvData e) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + int state = desc->inet.state; + + DEBUGF(("tcp_inet_timeout(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + if ((state & INET_F_MULTI_CLIENT)) { /* Multi-client always means multi-timers */ + fire_multi_timers(&(desc->mtd), desc->inet.port, e); + } else if ((state & TCP_STATE_CONNECTED) == TCP_STATE_CONNECTED) { + if (desc->busy_on_send) { + ASSERT(IS_BUSY(INETP(desc))); + desc->inet.caller = desc->inet.busy_caller; + desc->inet.state &= ~INET_F_BUSY; + desc->busy_on_send = 0; + set_busy_port(desc->inet.port, 0); + inet_reply_error_am(INETP(desc), am_timeout); + if (desc->send_timeout_close) { + erl_inet_close(INETP(desc)); + } + } + else { + /* assume recv timeout */ + ASSERT(!desc->inet.active); + sock_select(INETP(desc),(FD_READ|FD_CLOSE),0); + desc->i_remain = 0; + async_error_am(INETP(desc), am_timeout); + } + } + else if ((state & TCP_STATE_CONNECTING) == TCP_STATE_CONNECTING) { + /* assume connect timeout */ + /* close the socket since it's not usable (see man pages) */ + erl_inet_close(INETP(desc)); + async_error_am(INETP(desc), am_timeout); + } + else if ((state & TCP_STATE_ACCEPTING) == TCP_STATE_ACCEPTING) { + inet_async_op *this_op = desc->inet.opt; + /* timer is set on accept */ + sock_select(INETP(desc), FD_ACCEPT, 0); + if (this_op != NULL) { + driver_demonitor_process(desc->inet.port, &(this_op->monitor)); + } + desc->inet.state = TCP_STATE_LISTEN; + async_error_am(INETP(desc), am_timeout); + } + DEBUGF(("tcp_inet_timeout(%ld) }\r\n", (long)desc->inet.port)); +} + +static void tcp_inet_multi_timeout(ErlDrvData e, ErlDrvTermData caller) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + int id,req; + ErlDrvMonitor monitor; + + if (remove_multi_op(desc, &id, &req, caller, NULL, &monitor) != 0) { + return; + } + driver_demonitor_process(desc->inet.port, &monitor); + if (desc->multi_first == NULL) { + sock_select(INETP(desc),FD_ACCEPT,0); + desc->inet.state = TCP_STATE_LISTEN; /* restore state */ + } + send_async_error(desc->inet.port, desc->inet.dport, id, caller, am_timeout); +} + + + +/* +** command: +** output on a socket only ! +** a reply code will be sent to connected (caller later) +** {inet_reply, S, Status} +** NOTE! normal sockets use the the tcp_inet_commandv +** but distribution still uses the tcp_inet_command!! +*/ + +static void tcp_inet_command(ErlDrvData e, char *buf, int len) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + desc->inet.caller = driver_caller(desc->inet.port); + + DEBUGF(("tcp_inet_command(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + if (!IS_CONNECTED(INETP(desc))) + inet_reply_error(INETP(desc), ENOTCONN); + else if (tcp_send(desc, buf, len) == 0) + inet_reply_ok(INETP(desc)); + DEBUGF(("tcp_inet_command(%ld) }\r\n", (long)desc->inet.port)); +} + + +static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + desc->inet.caller = driver_caller(desc->inet.port); + + DEBUGF(("tcp_inet_commanv(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + if (!IS_CONNECTED(INETP(desc))) { + if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_SEND) { + desc->tcp_add_flags &= ~TCP_ADDF_DELAYED_CLOSE_SEND; + inet_reply_error_am(INETP(desc), am_closed); + } + else + inet_reply_error(INETP(desc), ENOTCONN); + } + else if (tcp_sendv(desc, ev) == 0) + inet_reply_ok(INETP(desc)); + DEBUGF(("tcp_inet_commandv(%ld) }\r\n", (long)desc->inet.port)); +} + +static void tcp_inet_flush(ErlDrvData e) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + if (!(desc->inet.event_mask & FD_WRITE)) { + /* Discard send queue to avoid hanging port (OTP-7615) */ + tcp_clear_output(desc); + } +} + +static void tcp_inet_process_exit(ErlDrvData e, ErlDrvMonitor *monitorp) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + ErlDrvTermData who = driver_get_monitored_process(desc->inet.port,monitorp); + int state = desc->inet.state; + + if ((state & TCP_STATE_MULTI_ACCEPTING) == TCP_STATE_MULTI_ACCEPTING) { + int id,req; + MultiTimerData *timeout; + if (remove_multi_op(desc, &id, &req, who, &timeout, NULL) != 0) { + return; + } + if (timeout != NULL) { + remove_multi_timer(&(desc->mtd), desc->inet.port, timeout); + } + if (desc->multi_first == NULL) { + sock_select(INETP(desc),FD_ACCEPT,0); + desc->inet.state = TCP_STATE_LISTEN; /* restore state */ + } + } else if ((state & TCP_STATE_ACCEPTING) == TCP_STATE_ACCEPTING) { + int did,drid; + ErlDrvTermData dcaller; + deq_async(INETP(desc), &did, &dcaller, &drid); + driver_cancel_timer(desc->inet.port); + sock_select(INETP(desc),FD_ACCEPT,0); + desc->inet.state = TCP_STATE_LISTEN; /* restore state */ + } +} + +static void inet_stop_select(ErlDrvEvent event, void* _) +{ +#ifdef __WIN32__ + WSACloseEvent((HANDLE)event); +#else + sock_close((SOCKET)(long)event); +#endif +} + +/* The peer socket has closed, cleanup and send event */ +static int tcp_recv_closed(tcp_descriptor* desc) +{ +#ifdef DEBUG + long port = (long) desc->inet.port; /* Used after driver_exit() */ +#endif + DEBUGF(("tcp_recv_closed(%ld): s=%d, in %s, line %d\r\n", + port, desc->inet.s, __FILE__, __LINE__)); + if (IS_BUSY(INETP(desc))) { + /* A send is blocked */ + desc->inet.caller = desc->inet.busy_caller; + tcp_clear_output(desc); + if (desc->busy_on_send) { + driver_cancel_timer(desc->inet.port); + desc->busy_on_send = 0; + DEBUGF(("tcp_recv_closed(%ld): busy on send\r\n", port)); + } + desc->inet.state &= ~INET_F_BUSY; + set_busy_port(desc->inet.port, 0); + inet_reply_error_am(INETP(desc), am_closed); + DEBUGF(("tcp_recv_closed(%ld): busy reply 'closed'\r\n", port)); + } + if (!desc->inet.active) { + /* We must cancel any timer here ! */ + driver_cancel_timer(desc->inet.port); + /* passive mode do not terminate port ! */ + tcp_clear_input(desc); + if (desc->inet.exitf) { + tcp_clear_output(desc); + desc_close(INETP(desc)); + } else { + desc_close_read(INETP(desc)); + } + async_error_am_all(INETP(desc), am_closed); + /* next time EXBADSEQ will be delivered */ + DEBUGF(("tcp_recv_closed(%ld): passive reply all 'closed'\r\n", port)); + } else { + tcp_clear_input(desc); + tcp_closed_message(desc); + if (desc->inet.exitf) { + driver_exit(desc->inet.port, 0); + } else { + desc_close_read(INETP(desc)); + } + DEBUGF(("tcp_recv_closed(%ld): active close\r\n", port)); + } + DEBUGF(("tcp_recv_closed(%ld): done\r\n", port)); + return -1; +} + + +/* We have a read error determine the action */ +static int tcp_recv_error(tcp_descriptor* desc, int err) +{ + if (err != ERRNO_BLOCK) { + if (IS_BUSY(INETP(desc))) { + /* A send is blocked */ + desc->inet.caller = desc->inet.busy_caller; + tcp_clear_output(desc); + if (desc->busy_on_send) { + driver_cancel_timer(desc->inet.port); + desc->busy_on_send = 0; + } + desc->inet.state &= ~INET_F_BUSY; + set_busy_port(desc->inet.port, 0); + inet_reply_error_am(INETP(desc), am_closed); + } + if (!desc->inet.active) { + /* We must cancel any timer here ! */ + driver_cancel_timer(desc->inet.port); + tcp_clear_input(desc); + if (desc->inet.exitf) { + desc_close(INETP(desc)); + } else { + desc_close_read(INETP(desc)); + } + async_error_am_all(INETP(desc), error_atom(err)); + } else { + tcp_clear_input(desc); + tcp_error_message(desc, err); /* first error */ + tcp_closed_message(desc); /* then closed */ + if (desc->inet.exitf) + driver_exit(desc->inet.port, err); + else + desc_close(INETP(desc)); + } + return -1; + } + return 0; +} + + + +/* +** Calculate number of bytes that remain to read before deliver +** Assume buf, ptr_start, ptr has been setup +** +** return > 0 if more to read +** = 0 if holding complete packet +** < 0 on error +** +** if return value == 0 then *len will hold the length of the first packet +** return value > 0 then if *len == 0 then value means upperbound +** *len > 0 then value means exact +** +*/ +static int tcp_remain(tcp_descriptor* desc, int* len) +{ + char* ptr = desc->i_ptr_start; + int nfill = (desc->i_ptr - desc->i_buf->orig_bytes); /* filled */ + int nsz = desc->i_bufsz - nfill; /* remain */ + int n = desc->i_ptr - ptr; /* number of bytes read */ + int tlen; + + DEBUGF(("tcp_remain(%ld): s=%d, n=%d, nfill=%d nsz=%d\r\n", + (long)desc->inet.port, desc->inet.s, n, nfill, nsz)); + + tlen = packet_get_length(desc->inet.htype, ptr, n, + desc->inet.psize, desc->i_bufsz, + &desc->http_state); + if (tlen > 0) { + if (tlen <= n) { /* got a packet */ + *len = tlen; + DEBUGF((" => nothing remain packet=%d\r\n", tlen)); + return 0; + } + else { /* need known more */ + if (tcp_expand_buffer(desc, tlen) < 0) + return -1; + *len = tlen - n; + DEBUGF((" => remain=%d\r\n", *len)); + return *len; + } + } + else if (tlen == 0) { /* need unknown more */ + *len = 0; + if (nsz == 0) { + if (nfill == n) + goto error; + DEBUGF((" => restart more=%d\r\n", nfill - n)); + return nfill - n; + } + else { + DEBUGF((" => more=%d \r\n", nsz)); + return nsz; + } + } + +error: + DEBUGF((" => packet error\r\n")); + return -1; +} + +/* +** Deliver all packets ready +** if len == 0 then check start with a check for ready packet +*/ +static int tcp_deliver(tcp_descriptor* desc, int len) +{ + int count = 0; + int n; + + /* Poll for ready packet */ + if (len == 0) { + /* empty buffer or waiting for more input */ + if ((desc->i_buf == NULL) || (desc->i_remain > 0)) + return count; + if ((n = tcp_remain(desc, &len)) != 0) { + if (n < 0) /* packet error */ + return n; + if (len > 0) /* more data pending */ + desc->i_remain = len; + return count; + } + } + + while (len > 0) { + int code = 0; + + inet_input_count(INETP(desc), len); + + /* deliver binary? */ + if (len*4 >= desc->i_buf->orig_size*3) { /* >=75% */ + /* something after? */ + if (desc->i_ptr_start + len == desc->i_ptr) { /* no */ + code = tcp_reply_binary_data(desc, desc->i_buf, + (desc->i_ptr_start - + desc->i_buf->orig_bytes), + len); + tcp_clear_input(desc); + } + else { /* move trail to beginning of a new buffer */ + ErlDrvBinary* bin; + char* ptr_end = desc->i_ptr_start + len; + int sz = desc->i_ptr - ptr_end; + + bin = alloc_buffer(desc->i_bufsz); + memcpy(bin->orig_bytes, ptr_end, sz); + + code = tcp_reply_binary_data(desc, desc->i_buf, + (desc->i_ptr_start- + desc->i_buf->orig_bytes), + len); + free_buffer(desc->i_buf); + desc->i_buf = bin; + desc->i_ptr_start = desc->i_buf->orig_bytes; + desc->i_ptr = desc->i_ptr_start + sz; + desc->i_remain = 0; + } + } + else { + code = tcp_reply_data(desc, desc->i_ptr_start, len); + /* XXX The buffer gets thrown away on error (code < 0) */ + /* Windows needs workaround for this in tcp_inet_event... */ + desc->i_ptr_start += len; + if (desc->i_ptr_start == desc->i_ptr) + tcp_clear_input(desc); + else + desc->i_remain = 0; + + } + + if (code < 0) + return code; + + count++; + len = 0; + + if (!desc->inet.active) { + driver_cancel_timer(desc->inet.port); + sock_select(INETP(desc),(FD_READ|FD_CLOSE),0); + if (desc->i_buf != NULL) + tcp_restart_input(desc); + } + else if (desc->i_buf != NULL) { + if ((n = tcp_remain(desc, &len)) != 0) { + if (n < 0) /* packet error */ + return n; + tcp_restart_input(desc); + if (len > 0) + desc->i_remain = len; + len = 0; + } + } + } + return count; +} + + +static int tcp_recv(tcp_descriptor* desc, int request_len) +{ + int n; + int len; + int nread; + + if (desc->i_buf == NULL) { /* allocte a read buffer */ + int sz = (request_len > 0) ? request_len : desc->inet.bufsz; + + if ((desc->i_buf = alloc_buffer(sz)) == NULL) + return -1; + /* XXX: changing bufsz during recv SHOULD/MAY? affect + * ongoing operation but is not now + */ + desc->i_bufsz = sz; /* use i_bufsz not i_buf->orig_size ! */ + desc->i_ptr_start = desc->i_buf->orig_bytes; + desc->i_ptr = desc->i_ptr_start; + nread = sz; + if (request_len > 0) + desc->i_remain = request_len; + else + desc->i_remain = 0; + } + else if (request_len > 0) { /* we have a data in buffer and a request */ + n = desc->i_ptr - desc->i_ptr_start; + if (n >= request_len) + return tcp_deliver(desc, request_len); + else if (tcp_expand_buffer(desc, request_len) < 0) + return tcp_recv_error(desc, ENOMEM); + else + desc->i_remain = nread = request_len - n; + } + else if (desc->i_remain == 0) { /* poll remain from buffer data */ + if ((nread = tcp_remain(desc, &len)) < 0) + return tcp_recv_error(desc, EMSGSIZE); + else if (nread == 0) + return tcp_deliver(desc, len); + else if (len > 0) + desc->i_remain = len; /* set remain */ + } + else /* remain already set use it */ + nread = desc->i_remain; + + DEBUGF(("tcp_recv(%ld): s=%d about to read %d bytes...\r\n", + (long)desc->inet.port, desc->inet.s, nread)); + + n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0); + + if (n == SOCKET_ERROR) { + int err = sock_errno(); + if (err == ECONNRESET) { + DEBUGF((" => detected close (connreset)\r\n")); + return tcp_recv_closed(desc); + } + if (err == ERRNO_BLOCK) { + DEBUGF((" => would block\r\n")); + return 0; + } + else { + DEBUGF((" => error: %d\r\n", err)); + return tcp_recv_error(desc, err); + } + } + else if (n == 0) { + DEBUGF((" => detected close\r\n")); + return tcp_recv_closed(desc); + } + + DEBUGF((" => got %d bytes\r\n", n)); + desc->i_ptr += n; + if (desc->i_remain > 0) { + desc->i_remain -= n; + if (desc->i_remain == 0) + return tcp_deliver(desc, desc->i_ptr - desc->i_ptr_start); + } + else { + if ((nread = tcp_remain(desc, &len)) < 0) + return tcp_recv_error(desc, EMSGSIZE); + else if (nread == 0) + return tcp_deliver(desc, len); + else if (len > 0) + desc->i_remain = len; /* set remain */ + } + return 0; +} + + +#ifdef __WIN32__ + + +static int winsock_event_select(inet_descriptor *desc, int flags, int on) +{ + int save_event_mask = desc->event_mask; + + desc->forced_events = 0; + if (on) + desc->event_mask |= flags; + else + desc->event_mask &= (~flags); + DEBUGF(("port %d: winsock_event_select: " + "flags=%02X, on=%d, event_mask=%02X\n", + desc->port, flags, on, desc->event_mask)); + /* The RIGHT WAY (TM) to do this is to make sure: + A) The cancelling of all network events is done with + NULL as the event parameter (bug in NT's winsock), + B) The actual event handle is reset so that it is only + raised if one of the requested network events is active, + C) Avoid race conditions by making sure that the event cannot be set + while we are preparing to set the correct network event mask. + The simplest way to do it is to turn off all events, reset the + event handle and then, if event_mask != 0, turn on the appropriate + events again. */ + if (WSAEventSelect(desc->s, NULL, 0) != 0) { + DEBUGF(("port %d: winsock_event_select: " + "WSAEventSelect returned error, code %d.\n", + sock_errno())); + desc->event_mask = save_event_mask; + return -1; + } + if (!ResetEvent(desc->event)) { + DEBUGF(("port %d: winsock_event_select: " + "ResetEvent returned error, code %d.\n", + GetLastError())); + desc->event_mask = 0; + return -1; + } + if (desc->event_mask != 0) { + if (WSAEventSelect(desc->s, + desc->event, + desc->event_mask) != 0) { + DEBUGF(("port %d: winsock_event_select: " + "WSAEventSelect returned error, code %d.\n", + sock_errno())); + desc->event_mask = 0; + return -1; + } + + /* Now, WSAEventSelect() is trigged only when the queue goes from + full to empty or from empty to full; therefore we need an extra test + to see whether it is writeable, readable or closed... */ + if ((desc->event_mask & FD_WRITE)) { + int do_force = 1; + if (desc->send_would_block) { + TIMEVAL tmo = {0,0}; + FD_SET fds; + int ret; + + FD_ZERO(&fds); + FD_SET(desc->s,&fds); + do_force = (select(desc->s+1,0,&fds,0,&tmo) > 0); + } + if (do_force) { + SetEvent(desc->event); + desc->forced_events |= FD_WRITE; + } + } + if ((desc->event_mask & (FD_READ|FD_CLOSE))) { + int readable = 0; + int closed = 0; + TIMEVAL tmo = {0,0}; + FD_SET fds; + int ret; + unsigned long arg; + + FD_ZERO(&fds); + FD_SET(desc->s,&fds); + ret = select(desc->s+1,&fds,0,0,&tmo); + if (ret > 0) { + ++readable; + if (ioctlsocket(desc->s,FIONREAD,&arg) != 0) { + ++closed; /* Which gives a FD_CLOSE event */ + } else { + closed = (arg == 0); + } + } + if ((desc->event_mask & FD_READ) && readable && !closed) { + SetEvent(desc->event); + desc->forced_events |= FD_READ; + } + if ((desc->event_mask & FD_CLOSE) && closed) { + SetEvent(desc->event); + desc->forced_events |= FD_CLOSE; + } + } + } + return 0; +} + +static void tcp_inet_event(ErlDrvData e, ErlDrvEvent event) +{ + tcp_descriptor* desc = (tcp_descriptor*)e; + WSANETWORKEVENTS netEv; + int err; + + DEBUGF(("tcp_inet_event(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + if (WSAEnumNetworkEvents(desc->inet.s, desc->inet.event, + &netEv) != 0) { + DEBUGF((" => EnumNetworkEvents = %d\r\n", sock_errno() )); + goto error; + } + + DEBUGF((" => event=%02X, mask=%02X\r\n", + netEv.lNetworkEvents, desc->inet.event_mask)); + + /* Add the forced events. */ + + netEv.lNetworkEvents |= desc->inet.forced_events; + + /* + * Calling WSAEventSelect() with a mask of 0 doesn't always turn off + * all events. To avoid acting on events we don't want, we mask + * the events with mask for the events we really want. + */ + +#ifdef DEBUG + if ((netEv.lNetworkEvents & ~(desc->inet.event_mask)) != 0) { + DEBUGF(("port %d: ... unexpected event: %d\r\n", + desc->inet.port, netEv.lNetworkEvents & ~(desc->inet.event_mask))); + } +#endif + netEv.lNetworkEvents &= desc->inet.event_mask; + + if (netEv.lNetworkEvents & FD_READ) { + if (tcp_inet_input(desc, event) < 0) { + goto error; + } + if (netEv.lNetworkEvents & FD_CLOSE) { + /* + * We must loop to read out the remaining packets (if any). + */ + for (;;) { + DEBUGF(("Retrying read due to closed port\r\n")); + /* XXX The buffer will be thrown away on error (empty que). + Possible SMP FIXME. */ + if (!desc->inet.active && (desc->inet.opt) == NULL) { + goto error; + } + if (tcp_inet_input(desc, event) < 0) { + goto error; + } + } + } + } + if (netEv.lNetworkEvents & FD_WRITE) { + desc->inet.send_would_block = 0; + if (tcp_inet_output(desc, event) < 0) + goto error; + } + if (netEv.lNetworkEvents & FD_CONNECT) { + if ((err = netEv.iErrorCode[FD_CONNECT_BIT]) != 0) { + async_error(INETP(desc), err); + } else { + tcp_inet_output(desc, event); + } + } else if (netEv.lNetworkEvents & FD_ACCEPT) { + if ((err = netEv.iErrorCode[FD_ACCEPT_BIT]) != 0) + async_error(INETP(desc), err); + else + tcp_inet_input(desc, event); + } + if (netEv.lNetworkEvents & FD_CLOSE) { + /* error in err = netEv.iErrorCode[FD_CLOSE_BIT] */ + DEBUGF(("Detected close in %s, line %d\r\n", __FILE__, __LINE__)); + tcp_recv_closed(desc); + } + DEBUGF(("tcp_inet_event(%ld) }\r\n", (long)desc->inet.port)); + return; + + error: + DEBUGF(("tcp_inet_event(%ld) error}\r\n", (long)desc->inet.port)); + return; +} + +#endif /* WIN32 */ + + +/* socket has input: +** 1. TCP_STATE_ACCEPTING => non block accept ? +** 2. TCP_STATE_CONNECTED => read input +*/ +static int tcp_inet_input(tcp_descriptor* desc, HANDLE event) +{ + int ret = 0; +#ifdef DEBUG + long port = (long) desc->inet.port; /* Used after driver_exit() */ +#endif + DEBUGF(("tcp_inet_input(%ld) {s=%d\r\n", port, desc->inet.s)); + if (desc->inet.state == TCP_STATE_ACCEPTING) { + SOCKET s; + unsigned int len; + inet_address remote; + inet_async_op *this_op = desc->inet.opt; + + len = sizeof(desc->inet.remote); + s = sock_accept(desc->inet.s, (struct sockaddr*) &remote, &len); + if (s == INVALID_SOCKET && sock_errno() == ERRNO_BLOCK) { + /* Just try again, no real error, just a ghost trigger from poll, + keep the default return code and everything else as is */ + goto done; + } + + sock_select(INETP(desc),FD_ACCEPT,0); + desc->inet.state = TCP_STATE_LISTEN; /* restore state */ + + if (this_op != NULL) { + driver_demonitor_process(desc->inet.port, &(this_op->monitor)); + } + + + driver_cancel_timer(desc->inet.port); /* posssibly cancel a timer */ + + if (s == INVALID_SOCKET) { + ret = async_error(INETP(desc), sock_errno()); + goto done; + } + else { + ErlDrvTermData caller; + tcp_descriptor* accept_desc; + int err; + + if (desc->inet.opt == NULL) { + /* No caller setup */ + sock_close(s); + ret = async_error(INETP(desc), EINVAL); + goto done; + } + caller = desc->inet.opt->caller; + if ((accept_desc = tcp_inet_copy(desc,s,caller,&err)) == NULL) { + sock_close(s); + ret = async_error(INETP(desc), err); + goto done; + } + /* FIXME: may MUST lock port + * 1 - Port is accessible via the erlang:ports() + * 2 - Port is accessible via callers process_info(links) + */ + accept_desc->inet.remote = remote; + SET_NONBLOCKING(accept_desc->inet.s); +#ifdef __WIN32__ + driver_select(accept_desc->inet.port, accept_desc->inet.event, + ERL_DRV_READ, 1); +#endif + accept_desc->inet.state = TCP_STATE_CONNECTED; + ret = async_ok_port(INETP(desc), accept_desc->inet.dport); + goto done; + } + } else if (desc->inet.state == TCP_STATE_MULTI_ACCEPTING) { + SOCKET s; + unsigned int len; + inet_address remote; + int id,req; + ErlDrvTermData caller; + MultiTimerData *timeout; + ErlDrvMonitor monitor; +#ifdef HARDDEBUG + int times = 0; +#endif + + while (desc->inet.state == TCP_STATE_MULTI_ACCEPTING) { + len = sizeof(desc->inet.remote); + s = sock_accept(desc->inet.s, (struct sockaddr*) &remote, &len); + + if (s == INVALID_SOCKET && sock_errno() == ERRNO_BLOCK) { + /* Just try again, no real error, keep the last return code */ + goto done; + } +#ifdef HARDDEBUG + if (++times > 1) { + erts_fprintf(stderr,"Accepts in one suite: %d :-)\r\n",times); + } +#endif + if (deq_multi_op(desc,&id,&req,&caller,&timeout,&monitor) != 0) { + ret = -1; + goto done; + } + + if (desc->multi_first == NULL) { + sock_select(INETP(desc),FD_ACCEPT,0); + desc->inet.state = TCP_STATE_LISTEN; /* restore state */ + } + + if (timeout != NULL) { + remove_multi_timer(&(desc->mtd), desc->inet.port, timeout); + } + + driver_demonitor_process(desc->inet.port, &monitor); + + + if (s == INVALID_SOCKET) { /* Not ERRNO_BLOCK, that's handled right away */ + ret = send_async_error(desc->inet.port, desc->inet.dport, + id, caller, error_atom(sock_errno())); + goto done; + } + else { + tcp_descriptor* accept_desc; + int err; + + if ((accept_desc = tcp_inet_copy(desc,s,caller,&err)) == NULL) { + sock_close(s); + ret = send_async_error(desc->inet.port, desc->inet.dport, + id, caller, error_atom(err)); + goto done; + } + accept_desc->inet.remote = remote; + SET_NONBLOCKING(accept_desc->inet.s); +#ifdef __WIN32__ + driver_select(accept_desc->inet.port, accept_desc->inet.event, + ERL_DRV_READ, 1); +#endif + accept_desc->inet.state = TCP_STATE_CONNECTED; + ret = send_async_ok_port(desc->inet.port, desc->inet.dport, + id, caller, accept_desc->inet.dport); + } + } + } + else if (IS_CONNECTED(INETP(desc))) { + ret = tcp_recv(desc, 0); + goto done; + } + else { + /* maybe a close op from connection attempt?? */ + sock_select(INETP(desc),FD_ACCEPT,0); + DEBUGF(("tcp_inet_input(%ld): s=%d bad state: %04x\r\n", + port, desc->inet.s, desc->inet.state)); + } + done: + DEBUGF(("tcp_inet_input(%ld) }\r\n", port)); + return ret; +} + +static int tcp_send_error(tcp_descriptor* desc, int err) +{ + /* + * If the port is busy, we must do some clean-up before proceeding. + */ + if (IS_BUSY(INETP(desc))) { + desc->inet.caller = desc->inet.busy_caller; + if (desc->busy_on_send) { + driver_cancel_timer(desc->inet.port); + desc->busy_on_send = 0; + } + desc->inet.state &= ~INET_F_BUSY; + set_busy_port(desc->inet.port, 0); + } + + /* + * We used to handle "expected errors" differently from unexpected ones. + * Now we handle all errors in the same way. We just have to distinguish + * between passive and active sockets. + */ + DEBUGF(("driver_failure_eof(%ld) in %s, line %d\r\n", + (long)desc->inet.port, __FILE__, __LINE__)); + if (desc->inet.active) { + tcp_closed_message(desc); + inet_reply_error_am(INETP(desc), am_closed); + if (desc->inet.exitf) + driver_exit(desc->inet.port, 0); + else + desc_close(INETP(desc)); + } else { + tcp_clear_output(desc); + tcp_clear_input(desc); + tcp_close_check(desc); + erl_inet_close(INETP(desc)); + + if (desc->inet.caller) { + inet_reply_error_am(INETP(desc), am_closed); + } + else { + /* No blocking send op to reply to right now. + * If next op is a send, make sure it returns {error,closed} + * rather than {error,enotconn}. + */ + desc->tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_SEND; + } + + /* + * Make sure that the next receive operation gets an {error,closed} + * result rather than {error,enotconn}. That means that the caller + * can safely ignore errors in the send operations and handle them + * in the receive operation. + */ + desc->tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_RECV; + } + return -1; +} + +/* +** Send non-blocking vector data +*/ +static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev) +{ + int sz; + char buf[4]; + int h_len; + int n; + ErlDrvPort ix = desc->inet.port; + int len = ev->size; + + switch(desc->inet.htype) { + case TCP_PB_1: + put_int8(len, buf); + h_len = 1; + break; + case TCP_PB_2: + put_int16(len, buf); + h_len = 2; + break; + case TCP_PB_4: + put_int32(len, buf); + h_len = 4; + break; + default: + if (len == 0) + return 0; + h_len = 0; + break; + } + + inet_output_count(INETP(desc), len+h_len); + + if (h_len > 0) { + ev->iov[0].iov_base = buf; + ev->iov[0].iov_len = h_len; + ev->size += h_len; + } + + if ((sz = driver_sizeq(ix)) > 0) { + driver_enqv(ix, ev, 0); + if (sz+ev->size >= desc->high) { + DEBUGF(("tcp_sendv(%ld): s=%d, sender forced busy\r\n", + (long)desc->inet.port, desc->inet.s)); + desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */ + desc->inet.busy_caller = desc->inet.caller; + set_busy_port(desc->inet.port, 1); + if (desc->send_timeout != INET_INFINITY) { + desc->busy_on_send = 1; + driver_set_timer(desc->inet.port, desc->send_timeout); + } + return 1; + } + } + else { + int vsize = (ev->vsize > MAX_VSIZE) ? MAX_VSIZE : ev->vsize; + + DEBUGF(("tcp_sendv(%ld): s=%d, about to send %d,%d bytes\r\n", + (long)desc->inet.port, desc->inet.s, h_len, len)); + if (desc->tcp_add_flags & TCP_ADDF_DELAY_SEND) { + n = 0; + } else if (sock_sendv(desc->inet.s, ev->iov, vsize, &n, 0) + == SOCKET_ERROR) { + if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) { + int err = sock_errno(); + DEBUGF(("tcp_sendv(%ld): s=%d, " + "sock_sendv(size=2) errno = %d\r\n", + (long)desc->inet.port, desc->inet.s, err)); + return tcp_send_error(desc, err); + } +#ifdef __WIN32__ + desc->inet.send_would_block = 1; +#endif + n = 0; + } + else if (n == ev->size) { + ASSERT(NO_SUBSCRIBERS(&INETP(desc)->empty_out_q_subs)); + return 0; + } + else { + DEBUGF(("tcp_sendv(%ld): s=%d, only sent %d/%d of %d/%d bytes/items\r\n", + (long)desc->inet.port, desc->inet.s, n, vsize, ev->size, ev->vsize)); + } + + DEBUGF(("tcp_sendv(%ld): s=%d, Send failed, queuing\r\n", + (long)desc->inet.port, desc->inet.s)); + driver_enqv(ix, ev, n); + sock_select(INETP(desc),(FD_WRITE|FD_CLOSE), 1); + } + return 0; +} + +/* +** Send non blocking data +*/ +static int tcp_send(tcp_descriptor* desc, char* ptr, int len) +{ + int sz; + char buf[4]; + int h_len; + int n; + ErlDrvPort ix = desc->inet.port; + SysIOVec iov[2]; + + switch(desc->inet.htype) { + case TCP_PB_1: + put_int8(len, buf); + h_len = 1; + break; + case TCP_PB_2: + put_int16(len, buf); + h_len = 2; + break; + case TCP_PB_4: + put_int32(len, buf); + h_len = 4; + break; + default: + if (len == 0) + return 0; + h_len = 0; + break; + } + + inet_output_count(INETP(desc), len+h_len); + + + if ((sz = driver_sizeq(ix)) > 0) { + if (h_len > 0) + driver_enq(ix, buf, h_len); + driver_enq(ix, ptr, len); + if (sz+h_len+len >= desc->high) { + DEBUGF(("tcp_send(%ld): s=%d, sender forced busy\r\n", + (long)desc->inet.port, desc->inet.s)); + desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */ + desc->inet.busy_caller = desc->inet.caller; + set_busy_port(desc->inet.port, 1); + if (desc->send_timeout != INET_INFINITY) { + desc->busy_on_send = 1; + driver_set_timer(desc->inet.port, desc->send_timeout); + } + return 1; + } + } + else { + iov[0].iov_base = buf; + iov[0].iov_len = h_len; + iov[1].iov_base = ptr; + iov[1].iov_len = len; + + DEBUGF(("tcp_send(%ld): s=%d, about to send %d,%d bytes\r\n", + (long)desc->inet.port, desc->inet.s, h_len, len)); + if (desc->tcp_add_flags & TCP_ADDF_DELAY_SEND) { + sock_send(desc->inet.s, buf, 0, 0); + n = 0; + } else if (sock_sendv(desc->inet.s,iov,2,&n,0) == SOCKET_ERROR) { + if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) { + int err = sock_errno(); + DEBUGF(("tcp_send(%ld): s=%d,sock_sendv(size=2) errno = %d\r\n", + (long)desc->inet.port, desc->inet.s, err)); + return tcp_send_error(desc, err); + } +#ifdef __WIN32__ + desc->inet.send_would_block = 1; +#endif + n = 0; + } + else if (n == len+h_len) { + ASSERT(NO_SUBSCRIBERS(&INETP(desc)->empty_out_q_subs)); + return 0; + } + + DEBUGF(("tcp_send(%ld): s=%d, Send failed, queuing", + (long)desc->inet.port, desc->inet.s)); + + if (n < h_len) { + driver_enq(ix, buf+n, h_len-n); + driver_enq(ix, ptr, len); + } + else { + n -= h_len; + driver_enq(ix, ptr+n, len-n); + } + sock_select(INETP(desc),(FD_WRITE|FD_CLOSE), 1); + } + return 0; +} + +static void tcp_inet_drv_output(ErlDrvData data, ErlDrvEvent event) +{ + (void)tcp_inet_output((tcp_descriptor*)data, (HANDLE)event); +} + +static void tcp_inet_drv_input(ErlDrvData data, ErlDrvEvent event) +{ + (void)tcp_inet_input((tcp_descriptor*)data, (HANDLE)event); +} + +/* socket ready for ouput: +** 1. TCP_STATE_CONNECTING => non block connect ? +** 2. TCP_STATE_CONNECTED => write output +*/ +static int tcp_inet_output(tcp_descriptor* desc, HANDLE event) +{ + int ret = 0; + ErlDrvPort ix = desc->inet.port; + + DEBUGF(("tcp_inet_output(%ld) {s=%d\r\n", + (long)desc->inet.port, desc->inet.s)); + if (desc->inet.state == TCP_STATE_CONNECTING) { + sock_select(INETP(desc),FD_CONNECT,0); + + driver_cancel_timer(ix); /* posssibly cancel a timer */ +#ifndef __WIN32__ + /* + * XXX This is strange. This *should* work on Windows NT too, + * but doesn't. An bug in Winsock 2.0 for Windows NT? + * + * See "Unix Netwok Programming", W.R.Stevens, p 412 for a + * discussion about Unix portability and non blocking connect. + */ + +#ifndef SO_ERROR + { + int sz = sizeof(desc->inet.remote); + int code = sock_peer(desc->inet.s, + (struct sockaddr*) &desc->inet.remote, &sz); + + if (code == SOCKET_ERROR) { + desc->inet.state = TCP_STATE_BOUND; /* restore state */ + ret = async_error(INETP(desc), sock_errno()); + goto done; + } + } +#else + { + int error = 0; /* Has to be initiated, we check it */ + unsigned int sz = sizeof(error); /* even if we get -1 */ + int code = sock_getopt(desc->inet.s, SOL_SOCKET, SO_ERROR, + (void *)&error, &sz); + + if ((code < 0) || error) { + desc->inet.state = TCP_STATE_BOUND; /* restore state */ + ret = async_error(INETP(desc), error); + goto done; + } + } +#endif /* SOCKOPT_CONNECT_STAT */ +#endif /* !__WIN32__ */ + + desc->inet.state = TCP_STATE_CONNECTED; + if (desc->inet.active) + sock_select(INETP(desc),(FD_READ|FD_CLOSE),1); + async_ok(INETP(desc)); + } + else if (IS_CONNECTED(INETP(desc))) { + for (;;) { + int vsize; + int n; + SysIOVec* iov; + + if ((iov = driver_peekq(ix, &vsize)) == NULL) { + sock_select(INETP(desc), FD_WRITE, 0); + send_empty_out_q_msgs(INETP(desc)); + goto done; + } + vsize = vsize > MAX_VSIZE ? MAX_VSIZE : vsize; + DEBUGF(("tcp_inet_output(%ld): s=%d, About to send %d items\r\n", + (long)desc->inet.port, desc->inet.s, vsize)); + if (sock_sendv(desc->inet.s, iov, vsize, &n, 0)==SOCKET_ERROR) { + if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) { + DEBUGF(("tcp_inet_output(%ld): sock_sendv(%d) errno = %d\r\n", + (long)desc->inet.port, vsize, sock_errno())); + ret = tcp_send_error(desc, sock_errno()); + goto done; + } +#ifdef __WIN32__ + desc->inet.send_would_block = 1; +#endif + goto done; + } + if (driver_deq(ix, n) <= desc->low) { + if (IS_BUSY(INETP(desc))) { + desc->inet.caller = desc->inet.busy_caller; + desc->inet.state &= ~INET_F_BUSY; + set_busy_port(desc->inet.port, 0); + /* if we have a timer then cancel and send ok to client */ + if (desc->busy_on_send) { + driver_cancel_timer(desc->inet.port); + desc->busy_on_send = 0; + } + inet_reply_ok(INETP(desc)); + } + } + } + } + else { + sock_select(INETP(desc),FD_CONNECT,0); + DEBUGF(("tcp_inet_output(%ld): bad state: %04x\r\n", + (long)desc->inet.port, desc->inet.state)); + } + done: + DEBUGF(("tcp_inet_output(%ld) }\r\n", (long)desc->inet.port)); + return ret; +} + +/*----------------------------------------------------------------------------- + + UDP & SCTP (the latter in a 1<->M Mode) + +-----------------------------------------------------------------------------*/ + +#if defined(HAVE_SO_BSDCOMPAT) +#if defined(__linux__) +#include <sys/utsname.h> +static int should_use_so_bsdcompat(void) +{ + /* SMP: FIXME this is probably not SMP safe but may be ok anyway? */ + static int init_done; + static int so_bsdcompat_is_obsolete; + + if (!init_done) { + struct utsname utsname; + unsigned int version, patchlevel; + + init_done = 1; + if (uname(&utsname) < 0) { + fprintf(stderr, "uname: %s\r\n", strerror(sock_errno())); + return 1; + } + /* Format is <version>.<patchlevel>.<sublevel><extraversion> + where the first three are unsigned integers and the last + is an arbitrary string. We only care about the first two. */ + if (sscanf(utsname.release, "%u.%u", &version, &patchlevel) != 2) { + fprintf(stderr, "uname: unexpected release '%s'\r\n", + utsname.release); + return 1; + } + /* SO_BSDCOMPAT is deprecated and triggers warnings in 2.5 + kernels. It is a no-op in 2.4 but not in 2.2 kernels. */ + if (version > 2 || (version == 2 && patchlevel >= 5)) + so_bsdcompat_is_obsolete = 1; + } + return !so_bsdcompat_is_obsolete; +} +#else /* __linux__ */ +#define should_use_so_bsdcompat() 1 +#endif /* __linux__ */ +#endif /* HAVE_SO_BSDCOMPAT */ + +static int packet_inet_init() +{ + return 0; +} + +static ErlDrvData packet_inet_start(ErlDrvPort port, char* args, int protocol) +{ + /* "inet_start" returns "ErlDrvData", but in fact it is "inet_descriptor*", + so we can preserve it as "ErlDrvData": + */ + ErlDrvData drvd = inet_start(port, sizeof(udp_descriptor), + protocol); + udp_descriptor* desc = (udp_descriptor*) drvd; + + if (desc == NULL) + return ERL_DRV_ERROR_ERRNO; + + desc->read_packets = INET_PACKET_POLL; + return drvd; +} + +static ErlDrvData udp_inet_start(ErlDrvPort port, char *args) +{ + return packet_inet_start(port, args, IPPROTO_UDP); +} + +#ifdef HAVE_SCTP +static ErlDrvData sctp_inet_start(ErlDrvPort port, char *args) +{ + return packet_inet_start(port, args, IPPROTO_SCTP); +} +#endif + +static void packet_inet_stop(ErlDrvData e) +{ + /* There should *never* be any "empty out q" subscribers on + an UDP or SCTP socket! + NB: as in "inet_start", we can always cast "ErlDRvData" + into "udp_descriptor*" or "inet_descriptor*": + */ + udp_descriptor * udesc = (udp_descriptor*) e; + inet_descriptor* descr = INETP(udesc); + + ASSERT(NO_SUBSCRIBERS(&(descr->empty_out_q_subs))); + inet_stop(descr); +} + +static int packet_error(udp_descriptor* udesc, int err) +{ + inet_descriptor * desc = INETP(udesc); + if (!desc->active) + async_error(desc, err); + driver_failure_posix(desc->port, err); + return -1; +} + +/* +** Various functions accessible via "port_control" on the Erlang side: +*/ +static int packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len, + char** rbuf, int rsize) +{ + int replen; + udp_descriptor * udesc = (udp_descriptor *) e; + inet_descriptor* desc = INETP(udesc); + int type = SOCK_DGRAM; + int af; +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) type = SOCK_SEQPACKET; +#endif + + switch(cmd) { + case INET_REQ_OPEN: /* open socket and return internal index */ + DEBUGF(("packet_inet_ctl(%ld): OPEN\r\n", (long)desc->port)); + if (len != 1) { + return ctl_error(EINVAL, rbuf, rsize); + } + switch (buf[0]) { + case INET_AF_INET: af = AF_INET; break; +#if defined(HAVE_IN6) && defined(AF_INET6) + case INET_AF_INET6: af = AF_INET6; break; +#endif + default: + return ctl_error(EINVAL, rbuf, rsize); + } + replen = inet_ctl_open(desc, af, type, rbuf, rsize); + + if ((*rbuf)[0] != INET_REP_ERROR) { + if (desc->active) + sock_select(desc,FD_READ,1); +#ifdef HAVE_SO_BSDCOMPAT + /* + * Make sure that sending UDP packets to a non existing port on an + * existing machine doesn't close the socket. (Linux behaves this + * way) + */ + if (should_use_so_bsdcompat()) { + int one = 1; + /* Ignore errors */ + sock_setopt(desc->s, SOL_SOCKET, SO_BSDCOMPAT, &one, + sizeof(one)); + } +#endif + } + return replen; + + + case INET_REQ_FDOPEN: /* pass in an open (and bound) socket */ + DEBUGF(("packet inet_ctl(%ld): FDOPEN\r\n", (long)desc->port)); + if ((len == 5) && (buf[0] == INET_AF_INET)) + replen = inet_ctl_fdopen(desc, AF_INET, SOCK_DGRAM, + (SOCKET)get_int32(buf+1),rbuf,rsize); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if ((len == 5) && (buf[0] == INET_AF_INET6)) + replen = inet_ctl_fdopen(desc, AF_INET6, SOCK_DGRAM, + (SOCKET)get_int32(buf+1),rbuf,rsize); +#endif + else + return ctl_error(EINVAL, rbuf, rsize); + + if ((*rbuf)[0] != INET_REP_ERROR) { + if (desc->active) + sock_select(desc,FD_READ,1); +#ifdef HAVE_SO_BSDCOMPAT + /* + * Make sure that sending UDP packets to a non existing port on an + * existing machine doesn't close the socket. (Linux behaves this + * way) + */ + if (should_use_so_bsdcompat()) { + int one = 1; + /* Ignore errors */ + sock_setopt(desc->s, SOL_SOCKET, SO_BSDCOMPAT, &one, + sizeof(one)); + } +#endif + } + return replen; + + + case INET_REQ_CLOSE: + DEBUGF(("packet_inet_ctl(%ld): CLOSE\r\n", (long)desc->port)); + erl_inet_close(desc); + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + return 0; + + + case INET_REQ_CONNECT: { + /* UDP and SCTP connect operations are completely different. UDP + connect means only setting the default peer addr locally, so + it is always synchronous. SCTP connect means actual establish- + ing of an SCTP association with a remote peer, so it is async- + ronous, and similar to TCP connect. However, unlike TCP, SCTP + allows the socket to have multiple simultaneous associations: + */ + int code; + char tbuf[2]; + unsigned timeout; + + DEBUGF(("packet_inet_ctl(%ld): CONNECT\r\n", (long)desc->port)); + + /* INPUT: [ Timeout(4), Port(2), Address(N) ] */ + + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + + if (!IS_BOUND(desc)) + return ctl_xerror(EXBADSEQ, rbuf, rsize); +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) { + inet_address remote; + + if (IS_CONNECTING(desc)) + return ctl_error(EINVAL, rbuf, rsize); + if (len < 6) + return ctl_error(EINVAL, rbuf, rsize); + timeout = get_int32(buf); + buf += 4; + len -= 4; + + /* For SCTP, we do not set the peer's addr in desc->remote, as + multiple peers are possible: */ + if (inet_set_address(desc->sfamily, &remote, buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + + sock_select(desc, FD_CONNECT, 1); + code = sock_connect(desc->s, &remote.sa, len); + + if ((code == SOCKET_ERROR) && (sock_errno() == EINPROGRESS)) { + /* XXX: Unix only -- WinSock would have a different cond! */ + desc->state = SCTP_STATE_CONNECTING; + if (timeout != INET_INFINITY) + driver_set_timer(desc->port, timeout); + enq_async(desc, tbuf, INET_REQ_CONNECT); + } + else if (code == 0) { /* OK we are connected */ + sock_select(desc, FD_CONNECT, 0); + desc->state = PACKET_STATE_CONNECTED; + enq_async(desc, tbuf, INET_REQ_CONNECT); + async_ok(desc); + } + else { + sock_select(desc, FD_CONNECT, 0); + return ctl_error(sock_errno(), rbuf, rsize); + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } +#endif + /* UDP */ + if (len == 0) { + /* What does it mean??? NULL sockaddr??? */ + sock_connect(desc->s, (struct sockaddr*) NULL, 0); + desc->state &= ~INET_F_ACTIVE; + enq_async(desc, tbuf, INET_REQ_CONNECT); + async_ok (desc); + } + else if (len < 6) + return ctl_error(EINVAL, rbuf, rsize); + else { + timeout = get_int32(buf); /* IGNORED */ + buf += 4; + len -= 4; + if (inet_set_address(desc->sfamily, + &desc->remote, buf, &len) == NULL) + return ctl_error(EINVAL, rbuf, rsize); + + code = sock_connect(desc->s, + (struct sockaddr*) &desc->remote, len); + if (code == SOCKET_ERROR) { + sock_connect(desc->s, (struct sockaddr*) NULL, 0); + desc->state &= ~INET_F_ACTIVE; + return ctl_error(sock_errno(), rbuf, rsize); + } + else /* ok we are connected */ { + enq_async(desc, tbuf, INET_REQ_CONNECT); + desc->state |= INET_F_ACTIVE; + async_ok (desc); + } + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + +#ifdef HAVE_SCTP + case SCTP_REQ_LISTEN: + { /* LISTEN is only for SCTP sockets, not UDP. This code is borrowed + from the TCP section. Returns: {ok,[]} on success. + */ + int flag; + + DEBUGF(("packet_inet_ctl(%ld): LISTEN\r\n", (long)desc->port)); + if (!IS_SCTP(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (!IS_BOUND(desc)) + return ctl_xerror(EXBADSEQ, rbuf, rsize); + + /* The arg is a binary value: 1:enable, 0:disable */ + if (len != 1) + return ctl_error(EINVAL, rbuf, rsize); + flag = get_int8(buf); + + if (sock_listen(desc->s, flag) == SOCKET_ERROR) + return ctl_error(sock_errno(), rbuf, rsize); + + desc->state = SCTP_STATE_LISTEN; /* XXX: not used? */ + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } + + case SCTP_REQ_BINDX: + { /* Multi-homing bind for SCTP: */ + /* Construct the list of addresses we bind to. The curr limit is + 256 addrs. Buff structure: Flags(1), ListItem,...: + */ + struct sockaddr addrs[256]; + char* curr; + int add_flag, n, rflag; + + if (!IS_SCTP(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + + curr = buf; + add_flag = get_int8(curr); + curr++; + + for(n=0; n < 256 && curr < buf+len; n++) + { + /* List item format: Port(2), IP(4|16) -- compatible with + "inet_set_address": */ + inet_address tmp; + int alen = buf + len - curr; + curr = inet_set_address(desc->sfamily, &tmp, curr, &alen); + if (curr == NULL) + return ctl_error(EINVAL, rbuf, rsize); + + /* Now: we need to squeeze "tmp" into the size of "sockaddr", + which is smaller than "tmp" for IPv6 (extra IN6 info will + be cut off): */ + memcpy(addrs + n, &tmp, sizeof(struct sockaddr)); + } + /* Make the real flags: */ + rflag = add_flag ? SCTP_BINDX_ADD_ADDR : SCTP_BINDX_REM_ADDR; + + /* Invoke the call: */ + if (p_sctp_bindx(desc->s, addrs, n, rflag) < 0) + return ctl_error(sock_errno(), rbuf, rsize); + + desc->state = INET_STATE_BOUND; + + return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); + } +#endif /* HAVE_SCTP */ + + case PACKET_REQ_RECV: + { /* THIS IS A FRONT-END for "recv*" requests. It only enqueues the + request and possibly returns the data immediately available. + The actual data returning function is the back-end ("*input"): + */ + unsigned timeout; + char tbuf[2]; + + DEBUGF(("packet_inet_ctl(%ld): RECV\r\n", (long)desc->port)); + /* INPUT: Timeout(4), Length(4) */ + if (!IS_OPEN(desc)) + return ctl_xerror(EXBADPORT, rbuf, rsize); + if (!IS_BOUND(desc)) + return ctl_error(EINVAL, rbuf, rsize); + if (desc->active || (len != 8)) + return ctl_error(EINVAL, rbuf, rsize); + timeout = get_int32(buf); + /* The 2nd arg, Length(4), is ignored for both UDP ans SCTP protocols, + since they are msg-oriented. */ + + if (enq_async(desc, tbuf, PACKET_REQ_RECV) < 0) + return ctl_error(EALREADY, rbuf, rsize); + + if (packet_inet_input(udesc, desc->event) == 0) { + if (timeout == 0) + async_error_am(desc, am_timeout); + else { + if (timeout != INET_INFINITY) + driver_set_timer(desc->port, timeout); + } + } + return ctl_reply(INET_REP_OK, tbuf, 2, rbuf, rsize); + } + + default: + /* Delegate the request to the INET layer. In particular, + INET_REQ_BIND goes here. If the req is not recognised + there either, an error is returned: + */ + return inet_ctl(desc, cmd, buf, len, rbuf, rsize); + } +} + +static void packet_inet_timeout(ErlDrvData e) +{ + udp_descriptor * udesc = (udp_descriptor*) e; + inet_descriptor * desc = INETP(udesc); + if (!(desc->active)) + sock_select(desc, FD_READ, 0); + async_error_am (desc, am_timeout); +} + + +/* THIS IS A "send*" REQUEST; on the Erlang side: "port_command". +** input should be: P1 P0 Address buffer . +** For UDP, buffer (after Address) is just data to be sent. +** For SCTP, buffer contains a list representing 2 items: +** (1) 6 parms for sctp_sndrcvinfo, as in sctp_get_sendparams(); +** (2) 0+ real data bytes. +** There is no destination address -- SCTYP send is performed over +** an existing association, using "sctp_sndrcvinfo" specified. +*/ +static void packet_inet_command(ErlDrvData e, char* buf, int len) +{ + udp_descriptor * udesc= (udp_descriptor*) e; + inet_descriptor* desc = INETP(udesc); + char* ptr = buf; + char* qtr; + int sz; + int code; + inet_address other; + + desc->caller = driver_caller(desc->port); + + if (!IS_OPEN(desc)) { + inet_reply_error(desc, EINVAL); + return; + } + if (!IS_BOUND(desc)) { + inet_reply_error(desc, EINVAL); + return; + } + +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) + { + int data_len; + struct iovec iov[1]; /* For real data */ + struct msghdr mhdr; /* Message wrapper */ + struct sctp_sndrcvinfo *sri; /* The actual ancilary data */ + union { /* For ancilary data */ + struct cmsghdr hdr; + char ancd[CMSG_SPACE(sizeof(*sri))]; + } cmsg; + + if (len < SCTP_GET_SENDPARAMS_LEN) { + inet_reply_error(desc, EINVAL); + return; + } + + /* The ancilary data */ + sri = (struct sctp_sndrcvinfo *) (CMSG_DATA(&cmsg.hdr)); + /* Get the "sndrcvinfo" from the buffer, advancing the "ptr": */ + ptr = sctp_get_sendparams(sri, ptr); + + /* The ancilary data wrapper */ + cmsg.hdr.cmsg_level = IPPROTO_SCTP; + cmsg.hdr.cmsg_type = SCTP_SNDRCV; + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(*sri)); + + data_len = (buf + len) - ptr; + /* The whole msg. + * Solaris (XPG 4.2) requires iovlen >= 1 even for data_len == 0. + */ + mhdr.msg_name = NULL; /* Already connected */ + mhdr.msg_namelen = 0; + iov[0].iov_len = data_len; + iov[0].iov_base = ptr; /* The real data */ + mhdr.msg_iov = iov; + mhdr.msg_iovlen = 1; + mhdr.msg_control = cmsg.ancd; /* For ancilary data */ + mhdr.msg_controllen = cmsg.hdr.cmsg_len; + mhdr.msg_flags = 0; /* Not used with "sendmsg" */ + + /* Now do the actual sending. NB: "flags" in "sendmsg" itself are NOT + used: */ + code = sock_sendmsg(desc->s, &mhdr, 0); + goto check_result_code; + } +#endif + /* UDP socket. Even if it is connected, there is an address prefix + here -- ignored for connected sockets: */ + sz = len; + qtr = inet_set_address(desc->sfamily, &other, ptr, &sz); + if (qtr == NULL) { + inet_reply_error(desc, EINVAL); + return; + } + len -= (qtr - ptr); + ptr = qtr; + /* Now "ptr" is the user data ptr, "len" is data length: */ + inet_output_count(desc, len); + + if (desc->state & INET_F_ACTIVE) { /* connected (ignore address) */ + code = sock_send(desc->s, ptr, len, 0); + } + else { + code = sock_sendto(desc->s, ptr, len, 0, &other.sa, sz); + } + +#ifdef HAVE_SCTP + check_result_code: + /* "code" analysis is the same for both SCTP and UDP cases above: */ +#endif + if (code == SOCKET_ERROR) { + int err = sock_errno(); + inet_reply_error(desc, err); + } + else + inet_reply_ok(desc); +} + + +#ifdef __WIN32__ +static void packet_inet_event(ErlDrvData e, ErlDrvEvent event) +{ + udp_descriptor * udesc = (udp_descriptor*)e; + inet_descriptor* desc = INETP(udesc); + WSANETWORKEVENTS netEv; + + if ((WSAEnumNetworkEvents)(desc->s, desc->event, &netEv) != 0) { + DEBUGF(( "port %d: EnumNetwrokEvents = %d\r\n", + desc->port, sock_errno() )); + return; /* -1; */ + } + netEv.lNetworkEvents |= desc->forced_events; + if (netEv.lNetworkEvents & FD_READ) { + packet_inet_input(udesc, (HANDLE)event); + } +} + +#endif + +static void packet_inet_drv_input(ErlDrvData e, ErlDrvEvent event) +{ + (void) packet_inet_input((udp_descriptor*)e, (HANDLE)event); +} + +/* +** THIS IS A BACK-END FOR "recv*" REQUEST, which actually receives the +** data requested, and delivers them to the caller: +*/ +static int packet_inet_input(udp_descriptor* udesc, HANDLE event) +{ + inet_descriptor* desc = INETP(udesc); + int n; + unsigned int len; + inet_address other; + char abuf[sizeof(inet_address)]; /* buffer address; enough??? */ + int sz; + char* ptr; + ErlDrvBinary* buf; /* binary */ + int packet_count = udesc->read_packets; + int count = 0; /* number of packets delivered to owner */ +#ifdef HAVE_SCTP + struct msghdr mhdr; /* Top-level msg structure */ + struct iovec iov[1]; /* Data or Notification Event */ + char ancd[SCTP_ANC_BUFF_SIZE]; /* Ancillary Data */ + int short_recv = 0; +#endif + + while(packet_count--) { + len = sizeof(other); + sz = desc->bufsz; + /* Allocate space for message and address. NB: "bufsz" is in "desc", + but the "buf" itself is allocated separately: + */ + if ((buf = alloc_buffer(sz+len)) == NULL) + return packet_error(udesc, ENOMEM); + ptr = buf->orig_bytes + len; /* pointer to message part */ + + /* Note: On Windows NT, recvfrom() fails if the socket is connected. */ +#ifdef HAVE_SCTP + /* For SCTP we must use recvmsg() */ + if (IS_SCTP(desc)) { + iov->iov_base = ptr; /* Data will come here */ + iov->iov_len = sz; /* Remaining buffer space */ + + mhdr.msg_name = &other; /* Peer addr comes into "other" */ + mhdr.msg_namelen = len; + mhdr.msg_iov = iov; + mhdr.msg_iovlen = 1; + mhdr.msg_control = ancd; + mhdr.msg_controllen = SCTP_ANC_BUFF_SIZE; + mhdr.msg_flags = 0; /* To be filled by "recvmsg" */ + + /* Do the actual SCTP receive: */ + n = sock_recvmsg(desc->s, &mhdr, 0); + goto check_result; + } +#endif + /* Use recv() instead on connected sockets. */ + if ((desc->state & INET_F_ACTIVE)) { + n = sock_recv(desc->s, ptr, sz, 0); + other = desc->remote; + } + else + n = sock_recvfrom(desc->s, ptr, sz, 0, &other.sa, &len); + +#ifdef HAVE_SCTP + check_result: +#endif + /* Analyse the result: */ + if (n == SOCKET_ERROR +#ifdef HAVE_SCTP + || (short_recv = (IS_SCTP(desc) && !(mhdr.msg_flags & MSG_EOR))) + /* NB: here we check for EOR not being set -- this is an error as + well, we don't support partial msgs: + */ +#endif + ) { + int err = sock_errno(); + release_buffer(buf); + if (err != ERRNO_BLOCK) { + if (!desc->active) { +#ifdef HAVE_SCTP + if (short_recv) + async_error_am(desc, am_short_recv); + else +#else + async_error(desc, err); +#endif + driver_cancel_timer(desc->port); + sock_select(desc,FD_READ,0); + } + else { + /* This is for an active desc only: */ + packet_error_message(udesc, err); + } + } + else if (!desc->active) + sock_select(desc,FD_READ,1); + return count; /* strange, not ready */ + } + else { + int offs; + int nsz; + int code; + unsigned int alen = len; + void * extra = NULL; + + inet_input_count(desc, n); + inet_get_address(desc->sfamily, abuf, &other, &alen); + /* Copy formatted address to the buffer allocated; "alen" is the + actual length which must be <= than the original reserved "len". + This means that the addr + data in the buffer are contiguous, + but they may start not at the "orig_bytes", but with some "offs" + from them: + */ + ASSERT (alen <= len); + sys_memcpy(ptr - alen, abuf, alen); + ptr -= alen; + nsz = n + alen; /* nsz = data + address */ + offs = ptr - buf->orig_bytes; /* initial pointer offset */ + + /* Check if we need to reallocate binary */ + if ((desc->mode == INET_MODE_BINARY) && + (desc->hsz < n) && (nsz < BIN_REALLOC_LIMIT(sz))) { + ErlDrvBinary* tmp; + if ((tmp = realloc_buffer(buf,nsz+offs)) != NULL) + buf = tmp; + } +#ifdef HAVE_SCTP + if (IS_SCTP(desc)) extra = &mhdr; +#endif + /* Actual parsing and return of the data received, occur here: */ + code = packet_reply_binary_data(desc, (unsigned int)alen, + buf, offs, nsz, extra); + free_buffer(buf); + if (code < 0) + return count; + count++; + if (!desc->active) { + driver_cancel_timer(desc->port); /* possibly cancel */ + sock_select(desc,FD_READ,0); + return count; /* passive mode (read one packet only) */ + } + } + } + return count; +} + +static void packet_inet_drv_output(ErlDrvData e, ErlDrvEvent event) +{ + (void) packet_inet_output((udp_descriptor*)e, (HANDLE)event); +} + +/* UDP/SCTP socket ready for output: +** This is a Back-End for Non-Block SCTP Connect (SCTP_STATE_CONNECTING) +*/ +static int packet_inet_output(udp_descriptor* udesc, HANDLE event) +{ + inet_descriptor* desc = INETP(udesc); + int ret = 0; + ErlDrvPort ix = desc->port; + + DEBUGF(("packet_inet_output(%ld) {s=%d\r\n", + (long)desc->port, desc->s)); + + if (desc->state == SCTP_STATE_CONNECTING) { + sock_select(desc, FD_CONNECT, 0); + + driver_cancel_timer(ix); /* posssibly cancel a timer */ +#ifndef __WIN32__ + /* + * XXX This is strange. This *should* work on Windows NT too, + * but doesn't. An bug in Winsock 2.0 for Windows NT? + * + * See "Unix Netwok Programming", W.R.Stevens, p 412 for a + * discussion about Unix portability and non blocking connect. + */ + +#ifndef SO_ERROR + { + int sz = sizeof(desc->remote); + int code = sock_peer(desc->s, + (struct sockaddr*) &desc->remote, &sz); + + if (code == SOCKET_ERROR) { + desc->state = PACKET_STATE_BOUND; /* restore state */ + ret = async_error(desc, sock_errno()); + goto done; + } + } +#else + { + int error = 0; /* Has to be initiated, we check it */ + unsigned int sz = sizeof(error); /* even if we get -1 */ + int code = sock_getopt(desc->s, SOL_SOCKET, SO_ERROR, + (void *)&error, &sz); + + if ((code < 0) || error) { + desc->state = PACKET_STATE_BOUND; /* restore state */ + ret = async_error(desc, error); + goto done; + } + } +#endif /* SOCKOPT_CONNECT_STAT */ +#endif /* !__WIN32__ */ + + desc->state = PACKET_STATE_CONNECTED; + async_ok(desc); + } + else { + sock_select(desc,FD_CONNECT,0); + + DEBUGF(("packet_inet_output(%ld): bad state: %04x\r\n", + (long)desc->port, desc->state)); + } + done: + DEBUGF(("packet_inet_output(%ld) }\r\n", (long)desc->port)); + return ret; +} + +/*---------------------------------------------------------------------------*/ + +#ifdef __WIN32__ + +/* + * Although we no longer need to lookup all of winsock2 dynamically, + * there are still some function(s) we need to look up. + */ +static void find_dynamic_functions(void) +{ + char kernel_dll_name[] = "kernel32"; + HMODULE module; + module = GetModuleHandle(kernel_dll_name); + fpSetHandleInformation = (module != NULL) ? + (BOOL (WINAPI *)(HANDLE,DWORD,DWORD)) + GetProcAddress(module,"SetHandleInformation") : + NULL; +} + + + +/* + * We must make sure that the socket handles are not inherited + * by port programs (if there are inherited, the sockets will not + * get closed when the emulator terminates, and epmd and other Erlang + * nodes will not notice that we have exited). + * + * XXX It is not clear whether this works/is necessary in Windows 95. + * There could also be problems with Winsock implementations from other + * suppliers than Microsoft. + */ + +static SOCKET +make_noninheritable_handle(SOCKET s) +{ + if (s != INVALID_SOCKET) { + if (fpSetHandleInformation != NULL) { + (*fpSetHandleInformation)((HANDLE) s, HANDLE_FLAG_INHERIT, 0); + } else { + HANDLE non_inherited; + HANDLE this_process = GetCurrentProcess(); + if (DuplicateHandle(this_process, (HANDLE) s, + this_process, &non_inherited, 0, + FALSE, DUPLICATE_SAME_ACCESS)) { + sock_close(s); + s = (SOCKET) non_inherited; + } + } + } + return s; +} + +#endif /* UDP for __WIN32__ */ + +/* + * Multi-timers + */ + +static void absolute_timeout(unsigned millis, ErlDrvNowData *out) +{ + unsigned rest; + unsigned long millipart; + unsigned long secpart; + unsigned long megasecpart; + unsigned tmo_secs = (millis / 1000U); + unsigned tmo_millis = (millis % 1000); + driver_get_now(out); + rest = (out->microsecs) % 1000; + millipart = ((out->microsecs) / 1000UL); + if (rest >= 500) { + ++millipart; + } + secpart = out->secs; + megasecpart = out->megasecs; + millipart += tmo_millis; + secpart += (millipart / 1000000UL); + millipart %= 1000000UL; + secpart += tmo_secs; + megasecpart += (secpart / 1000000UL); + secpart %= 1000000UL; + out->megasecs = megasecpart; + out->secs = secpart; + out->microsecs = (millipart * 1000UL); +} + +static unsigned relative_timeout(ErlDrvNowData *in) +{ + ErlDrvNowData now; + unsigned rest; + unsigned long millipart, in_millis, in_secs, in_megasecs; + + driver_get_now(&now); + + in_secs = in->secs; + in_megasecs = in->megasecs; + + rest = (now.microsecs) % 1000; + millipart = ((now.microsecs) / 1000UL); + if (rest >= 500) { + ++millipart; + } + in_millis = ((in->microsecs) / 1000UL); + if ( in_millis < millipart ) { + if (in_secs > 0) { + --in_secs; + } else { + in_secs = (1000000UL - 1UL); + if (in_megasecs <= now.megasecs) { + return 0; + } else { + --in_megasecs; + } + } + in_millis += 1000UL; + } + in_millis -= millipart; + + if (in_secs < now.secs) { + if (in_megasecs <= now.megasecs) { + return 0; + } else { + --in_megasecs; + } + in_secs += 1000000; + } + in_secs -= now.secs; + if (in_megasecs < now.megasecs) { + return 0; + } else { + in_megasecs -= now.megasecs; + } + return (unsigned) ((in_megasecs * 1000000000UL) + + (in_secs * 1000UL) + + in_millis); +} + +#ifdef DEBUG +static int nowcmp(ErlDrvNowData *d1, ErlDrvNowData *d2) +{ + /* Assume it's not safe to do signed conversion on megasecs... */ + if (d1->megasecs < d2->megasecs) { + return -1; + } else if (d1->megasecs > d2->megasecs) { + return 1; + } else if (d1->secs != d2->secs) { + return ((int) d1->secs) - ((int) d2->secs); + } + return ((int) d1->microsecs) - ((int) d2->microsecs); +} +#endif + +static void fire_multi_timers(MultiTimerData **first, ErlDrvPort port, + ErlDrvData data) +{ + unsigned next_timeout; + if (!*first) { + ASSERT(0); + return; + } +#ifdef DEBUG + { + ErlDrvNowData chk; + driver_get_now(&chk); + chk.microsecs /= 10000UL; + chk.microsecs *= 10000UL; + chk.microsecs += 10000; + ASSERT(nowcmp(&chk,&((*first)->when)) >= 0); + } +#endif + do { + MultiTimerData *save = *first; + *first = save->next; + (*(save->timeout_function))(data,save->caller); + FREE(save); + if (*first == NULL) { + return; + } + (*first)->prev = NULL; + next_timeout = relative_timeout(&((*first)->when)); + } while (next_timeout == 0); + driver_set_timer(port,next_timeout); +} + +static void clean_multi_timers(MultiTimerData **first, ErlDrvPort port) +{ + MultiTimerData *p; + if (*first) { + driver_cancel_timer(port); + } + while (*first) { + p = *first; + *first = p->next; + FREE(p); + } +} +static void remove_multi_timer(MultiTimerData **first, ErlDrvPort port, MultiTimerData *p) +{ + if (p->prev != NULL) { + p->prev->next = p->next; + } else { + driver_cancel_timer(port); + *first = p->next; + if (*first) { + unsigned ntmo = relative_timeout(&((*first)->when)); + driver_set_timer(port,ntmo); + } + } + if (p->next != NULL) { + p->next->prev = p->prev; + } + FREE(p); +} + +static MultiTimerData *add_multi_timer(MultiTimerData **first, ErlDrvPort port, + ErlDrvTermData caller, unsigned timeout, + void (*timeout_fun)(ErlDrvData drv_data, + ErlDrvTermData caller)) +{ + MultiTimerData *mtd, *p, *s; + mtd = ALLOC(sizeof(MultiTimerData)); + absolute_timeout(timeout, &(mtd->when)); + mtd->timeout_function = timeout_fun; + mtd->caller = caller; + mtd->next = mtd->prev = NULL; + for(p = *first,s = NULL; p != NULL; s = p, p = p->next) { + if (p->when.megasecs >= mtd->when.megasecs) { + break; + } + } + if (!p || p->when.megasecs > mtd->when.megasecs) { + goto found; + } + for (; p!= NULL; s = p, p = p->next) { + if (p->when.secs >= mtd->when.secs) { + break; + } + } + if (!p || p->when.secs > mtd->when.secs) { + goto found; + } + for (; p!= NULL; s = p, p = p->next) { + if (p->when.microsecs >= mtd->when.microsecs) { + break; + } + } + found: + if (!p) { + if (!s) { + *first = mtd; + } else { + s->next = mtd; + mtd->prev = s; + } + } else { + if (!s) { + *first = mtd; + } else { + s->next = mtd; + mtd->prev = s; + } + mtd->next = p; + p->prev = mtd; + } + if (!s) { + if (mtd->next) { + driver_cancel_timer(port); + } + driver_set_timer(port,timeout); + } + return mtd; +} + + + + + +/*----------------------------------------------------------------------------- + + Subscription + +-----------------------------------------------------------------------------*/ + +static int +save_subscriber(subs, subs_pid) +subs_list *subs; ErlDrvTermData subs_pid; +{ + subs_list *tmp; + + if(NO_SUBSCRIBERS(subs)) { + subs->subscriber = subs_pid; + subs->next = NULL; + } + else { + tmp = subs->next; + subs->next = ALLOC(sizeof(subs_list)); + if(subs->next == NULL) { + subs->next = tmp; + return 0; + } + subs->next->subscriber = subs_pid; + subs->next->next = tmp; + } + return 1; +} + +static void +free_subscribers(subs) +subs_list *subs; +{ + subs_list *this; + subs_list *next; + + this = subs->next; + while(this) { + next = this->next; + FREE((void *) this); + this = next; + } + + subs->subscriber = NO_PROCESS; + subs->next = NULL; +} + +static void send_to_subscribers +( + ErlDrvPort port, + subs_list *subs, + int free_subs, + ErlDrvTermData msg[], + int msg_len +) +{ + subs_list *this; + subs_list *next; + int first = 1; + + if(NO_SUBSCRIBERS(subs)) + return; + + this = subs; + while(this) { + + (void) driver_send_term(port, this->subscriber, msg, msg_len); + + if(free_subs && !first) { + next = this->next; + FREE((void *) this); + this = next; + } + else + this = this->next; + first = 0; + } + + if(free_subs) { + subs->subscriber = NO_PROCESS; + subs->next = NULL; + } + +} + +/* + * A *very* limited socket interface. Used by the memory tracer + * (erl_mtrace.c). + */ +#include "erl_sock.h" + +erts_sock_t erts_sock_open(void) +{ + SOCKET s; + + if(!sock_init()) + return ERTS_SOCK_INVALID_SOCKET; + + s = sock_open(AF_INET, SOCK_STREAM, 0); + + if (s == INVALID_SOCKET) + return ERTS_SOCK_INVALID_SOCKET; + + return (erts_sock_t) s; +} + +void erts_sock_close(erts_sock_t socket) +{ + if (socket != ERTS_SOCK_INVALID_SOCKET) + sock_close((SOCKET) socket); +} + + +int erts_sock_connect(erts_sock_t socket, byte *ip_addr, int len, Uint16 port) +{ + SOCKET s = (SOCKET) socket; + char buf[2 + 4]; + int blen = 6; + inet_address addr; + + if (socket == ERTS_SOCK_INVALID_SOCKET || len != 4) + return 0; + + put_int16(port, buf); + memcpy((void *) (buf + 2), (void *) ip_addr, 4); + + if (!inet_set_address(AF_INET, &addr, buf, &blen)) + return 0; + + if (SOCKET_ERROR == sock_connect(s, + (struct sockaddr *) &addr, + sizeof(struct sockaddr_in))) + return 0; + return 1; +} + +Sint erts_sock_send(erts_sock_t socket, const void *buf, Sint len) +{ + return (Sint) sock_send((SOCKET) socket, buf, (size_t) len, 0); +} + + +int erts_sock_gethostname(char *buf, int bufsz) +{ + if (sock_hostname(buf, bufsz) == SOCKET_ERROR) + return -1; + return 0; +} + + +int erts_sock_errno() +{ + return sock_errno(); +} diff --git a/erts/emulator/drivers/common/ram_file_drv.c b/erts/emulator/drivers/common/ram_file_drv.c new file mode 100644 index 0000000000..2e3aeb981e --- /dev/null +++ b/erts/emulator/drivers/common/ram_file_drv.c @@ -0,0 +1,692 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * RAM File operations + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Operations */ + +/* defined "file" functions */ +#define RAM_FILE_OPEN 1 +#define RAM_FILE_READ 2 +#define RAM_FILE_LSEEK 3 +#define RAM_FILE_WRITE 4 +#define RAM_FILE_FSYNC 9 +#define RAM_FILE_TRUNCATE 14 +#define RAM_FILE_PREAD 17 +#define RAM_FILE_PWRITE 18 + +/* other operations */ +#define RAM_FILE_GET 30 +#define RAM_FILE_SET 31 +#define RAM_FILE_GET_CLOSE 32 /* get_file/close */ +#define RAM_FILE_COMPRESS 33 /* compress file */ +#define RAM_FILE_UNCOMPRESS 34 /* uncompress file */ +#define RAM_FILE_UUENCODE 35 /* uuencode file */ +#define RAM_FILE_UUDECODE 36 /* uudecode file */ +#define RAM_FILE_SIZE 37 /* get file size */ +/* possible new operations include: + DES_ENCRYPT + DES_DECRYPT + CRC-32, CRC-16, CRC-CCITT + IP-CHECKSUM +*/ + +/* + * Open modes for RAM_FILE_OPEN. + */ +#define RAM_FILE_MODE_READ 1 +#define RAM_FILE_MODE_WRITE 2 /* Implies truncating file + * when used alone. */ +#define RAM_FILE_MODE_READ_WRITE 3 + +/* + * Seek modes for RAM_FILE_LSEEK. + */ +#define RAM_FILE_SEEK_SET 0 +#define RAM_FILE_SEEK_CUR 1 +#define RAM_FILE_SEEK_END 2 + +/* Return codes */ + +#define RAM_FILE_RESP_OK 0 +#define RAM_FILE_RESP_ERROR 1 +#define RAM_FILE_RESP_DATA 2 +#define RAM_FILE_RESP_NUMBER 3 +#define RAM_FILE_RESP_INFO 4 + +#include <stdio.h> +#include <ctype.h> +#include <limits.h> + +#include "sys.h" +#include "erl_driver.h" +#include "zlib.h" +#include "gzio.h" + +#ifndef NULL +#define NULL ((void*)0) +#endif + +#define BFILE_BLOCK 1024 + +typedef unsigned char uchar; + +static ErlDrvData rfile_start(ErlDrvPort, char*); +static int rfile_init(void); +static void rfile_stop(ErlDrvData); +static void rfile_command(ErlDrvData, char*, int); + + +struct erl_drv_entry ram_file_driver_entry = { + rfile_init, + rfile_start, + rfile_stop, + rfile_command, + NULL, + NULL, + "ram_file_drv" +}; + +/* A File is represented as a array of bytes, this array is + reallocated when needed. A possibly better implementation + whould be to have a vector of blocks. This may be implemented + when we have the commandv/driver_outputv +*/ +typedef struct ram_file { + ErlDrvPort port; /* the associcated port */ + int flags; /* flags read/write */ + ErlDrvBinary* bin; /* binary to hold binary file */ + char* buf; /* buffer start (in binary) */ + int size; /* buffer size (allocated) */ + int cur; /* current position in buffer */ + int end; /* end position in buffer */ +} RamFile; + +#ifdef LOADABLE +static int rfile_finish(DriverEntry* drv) +{ + return 0; +} + +DriverEntry* driver_init(void *handle) +{ + ram_file_driver_entry.handle = handle; + ram_file_driver_entry.driver_name = "ram_file_drv"; + ram_file_driver_entry.finish = rfile_finish; + ram_file_driver_entry.init = rfile_init; + ram_file_driver_entry.start = rfile_start; + ram_file_driver_entry.stop = rfile_stop; + ram_file_driver_entry.output = rfile_command; + ram_file_driver_entry.ready_input = NULL; + ram_file_driver_entry.ready_output = NULL; + return &ram_file_driver_entry; +} +#endif + +static int rfile_init(void) +{ + return 0; +} + +static ErlDrvData rfile_start(ErlDrvPort port, char* buf) +{ + RamFile* f; + + if ((f = (RamFile*) driver_alloc(sizeof(RamFile))) == NULL) { + errno = ENOMEM; + return ERL_DRV_ERROR_ERRNO; + } + f->port = port; + f->flags = 0; + f->bin = NULL; + f->buf = NULL; + f->size = f->cur = f->end = 0; + return (ErlDrvData)f; +} + +static void rfile_stop(ErlDrvData e) +{ + RamFile* f = (RamFile*)e; + if (f->bin != NULL) + driver_free_binary(f->bin); + driver_free(f); +} + +/* + * Sends back an error reply to Erlang. + */ + +static int error_reply(RamFile *f, int err) +{ + char response[256]; /* Response buffer. */ + char* s; + char* t; + + /* + * Contents of buffer sent back: + * + * +-----------------------------------------+ + * | RAM_FILE_RESP_ERROR | Posix error id string | + * +-----------------------------------------+ + */ + response[0] = RAM_FILE_RESP_ERROR; + for (s = erl_errno_id(err), t = response+1; *s; s++, t++) + *t = tolower(*s); + driver_output2(f->port, response, t-response, NULL, 0); + return 0; +} + +static int reply(RamFile *f, int ok, int err) +{ + if (!ok) + error_reply(f, err); + else { + char c = RAM_FILE_RESP_OK; + driver_output2(f->port, &c, 1, NULL, 0); + } + return 0; +} + +static int numeric_reply(RamFile *f, int result) +{ + char tmp[5]; + + /* + * Contents of buffer sent back: + * + * +-----------------------------------------------+ + * | RAM_FILE_RESP_NUMBER | 32-bit number (big-endian) | + * +-----------------------------------------------+ + */ + + tmp[0] = RAM_FILE_RESP_NUMBER; + put_int32(result, tmp+1); + driver_output2(f->port, tmp, sizeof(tmp), NULL, 0); + return 0; +} + +/* install bin as the new binary reset all pointer */ + +static void ram_file_set(RamFile *f, ErlDrvBinary *bin, int bsize, int len) +{ + f->size = bsize; + f->buf = bin->orig_bytes; + f->cur = 0; + f->end = len; + f->bin = bin; +} + +static int ram_file_init(RamFile *f, char *buf, int count, int *error) +{ + int bsize; + ErlDrvBinary* bin; + + if (count < 0) { + *error = EINVAL; + return -1; + } + if ((bsize = (count+BFILE_BLOCK+(BFILE_BLOCK>>1)) & ~(BFILE_BLOCK-1)) + < 0) { + bsize = INT_MAX; + } + + if (f->bin == NULL) + bin = driver_alloc_binary(bsize); + else + bin = driver_realloc_binary(f->bin, bsize); + if (bin == NULL) { + *error = ENOMEM; + return -1; + } + sys_memzero(bin->orig_bytes, bsize); + sys_memcpy(bin->orig_bytes, buf, count); + ram_file_set(f, bin, bsize, count); + return count; +} + +static int ram_file_expand(RamFile *f, int size, int *error) +{ + int bsize; + ErlDrvBinary* bin; + + if (size < 0) { + *error = EINVAL; + return -1; + } + if ((bsize = (size+BFILE_BLOCK+(BFILE_BLOCK>>1)) & ~(BFILE_BLOCK-1)) + < 0) { + bsize = INT_MAX; + } + + if (bsize <= f->size) + return f->size; + else { + if ((bin = driver_realloc_binary(f->bin, bsize)) == NULL) { + *error = ENOMEM; + return -1; + } + sys_memzero(bin->orig_bytes+f->size, bsize - f->size); + f->size = bsize; + f->buf = bin->orig_bytes; + f->bin = bin; + return bsize; + } +} + + +static int ram_file_write(RamFile *f, char *buf, int len, + int *location, int *error) +{ + int cur = f->cur; + + if (!(f->flags & RAM_FILE_MODE_WRITE)) { + *error = EBADF; + return -1; + } + if (location) cur = *location; + if (cur < 0 || len < 0 || cur+len < 0) { + *error = EINVAL; + return -1; + } + if (cur+len > f->size && ram_file_expand(f, cur+len, error) < 0) { + return -1; + } + if (len) sys_memcpy(f->buf+cur, buf, len); + cur += len; + if (cur > f->end) f->end = cur; + if (! location) f->cur = cur; + return len; +} + +static int ram_file_read(RamFile *f, int len, ErlDrvBinary **bp, + int *location, int *error) +{ + ErlDrvBinary* bin; + int cur = f->cur; + + if (!(f->flags & RAM_FILE_MODE_READ)) { + *error = EBADF; + return -1; + } + if (location) cur = *location; + if (cur < 0 || len < 0) { + *error = EINVAL; + return -1; + } + if (cur < f->end) { + if (len > f->end-cur) len = f->end - cur; + } else { + len = 0; /* eof */ + } + if ((bin = driver_alloc_binary(len)) == NULL) { + *error = ENOMEM; + return -1; + } + if (len) sys_memcpy(bin->orig_bytes, f->buf+cur, len); + *bp = bin; + if (! location) f->cur = cur + len; + return len; +} + +static int ram_file_seek(RamFile *f, int offset, int whence, int *error) +{ + int pos; + + if (f->flags == 0) { + *error = EBADF; + return -1; + } + switch(whence) { + case RAM_FILE_SEEK_SET: pos = offset; break; + case RAM_FILE_SEEK_CUR: pos = f->cur + offset; break; + case RAM_FILE_SEEK_END: pos = f->end + offset; break; + default: *error = EINVAL; return -1; + } + if (pos < 0) { + *error = EINVAL; + return -1; + } + return f->cur = pos; +} + +#define UUMASK(x) ((x)&0x3F) +#define uu_encode(x) (UUMASK(x)+32) + +/* calculate max number of quadrauple bytes given max line length */ +#define UULINE(n) ( (((n)-1) / 4) * 3) + +#define UNIX_LINE 61 /* 61 character lines => 45 uncoded => 60 coded */ + +#define uu_pack(p, c1, c2, c3) \ + (p)[0] = uu_encode((c1) >> 2), \ + (p)[1] = uu_encode(((c1) << 4) | ((c2) >> 4)), \ + (p)[2] = uu_encode(((c2) << 2) | ((c3) >> 6)), \ + (p)[3] = uu_encode(c3) + +static int ram_file_uuencode(RamFile *f) +{ + int code_len = UULINE(UNIX_LINE); + int len = f->end; + int usize = (len*4+2)/3 + 2*(len/code_len+1) + 2 + 1; + ErlDrvBinary* bin; + uchar* inp; + uchar* outp; + int count = 0; + + if ((bin = driver_alloc_binary(usize)) == NULL) + return error_reply(f, ENOMEM); + outp = (uchar*)bin->orig_bytes; + inp = (uchar*)f->buf; + + while(len > 0) { + int c1, c2, c3; + int n = (len >= code_len) ? code_len : len; + + len -= n; + *outp++ = uu_encode(UUMASK(n)); + count++; + while (n >= 3) { + c1 = inp[0]; + c2 = inp[1]; + c3 = inp[2]; + uu_pack(outp, c1, c2, c3); + inp += 3; n -= 3; + outp += 4; count += 4; + } + if (n == 2) { + c1 = inp[0]; + c2 = inp[1]; + uu_pack(outp, c1, c2, 0); + inp += 2; + outp += 4; count += 4; + } + else if (n == 1) { + c1 = inp[0]; + uu_pack(outp, c1, 0, 0); + inp += 1; + outp += 4; count += 4; + } + *outp++ = '\n'; + count++; + } + *outp++ = ' '; /* this end of file 0 length !!! */ + *outp++ = '\n'; + count += 2; + + driver_free_binary(f->bin); + ram_file_set(f, bin, usize, count); + return numeric_reply(f, count); +} + + +#define uu_decode(x) ((x)-32) + +static int ram_file_uudecode(RamFile *f) +{ + int len = f->end; + int usize = ( (len+3) / 4 ) * 3; + ErlDrvBinary* bin; + uchar* inp; + uchar* outp; + int count = 0; + int n; + + if ((bin = driver_alloc_binary(usize)) == NULL) + return error_reply(f, ENOMEM); + outp = (uchar*)bin->orig_bytes; + inp = (uchar*)f->buf; + + while(len > 0) { + if ((n = uu_decode(*inp++)) < 0) + goto error; + len--; + if ((n == 0) && (*inp == '\n')) + break; + count += n; /* count characters */ + while((n > 0) && (len >= 4)) { + int c1, c2, c3, c4; + c1 = uu_decode(inp[0]); + c2 = uu_decode(inp[1]); + c3 = uu_decode(inp[2]); + c4 = uu_decode(inp[3]); + inp += 4; + len -= 4; + + switch(n) { + case 1: + *outp++ = (c1 << 2) | (c2 >> 4); + n = 0; + break; + case 2: + *outp++ = (c1 << 2) | (c2 >> 4); + *outp++ = (c2 << 4) | (c3 >> 2); + n = 0; + break; + default: + *outp++ = (c1 << 2) | (c2 >> 4); + *outp++ = (c2 << 4) | (c3 >> 2); + *outp++ = (c3 << 6) | c4; + n -= 3; + break; + } + } + if ((n != 0) || (*inp++ != '\n')) + goto error; + len--; + } + driver_free_binary(f->bin); + ram_file_set(f, bin, usize, count); + return numeric_reply(f, count); + + error: + driver_free_binary(bin); + return error_reply(f, EINVAL); +} + + +static int ram_file_compress(RamFile *f) +{ + int size = f->end; + ErlDrvBinary* bin; + + if ((bin = erts_gzdeflate_buffer(f->buf, size)) == NULL) { + return error_reply(f, EINVAL); + } + driver_free_binary(f->bin); + size = bin->orig_size; + ram_file_set(f, bin, size, size); + return numeric_reply(f, size); +} + +/* Tricky since we dont know the expanded size !!! */ +/* First attempt is to double the size of input */ +/* loop until we don't get Z_BUF_ERROR */ + +static int ram_file_uncompress(RamFile *f) +{ + int size = f->end; + ErlDrvBinary* bin; + + if ((bin = erts_gzinflate_buffer(f->buf, size)) == NULL) { + return error_reply(f, EINVAL); + } + driver_free_binary(f->bin); + size = bin->orig_size; + ram_file_set(f, bin, size, size); + return numeric_reply(f, size); +} + + +static void rfile_command(ErlDrvData e, char* buf, int count) +{ + RamFile* f = (RamFile*)e; + int error = 0; + ErlDrvBinary* bin; + char header[5]; /* result code + count */ + int offset; + int origin; /* Origin of seek. */ + int n; + + count--; + switch(*(uchar*)buf++) { + case RAM_FILE_OPEN: /* args is initial data */ + f->flags = get_int32(buf); + if (ram_file_init(f, buf+4, count-4, &error) < 0) + error_reply(f, error); + else + numeric_reply(f, 0); /* 0 is not used */ + break; + + case RAM_FILE_FSYNC: + if (f->flags == 0) + error_reply(f, EBADF); + else + reply(f, 1, 0); + break; + + case RAM_FILE_WRITE: + if (ram_file_write(f, buf, count, NULL, &error) < 0) + error_reply(f, error); + else + numeric_reply(f, count); + break; + + case RAM_FILE_PWRITE: + if ((offset = get_int32(buf)) < 0) + error_reply(f, EINVAL); + else if (ram_file_write(f, buf+4, count-4, &offset, &error) < 0) + error_reply(f, error); + else + numeric_reply(f, count-4); + break; + + case RAM_FILE_LSEEK: + offset = get_int32(buf); + origin = get_int32(buf+4); + if ((offset = ram_file_seek(f, offset, origin, &error)) < 0) + error_reply(f, error); + else + numeric_reply(f, offset); + break; + + case RAM_FILE_PREAD: + if ((offset = get_int32(buf)) < 0) { + error_reply(f, EINVAL); + break; + } + + count = get_int32(buf+4); + if ((n = ram_file_read(f, count, &bin, &offset, &error)) < 0) { + error_reply(f, error); + } else { + header[0] = RAM_FILE_RESP_DATA; + put_int32(n, header+1); + driver_output_binary(f->port, header, sizeof(header), + bin, 0, n); + driver_free_binary(bin); + } + break; + + case RAM_FILE_READ: + count = get_int32(buf); + if ((n = ram_file_read(f, count, &bin, NULL, &error)) < 0) + error_reply(f, error); + else { + header[0] = RAM_FILE_RESP_DATA; + put_int32(n, header+1); + driver_output_binary(f->port, header, sizeof(header), + bin, 0, n); + driver_free_binary(bin); + } + break; + + case RAM_FILE_TRUNCATE: + if (!(f->flags & RAM_FILE_MODE_WRITE)) { + error_reply(f, EACCES); + break; + } + if (f->end > f->cur) + sys_memzero(f->buf + f->cur, f->end - f->cur); + f->end = f->cur; + reply(f, 1, 0); + break; + + case RAM_FILE_GET: /* return a copy of the file */ + n = f->end; /* length */ + if ((bin = driver_alloc_binary(n)) == NULL) { + error_reply(f, ENOMEM); + break; + } + sys_memcpy(bin->orig_bytes, f->buf, n); + + header[0] = RAM_FILE_RESP_DATA; + put_int32(n, header+1); + driver_output_binary(f->port, header, sizeof(header), + bin, 0, n); + driver_free_binary(bin); + break; + + case RAM_FILE_GET_CLOSE: /* return the file and close driver */ + n = f->end; /* length */ + bin = f->bin; + f->bin = NULL; /* NUKE IT */ + header[0] = RAM_FILE_RESP_DATA; + put_int32(n, header+1); + driver_output_binary(f->port, header, sizeof(header), + bin, 0, n); + driver_free_binary(bin); + driver_failure(f->port, 0); + break; + + case RAM_FILE_SIZE: + numeric_reply(f, f->end); + break; + + case RAM_FILE_SET: /* re-init file with new data */ + if ((n = ram_file_init(f, buf, count, &error)) < 0) + error_reply(f, error); + else + numeric_reply(f, n); /* 0 is not used */ + break; + + case RAM_FILE_COMPRESS: /* inline compress the file */ + ram_file_compress(f); + break; + + case RAM_FILE_UNCOMPRESS: /* inline uncompress file */ + ram_file_uncompress(f); + break; + + case RAM_FILE_UUENCODE: /* uuencode file */ + ram_file_uuencode(f); + break; + + case RAM_FILE_UUDECODE: /* uudecode file */ + ram_file_uudecode(f); + break; + } + /* + * Ignore anything else -- let the caller hang. + */ +} diff --git a/erts/emulator/drivers/common/zlib_drv.c b/erts/emulator/drivers/common/zlib_drv.c new file mode 100644 index 0000000000..723efeaa13 --- /dev/null +++ b/erts/emulator/drivers/common/zlib_drv.c @@ -0,0 +1,650 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2003-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * ZLib interface for erlang + * + */ +#include <stdio.h> +#include <zlib.h> +#include <errno.h> +#include <string.h> + +#include "erl_driver.h" + + +#define DEFLATE_INIT 1 +#define DEFLATE_INIT2 2 +#define DEFLATE_SETDICT 3 +#define DEFLATE_RESET 4 +#define DEFLATE_END 5 +#define DEFLATE_PARAMS 6 +#define DEFLATE 7 + +#define INFLATE_INIT 8 +#define INFLATE_INIT2 9 +#define INFLATE_SETDICT 10 +#define INFLATE_SYNC 11 +#define INFLATE_RESET 12 +#define INFLATE_END 13 +#define INFLATE 14 + +#define CRC32_0 15 +#define CRC32_1 16 +#define CRC32_2 17 + +#define SET_BUFSZ 18 +#define GET_BUFSZ 19 +#define GET_QSIZE 20 + +#define ADLER32_1 21 +#define ADLER32_2 22 + +#define CRC32_COMBINE 23 +#define ADLER32_COMBINE 24 + +#define DEFAULT_BUFSZ 4000 + +static int zlib_init(void); +static ErlDrvData zlib_start(ErlDrvPort port, char* buf); +static void zlib_stop(ErlDrvData e); +static int zlib_ctl(ErlDrvData drv_data, unsigned int command, char *buf, + int len, char **rbuf, int rlen); +static void zlib_outputv(ErlDrvData drv_data, ErlIOVec *ev); + +ErlDrvEntry zlib_driver_entry = { + zlib_init, + zlib_start, + zlib_stop, + NULL, /* output */ + NULL, /* ready_input */ + NULL, /* ready_output */ + "zlib_drv", + NULL, /* finish */ + NULL, /* handle */ + zlib_ctl, + NULL, /* timeout */ + zlib_outputv, + NULL, /* read_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL, /* handle2 */ + NULL, /* process_exit */ +}; + +typedef enum { + ST_NONE = 0, + ST_DEFLATE = 1, + ST_INFLATE = 2 +} ZLibState; + + +typedef struct { + z_stream s; + ZLibState state; + ErlDrvBinary* bin; + int binsz; + int binsz_need; + uLong crc; + int inflate_eos_seen; + int want_crc; /* 1 if crc is calculated on clear text */ + ErlDrvPort port; /* the associcated port */ +} ZLibData; + +static int zlib_inflate(ZLibData* d, int flush); +static int zlib_deflate(ZLibData* d, int flush); + +#if defined(_OSE_) || defined(__WIN32__) +static int i32(char* buf) +#else +static inline int i32(char* buf) +#endif +{ + return (int) ( + (((int)((unsigned char*)buf)[0]) << 24) | + (((int)((unsigned char*)buf)[1]) << 16) | + (((int)((unsigned char*)buf)[2]) << 8) | + (((int)((unsigned char*)buf)[3]) << 0)); +} + +static char* zlib_reason(int code, int* err) +{ + switch(code) { + case Z_OK: + *err = 0; + return "ok"; + case Z_STREAM_END: + *err = 0; + return "stream_end"; + case Z_ERRNO: + *err = 1; + return erl_errno_id(errno); + case Z_STREAM_ERROR: + *err = 1; + return "stream_error"; + case Z_DATA_ERROR: + *err = 1; + return "data_error"; + case Z_MEM_ERROR: + *err = 1; + return "mem_error"; + case Z_BUF_ERROR: + *err = 1; + return "buf_error"; + case Z_VERSION_ERROR: + *err = 1; + return "version_error"; + default: + *err = 1; + return "unknown_error"; + } +} + + +static int zlib_return(int code, char** rbuf, int rlen) +{ + int msg_code = 0; /* 0=ok, 1=error */ + char* dst = *rbuf; + char* src; + int len = 0; + + src = zlib_reason(code, &msg_code); + *dst++ = msg_code; + rlen--; + len = 1; + + while((rlen > 0) && *src) { + *dst++ = *src++; + rlen--; + len++; + } + return len; +} + +static int zlib_value2(int msg_code, int value, char** rbuf, int rlen) +{ + char* dst = *rbuf; + + if (rlen < 5) { + return -1; + } + *dst++ = msg_code; + *dst++ = (value >> 24) & 0xff; + *dst++ = (value >> 16) & 0xff; + *dst++ = (value >> 8) & 0xff; + *dst++ = value & 0xff; + return 5; +} + +static int zlib_value(int value, char** rbuf, int rlen) +{ + return zlib_value2(2, value, rbuf, rlen); +} + +static int zlib_output_init(ZLibData* d) +{ + if (d->bin != NULL) + driver_free_binary(d->bin); + if ((d->bin = driver_alloc_binary(d->binsz_need)) == NULL) + return -1; + d->binsz = d->binsz_need; + d->s.next_out = (unsigned char*)d->bin->orig_bytes; + d->s.avail_out = d->binsz; + return 0; +} + +/* + * Send compressed or uncompressed data + * and restart output procesing + */ +static int zlib_output(ZLibData* d) +{ + if (d->bin != NULL) { + int len = d->binsz - d->s.avail_out; + if (len > 0) { + if (driver_output_binary(d->port, NULL, 0, d->bin, 0, len) < 0) + return -1; + } + driver_free_binary(d->bin); + d->bin = NULL; + d->binsz = 0; + } + return zlib_output_init(d); +} + +static int zlib_inflate(ZLibData* d, int flush) +{ + int res = Z_OK; + + if ((d->bin == NULL) && (zlib_output_init(d) < 0)) { + errno = ENOMEM; + return Z_ERRNO; + } + + while ((driver_sizeq(d->port) > 0) && (res != Z_STREAM_END)) { + int vlen; + SysIOVec* iov = driver_peekq(d->port, &vlen); + int len; + int possibly_more_output = 0; + + d->s.next_in = iov[0].iov_base; + d->s.avail_in = iov[0].iov_len; + while((possibly_more_output || (d->s.avail_in > 0)) && (res != Z_STREAM_END)) { + res = inflate(&d->s, Z_NO_FLUSH); + if (res == Z_NEED_DICT) { + /* Essential to eat the header bytes that zlib has looked at */ + len = iov[0].iov_len - d->s.avail_in; + driver_deq(d->port, len); + return res; + } + if (res == Z_BUF_ERROR) { + /* Was possible more output, but actually not */ + res = Z_OK; + } + else if (res < 0) { + return res; + } + if (d->s.avail_out != 0) { + possibly_more_output = 0; + } else { + if (d->want_crc) + d->crc = crc32(d->crc, (unsigned char*)d->bin->orig_bytes, + d->binsz - d->s.avail_out); + zlib_output(d); + possibly_more_output = 1; + } + } + len = iov[0].iov_len - d->s.avail_in; + driver_deq(d->port, len); + } + + if (d->want_crc) { + d->crc = crc32(d->crc, (unsigned char*) d->bin->orig_bytes, + d->binsz - d->s.avail_out); + } + zlib_output(d); + if (res == Z_STREAM_END) { + d->inflate_eos_seen = 1; + } + return res; +} + +static int zlib_deflate(ZLibData* d, int flush) +{ + int res = Z_OK; + + if ((d->bin == NULL) && (zlib_output_init(d) < 0)) { + errno = ENOMEM; + return Z_ERRNO; + } + + while ((driver_sizeq(d->port) > 0) && (res != Z_STREAM_END)) { + int vlen; + SysIOVec* iov = driver_peekq(d->port, &vlen); + int len; + + d->s.next_in = iov[0].iov_base; + d->s.avail_in = iov[0].iov_len; + + while((d->s.avail_in > 0) && (res != Z_STREAM_END)) { + if ((res = deflate(&d->s, Z_NO_FLUSH)) < 0) { + return res; + } + if (d->s.avail_out == 0) { + zlib_output(d); + } + } + len = iov[0].iov_len - d->s.avail_in; + if (d->want_crc) { + d->crc = crc32(d->crc, iov[0].iov_base, len); + } + driver_deq(d->port, len); + } + + if (flush != Z_NO_FLUSH) { + if ((res = deflate(&d->s, flush)) < 0) { + return res; + } + if (flush == Z_FINISH) { + while (d->s.avail_out < d->binsz) { + zlib_output(d); + if (res == Z_STREAM_END) { + break; + } + if ((res = deflate(&d->s, flush)) < 0) { + return res; + } + } + } else { + while (d->s.avail_out == 0) { + zlib_output(d); + if ((res = deflate(&d->s, flush)) < 0) { + return res; + } + } + if (d->s.avail_out < d->binsz) { + zlib_output(d); + } + } + } + return res; +} + + + +static void* zlib_alloc(void* data, unsigned int items, unsigned int size) +{ + return (void*) driver_alloc(items*size); +} + +static void zlib_free(void* data, void* addr) +{ + driver_free(addr); +} + +static int zlib_init() +{ + return 0; +} + +static ErlDrvData zlib_start(ErlDrvPort port, char* buf) +{ + ZLibData* d; + + if ((d = (ZLibData*) driver_alloc(sizeof(ZLibData))) == NULL) + return ERL_DRV_ERROR_GENERAL; + + memset(&d->s, 0, sizeof(z_stream)); + + d->s.zalloc = zlib_alloc; + d->s.zfree = zlib_free; + d->s.opaque = d; + d->s.data_type = Z_BINARY; + + d->port = port; + d->state = ST_NONE; + d->bin = NULL; + d->binsz = 0; + d->binsz_need = DEFAULT_BUFSZ; + d->crc = crc32(0L, Z_NULL, 0); + d->inflate_eos_seen = 0; + d->want_crc = 0; + return (ErlDrvData)d; +} + + +static void zlib_stop(ErlDrvData e) +{ + ZLibData* d = (ZLibData*)e; + + if (d->state == ST_DEFLATE) + deflateEnd(&d->s); + else if (d->state == ST_INFLATE) + inflateEnd(&d->s); + + if (d->bin != NULL) + driver_free_binary(d->bin); + + driver_free(d); +} + +static int zlib_ctl(ErlDrvData drv_data, unsigned int command, char *buf, + int len, char **rbuf, int rlen) +{ + ZLibData* d = (ZLibData*)drv_data; + int res; + + switch(command) { + case DEFLATE_INIT: + if (len != 4) goto badarg; + if (d->state != ST_NONE) goto badarg; + res = deflateInit(&d->s, i32(buf)); + if (res == Z_OK) { + d->state = ST_DEFLATE; + d->want_crc = 0; + d->crc = crc32(0L, Z_NULL, 0); + } + return zlib_return(res, rbuf, rlen); + + case DEFLATE_INIT2: { + int wbits; + + if (len != 20) goto badarg; + if (d->state != ST_NONE) goto badarg; + wbits = i32(buf+8); + res = deflateInit2(&d->s, i32(buf), i32(buf+4), wbits, + i32(buf+12), i32(buf+16)); + if (res == Z_OK) { + d->state = ST_DEFLATE; + d->want_crc = (wbits < 0); + d->crc = crc32(0L, Z_NULL, 0); + } + return zlib_return(res, rbuf, rlen); + } + + case DEFLATE_SETDICT: + if (d->state != ST_DEFLATE) goto badarg; + res = deflateSetDictionary(&d->s, (unsigned char*)buf, len); + if (res == Z_OK) { + return zlib_value(d->s.adler, rbuf, rlen); + } else { + return zlib_return(res, rbuf, rlen); + } + + case DEFLATE_RESET: + if (len != 0) goto badarg; + if (d->state != ST_DEFLATE) goto badarg; + driver_deq(d->port, driver_sizeq(d->port)); + res = deflateReset(&d->s); + return zlib_return(res, rbuf, rlen); + + case DEFLATE_END: + if (len != 0) goto badarg; + if (d->state != ST_DEFLATE) goto badarg; + driver_deq(d->port, driver_sizeq(d->port)); + res = deflateEnd(&d->s); + d->state = ST_NONE; + return zlib_return(res, rbuf, rlen); + + case DEFLATE_PARAMS: + if (len != 8) goto badarg; + if (d->state != ST_DEFLATE) goto badarg; + res = deflateParams(&d->s, i32(buf), i32(buf+4)); + return zlib_return(res, rbuf, rlen); + + case DEFLATE: + if (d->state != ST_DEFLATE) goto badarg; + if (len != 4) goto badarg; + res = zlib_deflate(d, i32(buf)); + return zlib_return(res, rbuf, rlen); + + case INFLATE_INIT: + if (len != 0) goto badarg; + if (d->state != ST_NONE) goto badarg; + res = inflateInit(&d->s); + if (res == Z_OK) { + d->state = ST_INFLATE; + d->inflate_eos_seen = 0; + d->want_crc = 0; + d->crc = crc32(0L, Z_NULL, 0); + } + return zlib_return(res, rbuf, rlen); + + case INFLATE_INIT2: { + int wbits; + + if (len != 4) goto badarg; + if (d->state != ST_NONE) goto badarg; + wbits = i32(buf); + res = inflateInit2(&d->s, wbits); + if (res == Z_OK) { + d->state = ST_INFLATE; + d->inflate_eos_seen = 0; + d->want_crc = (wbits < 0); + d->crc = crc32(0L, Z_NULL, 0); + } + return zlib_return(res, rbuf, rlen); + } + + case INFLATE_SETDICT: + if (d->state != ST_INFLATE) goto badarg; + res = inflateSetDictionary(&d->s, (unsigned char*)buf, len); + return zlib_return(res, rbuf, rlen); + + case INFLATE_SYNC: + if (d->state != ST_INFLATE) goto badarg; + if (len != 0) goto badarg; + if (driver_sizeq(d->port) == 0) { + res = Z_BUF_ERROR; + } else { + int vlen; + SysIOVec* iov = driver_peekq(d->port, &vlen); + + d->s.next_in = iov[0].iov_base; + d->s.avail_in = iov[0].iov_len; + res = inflateSync(&d->s); + } + return zlib_return(res, rbuf, rlen); + + case INFLATE_RESET: + if (d->state != ST_INFLATE) goto badarg; + if (len != 0) goto badarg; + driver_deq(d->port, driver_sizeq(d->port)); + res = inflateReset(&d->s); + d->inflate_eos_seen = 0; + return zlib_return(res, rbuf, rlen); + + case INFLATE_END: + if (d->state != ST_INFLATE) goto badarg; + if (len != 0) goto badarg; + driver_deq(d->port, driver_sizeq(d->port)); + res = inflateEnd(&d->s); + if (res == Z_OK && d->inflate_eos_seen == 0) { + res = Z_DATA_ERROR; + } + d->state = ST_NONE; + return zlib_return(res, rbuf, rlen); + + case INFLATE: + if (d->state != ST_INFLATE) goto badarg; + if (len != 4) goto badarg; + res = zlib_inflate(d, i32(buf)); + if (res == Z_NEED_DICT) { + return zlib_value2(3, d->s.adler, rbuf, rlen); + } else { + return zlib_return(res, rbuf, rlen); + } + + case GET_QSIZE: + return zlib_value(driver_sizeq(d->port), rbuf, rlen); + + case GET_BUFSZ: + return zlib_value(d->binsz_need, rbuf, rlen); + + case SET_BUFSZ: { + int need; + if (len != 4) goto badarg; + need = i32(buf); + if ((need < 16) || (need > 0x00ffffff)) + goto badarg; + if (d->binsz_need != need) { + d->binsz_need = need; + if (d->bin != NULL) { + if (d->s.avail_out == d->binsz) { + driver_free_binary(d->bin); + d->bin = NULL; + d->binsz = 0; + } + else + zlib_output(d); + } + } + return zlib_return(Z_OK, rbuf, rlen); + } + + case CRC32_0: + return zlib_value(d->crc, rbuf, rlen); + + case CRC32_1: { + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned char*) buf, len); + return zlib_value(crc, rbuf, rlen); + } + + case CRC32_2: { + uLong crc; + if (len < 4) goto badarg; + crc = (unsigned int) i32(buf); + crc = crc32(crc, (unsigned char*) buf+4, len-4); + return zlib_value(crc, rbuf, rlen); + } + + case ADLER32_1: { + uLong adler = adler32(0L, Z_NULL, 0); + adler = adler32(adler, (unsigned char*) buf, len); + return zlib_value(adler, rbuf, rlen); + } + + case ADLER32_2: { + uLong adler; + if (len < 4) goto badarg; + adler = (unsigned int) i32(buf); + adler = adler32(adler, (unsigned char*) buf+4, len-4); + return zlib_value(adler, rbuf, rlen); + } + + case CRC32_COMBINE: { + uLong crc, crc1, crc2, len2; + if (len != 12) goto badarg; + crc1 = (unsigned int) i32(buf); + crc2 = (unsigned int) i32(buf+4); + len2 = (unsigned int) i32(buf+8); + crc = crc32_combine(crc1, crc2, len2); + return zlib_value(crc, rbuf, rlen); + } + + case ADLER32_COMBINE: { + uLong adler, adler1, adler2, len2; + if (len != 12) goto badarg; + adler1 = (unsigned int) i32(buf); + adler2 = (unsigned int) i32(buf+4); + len2 = (unsigned int) i32(buf+8); + adler = adler32_combine(adler1, adler2, len2); + return zlib_value(adler, rbuf, rlen); + } + } + + badarg: + errno = EINVAL; + return zlib_return(Z_ERRNO, rbuf, rlen); +} + + + +static void zlib_outputv(ErlDrvData drv_data, ErlIOVec *ev) +{ + ZLibData* d = (ZLibData*) drv_data; + + driver_enqv(d->port, ev, 0); +} diff --git a/erts/emulator/drivers/unix/bin_drv.c b/erts/emulator/drivers/unix/bin_drv.c new file mode 100644 index 0000000000..1827187d57 --- /dev/null +++ b/erts/emulator/drivers/unix/bin_drv.c @@ -0,0 +1,224 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Binary file driver interface , used for code loading */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "erl_driver.h" +#include <errno.h> + +#ifdef NO_UMASK +#define FILE_MODE 0644 +#else +#define FILE_MODE 0666 +#endif + +static int this_port; + +static long bin_start(); +static int bin_init(),bin_stop(),bin_erlang_read(); +static int read_fill(), write_fill(); + +const struct erl_drv_entry bin_driver_entry = { + bin_init, + bin_start, + bin_stop, + bin_erlang_read, + NULL, + NULL, + "binary_filer" +}; + + + +static int bin_init() +{ + this_port = -1; + return 0; +} + + +static long bin_start(port,buf) +int port; +char *buf; +{ + + if (this_port != -1) + return(-1); + this_port = port; + return(port); +} + + +static int bin_stop() +{ + this_port = -1; +} + + +static int bin_erlang_read(port,buf,count) +long port; +char *buf; +int count; + +{ + struct stat statbuf; + int i,size,fd,rval; + char *t,*rbuf; + + buf[count] = '\0'; + switch (*buf) { + case 'c' : + if (chdir(buf+1) == 0) /* sucsess */ + driver_output(this_port,"y",1); + else + driver_output(this_port,"n",1); + return 0; + case 'f': +#ifdef MSDOS + if ((fd = open(buf+1,O_RDONLY|O_BINARY)) < 0) { +#else + if ((fd = open(buf+1,O_RDONLY, 0)) < 0) { +#endif + driver_output(this_port,"n",1); + return 0; + } + if (stat(buf+1,&statbuf) < 0) { + driver_output(this_port,"n",1); + close(fd); + return 0; + } + if (S_ISREG(statbuf.st_mode)) + size = statbuf.st_size; + else + size = BUFSIZ; + + if ((rbuf = (char*) driver_alloc(1 + size)) == NULL) { + fprintf(stderr,"Out of memory\n"); + close(fd); + driver_output(this_port,"n",1); + return 0; + } + if (S_ISREG(statbuf.st_mode)) { + if (read_fill(fd,1+rbuf,size) != size) { + driver_free(rbuf); + close(fd); + driver_output(this_port,"n",1); + return 0;; + } + } + /* The idea here is that if it's a regular file read the entire + * entire file and if it's a device file or a tty try to read + * until errno != EINTR + */ + + else { + while (1) { + rval = read(fd,1+rbuf,size); + if (rval < 0 && errno == EINTR) + continue; + if (rval < 0) { + driver_free(rbuf); + close(fd); + driver_output(this_port,"n",1); + return 0; + } + size = rval; + break; + } + } + rbuf[0] = 'y'; + driver_output(this_port,rbuf,1+size); + driver_free(rbuf); + close(fd); + return 0; + case 'w': +#ifdef MSDOS + if ((fd = open(buf+1, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + FILE_MODE)) < 0) { +#else + if ((fd = open(buf+1, O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE)) < 0) { +#endif + driver_output(this_port,"n",1); + return 0; + } + t = buf+1; i = 1; + while(*t && i++ < count) t++; + t++; + /* t now points to the contents of what we shall write */ + size = count - 1 - i; + + /* gotta write_fill if we are writing to a slow device + such as /dev/audio */ + + if (write_fill (fd,t,size) != size) { + driver_output(this_port,"n",1); + close(fd); + return 0; + } + driver_output(this_port,"y",1); + close(fd); + return 0; + default: + driver_failure(this_port,-1); + return 0; + } +} + + +static int write_fill(fd, buf, len) +char *buf; +{ + int i, done = 0; + + do { + if ((i = write(fd, buf+done, len-done)) < 0) { + if (errno != EINTR) + return (i); + i = 0; + } + done += i; + } while (done < len); + return (len); +} + + +static int read_fill(fd, buf, len) +char *buf; +{ + int i, got = 0; + + do { + if ((i = read(fd, buf+got, len-got)) <= 0) { + if (i == 0 || errno != EINTR) + return (i); + i = 0; + } + got += i; + } while (got < len); + return (len); +} + diff --git a/erts/emulator/drivers/unix/mem_drv.c b/erts/emulator/drivers/unix/mem_drv.c new file mode 100644 index 0000000000..1417ca1121 --- /dev/null +++ b/erts/emulator/drivers/unix/mem_drv.c @@ -0,0 +1,145 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Access to elib memory statistics */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_driver.h" +#include "elib_stat.h" + +#define MAP_BUF_SIZE 1000 /* Max map size */ +#define HISTO_BUF_SIZE 100 /* Max histogram buckets */ + +static ErlDrvData mem_start(ErlDrvPort); +static int mem_init(void); +static void mem_stop(ErlDrvData); +static void mem_command(ErlDrvData, char*, int); + +const struct driver_entry mem_driver_entry = { + mem_init, + mem_start, + mem_stop, + mem_command, + NULL, + NULL, + "mem_drv" +}; + +static int mem_init(void) +{ + return 0; +} + +static ErlDrvData mem_start(ErlDrvPort port, char* buf) +{ + return (ErlDrvData)port; +} + +static void mem_stop(ErlDrvData port) +{ +} + +void putint32(p, v) +byte* p; int v; +{ + p[0] = (v >> 24) & 0xff; + p[1] = (v >> 16) & 0xff; + p[2] = (v >> 8) & 0xff; + p[3] = (v) & 0xff; +} + +int getint16(p) +byte* p; +{ + return (p[0] << 8) | p[1]; +} + +/* +** Command: +** m L1 L0 -> a heap map of length L1*256 + L0 is returned +** s -> X3 X2 X1 X0 Y3 Y2 Y1 Y0 Z3 Z2 Z1 Z0 +** X == Total heap size bytes +** Y == Total free bytes +** Z == Size of largest free block in bytes +** +** h L1 L0 B0 -> Generate a logarithm historgram base B with L buckets +** l L1 L0 S0 -> Generate a linear histogram with step S with L buckets +*/ +unsigned char outbuf[HISTO_BUF_SIZE*2*4]; + +static void mem_command(ErlDrvData port, char* buf, int count) +{ + if ((count == 1) && buf[0] == 's') { + struct elib_stat info; + char v[3*4]; + + elib_stat(&info); + + putint32(v, info.mem_total*4); + putint32(v+4, info.mem_free*4); + putint32(v+8, info.max_free*4); + driver_output((ErlDrvPort)port, v, 12); + return; + } + else if ((count == 3) && buf[0] == 'm') { + char w[MAP_BUF_SIZE]; + int n = getint16(buf+1); + + if (n > MAP_BUF_SIZE) + n = MAP_BUF_SIZE; + elib_heap_map(w, n); + driver_output((ErlDrvPort)port, w, n); + return; + } + else if ((count == 4) && (buf[0] == 'h' || buf[0] == 'l')) { + unsigned long vf[HISTO_BUF_SIZE]; + unsigned long va[HISTO_BUF_SIZE]; + int n = getint16(buf+1); + int base = (unsigned char) buf[3]; + + if (n >= HISTO_BUF_SIZE) + n = HISTO_BUF_SIZE; + if (buf[0] == 'l') + base = -base; + if (elib_histo(vf, va, n, base) < 0) { + driver_failure((ErlDrvPort)port, -1); + return; + } + else { + char* p = outbuf; + int i; + + for (i = 0; i < n; i++) { + putint32(p, vf[i]); + p += 4; + } + for (i = 0; i < n; i++) { + putint32(p, va[i]); + p += 4; + } + driver_output((ErlDrvPort)port, outbuf, n*8); + } + return; + } + driver_failure((ErlDrvPort)port, -1); +} diff --git a/erts/emulator/drivers/unix/multi_drv.c b/erts/emulator/drivers/unix/multi_drv.c new file mode 100644 index 0000000000..822c96730c --- /dev/null +++ b/erts/emulator/drivers/unix/multi_drv.c @@ -0,0 +1,105 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Multidriver interface + This is an example of a driver which allows multiple instances of itself. + I.e have one erlang process execute open_port(multi......) and + at the same time have an other erlang process open an other port + running multi there as well. +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "erl_driver.h" +#include "sys.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define MAXCHANNEL 20 + +static char buf[BUFSIZ]; + +static ErlDrvData multi_start(ErlDrvPort, char*); +static int multi_init(void); +static void multi_stop(ErlDrvData),multi_erlang_read(ErlDrvData, char*, int); + +struct driver_entry multi_driver_entry = { + multi_init, + multi_start, + multi_stop, + multi_erlang_read, + NULL, + NULL, + "multi" +}; + + +struct channel { + ErlDrvPort portno; + int channel; +}; + +struct channel channels[MAXCHANNEL]; /* Max MAXCHANNEL instances */ + +static int multi_init(void) +{ + memzero(channels,MAXCHANNEL * sizeof(struct channel)); + return 0; +} + +static ErlDrvData multi_start(ErlDrvPort port, char* buf) +{ + int chan; + chan = get_new_channel(); + channels[port].portno = port; + channels[port].channel = chan; + fprintf(stderr,"Opening channel %d port is %d\n",chan,port); + return (ErlDrvData)port; +} + + +static int multi_stop(ErlDrvData port) +{ + fprintf(stderr,"Closing channel %d\n",channels[port].channel); + remove_channel(channels[(int)port].channel); +} + + +static int multi_erlang_read(ErlDrvData port, char* buf, int count) +{ + fprintf(stderr,"Writing %d bytes to channel %d\n", + count, + channels[(int)port].channel); +} + + +/* These two funs are fake */ + +int get_new_channel() +{ + static int ch = 1; + return(ch++); +} + +void remove_channel(int ch) +{ +} diff --git a/erts/emulator/drivers/unix/sig_drv.c b/erts/emulator/drivers/unix/sig_drv.c new file mode 100644 index 0000000000..aab5d63a40 --- /dev/null +++ b/erts/emulator/drivers/unix/sig_drv.c @@ -0,0 +1,81 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: demonstrate how to include interupt handlers in erlang */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_driver.h" +#include <signal.h> +#include <stdio.h> + +static ErlDrvData sig_start(ErlDrvPort, char*); +static int sig_init(void); +static void sig_stop(ErlDrvData), doio(ErlDrvData, ErlDrvEvent); + +ErlDrvEntry sig_driver_entry = { + sig_init, + sig_start, + sig_stop, + NULL, + doio, + NULL, + "sig_test" +}; + +static ErlDrvPort this_port; + +static int sig_init(void) +{ + this_port = (ErlDrvPort)-1; + return 0; +} + +static sigc(int ino) +{ + driver_interrupt(this_port, ino); +} + +static ErlDrvData sig_start(ErlDrvPort port, char* buf) +{ + if (this_port != (ErlDrvPort)-1) + return ERL_DRV_ERROR_GENERAL; + this_port = port; + signal(SIGUSR1, sigc); + return (ErlDrvData)port; +} + +static void sig_stop(ErlDrvData port) +{ + this_port = (ErlDrvPort)-1; + signal(SIGUSR1, SIG_DFL); +} + +doio(ErlDrvData port, ErlDrvEvent ino) +{ + /* First go get the io, unless we already did that */ + /* In the sighandler */ + + /* Then send it to erlang */ + + driver_output(this_port, "y", 1); +} diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c new file mode 100644 index 0000000000..4c2514669b --- /dev/null +++ b/erts/emulator/drivers/unix/ttsl_drv.c @@ -0,0 +1,1299 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Tty driver that reads one character at the time and provides a + * smart line for output. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "erl_driver.h" + +static int ttysl_init(void); +static ErlDrvData ttysl_start(ErlDrvPort, char*); + +#ifdef HAVE_TERMCAP /* else make an empty driver that can not be opened */ + +#include "sys.h" +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> +#include <locale.h> +#include <unistd.h> +#include <termios.h> +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H) +#define PRIMITIVE_UTF8_CHECK 1 +#else +#include <langinfo.h> +#endif + +#define TRUE 1 +#define FALSE 0 + +/* Termcap functions. */ +int tgetent(char* bp, char *name); +int tgetnum(char* cap); +int tgetflag(char* cap); +char *tgetstr(char* cap, char** buf); +char *tgoto(char* cm, int col, int line); +int tputs(char* cp, int affcnt, int (*outc)(int c)); + +/* Terminal capabilites in which we are interested. */ +static char *capbuf; +static char *up, *down, *left, *right; +static int cols, xn; +static volatile int cols_needs_update = FALSE; + +/* The various opcodes. */ +#define OP_PUTC 0 +#define OP_MOVE 1 +#define OP_INSC 2 +#define OP_DELC 3 +#define OP_BEEP 4 +/* Control op */ +#define CTRL_OP_GET_WINSIZE 100 +#define CTRL_OP_GET_UNICODE_STATE 101 +#define CTRL_OP_SET_UNICODE_STATE 102 + + + +static int lbuf_size = BUFSIZ; +static Uint32 *lbuf; /* The current line buffer */ +static int llen; /* The current line length */ +static int lpos; /* The current "cursor position" in the line buffer */ + +/* + * Tags used in line buffer to show that these bytes represent special characters, + * Max unicode is 0x0010ffff, so we have lots of place for meta tags... + */ +#define CONTROL_TAG 0x10000000U /* Control character, value in first position */ +#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */ +#define TAG_MASK 0xFF000000U + +#define MAXSIZE (1 << 16) + +#define COL(_l) ((_l) % cols) +#define LINE(_l) ((_l) / cols) + +#define NL '\n' + +/* Main interface functions. */ +static void ttysl_stop(ErlDrvData); +static void ttysl_from_erlang(ErlDrvData, char*, int); +static void ttysl_from_tty(ErlDrvData, ErlDrvEvent); +static void ttysl_stop_select(ErlDrvEvent, void*); +static Sint16 get_sint16(char*); + +static ErlDrvPort ttysl_port; +static int ttysl_fd; +static FILE *ttysl_out; + +/* Functions that work on the line buffer. */ +static int start_lbuf(void); +static int stop_lbuf(void); +static int put_chars(byte*,int); +static int move_rel(int); +static int ins_chars(byte *,int); +static int del_chars(int); +static int step_over_chars(int); +static int insert_buf(byte*,int); +static int write_buf(Uint32 *,int); +static int outc(int c); +static int move_cursor(int,int); + +/* Termcap functions. */ +static int start_termcap(void); +static int stop_termcap(void); +static int move_left(int); +static int move_right(int); +static int move_up(int); +static int move_down(int); +static void update_cols(void); + +/* Terminal setting functions. */ +static int tty_init(int,int,int,int); +static int tty_set(int); +static int tty_reset(int); +static int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int); +static RETSIGTYPE suspend(int); +static RETSIGTYPE cont(int); +static RETSIGTYPE winch(int); + +/*#define LOG_DEBUG*/ + +#ifdef LOG_DEBUG +FILE *debuglog = NULL; + +#define DEBUGLOG(X) \ +do { \ + if (debuglog != NULL) { \ + my_debug_printf X; \ + } \ +} while (0) + +static void my_debug_printf(char *fmt, ...) +{ + char buffer[1024]; + va_list args; + + va_start(args, fmt); + erts_vsnprintf(buffer,1024,fmt,args); + va_end(args); + erts_fprintf(debuglog,"%s\n",buffer); + //erts_printf("Debuglog = %s\n",buffer); +} + +#else + +#define DEBUGLOG(X) + +#endif + +static int utf8_mode = 0; +static byte utf8buf[4]; /* for incomplete input */ +static int utf8buf_size; /* size of incomplete input */ + +# define IF_IMPL(x) x +#else +# define IF_IMPL(x) NULL +#endif /* HAVE_TERMCAP */ + +/* Define the driver table entry. */ +struct erl_drv_entry ttsl_driver_entry = { + ttysl_init, + ttysl_start, + IF_IMPL(ttysl_stop), + IF_IMPL(ttysl_from_erlang), + IF_IMPL(ttysl_from_tty), + NULL, + "tty_sl", + NULL, + NULL, + IF_IMPL(ttysl_control), + NULL, /* timeout */ + NULL, /* outputv */ + NULL, /* ready_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, + NULL, /* process_exit */ + IF_IMPL(ttysl_stop_select) +}; + + +static int ttysl_init(void) +{ +#ifdef HAVE_TERMCAP + ttysl_port = (ErlDrvPort)-1; + ttysl_fd = -1; + lbuf = NULL; /* For line buffer handling */ + capbuf = NULL; /* For termcap handling */ +#endif +#ifdef LOG_DEBUG + { + char *dl; + if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) { + debuglog = fopen(dl,"w+"); + if (debuglog != NULL) + setbuf(debuglog,NULL); + } + DEBUGLOG(("Debuglog = %s(0x%ld)\n",dl,(long) debuglog)); + } +#endif + return 0; +} + +static ErlDrvData ttysl_start(ErlDrvPort port, char* buf) +{ +#ifndef HAVE_TERMCAP + return ERL_DRV_ERROR_GENERAL; +#else + char *s, *t, c, *l; + int canon, echo, sig; /* Terminal characteristics */ + int flag; + extern int using_oldshell; /* set this to let the rest of erts know */ + + utf8buf_size = 0; + if (ttysl_port != (ErlDrvPort)-1) + return ERL_DRV_ERROR_GENERAL; + + if (!isatty(0) || !isatty(1)) + return ERL_DRV_ERROR_GENERAL; + + /* Set the terminal modes to default leave as is. */ + canon = echo = sig = 0; + + /* Parse the input parameters. */ + for (s = strchr(buf, ' '); s; s = t) { + s++; + /* Find end of this argument (start of next) and insert NUL. */ + if ((t = strchr(s, ' '))) { + c = *t; + *t = '\0'; + } + if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) { + if (s[1] == 'c') canon = flag; + if (s[1] == 'e') echo = flag; + if (s[1] == 's') sig = flag; + } + else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0) + return ERL_DRV_ERROR_GENERAL; + } + if (ttysl_fd < 0) + ttysl_fd = 0; + + if (tty_init(ttysl_fd, canon, echo, sig) < 0 || + tty_set(ttysl_fd) < 0) { + ttysl_port = (ErlDrvPort)-1; + tty_reset(ttysl_fd); + return ERL_DRV_ERROR_GENERAL; + } + + /* Set up smart line and termcap stuff. */ + if (!start_lbuf() || !start_termcap()) { + stop_lbuf(); /* Must free this */ + tty_reset(ttysl_fd); + return ERL_DRV_ERROR_GENERAL; + } + + /* Open the terminal and set the terminal */ + ttysl_out = fdopen(ttysl_fd, "w"); + +#ifdef PRIMITIVE_UTF8_CHECK + setlocale(LC_CTYPE, ""); /* Set international environment, + ignore result */ + if (((l = getenv("LC_ALL")) && *l) || + ((l = getenv("LC_CTYPE")) && *l) || + ((l = getenv("LANG")) && *l)) { + if (strstr(l, "UTF-8")) + utf8_mode = 1; + } + +#else + l = setlocale(LC_CTYPE, ""); /* Set international environment */ + if (l != NULL) { + utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0); + DEBUGLOG(("setlocale: %s\n",l)); + } +#endif + DEBUGLOG(("utf8_mode is %s\n",(utf8_mode) ? "on" : "off")); + sys_sigset(SIGCONT, cont); + sys_sigset(SIGWINCH, winch); + + driver_select(port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1); + ttysl_port = port; + + /* we need to know this when we enter the break handler */ + using_oldshell = 0; + + return (ErlDrvData)ttysl_port; /* Nothing important to return */ +#endif /* HAVE_TERMCAP */ +} + +#ifdef HAVE_TERMCAP + +#define DEF_HEIGHT 24 +#define DEF_WIDTH 80 +static void ttysl_get_window_size(Uint32 *width, Uint32 *height) +{ +#ifdef TIOCGWINSZ + struct winsize ws; + if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) { + *width = (Uint32) ws.ws_col; + *height = (Uint32) ws.ws_row; + if (*width <= 0) + *width = DEF_WIDTH; + if (*height <= 0) + *height = DEF_HEIGHT; + return; + } +#endif + *width = DEF_WIDTH; + *height = DEF_HEIGHT; +} + +static int ttysl_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + char resbuff[2*sizeof(Uint32)]; + int res_size; + switch (command) { + case CTRL_OP_GET_WINSIZE: + { + Uint32 w,h; + ttysl_get_window_size(&w,&h); + memcpy(resbuff,&w,sizeof(Uint32)); + memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32)); + res_size = 2*sizeof(Uint32); + } + break; + case CTRL_OP_GET_UNICODE_STATE: + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + break; + case CTRL_OP_SET_UNICODE_STATE: + if (len > 0) { + int m = (int) *buf; + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + utf8_mode = (m) ? 1 : 0; + } else { + return 0; + } + break; + default: + return 0; + } + if (rlen < res_size) { + *rbuf = driver_alloc(res_size); + } + memcpy(*rbuf,resbuff,res_size); + return res_size; +} + + +static void ttysl_stop(ErlDrvData ttysl_data) +{ + if (ttysl_port != (ErlDrvPort)-1) { + stop_lbuf(); + stop_termcap(); + tty_reset(ttysl_fd); + driver_select(ttysl_port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 0); + sys_sigset(SIGCONT, SIG_DFL); + sys_sigset(SIGWINCH, SIG_DFL); + } + ttysl_port = (ErlDrvPort)-1; + ttysl_fd = -1; + /* return TRUE; */ +} + +static int put_utf8(int ch, byte *target, int sz, int *pos) +{ + Uint x = (Uint) ch; + if (x < 0x80) { + if (*pos >= sz) { + return -1; + } + target[(*pos)++] = (byte) x; + } + else if (x < 0x800) { + if (((*pos) + 1) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 6)) | + ((byte) 0xC0)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x10000) { + if ((x >= 0xD800 && x <= 0xDFFF) || + (x == 0xFFFE) || + (x == 0xFFFF)) { /* Invalid unicode range */ + return -1; + } + if (((*pos) + 2) >= sz) { + return -1; + } + + target[(*pos)++] = (((byte) (x >> 12)) | + ((byte) 0xE0)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x110000) { /* Standard imposed max */ + if (((*pos) + 3) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 18)) | + ((byte) 0xF0)); + target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else { + return -1; + } + return 0; +} + + +static int pick_utf8(byte *s, int sz, int *pos) +{ + int size = sz - (*pos); + byte *source; + Uint unipoint; + + if (size > 0) { + source = s + (*pos); + if (((*source) & ((byte) 0x80)) == 0) { + unipoint = (int) *source; + ++(*pos); + return (int) unipoint; + } else if (((*source) & ((byte) 0xE0)) == 0xC0) { + if (size < 2) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((*source) < 0xC2) /* overlong */) { + return -1; + } + (*pos) += 2; + unipoint = + (((Uint) ((*source) & ((byte) 0x1F))) << 6) | + ((Uint) (source[1] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF0)) == 0xE0) { + if (size < 3) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) { + return -1; + } + if ((((*source) & ((byte) 0xF)) == 0xD) && + ((source[1] & 0x20) != 0)) { + return -1; + } + if (((*source) == 0xEF) && (source[1] == 0xBF) && + ((source[2] == 0xBE) || (source[2] == 0xBF))) { + return -1; + } + (*pos) += 3; + unipoint = + (((Uint) ((*source) & ((byte) 0xF))) << 12) | + (((Uint) (source[1] & ((byte) 0x3F))) << 6) | + ((Uint) (source[2] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF8)) == 0xF0) { + if (size < 4) { + return -2 ; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + ((source[3] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) { + return -1; + } + if ((((*source) & ((byte)0x7)) > 0x4U) || + ((((*source) & ((byte)0x7)) == 0x4U) && + ((source[1] & ((byte)0x3F)) > 0xFU))) { + return -1; + } + (*pos) += 4; + unipoint = + (((Uint) ((*source) & ((byte) 0x7))) << 18) | + (((Uint) (source[1] & ((byte) 0x3F))) << 12) | + (((Uint) (source[2] & ((byte) 0x3F))) << 6) | + ((Uint) (source[3] & ((byte) 0x3F))); + return (int) unipoint; + } else { + return -1; + } + } else { + return -1; + } +} + +static int octal_or_hex_positions(Uint c) +{ + int x = 0; + Uint ch = c; + if (!ch) { + return 1; + } + while(ch) { + ++x; + ch >>= 3; + } + if (x <= 3) { + return 3; + } + /* \x{H ...} format when larger than \777 */ + x = 0; + ch = c; + while(ch) { + ++x; + ch >>= 4; + } + return x+3; +} + +static void octal_or_hex_format(Uint ch, byte *buf, int *pos) +{ + static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + int num = octal_or_hex_positions(ch); + if (num != 3) { + buf[(*pos)++] = 'x'; + buf[(*pos)++] = '{'; + num -= 3; + while(num--) { + buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)]; + } + buf[(*pos)++] = '}'; + } else { + while(num--) { + buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0'); + } + } +} + +/* + * Check that there is enough room in all buffers to copy all pad chars + * and stiff we need If not, realloc lbuf. + */ +static int check_buf_size(byte *s, int n) +{ + int pos = 0; + int ch; + int size = 10; + + while(pos < n) { + /* Indata is always UTF-8 */ + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("Invalid UTF8:%d",ch)); + ++pos; + } + if (utf8_mode) { /* That is, terminal is UTF8 compliant */ + if (ch >= 128 || isprint(ch)) { + DEBUGLOG(("Printable(UTF-8:%d):%d",(pos - opos),ch)); + size++; /* Buffer contains wide characters... */ + } else if (ch == '\t') { + size += 8; + } else { + DEBUGLOG(("Magic(UTF-8:%d):%d",(pos - opos),ch)); + size += 2; + } + } else { + if (ch <= 255 && isprint(ch)) { + DEBUGLOG(("Printable:%d",ch)); + size++; + } else if (ch == '\t') + size += 8; + else if (ch >= 128) { + DEBUGLOG(("Non printable:%d",ch)); + size += (octal_or_hex_positions(ch) + 1); + } + else { + DEBUGLOG(("Magic:%d",ch)); + size += 2; + } + } + } + + if (size + lpos >= lbuf_size) { + + lbuf_size = size + lpos + BUFSIZ; + if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) { + driver_failure(ttysl_port, -1); + return(0); + } + } + return(1); +} + + +static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, int count) +{ + if (lpos > MAXSIZE) + put_chars((byte*)"\n", 1); + + switch (buf[0]) { + case OP_PUTC: + DEBUGLOG(("OP: Putc(%d)",count-1)); + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + put_chars((byte*)buf+1, count-1); + break; + case OP_MOVE: + move_rel(get_sint16(buf+1)); + break; + case OP_INSC: + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + ins_chars((byte*)buf+1, count-1); + break; + case OP_DELC: + del_chars(get_sint16(buf+1)); + break; + case OP_BEEP: + outc('\007'); + break; + default: + /* Unknown op, just ignore. */ + break; + } + fflush(ttysl_out); + return; /* TRUE; */ +} + +static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) +{ + byte b[1024]; + ssize_t i; + int ch = 0, pos = 0; + int left = 1024; + byte *p = b; + byte t[1024]; + int tpos; + + if (utf8buf_size > 0) { + memcpy(b,utf8buf,utf8buf_size); + left -= utf8buf_size; + p += utf8buf_size; + utf8buf_size = 0; + } + + if ((i = read((int)(Sint)fd, (char *) p, left)) >= 0) { + if (p != b) { + i += (p - b); + } + if (utf8_mode) { /* Hopefully an UTF8 terminal */ + while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0) + ; + if (ch == -2 && i - pos <= 4) { + /* bytes left to care for */ + utf8buf_size = i -pos; + memcpy(utf8buf,b+pos,utf8buf_size); + } else if (ch == -1) { + DEBUGLOG(("Giving up on UTF8 mode, invalid character")); + utf8_mode = 0; + goto latin_terminal; + } + driver_output(ttysl_port, (char *) b, pos); + } else { + latin_terminal: + tpos = 0; + while (pos < i) { + while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */ + put_utf8((int) b[pos++], t, 1024, &tpos); + } + driver_output(ttysl_port, (char *) t, tpos); + tpos = 0; + } + } + } else { + driver_failure(ttysl_port, -1); + } +} + +static void ttysl_stop_select(ErlDrvEvent e, void* _) +{ + int fd = (int)(long)e; + if (fd != 0) { + close(fd); + } +} + +/* Procedures for putting and getting integers to/from strings. */ +static Sint16 get_sint16(char *s) +{ + return ((*s << 8) | ((byte*)s)[1]); +} + +static int start_lbuf(void) +{ + if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32)))) + return FALSE; + llen = 0; + lpos = 0; + return TRUE; +} + +static int stop_lbuf(void) +{ + if (lbuf) { + driver_free(lbuf); + lbuf = NULL; + } + return TRUE; +} + +/* Put l bytes (in UTF8) from s into the buffer and output them. */ +static int put_chars(byte *s, int l) +{ + int n; + + n = insert_buf(s, l); + if (n > 0) + write_buf(lbuf + lpos - n, n); + if (lpos > llen) + llen = lpos; + return TRUE; +} + +/* + * Move the current postition forwards or backwards within the current + * line. We know about padding. + */ +static int move_rel(int n) +{ + int npos; /* The new position */ + + /* Step forwards or backwards over the buffer. */ + npos = step_over_chars(n); + + /* Calculate move, updates pointers and move the cursor. */ + move_cursor(lpos, npos); + lpos = npos; + return TRUE; +} + +/* Insert characters into the buffer at the current position. */ +static int ins_chars(byte *s, int l) +{ + int n, tl; + Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */ + + /* Move tail of buffer to make space. */ + if ((tl = llen - lpos) > 0) { + if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL) + return FALSE; + memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32)); + } + n = insert_buf(s, l); + if (tl > 0) { + memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32)); + driver_free(tbuf); + } + llen += n; + write_buf(lbuf + (lpos - n), llen - (lpos - n)); + move_cursor(llen, lpos); + return TRUE; +} + +/* + * Delete characters in the buffer. Can delete characters before (n < 0) + * and after (n > 0) the current position. Cursor left at beginning of + * deleted block. + */ +static int del_chars(int n) +{ + int i, l, r; + int pos; + + update_cols(); + + /* Step forward or backwards over n logical characters. */ + pos = step_over_chars(n); + + if (pos > lpos) { + l = pos - lpos; /* Buffer characters to delete */ + r = llen - lpos - l; /* Characters after deleted */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32)); + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + outc(' '); + if (COL(llen+l) == 0 && xn) + { + outc(' '); + move_left(1); + } + move_cursor(llen + l, lpos); + } + else if (pos < lpos) { + l = lpos - pos; /* Buffer characters */ + r = llen - lpos; /* Characters after deleted */ + move_cursor(lpos, lpos-l); /* Move back */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32)); + lpos -= l; + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + outc(' '); + if (COL(llen+l) == 0 && xn) + { + outc(' '); + move_left(1); + } + move_cursor(llen + l, lpos); + } + return TRUE; +} + +/* Step over n logical characters, check for overflow. */ +static int step_over_chars(int n) +{ + Uint32 *c, *beg, *end; + + beg = lbuf; + end = lbuf + llen; + c = lbuf + lpos; + for ( ; n > 0 && c < end; --n) { + c++; + while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + c++; + } + for ( ; n < 0 && c > beg; n++) { + --c; + while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + --c; + } + return c - lbuf; +} + +/* + * Insert n characters into the buffer at lpos. + * Know about pad characters and treat \n specially. + */ + +static int insert_buf(byte *s, int n) +{ + int pos = 0; + int buffpos = lpos; + int ch; + + while (pos < n) { + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch)); + ++pos; + } + if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) { + DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch)); + lbuf[lpos++] = (Uint32) ch; + } else if (ch >= 128) { /* not utf8 mode */ + int nc = octal_or_hex_positions(ch); + lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG; + while (nc--) { + lbuf[lpos++] = ESCAPED_TAG; + } + } else if (ch == '\t') { + do { + lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch)); + ch = 0; + } while (lpos % 8); + } else if (ch == '\n' || ch == '\r') { + write_buf(lbuf + buffpos, lpos - buffpos); + outc('\r'); + if (ch == '\n') + outc('\n'); + if (llen > lpos) { + memcpy(lbuf, lbuf + lpos, llen - lpos); + } + llen -= lpos; + lpos = buffpos = 0; + } else { + DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch)); + lbuf[lpos++] = ch | CONTROL_TAG; + lbuf[lpos++] = CONTROL_TAG; + } + } + return lpos - buffpos; /* characters "written" into + current buffer (may be less due to newline) */ +} + + + +/* + * Write n characters in line buffer starting at s. Be smart about + * non-printables. Know about pad characters and that \n can never + * occur normally. + */ + +static int write_buf(Uint32 *s, int n) +{ + byte ubuf[4]; + int ubytes = 0, i; + byte lastput = ' '; + + update_cols(); + + while (n > 0) { + if (!(*s & TAG_MASK) ) { + if (utf8_mode) { + ubytes = 0; + if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) { + for (i = 0; i < ubytes; ++i) { + outc(ubuf[i]); + } + lastput = 0; /* Means the last written character was multibyte UTF8 */ + } + } else { + outc((byte) *s); + lastput = (byte) *s; + } + --n; + ++s; + } + else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) { + outc(lastput = ' '); + --n; s++; + while (n > 0 && *s == CONTROL_TAG) { + outc(lastput = ' '); + --n; s++; + } + } else if (*s & CONTROL_TAG) { + outc('^'); + outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40))); + n -= 2; + s += 2; + } else if (*s & ESCAPED_TAG) { + Uint32 ch = *s & ~(TAG_MASK); + byte *octbuff; + byte octtmp[256]; + int octbytes; + DEBUGLOG(("Escaped: %d", ch)); + octbytes = octal_or_hex_positions(ch); + if (octbytes > 256) { + octbuff = driver_alloc(octbytes); + } else { + octbuff = octtmp; + } + octbytes = 0; + octal_or_hex_format(ch, octbuff, &octbytes); + DEBUGLOG(("octbytes: %d", octbytes)); + outc('\\'); + for (i = 0; i < octbytes; ++i) { + outc(lastput = octbuff[i]); + DEBUGLOG(("outc: %d", (int) lastput)); + } + n -= octbytes+1; + s += octbytes+1; + if (octbuff != octtmp) { + driver_free(octbuff); + } + } else { + DEBUGLOG(("Very unexpected character %d",(int) *s)); + ++n; + --s; + } + } + /* Check landed in first column of new line and have 'xn' bug. */ + n = s - lbuf; + if (COL(n) == 0 && xn && n != 0) { + if (n >= llen) { + outc(' '); + } else if (lastput == 0) { /* A multibyte UTF8 character */ + for (i = 0; i < ubytes; ++i) { + outc(ubuf[i]); + } + } else { + outc(lastput); + } + move_left(1); + } + return TRUE; +} + + +/* The basic procedure for outputting one character. */ +static int outc(int c) +{ + return (int)putc(c, ttysl_out); +} + +static int move_cursor(int from, int to) +{ + int dc, dl; + + update_cols(); + + dc = COL(to) - COL(from); + dl = LINE(to) - LINE(from); + if (dl > 0) + move_down(dl); + else if (dl < 0) + move_up(-dl); + if (dc > 0) + move_right(dc); + else if (dc < 0) + move_left(-dc); + return TRUE; +} + +static int start_termcap(void) +{ + int eres; + size_t envsz = 1024; + char *env = NULL; + char *c; + + capbuf = driver_alloc(1024); + if (!capbuf) + goto false; + eres = erl_drv_getenv("TERM", capbuf, &envsz); + if (eres == 0) + env = capbuf; + else if (eres < 0) + goto false; + else /* if (eres > 1) */ { + char *envbuf = driver_alloc(envsz); + if (!envbuf) + goto false; + while (1) { + char *newenvbuf; + eres = erl_drv_getenv("TERM", envbuf, &envsz); + if (eres == 0) + break; + newenvbuf = driver_realloc(envbuf, envsz); + if (eres < 0 || !newenvbuf) { + env = newenvbuf ? newenvbuf : envbuf; + goto false; + } + envbuf = newenvbuf; + } + env = envbuf; + } + if (tgetent((char*)lbuf, env) <= 0) + goto false; + if (env != capbuf) { + env = NULL; + driver_free(env); + } + c = capbuf; + cols = tgetnum("co"); + if (cols <= 0) + cols = DEF_WIDTH; + xn = tgetflag("xn"); + up = tgetstr("up", &c); + if (!(down = tgetstr("do", &c))) + down = "\n"; + if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c))) + left = "\b"; /* Can't happen - but does on Solaris 2 */ + right = tgetstr("nd", &c); + if (up && down && left && right) + return TRUE; + false: + if (env && env != capbuf) + driver_free(env); + if (capbuf) + driver_free(capbuf); + capbuf = NULL; + return FALSE; +} + +static int stop_termcap(void) +{ + if (capbuf) driver_free(capbuf); + capbuf = NULL; + return TRUE; +} + +static int move_left(int n) +{ + while (n-- > 0) + tputs(left, 1, outc); + return TRUE; +} + +static int move_right(int n) +{ + while (n-- > 0) + tputs(right, 1, outc); + return TRUE; +} + +static int move_up(int n) +{ + while (n-- > 0) + tputs(up, 1, outc); + return TRUE; +} + +static int move_down(int n) +{ + while (n-- > 0) + tputs(down, 1, outc); + return TRUE; +} + + +/* + * Updates cols if terminal has resized (SIGWINCH). Should be called + * at the start of any function that uses the COL or LINE macros. If + * the terminal is resized after calling this function but before use + * of the macros, then we may write to the wrong screen location. + * + * We cannot call this from the SIGWINCH handler because it uses + * ioctl() which is not a safe function as listed in the signal(7) + * man page. + */ +static void update_cols(void) +{ + Uint32 width, height; + + if (cols_needs_update) { + cols_needs_update = FALSE; + ttysl_get_window_size(&width, &height); + cols = width; + } +} + + +/* + * Put a terminal device into non-canonical mode with ECHO off. + * Before doing so we first save the terminal's current mode, + * assuming the caller will call the tty_reset() function + * (also in this file) when it's done with raw mode. + */ + +static struct termios tty_smode, tty_rmode; + +static int tty_init(int fd, int canon, int echo, int sig) +{ + if (tcgetattr(fd, &tty_rmode) < 0) + return -1; + tty_smode = tty_rmode; + + /* Default characteristics for all usage including termcap output. */ + tty_smode.c_iflag &= ~ISTRIP; + + /* Turn canonical (line mode) on off. */ + if (canon > 0) { + tty_smode.c_iflag |= ICRNL; + tty_smode.c_lflag |= ICANON; + tty_smode.c_oflag |= OPOST; + tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF]; +#ifdef VDSUSP + tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP]; +#endif + } + if (canon < 0) { + tty_smode.c_iflag &= ~ICRNL; + tty_smode.c_lflag &= ~ICANON; + tty_smode.c_oflag &= ~OPOST; + /* Must get these really right or funny effects can occur. */ + tty_smode.c_cc[VMIN] = 1; + tty_smode.c_cc[VTIME] = 0; +#ifdef VDSUSP + tty_smode.c_cc[VDSUSP] = 0; +#endif + } + + /* Turn echo on or off. */ + if (echo > 0) + tty_smode.c_lflag |= ECHO; + if (echo < 0) + tty_smode.c_lflag &= ~ECHO; + + /* Set extra characteristics for "RAW" mode, no signals. */ + if (sig > 0) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty_smode.c_lflag |= (ISIG|IEXTEN); + } + if (sig < 0) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty_smode.c_lflag &= ~(ISIG|IEXTEN); + } + return 0; +} + +/* + * Set/restore a terminal's mode to whatever it was on the most + * recent call to the tty_init() function above. + */ + +static int tty_set(int fd) +{ + DEBUGF(("Setting tty...\n")); + + if (tcsetattr(fd, TCSANOW, &tty_smode) < 0) + return(-1); + return(0); +} + +static int tty_reset(int fd) /* of terminal device */ +{ + DEBUGF(("Resetting tty...\n")); + + if (tcsetattr(fd, TCSANOW, &tty_rmode) < 0) + return(-1); + + return(0); +} + +/* + * Signal handler to cope with signals so that we can reset the tty + * to the orignal settings + */ + +static RETSIGTYPE suspend(int sig) +{ + if (tty_reset(ttysl_fd) < 0) { + fprintf(stderr,"Can't reset tty \n"); + exit(1); + } + + sys_sigset(sig, SIG_DFL); /* Set signal handler to default */ + sys_sigrelease(sig); /* Allow 'sig' to come through */ + kill(getpid(), sig); /* Send ourselves the signal */ + sys_sigblock(sig); /* Reset to old mask */ + sys_sigset(sig, suspend); /* Reset signal handler */ + + if (tty_set(ttysl_fd) < 0) { + fprintf(stderr,"Can't set tty raw \n"); + exit(1); + } +} + +static RETSIGTYPE cont(int sig) +{ + if (tty_set(ttysl_fd) < 0) { + fprintf(stderr,"Can't set tty raw\n"); + exit(1); + } +} + +static RETSIGTYPE winch(int sig) +{ + cols_needs_update = TRUE; +} +#endif /* HAVE_TERMCAP */ diff --git a/erts/emulator/drivers/unix/unix_efile.c b/erts/emulator/drivers/unix/unix_efile.c new file mode 100644 index 0000000000..d395b68691 --- /dev/null +++ b/erts/emulator/drivers/unix/unix_efile.c @@ -0,0 +1,1505 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations for Unix. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sys.h" +#include "erl_driver.h" +#include "erl_efile.h" +#include <utime.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/types.h> +#include <sys/uio.h> +#endif + +#ifdef _OSE_ +#include "efs.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#ifdef _OSE_SFK_ +#include <string.h> +#endif +#endif /* _OSE_ */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define DARWIN 1 +#endif + +#ifdef DARWIN +#include <fcntl.h> +#endif /* DARWIN */ + +#ifdef VXWORKS +#include <ioLib.h> +#include <dosFsLib.h> +#include <nfsLib.h> +#include <sys/stat.h> +/* +** Not nice to include usrLib.h as MANY normal variable names get reported +** as shadowing globals, like 'i' for example. +** Instead we declare the only function we use here +*/ +/* + * #include <usrLib.h> + */ +extern STATUS copy(char *, char *); +#include <errno.h> +#endif + +#ifdef SUNOS4 +# define getcwd(buf, size) getwd(buf) +#endif + +/* Find a definition of MAXIOV, that is used in the code later. */ +#if defined IOV_MAX +#define MAXIOV IOV_MAX +#elif defined UIO_MAXIOV +#define MAXIOV UIO_MAXIOV +#else +#define MAXIOV 16 +#endif + + +/* + * Macros for testing file types. + */ + +#ifdef _OSE_ + +#define ISDIR(st) S_ISDIR(((st).st_mode)) +#define ISREG(st) S_ISREG(((st).st_mode)) +#define ISDEV(st) (S_ISCHR(((st).st_mode)) || S_ISBLK(((st).st_mode))) +#define ISLNK(st) S_ISLNK(((st).st_mode)) +#ifdef NO_UMASK +#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +#define DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +#else +#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) +#define DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | \ + S_IWOTH | S_IXOTH) +#endif + +#else /* !_OSE_ */ + +#define ISDIR(st) (((st).st_mode & S_IFMT) == S_IFDIR) +#define ISREG(st) (((st).st_mode & S_IFMT) == S_IFREG) +#define ISDEV(st) \ + (((st).st_mode&S_IFMT) == S_IFCHR || ((st).st_mode&S_IFMT) == S_IFBLK) +#define ISLNK(st) (((st).st_mode & S_IFLNK) == S_IFLNK) +#ifdef NO_UMASK +#define FILE_MODE 0644 +#define DIR_MODE 0755 +#else +#define FILE_MODE 0666 +#define DIR_MODE 0777 +#endif + +#endif /* _OSE_ */ + +#ifdef VXWORKS /* Currently only used on vxworks */ + +#define EF_ALLOC(S) driver_alloc((S)) +#define EF_REALLOC(P, S) driver_realloc((P), (S)) +#define EF_SAFE_ALLOC(S) ef_safe_alloc((S)) +#define EF_SAFE_REALLOC(P, S) ef_safe_realloc((P), (S)) +#define EF_FREE(P) do { if((P)) driver_free((P)); } while(0) + +extern void erl_exit(int n, char *fmt, _DOTS_); + +static void *ef_safe_alloc(Uint s) +{ + void *p = EF_ALLOC(s); + if (!p) erl_exit(1, + "unix efile drv: Can't allocate %d bytes of memory\n", + s); + return p; +} + +#if 0 /* Currently not used */ + +static void *ef_safe_realloc(void *op, Uint s) +{ + void *p = EF_REALLOC(op, s); + if (!p) erl_exit(1, + "unix efile drv: Can't reallocate %d bytes of memory\n", + s); + return p; +} + +#endif /* #if 0 */ +#endif /* #ifdef VXWORKS */ + +#define IS_DOT_OR_DOTDOT(s) \ + (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) + +#ifdef VXWORKS +static FUNCTION(int, vxworks_to_posix, (int vx_errno)); +#endif + +/* +** VxWorks (not) strikes again. Too long RESULTING paths +** may give the infamous bus error. Have to check ALL +** filenames and pathnames. No wonder the emulator is slow on +** these cards... +*/ +#ifdef VXWORKS +#define CHECK_PATHLEN(Name, ErrInfo) \ + if (path_size(Name) > PATH_MAX) { \ + errno = ENAMETOOLONG; \ + return check_error(-1, ErrInfo); \ + } +#else +#define CHECK_PATHLEN(X,Y) /* Nothing */ +#endif + +static FUNCTION(int, check_error, (int result, Efile_error* errInfo)); + +static int +check_error(int result, Efile_error *errInfo) +{ + if (result < 0) { +#ifdef VXWORKS + errInfo->posix_errno = errInfo->os_errno = vxworks_to_posix(errno); +#else + errInfo->posix_errno = errInfo->os_errno = errno; +#endif + return 0; + } + return 1; +} + +#ifdef VXWORKS + +/* + * VxWorks has different error codes for different file systems. + * We map those to POSIX ones. + */ +static int +vxworks_to_posix(int vx_errno) +{ + DEBUGF(("[vxworks_to_posix] vx_errno: %08x\n", vx_errno)); + switch (vx_errno) { + /* dosFsLib mapping */ +#ifdef S_dosFsLib_FILE_ALREADY_EXISTS + case S_dosFsLib_FILE_ALREADY_EXISTS: return EEXIST; +#else + case S_dosFsLib_FILE_EXISTS: return EEXIST; +#endif +#ifdef S_dosFsLib_BAD_DISK + case S_dosFsLib_BAD_DISK: return EIO; +#endif +#ifdef S_dosFsLib_CANT_CHANGE_ROOT + case S_dosFsLib_CANT_CHANGE_ROOT: return EINVAL; +#endif +#ifdef S_dosFsLib_NO_BLOCK_DEVICE + case S_dosFsLib_NO_BLOCK_DEVICE: return ENOTBLK; +#endif +#ifdef S_dosFsLib_BAD_SEEK + case S_dosFsLib_BAD_SEEK: return ESPIPE; +#endif + case S_dosFsLib_VOLUME_NOT_AVAILABLE: return ENXIO; + case S_dosFsLib_DISK_FULL: return ENOSPC; + case S_dosFsLib_FILE_NOT_FOUND: return ENOENT; + case S_dosFsLib_NO_FREE_FILE_DESCRIPTORS: return ENFILE; + case S_dosFsLib_INVALID_NUMBER_OF_BYTES: return EINVAL; + case S_dosFsLib_ILLEGAL_NAME: return EINVAL; + case S_dosFsLib_CANT_DEL_ROOT: return EACCES; + case S_dosFsLib_NOT_FILE: return EISDIR; + case S_dosFsLib_NOT_DIRECTORY: return ENOTDIR; + case S_dosFsLib_NOT_SAME_VOLUME: return EXDEV; + case S_dosFsLib_READ_ONLY: return EACCES; + case S_dosFsLib_ROOT_DIR_FULL: return ENOSPC; + case S_dosFsLib_DIR_NOT_EMPTY: return EEXIST; + case S_dosFsLib_NO_LABEL: return ENXIO; + case S_dosFsLib_INVALID_PARAMETER: return EINVAL; + case S_dosFsLib_NO_CONTIG_SPACE: return ENOSPC; + case S_dosFsLib_FD_OBSOLETE: return EBADF; + case S_dosFsLib_DELETED: return EINVAL; + case S_dosFsLib_INTERNAL_ERROR: return EIO; + case S_dosFsLib_WRITE_ONLY: return EACCES; + /* nfsLib mapping - is needed since Windriver has used */ + /* inconsistent error codes (errno.h/nfsLib.h). */ + case S_nfsLib_NFS_OK: return 0; + case S_nfsLib_NFSERR_PERM: return EPERM; + case S_nfsLib_NFSERR_NOENT: return ENOENT; + case S_nfsLib_NFSERR_IO: return EIO; + case S_nfsLib_NFSERR_NXIO: return ENXIO; +#ifdef S_nfsLib_NFSERR_ACCES + case S_nfsLib_NFSERR_ACCES: return EACCES; +#else + case S_nfsLib_NFSERR_ACCESS: return EACCES; +#endif + case S_nfsLib_NFSERR_EXIST: return EEXIST; + case S_nfsLib_NFSERR_NODEV: return ENODEV; + case S_nfsLib_NFSERR_NOTDIR: return ENOTDIR; + case S_nfsLib_NFSERR_ISDIR: return EISDIR; + case S_nfsLib_NFSERR_FBIG: return EFBIG; + case S_nfsLib_NFSERR_NOSPC: return ENOSPC; + case S_nfsLib_NFSERR_ROFS: return EROFS; + case S_nfsLib_NFSERR_NAMETOOLONG: return ENAMETOOLONG; + case S_nfsLib_NFSERR_NOTEMPTY: return EEXIST; + case S_nfsLib_NFSERR_DQUOT: return ENOSPC; + case S_nfsLib_NFSERR_STALE: return EINVAL; + case S_nfsLib_NFSERR_WFLUSH: return ENXIO; + /* And sometimes (...) the error codes are from ioLib (as in the */ + /* case of the (for nfsLib) unimplemented rename function) */ + case S_ioLib_DISK_NOT_PRESENT: return EIO; +#if S_ioLib_DISK_NOT_PRESENT != S_ioLib_NO_DRIVER + case S_ioLib_NO_DRIVER: return ENXIO; +#endif + case S_ioLib_UNKNOWN_REQUEST: return ENOSYS; + case S_ioLib_DEVICE_TIMEOUT: return EIO; +#ifdef S_ioLib_UNFORMATED + /* Added (VxWorks 5.2 -> 5.3.1) */ + #if S_ioLib_UNFORMATED != S_ioLib_DEVICE_TIMEOUT + case S_ioLib_UNFORMATED: return EIO; + #endif +#endif +#if S_ioLib_DEVICE_TIMEOUT != S_ioLib_DEVICE_ERROR + case S_ioLib_DEVICE_ERROR: return ENXIO; +#endif + case S_ioLib_WRITE_PROTECTED: return EACCES; + case S_ioLib_NO_FILENAME: return EINVAL; + case S_ioLib_CANCELLED: return EINTR; + case S_ioLib_NO_DEVICE_NAME_IN_PATH: return EINVAL; + case S_ioLib_NAME_TOO_LONG: return ENAMETOOLONG; +#ifdef S_objLib_OBJ_UNAVAILABLE + case S_objLib_OBJ_UNAVAILABLE: return ENOENT; +#endif + + /* Temporary workaround for a weird error in passFs + (VxWorks Simsparc only). File operation fails because of + ENOENT, but errno is not set. */ +#ifdef SIMSPARCSOLARIS + case 0: return ENOENT; +#endif + + } + /* If the error code matches none of the above, assume */ + /* it is a POSIX one already. The upper bits (>=16) are */ + /* cleared since VxWorks uses those bits to indicate in */ + /* what module the error occured. */ + return vx_errno & 0xffff; +} + +static int +vxworks_enotsup(Efile_error *errInfo) +{ + errInfo->posix_errno = errInfo->os_errno = ENOTSUP; + return 0; +} + +static int +count_path_length(char *pathname, char *pathname2) +{ + static int stack[PATH_MAX / 2 + 1]; + int sp = 0; + char *tmp; + char *cpy = NULL; + int i; + int sum; + for(i = 0;i < 2;++i) { + if (!i) { + cpy = EF_SAFE_ALLOC(strlen(pathname)+1); + strcpy(cpy, pathname); + } else if (pathname2 != NULL) { + EF_FREE(cpy); + cpy = EF_SAFE_ALLOC(strlen(pathname2)+1); + strcpy(cpy, pathname2); + } else + break; + + for (tmp = strtok(cpy,"/"); tmp != NULL; tmp = strtok(NULL,"/")) { + if (!strcmp(tmp,"..") && sp > 0) + --sp; + else if (strcmp(tmp,".")) + stack[sp++] = strlen(tmp); + } + } + if (cpy != NULL) + EF_FREE(cpy); + sum = 0; + for(i = 0;i < sp; ++i) + sum += stack[i]+1; + return (sum) ? sum : 1; +} + +static int +path_size(char *pathname) +{ + static char currdir[PATH_MAX+2]; + if (*pathname == '/') + return count_path_length(pathname,NULL); + ioDefPathGet(currdir); + strcat(currdir,"/"); + return count_path_length(currdir,pathname); +} + +#endif /* VXWORKS */ + +#ifdef _OSE_ +static int +ose_enotsup(Efile_error *errInfo) +{ + errInfo->posix_errno = errInfo->os_errno = ENOTSUP; + return 0; +} +#endif /* _OSE_ */ + +int +efile_mkdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to create. */ +{ + CHECK_PATHLEN(name,errInfo); +#ifdef NO_MKDIR_MODE +#ifdef VXWORKS + /* This is a VxWorks/nfs workaround for erl_tar to create + * non-existant directories. (of some reason (...) VxWorks + * returns, the *non-module-prefixed*, 0xd code when + * trying to create a directory in a directory that doesn't exist). + * (see efile_openfile) + */ + if (mkdir(name) < 0) { + struct stat sb; + if (name[0] == '\0') { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } else if (stat(name, &sb) == OK) { + errno = S_nfsLib_NFSERR_EXIST; + } else if((strchr(name, '/') != NULL) && (errno == 0xd)) { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } + return check_error(-1, errInfo); + } else return 1; +#else + return check_error(mkdir(name), errInfo); +#endif +#else + return check_error(mkdir(name, DIR_MODE), errInfo); +#endif +} + +int +efile_rmdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to delete. */ +{ + CHECK_PATHLEN(name, errInfo); + if (rmdir(name) == 0) { + return 1; + } +#ifdef VXWORKS + if (name[0] == '\0') { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } +#else + if (errno == ENOTEMPTY) { + errno = EEXIST; + } + if (errno == EEXIST) { + int saved_errno = errno; + struct stat file_stat; + struct stat cwd_stat; + + /* + * The error code might be wrong if this is the current directory. + */ + + if (stat(name, &file_stat) == 0 && stat(".", &cwd_stat) == 0 && + file_stat.st_ino == cwd_stat.st_ino && + file_stat.st_dev == cwd_stat.st_dev) { + saved_errno = EINVAL; + } + errno = saved_errno; + } +#endif + return check_error(-1, errInfo); +} + +int +efile_delete_file(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of file to delete. */ +{ + CHECK_PATHLEN(name,errInfo); +#ifdef _OSE_ + if (remove(name) == 0) { + return 1; + } +#else + if (unlink(name) == 0) { + return 1; + } + if (errno == EISDIR) { /* Linux sets the wrong error code. */ + errno = EPERM; + } +#endif + return check_error(-1, errInfo); +} + +/* + *--------------------------------------------------------------------------- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Results: + * If the directory was successfully created, returns 1. + * Otherwise the return value is 0 and errno is set to + * indicate the error. Some possible values for errno are: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist, or src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +efile_rename(Efile_error* errInfo, /* Where to return error codes. */ + char* src, /* Original name. */ + char* dst) /* New name. */ +{ + CHECK_PATHLEN(src,errInfo); + CHECK_PATHLEN(dst,errInfo); +#ifdef VXWORKS + + /* First check if src == dst, if so, just return. */ + /* VxWorks dos file system destroys the file otherwise, */ + /* VxWorks nfs file system rename doesn't work at all. */ + if(strcmp(src, dst) == 0) + return 1; +#endif + if (rename(src, dst) == 0) { + return 1; + } +#ifdef VXWORKS + /* nfs for VxWorks doesn't support rename. We try to emulate it */ + /* (by first copying src to dst and then deleting src). */ + if(errno == S_ioLib_UNKNOWN_REQUEST && /* error code returned + by ioLib (!) */ + copy(src, dst) == OK && + unlink(src) == OK) + return 1; +#endif + if (errno == ENOTEMPTY) { + errno = EEXIST; + } +#if defined (sparc) && !defined(VXWORKS) && !defined(_OSE_) + /* + * SunOS 4.1.4 reports overwriting a non-empty directory with a + * directory as EINVAL instead of EEXIST (first rule out the correct + * EINVAL result code for moving a directory into itself). Must be + * conditionally compiled because realpath() is only defined on SunOS. + */ + + if (errno == EINVAL) { + char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN]; + DIR *dirPtr; + struct dirent *dirEntPtr; + +#ifdef PURIFY + memset(srcPath, '\0', sizeof(srcPath)); + memset(dstPath, '\0', sizeof(dstPath)); +#endif + + if ((realpath(src, srcPath) != NULL) + && (realpath(dst, dstPath) != NULL) + && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) { + dirPtr = opendir(dst); + if (dirPtr != NULL) { + while ((dirEntPtr = readdir(dirPtr)) != NULL) { + if ((strcmp(dirEntPtr->d_name, ".") != 0) && + (strcmp(dirEntPtr->d_name, "..") != 0)) { + errno = EEXIST; + closedir(dirPtr); + return check_error(-1, errInfo); + } + } + closedir(dirPtr); + } + } + errno = EINVAL; + } +#endif /* sparc */ + + if (strcmp(src, "/") == 0) { + /* + * Alpha reports renaming / as EBUSY and Linux reports it as EACCES, + * instead of EINVAL. + */ + + errno = EINVAL; + } + + /* + * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a + * file across filesystems and the parent directory of that file is + * not writable. Most other systems return EXDEV. Does nothing to + * correct this behavior. + */ + + return check_error(-1, errInfo); +} + +int +efile_chdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to make current. */ +{ + CHECK_PATHLEN(name, errInfo); + return check_error(chdir(name), errInfo); +} + + +int +efile_getdcwd(Efile_error* errInfo, /* Where to return error codes. */ + int drive, /* 0 - current, 1 - A, 2 - B etc. */ + char* buffer, /* Where to return the current + directory. */ + size_t size) /* Size of buffer. */ +{ + if (drive == 0) { + if (getcwd(buffer, size) == NULL) + return check_error(-1, errInfo); + +#ifdef SIMSPARCSOLARIS + /* We get "host:" prepended to the dirname - remove!. */ + { + int i = 0; + int j = 0; + while ((buffer[i] != ':') && (buffer[i] != '\0')) i++; + if (buffer[i] == ':') { + i++; + while ((buffer[j++] = buffer[i++]) != '\0'); + } + } +#endif + return 1; + } + + /* + * Drives other than 0 is not supported on Unix. + */ + + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_readdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name, /* Name of directory to open. */ + EFILE_DIR_HANDLE* p_dir_handle, /* Pointer to directory + handle of + open directory.*/ + char* buffer, /* Pointer to buffer for + one filename. */ + size_t size) /* Size of buffer. */ +{ + DIR *dp; /* Pointer to directory structure. */ + struct dirent* dirp; /* Pointer to directory entry. */ + + /* + * If this is the first call, we must open the directory. + */ + + CHECK_PATHLEN(name, errInfo); + + if (*p_dir_handle == NULL) { + dp = opendir(name); + if (dp == NULL) + return check_error(-1, errInfo); + *p_dir_handle = (EFILE_DIR_HANDLE) dp; + } + + /* + * Retrieve the name of the next file using the directory handle. + */ + + dp = *((DIR **)((void *)p_dir_handle)); + for (;;) { + dirp = readdir(dp); + if (dirp == NULL) { + closedir(dp); + return 0; + } + if (IS_DOT_OR_DOTDOT(dirp->d_name)) + continue; + buffer[0] = '\0'; + strncat(buffer, dirp->d_name, size-1); + return 1; + } +} + +int +efile_openfile(Efile_error* errInfo, /* Where to return error codes. */ + char* name, /* Name of directory to open. */ + int flags, /* Flags to user for opening. */ + int* pfd, /* Where to store the file + descriptor. */ + Sint64 *pSize) /* Where to store the size of the + file. */ +{ + struct stat statbuf; + int fd; + int mode; /* Open mode. */ +#ifdef VXWORKS + char pathbuff[PATH_MAX+2]; + char sbuff[PATH_MAX*2]; + char *totbuff = sbuff; + int nameneed; +#endif + + + CHECK_PATHLEN(name, errInfo); + +#ifdef VXWORKS + /* Have to check that it's not a directory. */ + if (stat(name,&statbuf) != ERROR && ISDIR(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } +#endif + + if (stat(name, &statbuf) >= 0 && !ISREG(statbuf)) { +#if !defined(VXWORKS) && !defined(OSE) + /* + * For UNIX only, here is some ugly code to allow + * /dev/null to be opened as a file. + * + * Assumption: The i-node number for /dev/null cannot be zero. + */ + static ino_t dev_null_ino = 0; + + if (dev_null_ino == 0) { + struct stat nullstatbuf; + + if (stat("/dev/null", &nullstatbuf) >= 0) { + dev_null_ino = nullstatbuf.st_ino; + } + } + if (!(dev_null_ino && statbuf.st_ino == dev_null_ino)) { +#endif + errno = EISDIR; + return check_error(-1, errInfo); +#if !defined(VXWORKS) && !defined(OSE) + } +#endif + } + + switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) { + case EFILE_MODE_READ: + mode = O_RDONLY; + break; + case EFILE_MODE_WRITE: + if (flags & EFILE_NO_TRUNCATE) + mode = O_WRONLY | O_CREAT; + else + mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case EFILE_MODE_READ_WRITE: + mode = O_RDWR | O_CREAT; + break; + default: + errno = EINVAL; + return check_error(-1, errInfo); + } + + + if (flags & EFILE_MODE_APPEND) { + mode &= ~O_TRUNC; +#ifndef VXWORKS + mode |= O_APPEND; /* Dont make VxWorks think things it shouldn't */ +#endif + } + + +#ifdef VXWORKS + if (*name != '/') { + /* Make sure it is an absolute pathname, because ftruncate needs it */ + ioDefPathGet(pathbuff); + strcat(pathbuff,"/"); + nameneed = strlen(pathbuff) + strlen(name) + 1; + if (nameneed > PATH_MAX*2) + totbuff = EF_SAFE_ALLOC(nameneed); + strcpy(totbuff,pathbuff); + strcat(totbuff,name); + fd = open(totbuff, mode, FILE_MODE); + if (totbuff != sbuff) + EF_FREE(totbuff); + } else { + fd = open(name, mode, FILE_MODE); + } +#else + fd = open(name, mode, FILE_MODE); +#endif + +#ifdef VXWORKS + + /* This is a VxWorks/nfs workaround for erl_tar to create + * non-existant directories. (of some reason (...) VxWorks + * returns, the *non-module-prefixed*, 0xd code when + * trying to write a file in a directory that doesn't exist). + * (see efile_mkdir) + */ + if ((fd < 0) && (strchr(name, '/') != NULL) && (errno == 0xd)) { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + return check_error(-1, errInfo); + } +#endif + + if (!check_error(fd, errInfo)) + return 0; + + *pfd = fd; + if (pSize) { + *pSize = statbuf.st_size; + } + return 1; +} + +int +efile_may_openfile(Efile_error* errInfo, char *name) { + struct stat statbuf; /* Information about the file */ + int result; + + result = stat(name, &statbuf); + if (!check_error(result, errInfo)) + return 0; + if (!ISREG(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +} + +void +efile_closefile(int fd) +{ + close(fd); +} + +int +efile_fsync(Efile_error *errInfo, /* Where to return error codes. */ + int fd) /* File descriptor for file to sync. */ +{ +#ifdef NO_FSYNC +#ifdef VXWORKS + return check_error(ioctl(fd, FIOSYNC, 0), errInfo); +#else + undefined fsync +#endif /* VXWORKS */ +#else +#if defined(DARWIN) && defined(F_FULLFSYNC) + return check_error(fcntl(fd, F_FULLFSYNC), errInfo); +#else + return check_error(fsync(fd), errInfo); +#endif /* DARWIN */ +#endif /* NO_FSYNC */ +} + +int +efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char* name, int info_for_link) +{ + struct stat statbuf; /* Information about the file */ + struct tm *timep; /* Broken-apart filetime. */ + int result; + +#ifdef VXWORKS + if (*name == '\0') { + errInfo->posix_errno = errInfo->os_errno = ENOENT; + return 0; + } +#endif + + CHECK_PATHLEN(name, errInfo); + + if (info_for_link) { +#if (defined(VXWORKS) || defined(_OSE_)) + result = stat(name, &statbuf); +#else + result = lstat(name, &statbuf); +#endif + } else { + result = stat(name, &statbuf); + } + if (!check_error(result, errInfo)) { + return 0; + } + +#if SIZEOF_OFF_T == 4 + pInfo->size_high = 0; +#else + pInfo->size_high = (Uint32)(statbuf.st_size >> 32); +#endif + pInfo->size_low = (Uint32)statbuf.st_size; + +#ifdef NO_ACCESS + /* Just look at read/write access for owner. */ +#ifdef VXWORKS + + pInfo->access = FA_NONE; + if(statbuf.st_mode & S_IRUSR) + pInfo->access |= FA_READ; + if(statbuf.st_mode & S_IWUSR) + pInfo->access |= FA_WRITE; + +#else + + pInfo->access = ((statbuf.st_mode >> 6) & 07) >> 1; + +#endif /* VXWORKS */ +#else + pInfo->access = FA_NONE; + if (access(name, R_OK) == 0) + pInfo->access |= FA_READ; + if (access(name, W_OK) == 0) + pInfo->access |= FA_WRITE; + +#endif + + if (ISDEV(statbuf)) + pInfo->type = FT_DEVICE; + else if (ISDIR(statbuf)) + pInfo->type = FT_DIRECTORY; + else if (ISREG(statbuf)) + pInfo->type = FT_REGULAR; + else if (ISLNK(statbuf)) + pInfo->type = FT_SYMLINK; + else + pInfo->type = FT_OTHER; + +#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS) + { + /* Use the reentrant version of localtime() */ + static struct tm local_tm; +#define localtime(a) (localtime_r((a), &local_tm), &local_tm) +#endif + + +#define GET_TIME(dst, src) \ + timep = localtime(&statbuf.src); \ + (dst).year = timep->tm_year+1900; \ + (dst).month = timep->tm_mon+1; \ + (dst).day = timep->tm_mday; \ + (dst).hour = timep->tm_hour; \ + (dst).minute = timep->tm_min; \ + (dst).second = timep->tm_sec + + GET_TIME(pInfo->accessTime, st_atime); + GET_TIME(pInfo->modifyTime, st_mtime); + GET_TIME(pInfo->cTime, st_ctime); + +#undef GET_TIME + +#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS) + } +#endif + + pInfo->mode = statbuf.st_mode; + pInfo->links = statbuf.st_nlink; + pInfo->major_device = statbuf.st_dev; +#ifdef _OSE_ + pInfo->minor_device = 0; +#else + pInfo->minor_device = statbuf.st_rdev; +#endif + pInfo->inode = statbuf.st_ino; + pInfo->uid = statbuf.st_uid; + pInfo->gid = statbuf.st_gid; + + return 1; +} + +int +efile_write_info(Efile_error *errInfo, Efile_info *pInfo, char *name) +{ + CHECK_PATHLEN(name, errInfo); + +#ifdef VXWORKS + + if (pInfo->mode != -1) { + int fd; + struct stat statbuf; + + fd = open(name, O_RDONLY, 0); + if (!check_error(fd, errInfo)) + return 0; + if (fstat(fd, &statbuf) < 0) { + close(fd); + return check_error(-1, errInfo); + } + if (pInfo->mode & S_IWUSR) { + /* clear read only bit */ + statbuf.st_attrib &= ~DOS_ATTR_RDONLY; + } else { + /* set read only bit */ + statbuf.st_attrib |= DOS_ATTR_RDONLY; + } + /* This should work for dos files but not for nfs ditos, so don't + * report errors (to avoid problems when running e.g. erl_tar) + */ + ioctl(fd, FIOATTRIBSET, statbuf.st_attrib); + close(fd); + } +#else + /* + * On some systems chown will always fail for a non-root user unless + * POSIX_CHOWN_RESTRICTED is not set. Others will succeed as long as + * you don't try to chown a file to someone besides youself. + */ + +#ifndef _OSE_ + if (chown(name, pInfo->uid, pInfo->gid) && errno != EPERM) { + return check_error(-1, errInfo); + } +#endif + + if (pInfo->mode != -1) { + mode_t newMode = pInfo->mode & (S_ISUID | S_ISGID | + S_IRWXU | S_IRWXG | S_IRWXO); + if (chmod(name, newMode)) { + newMode &= ~(S_ISUID | S_ISGID); + if (chmod(name, newMode)) { + return check_error(-1, errInfo); + } + } + } + +#endif /* !VXWORKS */ + +#ifndef _OSE_ + + if (pInfo->accessTime.year != -1 && pInfo->modifyTime.year != -1) { + struct utimbuf tval; + struct tm timebuf; + +#define MKTIME(tb, ts) \ + timebuf.tm_year = ts.year-1900; \ + timebuf.tm_mon = ts.month-1; \ + timebuf.tm_mday = ts.day; \ + timebuf.tm_hour = ts.hour; \ + timebuf.tm_min = ts.minute; \ + timebuf.tm_sec = ts.second; \ + timebuf.tm_isdst = -1; \ + if ((tb = mktime(&timebuf)) == (time_t) -1) { \ + errno = EINVAL; \ + return check_error(-1, errInfo); \ + } + + MKTIME(tval.actime, pInfo->accessTime); + MKTIME(tval.modtime, pInfo->modifyTime); +#undef MKTIME + +#ifdef VXWORKS + /* VxWorks' utime doesn't work when the file is a nfs mounted + * one, don't report error if utime fails. + */ + utime(name, &tval); + return 1; +#else + return check_error(utime(name, &tval), errInfo); +#endif + } +#endif /* !_OSE_ */ + return 1; +} + + +int +efile_write(Efile_error* errInfo, /* Where to return error codes. */ + int flags, /* Flags given when file was + opened. */ + int fd, /* File descriptor to write to. */ + char* buf, /* Buffer to write. */ + size_t count) /* Number of bytes to write. */ +{ + ssize_t written; /* Bytes written in last operation. */ + +#ifdef VXWORKS + if (flags & EFILE_MODE_APPEND) { + lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */ + } +#endif + while (count > 0) { + if ((written = write(fd, buf, count)) < 0) { + if (errno != EINTR) + return check_error(-1, errInfo); + else + written = 0; + } + ASSERT(written <= count); + buf += written; + count -= written; + } + return 1; +} + +int +efile_writev(Efile_error* errInfo, /* Where to return error codes */ + int flags, /* Flags given when file was + * opened */ + int fd, /* File descriptor to write to */ + SysIOVec* iov, /* Vector of buffer structs. + * The structs are unchanged + * after the call */ + int iovcnt, /* Number of structs in vector */ + size_t size) /* Number of bytes to write */ +{ + int cnt = 0; /* Buffers so far written */ + int p = 0; /* Position in next buffer */ + + ASSERT(iovcnt >= 0); + +#ifdef VXWORKS + if (flags & EFILE_MODE_APPEND) { + lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */ + } +#endif + + while (cnt < iovcnt) { +#ifdef HAVE_WRITEV + int w; /* Bytes written in this call */ + int b = iovcnt - cnt; /* Buffers to write */ + if (b > MAXIOV) + b = MAXIOV; + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + if (b == 1) { + /* Degenerated io vector */ + do { + w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p); + } while (w < 0 && errno == EINTR); + } else { + /* Non-empty vector first. + * Adjust pos in first buffer in case of + * previous incomplete writev */ + iov[cnt].iov_base += p; + iov[cnt].iov_len -= p; + do { + w = writev(fd, &iov[cnt], b); + } while (w < 0 && errno == EINTR); + iov[cnt].iov_base -= p; + iov[cnt].iov_len += p; + } + if (w < 0) + return check_error(-1, errInfo); + } else { + /* Empty vector first - skip */ + cnt++; + continue; + } + ASSERT(w >= 0); + /* Move forward to next vector to write */ + for (; cnt < iovcnt; cnt++) { + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + if (w < iov[cnt].iov_len) + break; + else + w -= iov[cnt].iov_len; + } + } + ASSERT(w >= 0); + p = w > 0 ? w : 0; /* Skip p bytes next writev */ +#else /* #ifdef HAVE_WRITEV */ + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + /* Non-empty vector */ + int w; /* Bytes written in this call */ + while (p < iov[cnt].iov_len) { + do { + w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p); + } while (w < 0 && errno == EINTR); + if (w < 0) + return check_error(-1, errInfo); + p += w; + } + } + cnt++; + p = 0; +#endif /* #ifdef HAVE_WRITEV */ + } /* while (cnt< iovcnt) */ + size = 0; /* Avoid compiler warning */ + return 1; +} + +int +efile_read(Efile_error* errInfo, /* Where to return error codes. */ + int flags, /* Flags given when file was opened. */ + int fd, /* File descriptor to read from. */ + char* buf, /* Buffer to read into. */ + size_t count, /* Number of bytes to read. */ + size_t *pBytesRead) /* Where to return number of + bytes read. */ +{ + ssize_t n; + + for (;;) { + if ((n = read(fd, buf, count)) >= 0) + break; + else if (errno != EINTR) + return check_error(-1, errInfo); + } + *pBytesRead = (size_t) n; + return 1; +} + + +/* pread() and pwrite() */ +/* Some unix systems, notably Solaris has these syscalls */ +/* It is especially nice for i.e. the dets module to have support */ +/* for this, even if the underlying OS dosn't support it, it is */ +/* reasonably easy to work around by first calling seek, and then */ +/* calling read(). */ +/* This later strategy however changes the file pointer, which pread() */ +/* does not do. We choose to ignore this and say that the location */ +/* of the file pointer is undefined after a call to any of the p functions*/ + + +int +efile_pread(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to read from. */ + Sint64 offset, /* Offset in bytes from BOF. */ + char* buf, /* Buffer to read into. */ + size_t count, /* Number of bytes to read. */ + size_t *pBytesRead) /* Where to return + number of bytes read. */ +{ +#if defined(HAVE_PREAD) && defined(HAVE_PWRITE) + ssize_t n; + off_t off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + for (;;) { + if ((n = pread(fd, buf, count, offset)) >= 0) + break; + else if (errno != EINTR) + return check_error(-1, errInfo); + } + *pBytesRead = (size_t) n; + return 1; +#else + { + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_read(errInfo, 0, fd, buf, count, pBytesRead); + } else { + return res; + } + } +#endif +} + + + +int +efile_pwrite(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to write to. */ + char* buf, /* Buffer to write. */ + size_t count, /* Number of bytes to write. */ + Sint64 offset) /* where to write it */ +{ +#if defined(HAVE_PREAD) && defined(HAVE_PWRITE) + ssize_t written; /* Bytes written in last operation. */ + off_t off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + + while (count > 0) { + if ((written = pwrite(fd, buf, count, offset)) < 0) { + if (errno != EINTR) + return check_error(-1, errInfo); + else + written = 0; + } + ASSERT(written <= count); + buf += written; + count -= written; + offset += written; + } + return 1; +#else /* For unix systems that don't support pread() and pwrite() */ + { + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + + if (res) { + return efile_write(errInfo, 0, fd, buf, count); + } else { + return res; + } + } +#endif +} + + +int +efile_seek(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to do the seek on. */ + Sint64 offset, /* Offset in bytes from the given + origin. */ + int origin, /* Origin of seek (SEEK_SET, SEEK_CUR, + SEEK_END). */ + Sint64 *new_location) /* Resulting new location in file. */ +{ + off_t off, result; + + switch (origin) { + case EFILE_SEEK_SET: origin = SEEK_SET; break; + case EFILE_SEEK_CUR: origin = SEEK_CUR; break; + case EFILE_SEEK_END: origin = SEEK_END; break; + default: + errno = EINVAL; + return check_error(-1, errInfo); + } + off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + + errno = 0; + result = lseek(fd, off, origin); + + /* + * Note that the man page for lseek (on SunOs 5) says: + * + * "if fildes is a remote file descriptor and offset is + * negative, lseek() returns the file pointer even if it is + * negative." + */ + + if (result < 0 && errno == 0) + errno = EINVAL; + if (result < 0) + return check_error(-1, errInfo); + if (new_location) { + *new_location = result; + } + return 1; +} + + +int +efile_truncate_file(Efile_error* errInfo, int *fd, int flags) +{ +#ifdef VXWORKS + off_t offset; + char namebuf[PATH_MAX+1]; + char namebuf2[PATH_MAX+10]; + int new; + int dummy; + int i; + int left; + static char buff[1024]; + struct stat st; + Efile_error tmperr; + + if ((offset = lseek(*fd, 0, 1)) < 0) { + return check_error((int) offset,errInfo); + } + if (ftruncate(*fd, offset) < 0) { + if (vxworks_to_posix(errno) != EINVAL) { + return check_error(-1, errInfo); + } + /* + ** Kludge + */ + if(ioctl(*fd,FIOGETNAME,(int) namebuf) < 0) { + return check_error(-1, errInfo); + } + for(i=0;i<1000;++i) { + sprintf(namebuf2,"%s%d",namebuf,i); + CHECK_PATHLEN(namebuf2,errInfo); + if (stat(namebuf2,&st) < 0) { + break; + } + } + if (i > 1000) { + errno = EINVAL; + return check_error(-1, errInfo); + } + if (close(*fd) < 0) { + return check_error(-1, errInfo); + } + if (efile_rename(&tmperr,namebuf,namebuf2) < 0) { + i = check_error(-1,&tmperr); + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + *errInfo = tmperr; + } + return i; + } + if ((*fd = open(namebuf2, O_RDONLY, 0)) < 0) { + i = check_error(-1,errInfo); + efile_rename(&tmperr,namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + /* Point of no return... */ + + if ((new = open(namebuf,O_RDWR | O_CREAT, FILE_MODE)) < 0) { + close(*fd); + *fd = -1; + return 0; + } + left = offset; + + while (left) { + if ((i = read(*fd,buff,(left > 1024) ? 1024 : left)) < 0) { + i = check_error(-1,errInfo); + close(new); + close(*fd); + unlink(namebuf); + efile_rename(&tmperr,namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + left -= i; + if (write(new,buff,i) < 0) { + i = check_error(-1,errInfo); + close(new); + close(*fd); + unlink(namebuf); + rename(namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + } + close(*fd); + unlink(namebuf2); + close(new); + i = efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,fd, + &dummy); + if (i) { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + return 1; +#else +#ifndef NO_FTRUNCATE + off_t offset; + + return check_error((offset = lseek(*fd, 0, 1)) >= 0 && + ftruncate(*fd, offset) == 0 ? 1 : -1, + errInfo); +#else + return 1; +#endif +#endif +} + +int +efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + int len; + ASSERT(size > 0); + len = readlink(name, buffer, size-1); + if (len == -1) { + return check_error(-1, errInfo); + } + buffer[len] = '\0'; + return 1; +#endif +#endif +} + +int +efile_altname(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_link(Efile_error* errInfo, char* old, char* new) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + return check_error(link(old, new), errInfo); +#endif +#endif +} + +int +efile_symlink(Efile_error* errInfo, char* old, char* new) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + return check_error(symlink(old, new), errInfo); +#endif +#endif +} diff --git a/erts/emulator/drivers/vxworks/vxworks_resolv.c b/erts/emulator/drivers/vxworks/vxworks_resolv.c new file mode 100644 index 0000000000..8fcbb736f7 --- /dev/null +++ b/erts/emulator/drivers/vxworks/vxworks_resolv.c @@ -0,0 +1,44 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Interface functions to different versions of gethostbyname. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "netdb.h" +#include "erl_resolv.h" + +struct hostent *erl_gethostbyname(name) + char *name; +{ + return gethostbyname(name); +} + +struct hostent *erl_gethostbyaddr(addr, len, type) + char *addr; + int len; + int type; +{ + return gethostbyaddr(addr, len, type); +} + + diff --git a/erts/emulator/drivers/win32/mem_drv.c b/erts/emulator/drivers/win32/mem_drv.c new file mode 100644 index 0000000000..fa7c46eca8 --- /dev/null +++ b/erts/emulator/drivers/win32/mem_drv.c @@ -0,0 +1,141 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* Purpose: Access to elib memory statistics */ + +#include "sys.h" +#include "erl_driver.h" +#include "elib_stat.h" + +#define MAP_BUF_SIZE 1000 /* Max map size */ +#define HISTO_BUF_SIZE 100 /* Max histogram buckets */ + +static ErlDrvData mem_start(ErlDrvPort, char*); +static int mem_init(void); +static void mem_stop(ErlDrvData); +static void mem_command(ErlDrvData); + +ErlDrvEntry mem_driver_entry = { + mem_init, + mem_start, + mem_stop, + mem_command, + NULL, + NULL, + "mem_drv" +}; + +static int mem_init(void) +{ + return 0; +} + +static ErlDrvData mem_start(ErlDrvPort port, char* buf) +{ + return (ErlDrvData)port; +} + +static void mem_stop(ErlDrvData port) +{ +} + +void putint32(p, v) +byte* p; int v; +{ + p[0] = (v >> 24) & 0xff; + p[1] = (v >> 16) & 0xff; + p[2] = (v >> 8) & 0xff; + p[3] = (v) & 0xff; +} + +int getint16(p) +byte* p; +{ + return (p[0] << 8) | p[1]; +} + +/* +** Command: +** m L1 L0 -> a heap map of length L1*256 + L0 is returned +** s -> X3 X2 X1 X0 Y3 Y2 Y1 Y0 Z3 Z2 Z1 Z0 +** X == Total heap size bytes +** Y == Total free bytes +** Z == Size of largest free block in bytes +** +** h L1 L0 B0 -> Generate a logarithm histogram base B with L buckets +** l L1 L0 S0 -> Generate a linear histogram with step S with L buckets +*/ +unsigned char outbuf[HISTO_BUF_SIZE*2*4]; + +static void mem_command(ErlDrvData port, char* buf, int count) +{ + if ((count == 1) && buf[0] == 's') { + struct elib_stat info; + char v[3*4]; + + elib_stat(&info); + + putint32(v, info.mem_total*4); + putint32(v+4, info.mem_free*4); + putint32(v+8, info.max_free*4); + driver_output((ErlDrvPort)port, v, 12); + return; + } + else if ((count == 3) && buf[0] == 'm') { + char w[MAP_BUF_SIZE]; + int n = getint16(buf+1); + + if (n > MAP_BUF_SIZE) + n = MAP_BUF_SIZE; + elib_heap_map(w, n); + driver_output((ErlDrvPort)port, w, n); + return; + } + else if ((count == 4) && (buf[0] == 'h' || buf[0] == 'l')) { + unsigned long vf[HISTO_BUF_SIZE]; + unsigned long va[HISTO_BUF_SIZE]; + int n = getint16(buf+1); + int base = (unsigned char) buf[3]; + + if (n >= HISTO_BUF_SIZE) + n = HISTO_BUF_SIZE; + if (buf[0] == 'l') + base = -base; + if (elib_histo(vf, va, n, base) < 0) { + driver_failure((ErlDrvPort)port, -1); + return; + } + else { + char* p = outbuf; + int i; + + for (i = 0; i < n; i++) { + putint32(p, vf[i]); + p += 4; + } + for (i = 0; i < n; i++) { + putint32(p, va[i]); + p += 4; + } + driver_output((ErlDrvPort)port, outbuf, n*8); + } + return; + } + driver_failure((ErlDrvPort)port, -1); +} + diff --git a/erts/emulator/drivers/win32/registry_drv.c b/erts/emulator/drivers/win32/registry_drv.c new file mode 100644 index 0000000000..05fd2ea55f --- /dev/null +++ b/erts/emulator/drivers/win32/registry_drv.c @@ -0,0 +1,535 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Purpose: Interface to the registry API. + */ + +#include <windows.h> +#include "erl_driver.h" +#include "sys.h" + +/* + * Commands recognised by this driver. + */ + +#define CMD_GET_CURRENT 0 +#define CMD_OPEN_KEY 1 +#define CMD_CREATE_KEY 2 +#define CMD_GET_ALL_SUBKEYS 3 +#define CMD_GET_VALUE 4 +#define CMD_GET_ALL_VALUES 5 +#define CMD_SET_VALUE 6 +#define CMD_DELETE_KEY 7 +#define CMD_DELETE_VALUE 8 + +/* + * Microsoft-specific function to map a WIN32 error code to a Posix errno. + */ + +extern void _dosmaperr(DWORD); + +/* + * Information kept for a registry port (since there is no controlling + * Erlang process, all state must be kept here). + */ + +typedef struct { + ErlDrvPort port; /* Port handle. */ + REGSAM sam; /* Access for handles. */ + HKEY hkey; /* Handle to open key. */ + HKEY hkey_root; /* Root handle for current key. */ + char* key; /* Name of key. */ + DWORD key_size; /* Size of key. */ + LPSTR name_buf; /* Buffer for names. */ + DWORD name_buf_size; /* Size of name buffer. */ + LPSTR value_buf; /* Buffer for values. */ + DWORD value_buf_size; /* Size of value buffer. */ +} RegPort; + + +/* + * Local functions. + */ + +static void reply(RegPort* rp, LONG result); +static BOOL fix_value_result(RegPort* rp, LONG result, DWORD type, + LPSTR name, DWORD nameSize, LPSTR value, + DWORD valueSize); +static int key_reply(RegPort* rp, LPSTR name, DWORD nameSize); +static int value_reply(RegPort* rp, DWORD type, LPSTR name, DWORD nameSize, + LPSTR value, DWORD valueSize); +static int state_reply(RegPort* rp, HKEY root, LPSTR name, DWORD nameSize); +static int maperror(DWORD error); +/* + * Local variables. + */ + +static int reg_init(void); +static ErlDrvData reg_start(ErlDrvPort, char*); +static void reg_stop(ErlDrvData); +static void reg_from_erlang(ErlDrvData, char*, int); + +struct erl_drv_entry registry_driver_entry = { + reg_init, + reg_start, + reg_stop, + reg_from_erlang, + NULL, + NULL, + "registry__drv__", + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static int +reg_init(void) +{ + DEBUGF(("reg_init()\n")); + return 0; +} + +static ErlDrvData +reg_start(ErlDrvPort port, char* buf) +{ + RegPort* rp; + char* s; + REGSAM sam = KEY_READ; + + if ((s = strchr(buf, ' ')) != NULL) { + while (isspace(*s)) + s++; + while (*s != '\0') { + if (*s == 'r') { + sam |= KEY_READ; + } else if (*s == 'w') { + sam |= KEY_WRITE; + } + s++; + } + } + + rp = driver_alloc(sizeof(RegPort)); + if (rp == NULL) { + return ERL_DRV_ERROR_GENERAL; + } + rp->port = port; + rp->hkey = rp->hkey_root = HKEY_CLASSES_ROOT; + rp->sam = sam; + rp->key = driver_alloc(1); + rp->key_size = 0; + rp->name_buf_size = 64; + rp->name_buf = driver_alloc(rp->name_buf_size); + rp->value_buf_size = 64; + rp->value_buf = driver_alloc(rp->value_buf_size); + return (ErlDrvData) rp; +} + +static void +reg_stop(ErlDrvData clientData) +{ + RegPort* rp = (RegPort *) clientData; + + (void) RegCloseKey(rp->hkey); + driver_free(rp->name_buf); + driver_free(rp->value_buf); + driver_free(rp->key); + driver_free(rp); + /* return 1; */ +} + +static void +reg_from_erlang(ErlDrvData clientData, char* buf, int count) +{ + RegPort* rp = (RegPort *) clientData; + int cmd; + HKEY hkey; + LONG result; + DWORD nameSize; + DWORD type; /* Type of data in buffer. */ + DWORD valueSize; /* Size of value buffer. */ + + cmd = buf[0]; + buf++, count--; + switch (cmd) { + case CMD_GET_CURRENT: + state_reply(rp, rp->hkey_root, rp->key, rp->key_size); + break; + case CMD_OPEN_KEY: + { + char* key; + HKEY newKey; + + /* + * [HKEY(DWORD), KeyString(string)] + */ + + hkey = (HKEY) get_int32(buf+0); + rp->hkey_root = hkey; + key = buf+4; + result = RegOpenKeyEx(hkey, key, 0, rp->sam, &newKey); + if (result == ERROR_SUCCESS) { + RegCloseKey(rp->hkey); + rp->hkey = newKey; + driver_free(rp->key); + rp->key_size = strlen(key); + rp->key = driver_alloc(rp->key_size+1); + strcpy(rp->key, key); + } + reply(rp, result); + return; + } + break; + case CMD_CREATE_KEY: + { + char* key; + HKEY newKey; + DWORD disposition; + + hkey = (HKEY) get_int32(buf+0); + rp->hkey_root = hkey; + key = buf+4; + result = RegCreateKeyEx(hkey, key, 0, "", 0, rp->sam, NULL, + &newKey, &disposition); + if (result == ERROR_SUCCESS) { + RegCloseKey(rp->hkey); + rp->hkey = newKey; + driver_free(rp->key); + rp->key_size = strlen(key); + rp->key = driver_alloc(rp->key_size+1); + strcpy(rp->key, key); + } + reply(rp, result); + return; + } + break; + case CMD_GET_ALL_SUBKEYS: + { + int i; + + i = 0; + for (;;) { + nameSize = rp->name_buf_size; + result = RegEnumKeyEx(rp->hkey, i, rp->name_buf, &nameSize, + NULL, NULL, NULL, NULL); + if (result == ERROR_MORE_DATA) { + rp->name_buf_size *= 2; + rp->name_buf = driver_realloc(rp->name_buf, + rp->name_buf_size); + continue; + } else if (result == ERROR_NO_MORE_ITEMS) { + reply(rp, ERROR_SUCCESS); + return; + } else if (result != ERROR_SUCCESS) { + reply(rp, result); + return; + } + key_reply(rp, rp->name_buf, nameSize); + i++; + } + } + break; + case CMD_GET_VALUE: + do { + valueSize = rp->value_buf_size; + result = RegQueryValueEx(rp->hkey, buf, NULL, &type, + rp->value_buf, &valueSize); + } while (!fix_value_result(rp, result, type, buf, strlen(buf), + rp->value_buf, valueSize)); + break; + case CMD_GET_ALL_VALUES: + { + int i; + + i = 0; + for (;;) { + nameSize = rp->name_buf_size; + valueSize = rp->value_buf_size; + result = RegEnumValue(rp->hkey, i, rp->name_buf, &nameSize, + NULL, &type, rp->value_buf, &valueSize); + if (result == ERROR_NO_MORE_ITEMS) { + reply(rp, ERROR_SUCCESS); + return; + } + if (fix_value_result(rp, result, type, rp->name_buf, nameSize, + rp->value_buf, valueSize)) { + i++; + } + } + } + break; + case CMD_SET_VALUE: + { + LPSTR name; + DWORD dword; + + /* + * [Type(DWORD), Name(string), Value(bytes)] + */ + + type = get_int32(buf); + buf += 4; + count -= 4; + name = buf; + nameSize = strlen(buf) + 1; + buf += nameSize; + count -= nameSize; + if (type == REG_DWORD) { + /* + * Must pass a pointer to a DWORD in host byte order. + */ + dword = get_int32(buf); + buf = (char *) &dword; + ASSERT(count == 4); + } + result = RegSetValueEx(rp->hkey, name, 0, type, buf, count); + reply(rp, result); + } + break; + case CMD_DELETE_KEY: + result = RegDeleteKey(rp->hkey, NULL); + reply(rp, result); + break; + case CMD_DELETE_VALUE: + result = RegDeleteValue(rp->hkey, buf); + reply(rp, result); + break; + } + /* return 1; */ +} + +static BOOL +fix_value_result(RegPort* rp, LONG result, DWORD type, + LPSTR name, DWORD nameSize, LPSTR value, DWORD valueSize) +{ + if (result == ERROR_MORE_DATA) { + DWORD max_name1; + DWORD max_name2; + DWORD max_value; + int ok; + + ok = RegQueryInfoKey(rp->hkey, NULL, NULL, NULL, + NULL, &max_name1, NULL, NULL, &max_name2, + &max_value, NULL, NULL); +#ifdef DEBUG + if (ok != ERROR_SUCCESS) { + char buff[256]; + sprintf(buff,"Failure in registry_drv line %d, error = %d", + __LINE__, GetLastError()); + MessageBox(NULL, buff, "Internal error", MB_OK); + ASSERT(ok == ERROR_SUCCESS); + } +#endif + rp->name_buf_size = (max_name1 > max_name2 ? max_name1 : max_name2) + + 1; + rp->value_buf_size = max_value + 1; + rp->name_buf = driver_realloc(rp->name_buf, rp->name_buf_size); + rp->value_buf = driver_realloc(rp->value_buf, rp->value_buf_size); + return FALSE; + } else if (result != ERROR_SUCCESS) { + reply(rp, result); + return TRUE; + } + + /* + * Do some data conversion which is easier to do here + * than in Erlang. + */ + + switch (type) { + case REG_SZ: + case REG_EXPAND_SZ: + valueSize--; /* No reason to send the '\0' to Erlang. */ + break; + case REG_DWORD_LITTLE_ENDIAN: + case REG_DWORD_BIG_ENDIAN: + /* + * The value is a DWORD stored in host byte order. + * We must retrieve it and store it in network byte order. + */ + { + DWORD dword = * ((DWORD *) value); + put_int32(dword, value); + type = REG_DWORD; /* Simplify life for Erlang code. */ + break; + } + } + + return value_reply(rp, type, name, nameSize, value, valueSize); +} + +/* + * Sends one of the following replies back to Erlang, + * depending on result: + * + * [$e|Posix error(string)] Error + * [$o] Ok + */ + +static void +reply(RegPort* rp, LONG result) +{ + char sbuf[256]; + + if (result == ERROR_SUCCESS) { + sbuf[0] = 'o'; + driver_output(rp->port, sbuf, 1); + } else { + char* s; + char* t; + int err; + + sbuf[0] = 'e'; + err = maperror(result); + for (s = erl_errno_id(err), t = sbuf+1; *s; s++, t++) { + *t = tolower(*s); + } + driver_output(rp->port, sbuf, t-sbuf); + } + /* return 1; */ +} + +/* + * Sends a key to Erlang: + * + * [$k, Keyname(string)] + */ + +static int +key_reply(RegPort* rp, /* Pointer to port structure. */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize) /* Length of name. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+nameSize; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 'k'; + memcpy(s+1, name, nameSize); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +/* + * Sends a value to Erlang: + * + * [$v, Type(DWORD), Valuename(string), 0, Value(bytes)] + */ + +static int +value_reply(RegPort* rp, /* Pointer to port structure. */ + DWORD type, /* Type of value */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize, /* Length of name. */ + LPSTR value, /* Pointer to value. */ + DWORD valueSize) /* Size of value. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+4+nameSize+1+valueSize; + int i; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 'v'; + i = 1; + put_int32(type, s+i); + i += 4; + memcpy(s+i, name, nameSize); + i += nameSize; + s[i++] = '\0'; + memcpy(s+i, value, valueSize); + ASSERT(i+valueSize == needed); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +/* + * Sends a key to Erlang: + * + * [$s, HKEY(DWORD), Keyname(string)] State + */ + +static int +state_reply(RegPort* rp, /* Pointer to port structure. */ + HKEY root, /* Handle to root key for this key. */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize) /* Length of name. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+4+nameSize; + int i; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 's'; + i = 1; + put_int32((DWORD) root, s+i); + i += 4; + memcpy(s+i, name, nameSize); + ASSERT(i+nameSize == needed); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +static int +maperror(DWORD error) +{ + DEBUGF(("Mapping %d\n", error)); + switch (error) { + case ERROR_BADDB: + case ERROR_BADKEY: + case ERROR_CANTOPEN: + case ERROR_CANTWRITE: + case ERROR_REGISTRY_RECOVERED: + case ERROR_REGISTRY_CORRUPT: + case ERROR_REGISTRY_IO_FAILED: + case ERROR_NOT_REGISTRY_FILE: + return EIO; + case ERROR_KEY_DELETED: + return EINVAL; + default: + _dosmaperr(error); + return errno; + } +} diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c new file mode 100644 index 0000000000..fd88dafd34 --- /dev/null +++ b/erts/emulator/drivers/win32/ttsl_drv.c @@ -0,0 +1,751 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Tty driver that reads one character at the time and provides a + * smart line for output. + */ + +#include "sys.h" +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> + +#include "erl_driver.h" +#include "win_con.h" + +#define TRUE 1 +#define FALSE 0 + +static int cols; /* Number of columns available. */ +static int rows; /* Number of rows available. */ + +/* The various opcodes. */ +#define OP_PUTC 0 +#define OP_MOVE 1 +#define OP_INSC 2 +#define OP_DELC 3 +#define OP_BEEP 4 + +/* Control op */ +#define CTRL_OP_GET_WINSIZE 100 +#define CTRL_OP_GET_UNICODE_STATE 101 +#define CTRL_OP_SET_UNICODE_STATE 102 + +static int lbuf_size = BUFSIZ; +Uint32 *lbuf; /* The current line buffer */ +int llen; /* The current line length */ +int lpos; /* The current "cursor position" in the line buffer */ + +/* + * Tags used in line buffer to show that these bytes represent special characters, + * Max unicode is 0x0010ffff, so we have lots of place for meta tags... + */ +#define CONTROL_TAG 0x10000000U /* Control character, value in first position */ +#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */ +#define TAG_MASK 0xFF000000U + +#define MAXSIZE (1 << 16) + +#define ISPRINT(c) (isprint(c) || (128+32 <= (c) && (c) < 256)) + +#define DEBUGLOG(X) /* nothing */ + +/* + * XXX These are used by win_con.c (for command history). + * Should be cleaned up. + */ + + +#define NL '\n' + +/* Main interface functions. */ +static int ttysl_init(void); +static ErlDrvData ttysl_start(ErlDrvPort, char*); +static void ttysl_stop(ErlDrvData); +static int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int); +static void ttysl_from_erlang(ErlDrvData, char*, int); +static void ttysl_from_tty(ErlDrvData, ErlDrvEvent); +static Sint16 get_sint16(char *s); + +static ErlDrvPort ttysl_port; + +extern ErlDrvEvent console_input_event; +extern HANDLE console_thread; + +static HANDLE ttysl_in = INVALID_HANDLE_VALUE; /* Handle for console input. */ +static HANDLE ttysl_out = INVALID_HANDLE_VALUE; /* Handle for console output */ + +/* Functions that work on the line buffer. */ +static int start_lbuf(); +static int stop_lbuf(); +static int put_chars(); +static int move_rel(); +static int ins_chars(); +static int del_chars(); +static int step_over_chars(int n); +static int insert_buf(); +static int write_buf(); +static void move_cursor(int, int); + +/* Define the driver table entry. */ +struct erl_drv_entry ttsl_driver_entry = { + ttysl_init, + ttysl_start, + ttysl_stop, + ttysl_from_erlang, + ttysl_from_tty, + NULL, + "tty_sl", + NULL, + NULL, + ttysl_control, + NULL +}; + +static int utf8_mode = 0; + +static int ttysl_init() +{ + lbuf = NULL; /* For line buffer handling */ + ttysl_port = (ErlDrvPort)-1; + return 0; +} + +static ErlDrvData ttysl_start(ErlDrvPort port, char* buf) +{ + if ((int)ttysl_port != -1 || console_thread == NULL) { + return ERL_DRV_ERROR_GENERAL; + } + start_lbuf(); + utf8_mode = 1; + driver_select(port, console_input_event, ERL_DRV_READ, 1); + ttysl_port = port; + return (ErlDrvData)ttysl_port;/* Nothing important to return */ +} + +#define DEF_HEIGHT 24 +#define DEF_WIDTH 80 + +static void ttysl_get_window_size(Uint32 *width, Uint32 *height) +{ + *width = ConGetColumns(); + *height = ConGetRows(); +} + + +static int ttysl_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + char resbuff[2*sizeof(Uint32)]; + int res_size; + switch (command) { + case CTRL_OP_GET_WINSIZE: + { + Uint32 w,h; + ttysl_get_window_size(&w,&h); + memcpy(resbuff,&w,sizeof(Uint32)); + memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32)); + res_size = 2*sizeof(Uint32); + } + break; + case CTRL_OP_GET_UNICODE_STATE: + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + break; + case CTRL_OP_SET_UNICODE_STATE: + if (len > 0) { + int m = (int) *buf; + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + utf8_mode = (m) ? 1 : 0; + } else { + return 0; + } + break; + default: + return 0; + } + if (rlen < res_size) { + *rbuf = driver_alloc(res_size); + } + memcpy(*rbuf,resbuff,res_size); + return res_size; +} + + +static void ttysl_stop(ErlDrvData ttysl_data) +{ + if ((int)ttysl_port != -1) { + driver_select(ttysl_port, console_input_event, ERL_DRV_READ, 0); + } + + ttysl_in = ttysl_out = INVALID_HANDLE_VALUE; + stop_lbuf(); + ttysl_port = (ErlDrvPort)-1; +} + +static int put_utf8(int ch, byte *target, int sz, int *pos) +{ + Uint x = (Uint) ch; + if (x < 0x80) { + if (*pos >= sz) { + return -1; + } + target[(*pos)++] = (byte) x; + } + else if (x < 0x800) { + if (((*pos) + 1) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 6)) | + ((byte) 0xC0)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x10000) { + if ((x >= 0xD800 && x <= 0xDFFF) || + (x == 0xFFFE) || + (x == 0xFFFF)) { /* Invalid unicode range */ + return -1; + } + if (((*pos) + 2) >= sz) { + return -1; + } + + target[(*pos)++] = (((byte) (x >> 12)) | + ((byte) 0xE0)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x110000) { /* Standard imposed max */ + if (((*pos) + 3) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 18)) | + ((byte) 0xF0)); + target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else { + return -1; + } + return 0; +} + + +static int pick_utf8(byte *s, int sz, int *pos) +{ + int size = sz - (*pos); + byte *source; + Uint unipoint; + + if (size > 0) { + source = s + (*pos); + if (((*source) & ((byte) 0x80)) == 0) { + unipoint = (int) *source; + ++(*pos); + return (int) unipoint; + } else if (((*source) & ((byte) 0xE0)) == 0xC0) { + if (size < 2) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((*source) < 0xC2) /* overlong */) { + return -1; + } + (*pos) += 2; + unipoint = + (((Uint) ((*source) & ((byte) 0x1F))) << 6) | + ((Uint) (source[1] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF0)) == 0xE0) { + if (size < 3) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) { + return -1; + } + if ((((*source) & ((byte) 0xF)) == 0xD) && + ((source[1] & 0x20) != 0)) { + return -1; + } + if (((*source) == 0xEF) && (source[1] == 0xBF) && + ((source[2] == 0xBE) || (source[2] == 0xBF))) { + return -1; + } + (*pos) += 3; + unipoint = + (((Uint) ((*source) & ((byte) 0xF))) << 12) | + (((Uint) (source[1] & ((byte) 0x3F))) << 6) | + ((Uint) (source[2] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF8)) == 0xF0) { + if (size < 4) { + return -2 ; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + ((source[3] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) { + return -1; + } + if ((((*source) & ((byte)0x7)) > 0x4U) || + ((((*source) & ((byte)0x7)) == 0x4U) && + ((source[1] & ((byte)0x3F)) > 0xFU))) { + return -1; + } + (*pos) += 4; + unipoint = + (((Uint) ((*source) & ((byte) 0x7))) << 18) | + (((Uint) (source[1] & ((byte) 0x3F))) << 12) | + (((Uint) (source[2] & ((byte) 0x3F))) << 6) | + ((Uint) (source[3] & ((byte) 0x3F))); + return (int) unipoint; + } else { + return -1; + } + } else { + return -1; + } +} + +static int octal_or_hex_positions(Uint c) +{ + int x = 0; + Uint ch = c; + if (!ch) { + return 1; + } + while(ch) { + ++x; + ch >>= 3; + } + if (x <= 3) { + return 3; + } + /* \x{H ...} format when larger than \777 */ + x = 0; + ch = c; + while(ch) { + ++x; + ch >>= 4; + } + return x+3; +} + +static void octal_or_hex_format(Uint ch, byte *buf, int *pos) +{ + static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + int num = octal_or_hex_positions(ch); + if (num != 3) { + buf[(*pos)++] = 'x'; + buf[(*pos)++] = '{'; + num -= 3; + while(num--) { + buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)]; + } + buf[(*pos)++] = '}'; + } else { + while(num--) { + buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0'); + } + } +} + +/* + * Check that there is enough room in all buffers to copy all pad chars + * and stiff we need If not, realloc lbuf. + */ +static int check_buf_size(byte *s, int n) +{ + int pos = 0; + int ch; + int size = 10; + + while(pos < n) { + /* Indata is always UTF-8 */ + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("Invalid UTF8:%d",ch)); + ++pos; + } + if (utf8_mode) { /* That is, terminal is UTF8 compliant */ + if (ch >= 128 || isprint(ch)) { + DEBUGLOG(("Printable(UTF-8:%d):%d",(pos - opos),ch)); + size++; /* Buffer contains wide characters... */ + } else if (ch == '\t') { + size += 8; + } else { + DEBUGLOG(("Magic(UTF-8:%d):%d",(pos - opos),ch)); + size += 2; + } + } else { + if (ch <= 255 && isprint(ch)) { + DEBUGLOG(("Printable:%d",ch)); + size++; + } else if (ch == '\t') + size += 8; + else if (ch >= 128) { + DEBUGLOG(("Non printable:%d",ch)); + size += (octal_or_hex_positions(ch) + 1); + } + else { + DEBUGLOG(("Magic:%d",ch)); + size += 2; + } + } + } + + if (size + lpos >= lbuf_size) { + + lbuf_size = size + lpos + BUFSIZ; + if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) { + driver_failure(ttysl_port, -1); + return(0); + } + } + return(1); +} + + +static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, int count) +{ + if (lpos > MAXSIZE) + put_chars((byte*)"\n", 1); + + switch (buf[0]) { + case OP_PUTC: + DEBUGLOG(("OP: Putc(%d)",count-1)); + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + put_chars((byte*)buf+1, count-1); + break; + case OP_MOVE: + move_rel(get_sint16(buf+1)); + break; + case OP_INSC: + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + ins_chars((byte*)buf+1, count-1); + break; + case OP_DELC: + del_chars(get_sint16(buf+1)); + break; + case OP_BEEP: + ConBeep(); + break; + default: + /* Unknown op, just ignore. */ + break; + } + return; +} + +extern int read_inbuf(char *data, int n); + +static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) +{ + Uint32 inbuf[64]; + byte t[1024]; + int i,pos,tpos; + + i = ConReadInput(inbuf,1); + + pos = 0; + tpos = 0; + + while (pos < i) { + while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */ + put_utf8((int) inbuf[pos++], t, 1024, &tpos); + } + driver_output(ttysl_port, (char *) t, tpos); + tpos = 0; + } +} + +/* + * Gets signed 16 bit integer from binary buffer. + */ +static Sint16 +get_sint16(char *s) +{ + return ((*s << 8) | ((byte*)s)[1]); +} + + +static int start_lbuf(void) +{ + if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32)))) + return FALSE; + llen = 0; + lpos = 0; + return TRUE; +} + +static int stop_lbuf(void) +{ + if (lbuf) { + driver_free(lbuf); + lbuf = NULL; + } + llen = 0; /* To avoid access error in win_con:AddToCmdHistory during exit*/ + return TRUE; +} + +/* Put l bytes (in UTF8) from s into the buffer and output them. */ +static int put_chars(byte *s, int l) +{ + int n; + + n = insert_buf(s, l); + if (n > 0) + write_buf(lbuf + lpos - n, n); + if (lpos > llen) + llen = lpos; + return TRUE; +} + +/* + * Move the current postition forwards or backwards within the current + * line. We know about padding. + */ +static int move_rel(int n) +{ + int npos; /* The new position */ + + /* Step forwards or backwards over the buffer. */ + npos = step_over_chars(n); + + /* Calculate move, updates pointers and move the cursor. */ + move_cursor(lpos, npos); + lpos = npos; + return TRUE; +} + +/* Insert characters into the buffer at the current position. */ +static int ins_chars(byte *s, int l) +{ + int n, tl; + Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */ + + /* Move tail of buffer to make space. */ + if ((tl = llen - lpos) > 0) { + if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL) + return FALSE; + memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32)); + } + n = insert_buf(s, l); + if (tl > 0) { + memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32)); + driver_free(tbuf); + } + llen += n; + write_buf(lbuf + (lpos - n), llen - (lpos - n)); + move_cursor(llen, lpos); + return TRUE; +} + +/* + * Delete characters in the buffer. Can delete characters before (n < 0) + * and after (n > 0) the current position. Cursor left at beginning of + * deleted block. + */ +static int del_chars(int n) +{ + int i, l, r; + int pos; + + /*update_cols();*/ + + /* Step forward or backwards over n logical characters. */ + pos = step_over_chars(n); + + if (pos > lpos) { + l = pos - lpos; /* Buffer characters to delete */ + r = llen - lpos - l; /* Characters after deleted */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32)); + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + ConPutChar(' '); + move_cursor(llen + l, lpos); + } + else if (pos < lpos) { + l = lpos - pos; /* Buffer characters */ + r = llen - lpos; /* Characters after deleted */ + move_cursor(lpos, lpos-l); /* Move back */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32)); + lpos -= l; + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + ConPutChar(' '); + move_cursor(llen + l, lpos); + } + return TRUE; +} + + +/* Step over n logical characters, check for overflow. */ +static int step_over_chars(int n) +{ + Uint32 *c, *beg, *end; + + beg = lbuf; + end = lbuf + llen; + c = lbuf + lpos; + for ( ; n > 0 && c < end; --n) { + c++; + while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + c++; + } + for ( ; n < 0 && c > beg; n++) { + --c; + while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + --c; + } + return c - lbuf; +} + +static int insert_buf(byte *s, int n) +{ + int pos = 0; + int buffpos = lpos; + int ch; + + while (pos < n) { + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch)); + ++pos; + } + if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) { + DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch)); + lbuf[lpos++] = (Uint32) ch; + } else if (ch >= 128) { /* not utf8 mode */ + int nc = octal_or_hex_positions(ch); + lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG; + while (nc--) { + lbuf[lpos++] = ESCAPED_TAG; + } + } else if (ch == '\t') { + do { + lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch)); + ch = 0; + } while (lpos % 8); + } else if (ch == '\n' || ch == '\r') { + write_buf(lbuf + buffpos, lpos - buffpos); + ConPutChar('\r'); + if (ch == '\n') + ConPutChar('\n'); + if (llen > lpos) { + memcpy(lbuf, lbuf + lpos, llen - lpos); + } + llen -= lpos; + lpos = buffpos = 0; + } else { + DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch)); + lbuf[lpos++] = ch | CONTROL_TAG; + lbuf[lpos++] = CONTROL_TAG; + } + } + return lpos - buffpos; /* characters "written" into + current buffer (may be less due to newline) */ +} +static int write_buf(Uint32 *s, int n) +{ + int i; + + /*update_cols();*/ + + while (n > 0) { + if (!(*s & TAG_MASK) ) { + ConPutChar(*s); + --n; + ++s; + } + else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) { + ConPutChar(' '); + --n; s++; + while (n > 0 && *s == CONTROL_TAG) { + ConPutChar(' '); + --n; s++; + } + } else if (*s & CONTROL_TAG) { + ConPutChar('^'); + ConPutChar((*s == 0177) ? '?' : *s | 0x40); + n -= 2; + s += 2; + } else if (*s & ESCAPED_TAG) { + Uint32 ch = *s & ~(TAG_MASK); + byte *octbuff; + byte octtmp[256]; + int octbytes; + DEBUGLOG(("Escaped: %d", ch)); + octbytes = octal_or_hex_positions(ch); + if (octbytes > 256) { + octbuff = driver_alloc(octbytes); + } else { + octbuff = octtmp; + } + octbytes = 0; + octal_or_hex_format(ch, octbuff, &octbytes); + DEBUGLOG(("octbytes: %d", octbytes)); + ConPutChar('\\'); + for (i = 0; i < octbytes; ++i) { + ConPutChar(octbuff[i]); + } + n -= octbytes+1; + s += octbytes+1; + if (octbuff != octtmp) { + driver_free(octbuff); + } + } else { + DEBUGLOG(("Very unexpected character %d",(int) *s)); + ++n; + --s; + } + } + return TRUE; +} + + +static void +move_cursor(int from, int to) +{ + ConSetCursor(from,to); +} diff --git a/erts/emulator/drivers/win32/win_con.c b/erts/emulator/drivers/win32/win_con.c new file mode 100644 index 0000000000..2202ca655f --- /dev/null +++ b/erts/emulator/drivers/win32/win_con.c @@ -0,0 +1,2259 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#define UNICODE 1 +#define _UNICODE 1 +#include <tchar.h> +#include <stdio.h> +#include "sys.h" +#include <windowsx.h> +#include "resource.h" +#include "erl_version.h" +#include <commdlg.h> +#include <commctrl.h> +#include "erl_driver.h" +#include "win_con.h" + +#define ALLOC(X) malloc(X) +#define REALLOC(X,Y) realloc(X,Y) +#define FREE(X) free(X) + +#ifndef STATE_SYSTEM_INVISIBLE +/* Mingw problem with oleacc.h and WIN32_LEAN_AND_MEAN */ +#define STATE_SYSTEM_INVISIBLE 0x00008000 +#endif + +#define WM_CONTEXT (0x0401) +#define WM_CONBEEP (0x0402) +#define WM_SAVE_PREFS (0x0403) + +#define USER_KEY TEXT("Software\\Ericsson\\Erlang\\") TEXT(ERLANG_VERSION) + +#define FRAME_HEIGHT ((2*GetSystemMetrics(SM_CYEDGE))+(2*GetSystemMetrics(SM_CYFRAME))+GetSystemMetrics(SM_CYCAPTION)) +#define FRAME_WIDTH (2*GetSystemMetrics(SM_CXFRAME)+(2*GetSystemMetrics(SM_CXFRAME))+GetSystemMetrics(SM_CXVSCROLL)) + +#define LINE_LENGTH canvasColumns +#define COL(_l) ((_l) % LINE_LENGTH) +#define LINE(_l) ((_l) / LINE_LENGTH) + +#ifdef UNICODE +/* + * We use a character in the invalid unicode range + */ +#define SET_CURSOR (0xD8FF) +#else +/* + * XXX There is no escape to send a character 0x80. Fortunately, + * the ttsl driver currently replaces 0x80 with an octal sequence. + */ +#define SET_CURSOR (0x80) +#endif + +#define SCAN_CODE_BREAK 0x46 /* scan code for Ctrl-Break */ + + +typedef struct ScreenLine_s { + struct ScreenLine_s* next; + struct ScreenLine_s* prev; + int width; +#ifdef HARDDEBUG + int allocated; +#endif + int newline; /* Ends with hard newline: 1, wrapped at end: 0 */ + TCHAR *text; +} ScreenLine_t; + +extern Uint32 *lbuf; /* The current line buffer */ +extern int llen; /* The current line length */ +extern int lpos; + +HANDLE console_input_event; +HANDLE console_thread = NULL; + +#define DEF_CANVAS_COLUMNS 80 +#define DEF_CANVAS_ROWS 26 + +#define BUFSIZE 4096 +#define MAXBUFSIZE 32768 +typedef struct { + TCHAR *data; + int size; + int wrPos; + int rdPos; +} buffer_t; + +static buffer_t inbuf; +static buffer_t outbuf; + +static CHOOSEFONT cf; + +static TCHAR szFrameClass[] = TEXT("FrameClass"); +static TCHAR szClientClass[] = TEXT("ClientClass"); +static HWND hFrameWnd; +static HWND hClientWnd; +static HWND hTBWnd; +static HWND hComboWnd; +static HANDLE console_input; +static HANDLE console_output; +static int cxChar,cyChar, cxCharMax; +static int cxClient,cyClient; +static int cyToolBar; +static int iVscrollPos,iHscrollPos; +static int iVscrollMax,iHscrollMax; +static int nBufLines; +static int cur_x; +static int cur_y; +static int canvasColumns = DEF_CANVAS_COLUMNS; +static int canvasRows = DEF_CANVAS_ROWS; +static ScreenLine_t *buffer_top,*buffer_bottom; +static ScreenLine_t* cur_line; +static POINT editBeg,editEnd; +static BOOL fSelecting = FALSE; +static BOOL fTextSelected = FALSE; +static HKEY key; +static BOOL has_key = FALSE; +static LOGFONT logfont; +static DWORD fgColor; +static DWORD bkgColor; +static FILE *logfile = NULL; +static RECT winPos; +static BOOL toolbarVisible; +static BOOL destroyed = FALSE; + +static int lines_to_save = 1000; /* Maximum number of screen lines to save. */ + +#define TITLE_BUF_SZ 256 + +struct title_buf { + TCHAR *name; + TCHAR buf[TITLE_BUF_SZ]; +}; + +static TCHAR *erlang_window_title = TEXT("Erlang"); + +static unsigned __stdcall ConThreadInit(LPVOID param); +static LRESULT CALLBACK ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); +static LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); +static BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam); +static ScreenLine_t *ConNewLine(void); +static void DeleteTopLine(void); +static void ensure_line_below(void); +static ScreenLine_t *GetLineFromY(int y); +static void LoadUserPreferences(void); +static void SaveUserPreferences(void); +static void set_scroll_info(HWND hwnd); +static void ConCarriageFeed(int); +static void ConScrollScreen(void); +static BOOL ConChooseFont(HWND hwnd); +static void ConFontInitialize(HWND hwnd); +static void ConSetFont(HWND hwnd); +static void ConChooseColor(HWND hwnd); +static void DrawSelection(HWND hwnd, POINT pt1, POINT pt2); +static void InvertSelectionArea(HWND hwnd); +static void OnEditCopy(HWND hwnd); +static void OnEditPaste(HWND hwnd); +static void OnEditSelAll(HWND hwnd); +static void GetFileName(HWND hwnd, TCHAR *pFile); +static void OpenLogFile(HWND hwnd); +static void CloseLogFile(HWND hwnd); +static void LogFileWrite(TCHAR *buf, int n); +static int write_inbuf(TCHAR *data, int n); +static void init_buffers(void); +static void AddToCmdHistory(void); +static int write_outbuf(TCHAR *data, int num_chars); +static void ConDrawText(HWND hwnd); +static BOOL (WINAPI *ctrl_handler)(DWORD); +static HWND InitToolBar(HWND hwndParent); +static void window_title(struct title_buf *); +static void free_window_title(struct title_buf *); +static void Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); + +#define CON_VPRINTF_BUF_INC_SIZE 1024 + +static erts_dsprintf_buf_t * +grow_con_vprintf_buf(erts_dsprintf_buf_t *dsbufp, size_t need) +{ + char *buf; + size_t size; + + ASSERT(dsbufp); + + if (!dsbufp->str) { + size = (((need + CON_VPRINTF_BUF_INC_SIZE - 1) + / CON_VPRINTF_BUF_INC_SIZE) + * CON_VPRINTF_BUF_INC_SIZE); + buf = (char *) ALLOC(size * sizeof(char)); + } + else { + size_t free_size = dsbufp->size - dsbufp->str_len; + + if (need <= free_size) + return dsbufp; + + size = need - free_size + CON_VPRINTF_BUF_INC_SIZE; + size = (((size + CON_VPRINTF_BUF_INC_SIZE - 1) + / CON_VPRINTF_BUF_INC_SIZE) + * CON_VPRINTF_BUF_INC_SIZE); + size += dsbufp->size; + buf = (char *) REALLOC((void *) dsbufp->str, + size * sizeof(char)); + } + if (!buf) + return NULL; + if (buf != dsbufp->str) + dsbufp->str = buf; + dsbufp->size = size; + return dsbufp; +} + +static int con_vprintf(char *format, va_list arg_list) +{ + int res,i; + erts_dsprintf_buf_t dsbuf = ERTS_DSPRINTF_BUF_INITER(grow_con_vprintf_buf); + res = erts_vdsprintf(&dsbuf, format, arg_list); + if (res >= 0) { + TCHAR *tmp = ALLOC(dsbuf.str_len*sizeof(TCHAR)); + for (i=0;i<dsbuf.str_len;++i) { + tmp[i] = dsbuf.str[i]; + } + write_outbuf(tmp, dsbuf.str_len); + FREE(tmp); + } + if (dsbuf.str) + FREE((void *) dsbuf.str); + return res; +} + +void +ConInit(void) +{ + unsigned tid; + + console_input = CreateSemaphore(NULL, 0, 1, NULL); + console_output = CreateSemaphore(NULL, 0, 1, NULL); + console_input_event = CreateManualEvent(FALSE); + console_thread = (HANDLE *) _beginthreadex(NULL, 0, + ConThreadInit, + 0, 0, &tid); + + /* Make all erts_*printf on stdout and stderr use con_vprintf */ + erts_printf_stdout_func = con_vprintf; + erts_printf_stderr_func = con_vprintf; +} + +/* + ConNormalExit() is called from erl_exit() when the emulator + is stopping. If the exit has not been initiated by this + console thread (WM_DESTROY or ID_BREAK), the function must + invoke the console thread to save the user preferences. +*/ +void +ConNormalExit(void) +{ + if (!destroyed) + SendMessage(hFrameWnd, WM_SAVE_PREFS, 0L, 0L); +} + +void +ConWaitForExit(void) +{ + ConPrintf("\n\nAbnormal termination\n"); + WaitForSingleObject(console_thread, INFINITE); +} + +void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD)) +{ + ctrl_handler = handler; +} + +int ConPutChar(Uint32 c) +{ + TCHAR sbuf[1]; +#ifdef HARDDEBUG + fprintf(stderr,"ConPutChar: %d\n",(int) c); + fflush(stderr); +#endif + sbuf[0] = c; + write_outbuf(sbuf, 1); + return 1; +} + +static int GetXFromLine(HDC hdc, int hscroll, int xpos,ScreenLine_t *pLine) +{ + SIZE size; + int hscrollPix = hscroll * cxChar; + + if (pLine == NULL) { + return 0; + } + + if (pLine->width < xpos) { + return (canvasColumns-hscroll)*cxChar; + } + /* Not needed (?): SelectObject(hdc,CreateFontIndirect(&logfont)); */ + if (GetTextExtentPoint32(hdc,pLine->text,xpos,&size)) { +#ifdef HARDDEBUG + fprintf(stderr,"size.cx:%d\n",(int)size.cx); + fflush(stderr); +#endif + if (hscrollPix >= size.cx) { + return 0; + } + return ((int) size.cx) - hscrollPix; + } else { + return (xpos-hscroll)*cxChar; + } +} + +static int GetXFromCurrentY(HDC hdc, int hscroll, int xpos) { + return GetXFromLine(hdc, hscroll, xpos, GetLineFromY(cur_y)); +} + +void ConSetCursor(int from, int to) +{ TCHAR cmd[9]; + int *p; + //DebugBreak(); + cmd[0] = SET_CURSOR; + /* + * XXX Expect trouble on CPUs which don't allow misaligned read and writes. + */ + p = (int *)&cmd[1]; + *p++ = from; + *p = to; + write_outbuf(cmd, 1 + (2*sizeof(int)/sizeof(TCHAR))); +} + +void ConPrintf(char *format, ...) +{ + va_list va; + + va_start(va, format); + (void) con_vprintf(format, va); + va_end(va); +} + +void ConBeep(void) +{ + SendMessage(hClientWnd, WM_CONBEEP, 0L, 0L); +} + +int ConReadInput(Uint32 *data, int num_chars) +{ + TCHAR *buf; + int nread; + WaitForSingleObject(console_input,INFINITE); + nread = num_chars = min(num_chars,inbuf.wrPos-inbuf.rdPos); + buf = &inbuf.data[inbuf.rdPos]; + inbuf.rdPos += nread; + while (nread--) + *data++ = *buf++; + if (inbuf.rdPos >= inbuf.wrPos) { + inbuf.rdPos = 0; + inbuf.wrPos = 0; + ResetEvent(console_input_event); + } + ReleaseSemaphore(console_input,1,NULL); + return num_chars; +} + +int ConGetKey(void) +{ + Uint32 c; + WaitForSingleObject(console_input,INFINITE); + ResetEvent(console_input_event); + inbuf.rdPos = inbuf.wrPos = 0; + ReleaseSemaphore(console_input,1,NULL); + WaitForSingleObject(console_input_event,INFINITE); + ConReadInput(&c, 1); + return (int) c; +} + +int ConGetColumns(void) +{ + return (int) canvasColumns; /* 32bit atomic on windows */ +} + +int ConGetRows(void) { + return (int) canvasRows; +} + + +static HINSTANCE hInstance; +extern HMODULE beam_module; + +static unsigned __stdcall +ConThreadInit(LPVOID param) +{ + MSG msg; + WNDCLASSEX wndclass; + int iCmdShow; + STARTUPINFO StartupInfo; + HACCEL hAccel; + int x, y, w, h; + struct title_buf title; + + /*DebugBreak();*/ + hInstance = GetModuleHandle(NULL); + StartupInfo.dwFlags = 0; + GetStartupInfo(&StartupInfo); + iCmdShow = StartupInfo.dwFlags & STARTF_USESHOWWINDOW ? + StartupInfo.wShowWindow : SW_SHOWDEFAULT; + + LoadUserPreferences(); + + /* frame window class */ + wndclass.cbSize = sizeof (wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT; + wndclass.lpfnWndProc = FrameWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = szFrameClass; + wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + RegisterClassExW (&wndclass); + + /* client window class */ + wndclass.cbSize = sizeof (wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclass.lpfnWndProc = ClientWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = CreateSolidBrush(bkgColor); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = szClientClass; + wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + RegisterClassExW (&wndclass); + + InitCommonControls(); + init_buffers(); + + nBufLines = 0; + buffer_top = cur_line = ConNewLine(); + cur_line->next = buffer_bottom = ConNewLine(); + buffer_bottom->prev = cur_line; + + /* Create Frame Window */ + window_title(&title); + hFrameWnd = CreateWindowEx(0, szFrameClass, title.name, + WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, + CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, + NULL,LoadMenu(beam_module,MAKEINTRESOURCE(1)), + hInstance,NULL); + free_window_title(&title); + + /* XXX OTP-5522: + The window position is not saved correctly and if the window + is closed when minimized, it's not possible to start werl again + with the window open. Temporary fix so far is to ignore saved values + and always start with initial settings. */ + /* Original: if (winPos.left == -1) { */ + /* Temporary: if (1) { */ + if (1) { + + /* initial window position */ + x = 0; + y = 0; + w = cxChar*LINE_LENGTH+FRAME_WIDTH+GetSystemMetrics(SM_CXVSCROLL); + h = cyChar*30+FRAME_HEIGHT; + } else { + /* saved window position */ + x = winPos.left; + y = winPos.top; + w = winPos.right - x; + h = winPos.bottom - y; + } + SetWindowPos(hFrameWnd, NULL, x, y, w, h, SWP_NOZORDER); + + ShowWindow(hFrameWnd, iCmdShow); + UpdateWindow(hFrameWnd); + + hAccel = LoadAccelerators(beam_module,MAKEINTRESOURCE(1)); + + ReleaseSemaphore(console_input, 1, NULL); + ReleaseSemaphore(console_output, 1, NULL); + + + /* Main message loop */ + while (GetMessage (&msg, NULL, 0, 0)) + { + if (!TranslateAccelerator(hFrameWnd,hAccel,&msg)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + /* + PostQuitMessage() results in WM_QUIT which makes GetMessage() + return 0 (which stops the main loop). Before we return from + the console thread, the ctrl_handler is called to do erl_exit. + */ + (*ctrl_handler)(CTRL_CLOSE_EVENT); + return msg.wParam; +} + +static LRESULT CALLBACK +FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + RECT r; + int cy,i,bufsize; + TCHAR c; + unsigned long l; + TCHAR buf[128]; + struct title_buf title; + + switch (iMsg) { + case WM_CREATE: + /* client window creation */ + window_title(&title); + hClientWnd = CreateWindowEx(WS_EX_CLIENTEDGE, szClientClass, title.name, + WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hwnd, (HMENU)0, hInstance, NULL); + free_window_title(&title); + hTBWnd = InitToolBar(hwnd); + UpdateWindow (hClientWnd); + return 0; + case WM_SIZE : + if (IsWindowVisible(hTBWnd)) { + SendMessage(hTBWnd,TB_AUTOSIZE,0,0L); + GetWindowRect(hTBWnd,&r); + cy = r.bottom-r.top; + } else cy = 0; + MoveWindow(hClientWnd,0,cy,LOWORD(lParam),HIWORD(lParam)-cy,TRUE); + return 0; + case WM_ERASEBKGND: + return 1; + case WM_SETFOCUS : + CreateCaret(hClientWnd, NULL, cxChar, cyChar); + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),0,cur_x), (cur_y-iVscrollPos)*cyChar); + ShowCaret(hClientWnd); + return 0; + case WM_KILLFOCUS: + HideCaret(hClientWnd); + DestroyCaret(); + return 0; + case WM_INITMENUPOPUP : + if (lParam == 0) /* File popup menu */ + { + EnableMenuItem((HMENU)wParam, IDMENU_STARTLOG, + logfile ? MF_GRAYED : MF_ENABLED); + EnableMenuItem((HMENU)wParam, IDMENU_STOPLOG, + logfile ? MF_ENABLED : MF_GRAYED); + return 0; + } + else if (lParam == 1) /* Edit popup menu */ + { + EnableMenuItem((HMENU)wParam, IDMENU_COPY, + fTextSelected ? MF_ENABLED : MF_GRAYED); + EnableMenuItem((HMENU)wParam, IDMENU_PASTE, + IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED); + return 0; + } + else if (lParam == 3) /* View popup menu */ + { + CheckMenuItem((HMENU)wParam,IDMENU_TOOLBAR, + IsWindowVisible(hTBWnd) ? MF_CHECKED : MF_UNCHECKED); + return 0; + } + break; + case WM_NOTIFY: + switch (((LPNMHDR) lParam)->code) { + case TTN_NEEDTEXT: + { + LPTOOLTIPTEXT lpttt; + lpttt = (LPTOOLTIPTEXT) lParam; + lpttt->hinst = hInstance; + /* check for combobox handle */ + if (lpttt->uFlags&TTF_IDISHWND) { + if ((lpttt->hdr.idFrom == (UINT) hComboWnd)) { + lstrcpy(lpttt->lpszText,TEXT("Command History")); + break; + } + } + /* check for toolbar buttons */ + switch (lpttt->hdr.idFrom) { + case IDMENU_COPY: + lstrcpy(lpttt->lpszText,TEXT("Copy (Ctrl+C)")); + break; + case IDMENU_PASTE: + lstrcpy(lpttt->lpszText,TEXT("Paste (Ctrl+V)")); + break; + case IDMENU_FONT: + lstrcpy(lpttt->lpszText,TEXT("Fonts")); + break; + case IDMENU_ABOUT: + lstrcpy(lpttt->lpszText,TEXT("Help")); + break; + } + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDMENU_STARTLOG: + OpenLogFile(hwnd); + return 0; + case IDMENU_STOPLOG: + CloseLogFile(hwnd); + return 0; + case IDMENU_EXIT: + SendMessage(hwnd, WM_CLOSE, 0, 0L); + return 0; + case IDMENU_COPY: + if (fTextSelected) + OnEditCopy(hClientWnd); + return 0; + case IDMENU_PASTE: + OnEditPaste(hClientWnd); + return 0; + case IDMENU_SELALL: + OnEditSelAll(hClientWnd); + return 0; + case IDMENU_FONT: + if (ConChooseFont(hClientWnd)) { + ConSetFont(hClientWnd); + } + SaveUserPreferences(); + return 0; + case IDMENU_SELECTBKG: + ConChooseColor(hClientWnd); + SaveUserPreferences(); + return 0; + case IDMENU_TOOLBAR: + if (toolbarVisible) { + ShowWindow(hTBWnd,SW_HIDE); + toolbarVisible = FALSE; + } else { + ShowWindow(hTBWnd,SW_SHOW); + toolbarVisible = TRUE; + } + GetClientRect(hwnd,&r); + PostMessage(hwnd,WM_SIZE,0,MAKELPARAM(r.right,r.bottom)); + return 0; + case IDMENU_ABOUT: + DialogBox(beam_module,TEXT("AboutBox"),hwnd,AboutDlgProc); + return 0; + case ID_COMBOBOX: + switch (HIWORD(wParam)) { + case CBN_SELENDOK: + i = SendMessage(hComboWnd,CB_GETCURSEL,0,0); + if (i != CB_ERR) { + buf[0] = 0x01; /* CTRL+A */ + buf[1] = 0x0B; /* CTRL+K */ + bufsize = SendMessage(hComboWnd,CB_GETLBTEXT,i,(LPARAM)&buf[2]); + if (bufsize != CB_ERR) + write_inbuf(buf,bufsize+2); + SetFocus(hwnd); + } + break; + case CBN_SELENDCANCEL: + break; + } + break; + case ID_BREAK: /* CTRL+BRK */ + /* pass on break char if the ctrl_handler is disabled */ + if ((*ctrl_handler)(CTRL_C_EVENT) == FALSE) { + c = 0x03; + write_inbuf(&c,1); + } + return 0; + } + break; + case WM_KEYDOWN : + switch (wParam) { + case VK_UP: c = 'P'-'@'; break; + case VK_DOWN : c = 'N'-'@'; break; + case VK_RIGHT : c = 'F'-'@'; break; + case VK_LEFT : c = 'B'-'@'; break; + case VK_DELETE : c = 'D' -'@'; break; + case VK_HOME : c = 'A'-'@'; break; + case VK_END : c = 'E'-'@'; break; + case VK_RETURN : AddToCmdHistory(); return 0; + case VK_PRIOR : /* PageUp */ + PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEUP, 0); + return 0; + case VK_NEXT : /* PageDown */ + PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEDOWN, 0); + return 0; + default: return 0; + } + write_inbuf(&c, 1); + return 0; + case WM_CHAR: + c = (TCHAR)wParam; + write_inbuf(&c,1); + return 0; + case WM_CLOSE : + break; + case WM_DESTROY : + SaveUserPreferences(); + destroyed = TRUE; + PostQuitMessage(0); + return 0; + case WM_SAVE_PREFS : + SaveUserPreferences(); + return 0; + } + return DefWindowProc(hwnd, iMsg, wParam, lParam); +} + +static BOOL +Client_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) +{ + ConFontInitialize(hwnd); + cur_x = cur_y = 0; + iVscrollPos = 0; + iHscrollPos = 0; + return TRUE; +} + +static void +Client_OnPaint(HWND hwnd) +{ + ScreenLine_t *pLine; + int x,y,i,iTop,iBot; + PAINTSTRUCT ps; + RECT rcInvalid; + HDC hdc; + + hdc = BeginPaint(hwnd, &ps); + rcInvalid = ps.rcPaint; + hdc = ps.hdc; + iTop = max(0, iVscrollPos + rcInvalid.top/cyChar); + iBot = min(nBufLines, iVscrollPos + rcInvalid.bottom/cyChar+1); + pLine = GetLineFromY(iTop); + for (i = iTop; i < iBot && pLine != NULL; i++) { + y = cyChar*(i-iVscrollPos); + x = -cxChar*iHscrollPos; + TextOut(hdc, x, y, &pLine->text[0], pLine->width); + pLine = pLine->next; + } + if (fTextSelected || fSelecting) { + InvertSelectionArea(hwnd); + } + SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + EndPaint(hwnd, &ps); +} +#ifdef HARDDEBUG +static void dump_linebufs(void) { + char *buff; + ScreenLine_t *s = buffer_top; + fprintf(stderr,"LinebufDump------------------------\n"); + while(s) { + if (s == buffer_top) fprintf(stderr,"BT-> "); + if (s == buffer_bottom) fprintf(stderr,"BB-> "); + if (s == cur_line) fprintf(stderr,"CL-> "); + + buff = (char *) ALLOC(s->width+1); + memcpy(buff,s->text,s->width); + buff[s->width] = '\0'; + fprintf(stderr,"{\"%s\",%d,%d}\n",buff,s->newline,s->allocated); + FREE(buff); + s = s->next; + } + fprintf(stderr,"LinebufDumpEnd---------------------\n"); + fflush(stderr); +} +#endif + +static void reorganize_linebufs(HWND hwnd) { + ScreenLine_t *otop = buffer_top; + ScreenLine_t *obot = buffer_bottom; + ScreenLine_t *next; + int i,cpos; + + cpos = 0; + i = nBufLines - cur_y; + while (i > 1) { + cpos += obot->width; + obot = obot->prev; + i--; + } + cpos += (obot->width - cur_x); +#ifdef HARDDEBUG + fprintf(stderr,"nBufLines = %d, cur_x = %d, cur_y = %d, cpos = %d\n", + nBufLines,cur_x,cur_y,cpos); + fflush(stderr); +#endif + + + nBufLines = 0; + buffer_top = cur_line = ConNewLine(); + cur_line->next = buffer_bottom = ConNewLine(); + buffer_bottom->prev = cur_line; + + cur_x = cur_y = 0; + iVscrollPos = 0; + iHscrollPos = 0; + + while(otop) { + for(i=0;i<otop->width;++i) { + cur_line->text[cur_x] = otop->text[i]; + cur_x++; + if (cur_x > cur_line->width) + cur_line->width = cur_x; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x) + cxChar > + (LINE_LENGTH * cxChar)) { + ConCarriageFeed(0); + } + } + if (otop->newline) { + ConCarriageFeed(1); + /*ConScrollScreen();*/ + } + next = otop->next; + FREE(otop->text); + FREE(otop); + otop = next; + } + while (cpos) { + cur_x--; + if (cur_x < 0) { + cur_y--; + cur_line = cur_line->prev; + cur_x = cur_line->width-1; + } + cpos--; + } + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); +#ifdef HARDDEBUG + fprintf(stderr,"canvasColumns = %d,nBufLines = %d, cur_x = %d, cur_y = %d\n", + canvasColumns,nBufLines,cur_x,cur_y); + fflush(stderr); +#endif +} + + +static void +Client_OnSize(HWND hwnd, UINT state, int cx, int cy) +{ + RECT r; + SCROLLBARINFO sbi; + int w,h,columns; + int scrollheight; + cxClient = cx; + cyClient = cy; + set_scroll_info(hwnd); + GetClientRect(hwnd,&r); + w = r.right - r.left; + h = r.bottom - r.top; + sbi.cbSize = sizeof(SCROLLBARINFO); + if (!GetScrollBarInfo(hwnd, OBJID_HSCROLL,&sbi) || + (sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE)) { + scrollheight = 0; + } else { + scrollheight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; + } + canvasRows = (h - scrollheight) / cyChar; + if (canvasRows < DEF_CANVAS_ROWS) { + canvasRows = DEF_CANVAS_ROWS; + } + columns = (w - GetSystemMetrics(SM_CXVSCROLL)) /cxChar; + if (columns < DEF_CANVAS_COLUMNS) + columns = DEF_CANVAS_COLUMNS; + if (columns != canvasColumns) { + canvasColumns = columns; + /*dump_linebufs();*/ + reorganize_linebufs(hwnd); + fSelecting = fTextSelected = FALSE; + InvalidateRect(hwnd, NULL, TRUE); +#ifdef HARDDEBUG + fprintf(stderr,"Paint: cols = %d, rows = %d\n",canvasColumns,canvasRows); + fflush(stderr); +#endif + } + + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); +} + +static void calc_charpoint_from_point(HDC dc, int x, int y, int y_offset, POINT *pt) +{ + int r; + int hscrollPix = iHscrollPos * cxChar; + + pt->y = y/cyChar + iVscrollPos + y_offset; + + if (x > (LINE_LENGTH-iHscrollPos) * cxChar) { + x = (LINE_LENGTH-iHscrollPos) * cxChar; + } + if (pt->y - y_offset > 0 && GetLineFromY(pt->y - y_offset) == NULL) { + pt->y = nBufLines - 1 + y_offset; + pt->x = GetLineFromY(pt->y - y_offset)->width; + } else { + for (pt->x = 1; + (r = GetXFromLine(dc, 0, pt->x, GetLineFromY(pt->y - y_offset))) != 0 && + (r - hscrollPix) < x; + ++(pt->x)) + ; + if ((r - hscrollPix) > x) + --(pt->x); +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt->x = %d, iHscrollPos = %d\n",(int) pt->x, iHscrollPos); + fflush(stderr); +#endif + if (pt->x <= 0) { + pt->x = x/cxChar + iHscrollPos; + } + } +} + + +static void +Client_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) +{ + int r; + SetFocus(GetParent(hwnd)); /* In case combobox steals the focus */ +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonDown fSelecting = %d, fTextSelected = %d:\n", + fSelecting,fTextSelected); + fflush(stderr); +#endif + if (fTextSelected) { + InvertSelectionArea(hwnd); + } + fTextSelected = FALSE; + + calc_charpoint_from_point(GetDC(hwnd), x, y, 0, &editBeg); + + editEnd.x = editBeg.x; + editEnd.y = editBeg.y + 1; + fSelecting = TRUE; + SetCapture(hwnd); +} + +static void +Client_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) +{ + if (fTextSelected) { + fSelecting = TRUE; + Client_OnMouseMove(hwnd,x,y,keyFlags); + fSelecting = FALSE; + } +} + +static void +Client_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) +{ +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonUp fSelecting = %d, fTextSelected = %d:\n", + fSelecting,fTextSelected); + fprintf(stderr,"(Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, + editEnd.x,editEnd.y); +#endif + if (fSelecting && + !(editBeg.x == editEnd.x && editBeg.y == (editEnd.y - 1))) { + fTextSelected = TRUE; + } +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonUp fTextSelected = %d:\n", + fTextSelected); + fflush(stderr); +#endif + fSelecting = FALSE; + ReleaseCapture(); +} + +#define EMPTY_RECT(R) \ +(((R).bottom - (R).top == 0) || ((R).right - (R).left == 0)) +#define ABS(X) (((X)< 0) ? -1 * (X) : X) +#define DIFF(A,B) ABS(((int)(A)) - ((int)(B))) + +static int diff_sel_area(RECT old[3], RECT new[3], RECT result[6]) +{ + int absposold = old[0].left + old[0].top * canvasColumns; + int absposnew = new[0].left + new[0].top * canvasColumns; + int absendold = absposold, absendnew = absposnew; + int i, x, ret = 0; + int abspos[2],absend[2]; + for(i = 0; i < 3; ++i) { + if (!EMPTY_RECT(old[i])) { + absendold += (old[i].right - old[i].left) * + (old[i].bottom - old[i].top); + } + if (!EMPTY_RECT(new[i])) { + absendnew += (new[i].right - new[i].left) * + (new[i].bottom - new[i].top); + } + } + abspos[0] = min(absposold, absposnew); + absend[0] = DIFF(absposold, absposnew) + abspos[0]; + abspos[1] = min(absendold, absendnew); + absend[1] = DIFF(absendold, absendnew) + abspos[1]; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"abspos[0] = %d, absend[0] = %d, abspos[1] = %d, absend[1] = %d\n",abspos[0],absend[0],abspos[1],absend[1]); + fflush(stderr); +#endif + i = 0; + for (x = 0; x < 2; ++x) { + if (abspos[x] != absend[x]) { + int consumed = 0; + result[i].left = abspos[x] % canvasColumns; + result[i].top = abspos[x] / canvasColumns; + result[i].bottom = result[i].top + 1; + if ((absend[x] - abspos[x]) + result[i].left < canvasColumns) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Nowrap, %d < canvasColumns\n", + (absend[x] - abspos[x]) + result[i].left); + fflush(stderr); +#endif + result[i].right = (absend[x] - abspos[x]) + result[i].left; + consumed += result[i].right - result[i].left; + } else { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Wrap, %d >= canvasColumns\n", + (absend[x] - abspos[x]) + result[i].left); + fflush(stderr); +#endif + result[i].right = canvasColumns; + consumed += result[i].right - result[i].left; + if (absend[x] - abspos[x] - consumed >= canvasColumns) { + ++i; + result[i].top = result[i-1].bottom; + result[i].left = 0; + result[i].right = canvasColumns; + result[i].bottom = (absend[x] - abspos[x] - consumed) / canvasColumns + result[i].top; + consumed += (result[i].bottom - result[i].top) * canvasColumns; + } + if (absend[x] - abspos[x] - consumed > 0) { + ++i; + result[i].top = result[i-1].bottom; + result[i].bottom = result[i].top + 1; + result[i].left = 0; + result[i].right = absend[x] - abspos[x] - consumed; + } + } + ++i; + } + } +#ifdef HARD_SEL_DEBUG + if (i > 2) { + int x; + fprintf(stderr,"i = %d\n",i); + fflush(stderr); + for (x = 0; x < i; ++x) { + fprintf(stderr, "result[%d]: top = %d, left = %d, " + "bottom = %d. right = %d\n", + x, result[x].top, result[x].left, + result[x].bottom, result[x].right); + } + } +#endif + return i; +} + + + +static void calc_sel_area(RECT rects[3], POINT beg, POINT end) +{ + /* These are not really rects and points, these are character + based positions, need to be multiplied by cxChar and cyChar to + make up canvas coordinates */ + memset(rects,0,3*sizeof(RECT)); + rects[0].left = beg.x; + rects[0].top = beg.y; + rects[0].bottom = beg.y+1; + if (end.y - beg.y == 1) { /* Only one row */ + rects[0].right = end.x; + goto out; + } + rects[0].right = canvasColumns; + if (end.y - beg.y > 2) { + rects[1].left = 0; + rects[1].top = rects[0].bottom; + rects[1].right = canvasColumns; + rects[1].bottom = end.y - 1; + } + rects[2].left = 0; + rects[2].top = end.y - 1; + rects[2].bottom = end.y; + rects[2].right = end.x; + + out: +#ifdef HARD_SEL_DEBUG + { + int i; + fprintf(stderr,"beg.x = %d, beg.y = %d, end.x = %d, end.y = %d\n", + beg.x,beg.y,end.x,end.y); + for (i = 0; i < 3; ++i) { + fprintf(stderr,"[%d] left = %d, top = %d, " + "right = %d, bottom = %d\n", + i, rects[i].left, rects[i].top, + rects[i].right, rects[i].bottom); + } + fflush(stderr); + } +#endif + return; +} + +static void calc_sel_area_turned(RECT rects[3], POINT eBeg, POINT eEnd) { + POINT from,to; + if (eBeg.y >= eEnd.y || + (eBeg.y == eEnd.y - 1 && eBeg.x > eEnd.x)) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Reverting (Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",eBeg.x,eBeg.y, + eEnd.x,eEnd.y); + fflush(stderr); +#endif + from.x = eEnd.x; + from.y = eEnd.y - 1; + to.x = eBeg.x; + to.y = eBeg.y + 1; + calc_sel_area(rects,from,to); + } else { + calc_sel_area(rects,eBeg,eEnd); + } +} + + +static void InvertSelectionArea(HWND hwnd) +{ + RECT rects[3]; + POINT from,to; + int i; + calc_sel_area_turned(rects,editBeg,editEnd); + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + from.x = rects[i].left; + to.x = rects[i].right; + from.y = rects[i].top; + to.y = rects[i].bottom; + DrawSelection(hwnd,from,to); + } + } +} + +static void +Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) +{ + if (fSelecting) { + RECT rold[3], rnew[3], rupdate[6]; + int num_updates,i,r; + POINT from,to; + calc_sel_area_turned(rold,editBeg,editEnd); + + calc_charpoint_from_point(GetDC(hwnd), x, y, 1, &editEnd); + + calc_sel_area_turned(rnew,editBeg,editEnd); + num_updates = diff_sel_area(rold,rnew,rupdate); + for (i = 0; i < num_updates;++i) { + from.x = rupdate[i].left; + to.x = rupdate[i].right; + from.y = rupdate[i].top; + to.y = rupdate[i].bottom; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"from: x=%d,y=%d, to: x=%d, y=%d\n", + from.x, from.y,to.x,to.y); + fflush(stderr); +#endif + DrawSelection(hwnd,from,to); + } + } +} + +static void +Client_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) +{ + int iVscroll; + + switch(code) { + case SB_LINEDOWN: + iVscroll = 1; + break; + case SB_LINEUP: + iVscroll = -1; + break; + case SB_PAGEDOWN: + iVscroll = max(1, cyClient/cyChar); + break; + case SB_PAGEUP: + iVscroll = min(-1, -cyClient/cyChar); + break; + case SB_THUMBTRACK: + iVscroll = pos - iVscrollPos; + break; + default: + iVscroll = 0; + } + iVscroll = max(-iVscrollPos, min(iVscroll, iVscrollMax-iVscrollPos)); + if (iVscroll != 0) { + iVscrollPos += iVscroll; + ScrollWindowEx(hwnd, 0, -cyChar*iVscroll, NULL, NULL, + NULL, NULL, SW_ERASE | SW_INVALIDATE); + SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE); + iVscroll = GetScrollPos(hwnd, SB_VERT); + UpdateWindow(hwnd); + } +} + +static void +Client_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) +{ + int iHscroll, curCharWidth = cxClient/cxChar; + + switch(code) { + case SB_LINEDOWN: + iHscroll = 1; + break; + case SB_LINEUP: + iHscroll = -1; + break; + case SB_PAGEDOWN: + iHscroll = max(1,curCharWidth-1); + break; + case SB_PAGEUP: + iHscroll = min(-1,-(curCharWidth-1)); + break; + case SB_THUMBTRACK: + iHscroll = pos - iHscrollPos; + break; + default: + iHscroll = 0; + } + iHscroll = max(-iHscrollPos, min(iHscroll, iHscrollMax-iHscrollPos-(curCharWidth-1))); + if (iHscroll != 0) { + iHscrollPos += iHscroll; + ScrollWindow(hwnd, -cxChar*iHscroll, 0, NULL, NULL); + SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE); + UpdateWindow(hwnd); + } +} + +static LRESULT CALLBACK +ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + switch (iMsg) { + HANDLE_MSG(hwnd, WM_CREATE, Client_OnCreate); + HANDLE_MSG(hwnd, WM_SIZE, Client_OnSize); + HANDLE_MSG(hwnd, WM_PAINT, Client_OnPaint); + HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Client_OnLButtonDown); + HANDLE_MSG(hwnd, WM_RBUTTONDOWN, Client_OnRButtonDown); + HANDLE_MSG(hwnd, WM_LBUTTONUP, Client_OnLButtonUp); + HANDLE_MSG(hwnd, WM_MOUSEMOVE, Client_OnMouseMove); + HANDLE_MSG(hwnd, WM_VSCROLL, Client_OnVScroll); + HANDLE_MSG(hwnd, WM_HSCROLL, Client_OnHScroll); + case WM_CONBEEP: + if (0) Beep(440, 400); + return 0; + case WM_CONTEXT: + ConDrawText(hwnd); + return 0; + case WM_CLOSE: + break; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProc (hwnd, iMsg, wParam, lParam); +} + +static void +LoadUserPreferences(void) +{ + DWORD size; + DWORD res; + DWORD type; + + /* default prefs */ + GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont); + fgColor = GetSysColor(COLOR_WINDOWTEXT); + bkgColor = GetSysColor(COLOR_WINDOW); + winPos.left = -1; + toolbarVisible = TRUE; + + if (RegCreateKeyEx(HKEY_CURRENT_USER, USER_KEY, 0, 0, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, + &key, &res) != ERROR_SUCCESS) + return; + has_key = TRUE; + if (res == REG_CREATED_NEW_KEY) + return; + size = sizeof(logfont); + res = RegQueryValueEx(key,TEXT("Font"),NULL,&type,(LPBYTE)&logfont,&size); + size = sizeof(fgColor); + res = RegQueryValueEx(key,TEXT("FgColor"),NULL,&type,(LPBYTE)&fgColor,&size); + size = sizeof(bkgColor); + res = RegQueryValueEx(key,TEXT("BkColor"),NULL,&type,(LPBYTE)&bkgColor,&size); + size = sizeof(winPos); + res = RegQueryValueEx(key,TEXT("Pos"),NULL,&type,(LPBYTE)&winPos,&size); + size = sizeof(toolbarVisible); + res = RegQueryValueEx(key,TEXT("Toolbar"),NULL,&type,(LPBYTE)&toolbarVisible,&size); +} + +static void +SaveUserPreferences(void) +{ + WINDOWPLACEMENT wndPlace; + + if (has_key == TRUE) { + RegSetValueEx(key,TEXT("Font"),0,REG_BINARY,(CONST BYTE *)&logfont,sizeof(LOGFONT)); + RegSetValueEx(key,TEXT("FgColor"),0,REG_DWORD,(CONST BYTE *)&fgColor,sizeof(fgColor)); + RegSetValueEx(key,TEXT("BkColor"),0,REG_DWORD,(CONST BYTE *)&bkgColor,sizeof(bkgColor)); + RegSetValueEx(key,TEXT("Toolbar"),0,REG_DWORD,(CONST BYTE *)&toolbarVisible,sizeof(toolbarVisible)); + + wndPlace.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(hFrameWnd,&wndPlace); + /* If wndPlace.showCmd == SW_MINIMIZE, then the window is minimized. + We don't care, wndPlace.rcNormalPosition always holds the last known position. */ + winPos = wndPlace.rcNormalPosition; + RegSetValueEx(key,TEXT("Pos"),0,REG_BINARY,(CONST BYTE *)&winPos,sizeof(winPos)); + } +} + + +static void +set_scroll_info(HWND hwnd) +{ + SCROLLINFO info; + int hScrollBy; + /* + * Set vertical scrolling range and scroll box position. + */ + + iVscrollMax = nBufLines-1; + iVscrollPos = min(iVscrollPos, iVscrollMax); + info.cbSize = sizeof(info); + info.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; + info.nMin = 0; + info.nPos = iVscrollPos; + info.nPage = min(cyClient/cyChar, iVscrollMax); + info.nMax = iVscrollMax; + SetScrollInfo(hwnd, SB_VERT, &info, TRUE); + + /* + * Set horizontal scrolling range and scroll box position. + */ + + iHscrollMax = LINE_LENGTH-1; + hScrollBy = max(0, (iHscrollPos - (iHscrollMax-cxClient/cxChar))*cxChar); + iHscrollPos = min(iHscrollPos, iHscrollMax); + info.nPos = iHscrollPos; + info.nPage = cxClient/cxChar; + info.nMax = iHscrollMax; + SetScrollInfo(hwnd, SB_HORZ, &info, TRUE); + /*ScrollWindow(hwnd, hScrollBy, 0, NULL, NULL);*/ +} + + +static void +ensure_line_below(void) +{ + if (cur_line->next == NULL) { + if (nBufLines >= lines_to_save) { + ScreenLine_t* pLine = buffer_top->next; + FREE(buffer_top->text); + FREE(buffer_top); + buffer_top = pLine; + buffer_top->prev = NULL; + nBufLines--; + } + cur_line->next = ConNewLine(); + cur_line->next->prev = cur_line; + buffer_bottom = cur_line->next; + set_scroll_info(hClientWnd); + } +} + +static ScreenLine_t* +ConNewLine(void) +{ + ScreenLine_t *pLine; + + pLine = (ScreenLine_t *)ALLOC(sizeof(ScreenLine_t)); + if (!pLine) + return NULL; + pLine->text = (TCHAR *) ALLOC(canvasColumns * sizeof(TCHAR)); +#ifdef HARDDEBUG + pLine->allocated = canvasColumns; +#endif + pLine->width = 0; + pLine->prev = pLine->next = NULL; + pLine->newline = 0; + nBufLines++; + return pLine; +} + +static ScreenLine_t* +GetLineFromY(int y) +{ + ScreenLine_t *pLine = buffer_top; + int i; + + for (i = 0; i < nBufLines && pLine != NULL; i++) { + if (i == y) + return pLine; + pLine = pLine->next; + } + return NULL; +} + +void ConCarriageFeed(int hard_newline) +{ + cur_x = 0; + ensure_line_below(); + cur_line->newline = hard_newline; + cur_line = cur_line->next; + if (cur_y < nBufLines-1) { + cur_y++; + } else if (iVscrollPos > 0) { + iVscrollPos--; + } +} + +/* + * Scroll screen if cursor is not visible. + */ +static void +ConScrollScreen(void) +{ + if (cur_y >= iVscrollPos + cyClient/cyChar) { + int iVscroll; + + iVscroll = cur_y - iVscrollPos - cyClient/cyChar + 1; + iVscrollPos += iVscroll; + ScrollWindowEx(hClientWnd, 0, -cyChar*iVscroll, NULL, NULL, + NULL, NULL, SW_ERASE | SW_INVALIDATE); + SetScrollPos(hClientWnd, SB_VERT, iVscrollPos, TRUE); + UpdateWindow(hClientWnd); + } +} + +static void +DrawSelection(HWND hwnd, POINT pt1, POINT pt2) +{ + HDC hdc; + int width,height; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", + (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); +#endif + pt1.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt1.x,GetLineFromY(pt1.y)); + pt2.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt2.x,GetLineFromY(pt2.y-1)); + pt1.y -= iVscrollPos; + pt2.y -= iVscrollPos; + pt1.y *= cyChar; + pt2.y *= cyChar; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", + (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); + fflush(stderr); +#endif + width = pt2.x-pt1.x; + height = pt2.y - pt1.y; + hdc = GetDC(hwnd); + PatBlt(hdc,pt1.x,pt1.y,width,height,DSTINVERT); + ReleaseDC(hwnd,hdc); +} + +static void +OnEditCopy(HWND hwnd) +{ + HGLOBAL hMem; + TCHAR *pMem; + ScreenLine_t *pLine; + RECT rects[3]; + POINT from,to; + int i,j,sum,len; + if (editBeg.y >= editEnd.y || + (editBeg.y == editEnd.y - 1 && editBeg.x > editEnd.x)) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"CopyReverting (Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, + editEnd.x,editEnd.y); + fflush(stderr); +#endif + from.x = editEnd.x; + from.y = editEnd.y - 1; + to.x = editBeg.x; + to.y = editBeg.y + 1; + calc_sel_area(rects,from,to); + } else { + calc_sel_area(rects,editBeg,editEnd); + } + sum = 1; + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + pLine = GetLineFromY(rects[i].top); + for (j = rects[i].top; j < rects[i].bottom ;++j) { + if (pLine == NULL) { + sum += 2; + break; + } + if (pLine->width > rects[i].left) { + sum += (pLine->width < rects[i].right) ? + pLine->width - rects[i].left : + rects[i].right - rects[i].left; + } + if(pLine->newline && rects[i].right >= pLine->width) { + sum += 2; + } + pLine = pLine->next; + } + } + } +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"sum = %d\n",sum); + fflush(stderr); +#endif + hMem = GlobalAlloc(GHND, sum * sizeof(TCHAR)); + pMem = GlobalLock(hMem); + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + pLine = GetLineFromY(rects[i].top); + for (j = rects[i].top; j < rects[i].bottom; ++j) { + if (pLine == NULL) { + memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); + pMem += 2; + break; + } + if (pLine->width > rects[i].left) { + len = (pLine->width < rects[i].right) ? + pLine->width - rects[i].left : + rects[i].right - rects[i].left; + memcpy(pMem,pLine->text + rects[i].left,len * sizeof(TCHAR)); + pMem +=len; + } + if(pLine->newline && rects[i].right >= pLine->width) { + memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); + pMem += 2; + } + pLine = pLine->next; + } + } + } + *pMem = TEXT('\0'); + /* Flash de selection area to give user feedback about copying */ + InvertSelectionArea(hwnd); + Sleep(100); + InvertSelectionArea(hwnd); + + OpenClipboard(hwnd); + EmptyClipboard(); + GlobalUnlock(hMem); + SetClipboardData(CF_UNICODETEXT,hMem); + CloseClipboard(); +} + +/* XXX:PaN Tchar or char? */ +static void +OnEditPaste(HWND hwnd) +{ + HANDLE hClipMem; + TCHAR *pClipMem,*pMem,*pMem2; + if (!OpenClipboard(hwnd)) + return; + if ((hClipMem = GetClipboardData(CF_UNICODETEXT)) != NULL) { + pClipMem = GlobalLock(hClipMem); + pMem = (TCHAR *)ALLOC(GlobalSize(hClipMem) * sizeof(TCHAR)); + pMem2 = pMem; + while ((*pMem2 = *pClipMem) != TEXT('\0')) { + if (*pClipMem == TEXT('\r')) + *pMem2 = TEXT('\n'); + ++pMem2; + ++pClipMem; + } + GlobalUnlock(hClipMem); + write_inbuf(pMem, _tcsclen(pMem)); + } + CloseClipboard(); +} + +static void +OnEditSelAll(HWND hwnd) +{ + editBeg.x = 0; + editBeg.y = 0; + editEnd.x = LINE_LENGTH-1; + editEnd.y = cur_y; + fTextSelected = TRUE; + InvalidateRect(hwnd, NULL, TRUE); +} + +UINT APIENTRY CFHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for font dialog box */ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* Let the default procedure process the message */ +} + +static BOOL +ConChooseFont(HWND hwnd) +{ + HDC hdc; + hdc = GetDC(hwnd); + cf.lStructSize = sizeof(CHOOSEFONT); + cf.hwndOwner = hwnd; + cf.hDC = NULL; + cf.lpLogFont = &logfont; + cf.iPointSize = 0; + cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_SCREENFONTS|CF_FIXEDPITCHONLY|CF_EFFECTS|CF_ENABLEHOOK; + cf.rgbColors = GetTextColor(hdc); + cf.lCustData = 0L; + cf.lpfnHook = CFHookProc; + cf.lpTemplateName = NULL; + cf.hInstance = NULL; + cf.lpszStyle = NULL; + cf.nFontType = 0; + cf.nSizeMin = 0; + cf.nSizeMax = 0; + ReleaseDC(hwnd,hdc); + return ChooseFont(&cf); +} + +static void +ConFontInitialize(HWND hwnd) +{ + HDC hdc; + TEXTMETRIC tm; + HFONT hFont; + + hFont = CreateFontIndirect(&logfont); + hdc = GetDC(hwnd); + SelectObject(hdc, hFont); + SetTextColor(hdc,fgColor); + SetBkColor(hdc,bkgColor); + GetTextMetrics(hdc, &tm); + cxChar = tm.tmAveCharWidth; + cxCharMax = tm.tmMaxCharWidth; + cyChar = tm.tmHeight + tm.tmExternalLeading; + ReleaseDC(hwnd, hdc); +} + +static void +ConSetFont(HWND hwnd) +{ + HDC hdc; + TEXTMETRIC tm; + HFONT hFontNew; + + hFontNew = CreateFontIndirect(&logfont); + SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, + MAKELPARAM(1,0)); + hdc = GetDC(hwnd); + DeleteObject(SelectObject(hdc, hFontNew)); + GetTextMetrics(hdc, &tm); + cxChar = tm.tmAveCharWidth; + cxCharMax = tm.tmMaxCharWidth; + cyChar = tm.tmHeight + tm.tmExternalLeading; + fgColor = cf.rgbColors; + SetTextColor(hdc,fgColor); + ReleaseDC(hwnd, hdc); + set_scroll_info(hwnd); + HideCaret(hwnd); + if (DestroyCaret()) { + CreateCaret(hwnd, NULL, cxChar, cyChar); + SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + } + ShowCaret(hwnd); + InvalidateRect(hwnd, NULL, TRUE); +} + +UINT APIENTRY +CCHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for choose color dialog box */ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* Let the default procedure process the message */ +} + +void ConChooseColor(HWND hwnd) +{ + CHOOSECOLOR cc; + static COLORREF acrCustClr[16]; + HBRUSH hbrush; + HDC hdc; + + /* Initialize CHOOSECOLOR */ + ZeroMemory(&cc, sizeof(CHOOSECOLOR)); + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = hwnd; + cc.lpCustColors = (LPDWORD) acrCustClr; + cc.rgbResult = bkgColor; + cc.lpfnHook = CCHookProc; + cc.Flags = CC_FULLOPEN|CC_RGBINIT|CC_SOLIDCOLOR|CC_ENABLEHOOK; + + if (ChooseColor(&cc)==TRUE) { + bkgColor = cc.rgbResult; + hdc = GetDC(hwnd); + SetBkColor(hdc,bkgColor); + ReleaseDC(hwnd,hdc); + hbrush = CreateSolidBrush(bkgColor); + DeleteObject((HBRUSH)SetClassLong(hClientWnd,GCL_HBRBACKGROUND,(LONG)hbrush)); + InvalidateRect(hwnd,NULL,TRUE); + } +} + +UINT APIENTRY OFNHookProc(HWND hwndDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for open file dialog box */ + HWND hOwner,hDlg; + RECT rc,rcOwner,rcDlg; + hDlg = GetParent(hwndDlg); + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* the let default procedure process the message */ +} + +static void +GetFileName(HWND hwnd, TCHAR *pFile) +{ + /* Open the File Open dialog box and */ + /* retrieve the file name */ + OPENFILENAME ofn; + TCHAR szFilterSpec [128] = TEXT("logfiles (*.log)\0*.log\0All files (*.*)\0*.*\0\0"); + #define MAXFILENAME 256 + TCHAR szFileName[MAXFILENAME]; + TCHAR szFileTitle[MAXFILENAME]; + + /* these need to be filled in */ + _tcscpy(szFileName, TEXT("erlshell.log")); + _tcscpy(szFileTitle, TEXT("")); /* must be NULL */ + + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = NULL; + ofn.lpstrFilter = szFilterSpec; + ofn.lpstrCustomFilter = NULL; + ofn.nMaxCustFilter = 0; + ofn.nFilterIndex = 0; + ofn.lpstrFile = szFileName; + ofn.nMaxFile = MAXFILENAME; + ofn.lpstrInitialDir = NULL; + ofn.lpstrFileTitle = szFileTitle; + ofn.nMaxFileTitle = MAXFILENAME; + ofn.lpstrTitle = TEXT("Open logfile"); + ofn.lpstrDefExt = TEXT("log"); + ofn.Flags = OFN_CREATEPROMPT|OFN_HIDEREADONLY|OFN_EXPLORER|OFN_ENABLEHOOK|OFN_NOCHANGEDIR; /* OFN_NOCHANGEDIR only works in Vista :( */ + ofn.lpfnHook = OFNHookProc; + + if (!GetOpenFileName ((LPOPENFILENAME)&ofn)){ + *pFile = TEXT('\0'); + } else { + _tcscpy(pFile, ofn.lpstrFile); + } +} + +void OpenLogFile(HWND hwnd) +{ + /* open a file for logging */ + TCHAR filename[_MAX_PATH]; + + GetFileName(hwnd, filename); + if (filename[0] == '\0') + return; + if (NULL == (logfile = _tfopen(filename,TEXT("w,ccs=UNICODE")))) + return; +} + +void CloseLogFile(HWND hwnd) +{ + /* close log file */ + fclose(logfile); + logfile = NULL; +} + +void LogFileWrite(TCHAR *buf, int num_chars) +{ + /* write to logfile */ + int from,to; + while (num_chars-- > 0) { + switch (*buf) { + case SET_CURSOR: + buf++; + from = *((int *)buf); + buf += sizeof(int)/sizeof(TCHAR); + to = *((int *)buf); + buf += (sizeof(int)/sizeof(TCHAR))-1; + num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); + // Wont seek in Unicode file, sorry... + // fseek(logfile,to-from *sizeof(TCHAR),SEEK_CUR); + break; + default: + _fputtc(*buf,logfile); + break; + } + buf++; + } +} + +static void +init_buffers(void) +{ + inbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); + outbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); + inbuf.size = BUFSIZE; + inbuf.rdPos = inbuf.wrPos = 0; + outbuf.size = BUFSIZE; + outbuf.rdPos = outbuf.wrPos = 0; +} + +static int +check_realloc(buffer_t *buf, int num_chars) +{ + if (buf->wrPos + num_chars >= buf->size) { + if (buf->size > MAXBUFSIZE) + return 0; + buf->size += num_chars + BUFSIZE; + if (!(buf->data = (TCHAR *)REALLOC(buf->data, buf->size * sizeof(TCHAR)))) { + buf->size = buf->rdPos = buf->wrPos = 0; + return 0; + } + } + return 1; +} + +static int +write_inbuf(TCHAR *data, int num_chars) +{ + TCHAR *buf; + int nwrite; + WaitForSingleObject(console_input,INFINITE); + if (!check_realloc(&inbuf,num_chars)) { + ReleaseSemaphore(console_input,1,NULL); + return -1; + } + buf = &inbuf.data[inbuf.wrPos]; + inbuf.wrPos += num_chars; + nwrite = num_chars; + while (nwrite--) + *buf++ = *data++; + SetEvent(console_input_event); + ReleaseSemaphore(console_input,1,NULL); + return num_chars; +} + +static int +write_outbuf(TCHAR *data, int num_chars) +{ + TCHAR *buf; + int nwrite; + + WaitForSingleObject(console_output,INFINITE); + if (!check_realloc(&outbuf, num_chars)) { + ReleaseSemaphore(console_output,1,NULL); + return -1; + } + if (outbuf.rdPos == outbuf.wrPos) + PostMessage(hClientWnd, WM_CONTEXT, 0L, 0L); + buf = &outbuf.data[outbuf.wrPos]; + outbuf.wrPos += num_chars; + nwrite = num_chars; + while (nwrite--) + *buf++ = *data++; + ReleaseSemaphore(console_output,1,NULL); + return num_chars; +} + +BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + SetDlgItemText(hDlg, ID_VERSIONSTRING, + TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION)); + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + EndDialog(hDlg,0); + return TRUE; + } + break; + } + return FALSE; +} + +static void +ConDrawText(HWND hwnd) +{ + int num_chars; + int nchars; + TCHAR *buf; + int from, to; + int dl; + int dc; + RECT rc; + + WaitForSingleObject(console_output, INFINITE); + nchars = 0; + num_chars = outbuf.wrPos - outbuf.rdPos; + buf = &outbuf.data[outbuf.rdPos]; + if (logfile != NULL) + LogFileWrite(buf, num_chars); + + +#ifdef HARDDEBUG + { + TCHAR *bu = (TCHAR *) ALLOC((num_chars+1) * sizeof(TCHAR)); + memcpy(bu,buf,num_chars * sizeof(TCHAR)); + bu[num_chars]='\0'; + fprintf(stderr,TEXT("ConDrawText\"%s\"\n"),bu); + FREE(bu); + fflush(stderr); + } +#endif + /* + * Don't draw any text in the window; just update the line buffers + * and invalidate the appropriate part of the window. The window + * will be updated on the next WM_PAINT message. + */ + + while (num_chars-- > 0) { + switch (*buf) { + case '\r': + break; + case '\n': + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + nchars = 0; + } + ConCarriageFeed(1); + ConScrollScreen(); + break; + case SET_CURSOR: + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + nchars = 0; + } + buf++; + from = *((int *)buf); + buf += sizeof(int)/sizeof(TCHAR); + to = *((int *)buf); + buf += (sizeof(int)/sizeof(TCHAR))-1; + num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); + while (to > from) { + cur_x++; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > + (LINE_LENGTH * cxChar)) { + cur_x = 0; + cur_y++; + ensure_line_below(); + cur_line = cur_line->next; + } + from++; + } + while (to < from) { + cur_x--; + if (cur_x < 0) { + cur_y--; + cur_line = cur_line->prev; + cur_x = cur_line->width-1; + } + from--; + } + + break; + default: + nchars++; + cur_line->text[cur_x] = *buf; + cur_x++; + if (cur_x > cur_line->width) + cur_line->width = cur_x; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > + (LINE_LENGTH * cxChar)) { + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + } + ConCarriageFeed(0); + nchars = 0; + } + } + buf++; + } + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + } + ConScrollScreen(); + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + outbuf.wrPos = outbuf.rdPos = 0; + ReleaseSemaphore(console_output, 1, NULL); +} + +static void +AddToCmdHistory(void) +{ + int i; + int size; + Uint32 *buf; + wchar_t cmdBuf[128]; + + if (llen != 0) { + for (i = 0, size = 0; i < llen-1; i++) { + /* + * Find end of prompt. + */ + if ((lbuf[i] == '>') && lbuf[i+1] == ' ') { + buf = &lbuf[i+2]; + size = llen-i-2; + break; + } + } + if (size > 0 && size < 128) { + for (i = 0;i < size; ++i) { + cmdBuf[i] = (wchar_t) buf[i]; + } + cmdBuf[size] = 0; + SendMessage(hComboWnd,CB_INSERTSTRING,0,(LPARAM)cmdBuf); + } + } +} + +static TBBUTTON tbb[] = +{ + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, +}; + +static TBADDBITMAP tbbitmap = +{ + HINST_COMMCTRL, IDB_STD_SMALL_COLOR, +}; + + +static HWND +InitToolBar(HWND hwndParent) +{ + int x,y,cx; + HWND hwndTB,hwndTT; + RECT r; + TOOLINFO ti; + HFONT hFontNew; + DWORD backgroundColor = GetSysColor(COLOR_BTNFACE); + COLORMAP colorMap; + colorMap.from = RGB(192, 192, 192); + colorMap.to = backgroundColor; + + /* Create toolbar window with tooltips */ + hwndTB = CreateWindowEx(0,TOOLBARCLASSNAME,(TCHAR *)NULL, + WS_CHILD|CCS_TOP|WS_CLIPSIBLINGS|TBSTYLE_TOOLTIPS, + 0,0,0,0,hwndParent, + (HMENU)2,hInstance,NULL); + SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE, + (WPARAM) sizeof(TBBUTTON),0); + tbbitmap.hInst = NULL; + tbbitmap.nID = (UINT) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1); + SendMessage(hwndTB, TB_ADDBITMAP, (WPARAM) 4, + (WPARAM) &tbbitmap); + SendMessage(hwndTB,TB_ADDBUTTONS, (WPARAM) 30, + (LPARAM) (LPTBBUTTON) tbb); + if (toolbarVisible) + ShowWindow(hwndTB, SW_SHOW); + + /* Create combobox window */ + SendMessage(hwndTB,TB_GETITEMRECT,0,(LPARAM)&r); + x = r.left; y = r.top; + SendMessage(hwndTB,TB_GETITEMRECT,23,(LPARAM)&r); + cx = r.right - x + 1; + hComboWnd = CreateWindow(TEXT("combobox"),NULL,WS_VSCROLL|WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST, + x,y,cx,100,hwndParent,(HMENU)ID_COMBOBOX, hInstance,NULL); + SetParent(hComboWnd,hwndTB); + hFontNew = CreateFontIndirect(&logfont); + SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, + MAKELPARAM(1,0)); + + /* Add tooltip for combo box */ + ZeroMemory(&ti,sizeof(TOOLINFO)); + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_IDISHWND|TTF_CENTERTIP|TTF_SUBCLASS; + ti.hwnd = hwndTB;; + ti.uId = (UINT)hComboWnd; + ti.lpszText = LPSTR_TEXTCALLBACK; + hwndTT = (HWND)SendMessage(hwndTB,TB_GETTOOLTIPS,0,0); + SendMessage(hwndTT,TTM_ADDTOOL,0,(LPARAM)&ti); + + return hwndTB; +} + +static void +window_title(struct title_buf *tbuf) +{ + int res, i; + size_t bufsz = TITLE_BUF_SZ; + unsigned char charbuff[TITLE_BUF_SZ]; + + res = erl_drv_getenv("ERL_WINDOW_TITLE", charbuff, &bufsz); + if (res < 0) + tbuf->name = erlang_window_title; + else if (res == 0) { + for (i = 0; i < bufsz; ++i) { + tbuf->buf[i] = charbuff[i]; + } + tbuf->buf[bufsz - 1] = 0; + tbuf->name = &tbuf->buf[0]; + } else { + char *buf = ALLOC(bufsz); + if (!buf) + tbuf->name = erlang_window_title; + else { + while (1) { + char *newbuf; + res = erl_drv_getenv("ERL_WINDOW_TITLE", buf, &bufsz); + if (res <= 0) { + if (res == 0) { + TCHAR *wbuf = ALLOC(bufsz *sizeof(TCHAR)); + for (i = 0; i < bufsz ; ++i) { + wbuf[i] = buf[i]; + } + wbuf[bufsz - 1] = 0; + FREE(buf); + tbuf->name = wbuf; + } else { + tbuf->name = erlang_window_title; + FREE(buf); + } + break; + } + newbuf = REALLOC(buf, bufsz); + if (newbuf) + buf = newbuf; + else { + tbuf->name = erlang_window_title; + FREE(buf); + break; + } + } + } + } +} + +static void +free_window_title(struct title_buf *tbuf) +{ + if (tbuf->name != erlang_window_title && tbuf->name != &tbuf->buf[0]) + FREE(tbuf->name); +} diff --git a/erts/emulator/drivers/win32/win_con.h b/erts/emulator/drivers/win32/win_con.h new file mode 100644 index 0000000000..d46af86ca5 --- /dev/null +++ b/erts/emulator/drivers/win32/win_con.h @@ -0,0 +1,39 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2007-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * External API for the windows console (aka werl window) + * used by ttsl_drv.c + */ +#ifndef _WIN_CON_H_VISITED +#define _WIN_CON_H_VISITED 1 +void ConNormalExit(void); +void ConWaitForExit(void); +void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD)); +int ConPutChar(Uint32 c); +void ConSetCursor(int from, int to); +void ConPrintf(char *format, ...); +void ConVprintf(char *format, va_list va); +void ConBeep(void); +int ConReadInput(Uint32 *data, int nbytes); +int ConGetKey(void); +int ConGetColumns(void); +int ConGetRows(void); +void ConInit(void); +#endif /* _WIN_CON_H_VISITED */ diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c new file mode 100644 index 0000000000..89aaad31da --- /dev/null +++ b/erts/emulator/drivers/win32/win_efile.c @@ -0,0 +1,1426 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations for Windows. + */ + +#include <windows.h> +#include "sys.h" +#include <ctype.h> + +#include "erl_efile.h" + +/* + * Microsoft-specific function to map a WIN32 error code to a Posix errno. + */ + +#define ISSLASH(a) ((a) == '\\' || (a) == '/') + +#define ISDIR(st) (((st).st_mode&S_IFMT) == S_IFDIR) +#define ISREG(st) (((st).st_mode&S_IFMT) == S_IFREG) + +#define IS_DOT_OR_DOTDOT(s) \ + (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD) 0xFFFFFFFF) +#endif + +static int check_error(int result, Efile_error* errInfo); +static int set_error(Efile_error* errInfo); +static int IsRootUNCName(const char* path); +static int extract_root(char* name); +static unsigned short dos_to_posix_mode(int attr, const char *name); + +static int errno_map(DWORD last_error) { + + switch (last_error) { + case ERROR_SUCCESS: + return 0; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_INVALID_CATEGORY: + case ERROR_NEGATIVE_SEEK: + return EINVAL; + case ERROR_DIR_NOT_EMPTY: + return EEXIST; + case ERROR_BAD_FORMAT: + return ENOEXEC; + case ERROR_PATH_NOT_FOUND: + case ERROR_FILE_NOT_FOUND: + case ERROR_NO_MORE_FILES: + return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_ACCESS_DENIED: + case ERROR_INVALID_ACCESS: + case ERROR_CURRENT_DIRECTORY: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_INVALID_PASSWORD: + case ERROR_DRIVE_LOCKED: + return EACCES; + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + case ERROR_OUT_OF_STRUCTURES: + return ENOMEM; + case ERROR_INVALID_DRIVE: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_REM_NOT_LIST: + case ERROR_DUP_NAME: + case ERROR_BAD_NETPATH: + case ERROR_NETWORK_BUSY: + case ERROR_DEV_NOT_EXIST: + case ERROR_BAD_NET_NAME: + return ENXIO; + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + case ERROR_WRITE_PROTECT: + return EROFS; + case ERROR_BAD_LENGTH: + case ERROR_BUFFER_OVERFLOW: + return E2BIG; + case ERROR_SEEK: + case ERROR_SECTOR_NOT_FOUND: + return ESPIPE; + case ERROR_NOT_DOS_DISK: + return ENODEV; + case ERROR_GEN_FAILURE: + return ENODEV; + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NO_MORE_SEARCH_HANDLES: + return EMFILE; + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + return EPIPE; + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + return ENOSPC; + case ERROR_NOT_SUPPORTED: + return ENOTSUP; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + return EEXIST; + case ERROR_ALREADY_ASSIGNED: + return EBUSY; + case ERROR_NO_PROC_SLOTS: + return EAGAIN; + case ERROR_ARENA_TRASHED: + case ERROR_INVALID_BLOCK: + case ERROR_BAD_ENVIRONMENT: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_OUT_OF_PAPER: + case ERROR_READ_FAULT: + case ERROR_WRITE_FAULT: + case ERROR_WRONG_DISK: + case ERROR_NET_WRITE_FAULT: + return EIO; + default: /* not to do with files I expect. */ + return EIO; + } +} + +static int +check_error(int result, Efile_error* errInfo) +{ + if (result < 0) { + errInfo->posix_errno = errno; + errInfo->os_errno = GetLastError(); + return 0; + } + return 1; +} + +/* + * Fills the provided error information structure with information + * with the error code given by GetLastError() and its corresponding + * Posix error number. + * + * Returns 0. + */ + +static int +set_error(Efile_error* errInfo) +{ + errInfo->posix_errno = errno_map(errInfo->os_errno = GetLastError()); + return 0; +} + +/* + * A writev with Unix semantics, but with Windows arguments + */ +static int +win_writev(Efile_error* errInfo, + HANDLE fd, /* handle to file */ + FILE_SEGMENT_ELEMENT iov[], /* array of buffer pointers */ + DWORD *size) /* number of bytes to write */ +{ + OVERLAPPED ov; + ov.Offset = 0L; + ov.OffsetHigh = 0L; + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) + return set_error(errInfo); + if (! write_file_gather(fd, iov, *size, NULL, &ov)) + return set_error(errInfo); + if (WaitForSingleObject(ov.hEvent, INFINITE) != WAIT_OBJECT_0) + return set_error(errInfo); + if (! GetOverlappedResult(fd, &ov, size, FALSE)) + return set_error(errInfo); + return 1; +} + + + +int +efile_mkdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to create. */ +{ + return check_error(mkdir(name), errInfo); +} + +int +efile_rmdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to delete. */ +{ + OSVERSIONINFO os; + DWORD attr; + + if (RemoveDirectory(name) != FALSE) { + return 1; + } + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * Windows 95 reports calling RemoveDirectory on a file as an + * EACCES, not an ENOTDIR. + */ + + errno = ENOTDIR; + goto end; + } + + /* + * Windows 95 reports removing a non-empty directory as + * an EACCES, not an EEXIST. If the directory is not empty, + * change errno so caller knows what's going on. + */ + + os.dwOSVersionInfoSize = sizeof(os); + GetVersionEx(&os); + if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + HANDLE handle; + WIN32_FIND_DATA data; + char buffer[2*MAX_PATH]; + int len; + + len = strlen(name); + strcpy(buffer, name); + if (buffer[0] && buffer[len-1] != '\\' && buffer[len-1] != '/') { + strcat(buffer, "\\"); + } + strcat(buffer, "*.*"); + handle = FindFirstFile(buffer, &data); + if (handle != INVALID_HANDLE_VALUE) { + while (1) { + if ((strcmp(data.cFileName, ".") != 0) + && (strcmp(data.cFileName, "..") != 0)) { + /* + * Found something in this directory. + */ + + errno = EEXIST; + break; + } + if (FindNextFile(handle, &data) == FALSE) { + break; + } + } + FindClose(handle); + } + } + } + } + + if (errno == ENOTEMPTY) { + /* + * Posix allows both EEXIST or ENOTEMPTY, but we'll always + * return EEXIST to allow easy matching in Erlang code. + */ + + errno = EEXIST; + } + + end: + return check_error(-1, errInfo); +} + +int +efile_delete_file(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of file to delete. */ +{ + DWORD attr; + + if (DeleteFile(name) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows NT reports removing a directory as EACCES instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == ENOENT) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows 95 reports removing a directory as ENOENT instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == EINVAL) { + /* + * Windows NT reports removing a char device as EINVAL instead of + * EACCES. + */ + + errno = EACCES; + } + + return check_error(-1, errInfo); +} + +/* + *--------------------------------------------------------------------------- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Some possible error codes: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist, or src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +efile_rename(errInfo, src, dst) +Efile_error* errInfo; /* Where to return error codes. */ +char* src; /* Original name. */ +char* dst; /* New name. */ +{ + DWORD srcAttr, dstAttr; + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + srcAttr = GetFileAttributes(src); + dstAttr = GetFileAttributes(dst); + if (srcAttr == (DWORD) -1) { + srcAttr = 0; + } + if (dstAttr == (DWORD) -1) { + dstAttr = 0; + } + + if (errno == EBADF) { + errno = EACCES; + return check_error(-1, errInfo); + } + if (errno == EACCES) { + decode: + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + char srcPath[MAX_PATH], dstPath[MAX_PATH]; + char *srcRest, *dstRest; + int size; + + size = GetFullPathName(src, sizeof(srcPath), srcPath, &srcRest); + if ((size == 0) || (size > sizeof(srcPath))) { + return check_error(-1, errInfo); + } + size = GetFullPathName(dst, sizeof(dstPath), dstPath, &dstRest); + if ((size == 0) || (size > sizeof(dstPath))) { + return check_error(-1, errInfo); + } + if (srcRest == NULL) { + srcRest = srcPath + strlen(srcPath); + } + if (strnicmp(srcPath, dstPath, srcRest - srcPath) == 0) { + /* + * Trying to move a directory into itself. + */ + + errno = EINVAL; + } + if (extract_root(srcPath)) { + /* + * Attempt to move a root directory. Never allowed. + */ + errno = EINVAL; + } + + (void) extract_root(dstPath); + if (dstPath[0] == '\0') { + /* + * The filename was invalid. (Don't know why, + * but play it safe.) + */ + errno = EINVAL; + } + if (stricmp(srcPath, dstPath) != 0) { + /* + * If src is a directory and dst filesystem != src + * filesystem, errno should be EXDEV. It is very + * important to get this behavior, so that the caller + * can respond to a cross filesystem rename by + * simulating it with copy and delete. The MoveFile + * system call already handles the case of moving a + * *file* between filesystems. + */ + + errno = EXDEV; + } + } + + /* + * Other types of access failure is that dst is a read-only + * filesystem, that an open file referred to src or dest, or that + * src or dest specified the current working directory on the + * current filesystem. EACCES is returned for those cases. + */ + + } else if (errno == EEXIST) { + /* + * Reports EEXIST any time the target already exists. If it makes + * sense, remove the old file and try renaming again. + */ + + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Overwrite empty dst directory with src directory. The + * following call will remove an empty directory. If it + * fails, it's because it wasn't empty. + */ + + if (RemoveDirectory(dst)) { + /* + * Now that that empty directory is gone, we can try + * renaming again. If that fails, we'll put this empty + * directory back, for completeness. + */ + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + /* + * Some new error has occurred. Don't know what it + * could be, but report this one. + */ + + errno = errno_map(GetLastError()); + CreateDirectory(dst, NULL); + SetFileAttributes(dst, dstAttr); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + } else { /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + errno = ENOTDIR; + } + } else { /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + } else { + /* + * Overwrite existing file by: + * + * 1. Rename existing file to temp name. + * 2. Rename old file to new name. + * 3. If success, delete temp file. If failure, + * put temp file back to old name. + */ + + char tempName[MAX_PATH]; + int result, size; + char *rest; + + size = GetFullPathName(dst, sizeof(tempName), tempName, &rest); + if ((size == 0) || (size > sizeof(tempName)) || (rest == NULL)) { + return check_error(-1, errInfo); + } + *rest = '\0'; + result = -1; + if (GetTempFileName(tempName, "erlr", 0, tempName) != 0) { + /* + * Strictly speaking, need the following DeleteFile and + * MoveFile to be joined as an atomic operation so no + * other app comes along in the meantime and creates the + * same temp file. + */ + + DeleteFile(tempName); + if (MoveFile(dst, tempName) != FALSE) { + if (MoveFile(src, dst) != FALSE) { + SetFileAttributes(tempName, FILE_ATTRIBUTE_NORMAL); + DeleteFile(tempName); + return 1; + } else { + DeleteFile(dst); + MoveFile(tempName, dst); + } + } + + /* + * Can't backup dst file or move src file. Return that + * error. Could happen if an open file refers to dst. + */ + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + return result; + } + } + } + return check_error(-1, errInfo); +} + +int +efile_chdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to make current. */ +{ + int success = check_error(chdir(name), errInfo); + if (!success && errInfo->posix_errno == EINVAL) + /* POSIXification of errno */ + errInfo->posix_errno = ENOENT; + return success; +} + +int +efile_getdcwd(errInfo, drive, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +int drive; /* 0 - current, 1 - A, 2 - B etc. */ +char* buffer; /* Where to return the current directory. */ +size_t size; /* Size of buffer. */ +{ + if (_getdcwd(drive, buffer, size) == NULL) + return check_error(-1, errInfo); + for ( ; *buffer; buffer++) + if (*buffer == '\\') + *buffer = '/'; + return 1; +} + +int +efile_readdir(errInfo, name, dir_handle, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +EFILE_DIR_HANDLE* dir_handle; /* Directory handle of open directory. */ +char* buffer; /* Pointer to buffer for one filename. */ +size_t size; /* Size of buffer. */ +{ + HANDLE dir; /* Handle to directory. */ + char wildcard[MAX_PATH]; /* Wildcard to search for. */ + WIN32_FIND_DATA findData; /* Data found by FindFirstFile() or FindNext(). */ + + /* + * First time we must setup everything. + */ + + if (*dir_handle == NULL) { + int length = strlen(name); + char* s; + + if (length+3 >= MAX_PATH) { + errno = ENAMETOOLONG; + return check_error(-1, errInfo); + } + + strcpy(wildcard, name); + s = wildcard+length-1; + if (*s != '/' && *s != '\\') + *++s = '\\'; + *++s = '*'; + *++s = '\0'; + DEBUGF(("Reading %s\n", wildcard)); + dir = FindFirstFile(wildcard, &findData); + if (dir == INVALID_HANDLE_VALUE) + return set_error(errInfo); + *dir_handle = (EFILE_DIR_HANDLE) dir; + + if (!IS_DOT_OR_DOTDOT(findData.cFileName)) { + strcpy(buffer, findData.cFileName); + return 1; + } + } + + + /* + * Retrieve the name of the next file using the directory handle. + */ + + dir = (HANDLE) *dir_handle; + + for (;;) { + if (FindNextFile(dir, &findData)) { + if (IS_DOT_OR_DOTDOT(findData.cFileName)) + continue; + strcpy(buffer, findData.cFileName); + return 1; + } + + if (GetLastError() == ERROR_NO_MORE_FILES) { + FindClose(dir); + errInfo->posix_errno = errInfo->os_errno = 0; + return 0; + } + + set_error(errInfo); + FindClose(dir); + return 0; + } +} + +int +efile_openfile(errInfo, name, flags, pfd, pSize) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +int flags; /* Flags to use for opening. */ +int* pfd; /* Where to store the file descriptor. */ +Sint64* pSize; /* Where to store the size of the file. */ +{ + BY_HANDLE_FILE_INFORMATION fileInfo; /* File information from a handle. */ + HANDLE fd; /* Handle to open file. */ + DWORD access; /* Access mode: GENERIC_READ, GENERIC_WRITE. */ + DWORD crFlags; + + switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) { + case EFILE_MODE_READ: + access = GENERIC_READ; + crFlags = OPEN_EXISTING; + break; + case EFILE_MODE_WRITE: + access = GENERIC_WRITE; + crFlags = CREATE_ALWAYS; + break; + case EFILE_MODE_READ_WRITE: + access = GENERIC_READ|GENERIC_WRITE; + crFlags = OPEN_ALWAYS; + break; + default: + errno = EINVAL; + check_error(-1, errInfo); + return 0; + } + + if (flags & EFILE_MODE_APPEND) { + crFlags = OPEN_ALWAYS; + } + fd = CreateFile(name, access, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, crFlags, FILE_ATTRIBUTE_NORMAL, NULL); + + /* + * Check for errors. + */ + + if (fd == INVALID_HANDLE_VALUE) { + DWORD attr; + + set_error(errInfo); + + /* + * If the error is EACESS, the reason could be that we tried to + * open a directory. In that case, we'll change the error code + * to EISDIR. + */ + if (errInfo->posix_errno && + (attr = GetFileAttributes(name)) != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_DIRECTORY)) { + errInfo->posix_errno = EISDIR; + } + return 0; + } + + /* + * Get and return the length of the open file. + */ + + if (!GetFileInformationByHandle(fd, &fileInfo)) + return set_error(errInfo); + *pfd = (int) fd; + if (pSize) { + *pSize = (Sint64) + (((Uint64)fileInfo.nFileSizeHigh << 32) | + (Uint64)fileInfo.nFileSizeLow); + } + return 1; +} + +int +efile_may_openfile(Efile_error* errInfo, char *name) { + DWORD attr; + + if ((attr = GetFileAttributes(name)) == INVALID_FILE_ATTRIBUTES) { + return check_error(-1, errInfo); + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#if 0 + struct stat statbuf; + + if (stat(name, &statbuf)) { + return check_error(-1, errInfo); + } + if (ISDIR(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#endif +} + +void +efile_closefile(fd) +int fd; /* File descriptor for file to close. */ +{ + CloseHandle((HANDLE) fd); +} + +int +efile_fsync(errInfo, fd) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor for file to sync. */ +{ + if (!FlushFileBuffers((HANDLE) fd)) { + return check_error(-1, errInfo); + } + return 1; +} + +int +efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char* orig_name, int info_for_link) +{ + HANDLE findhandle; /* Handle returned by FindFirstFile(). */ + WIN32_FIND_DATA findbuf; /* Data return by FindFirstFile(). */ + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } else + drive = tolower(*name) - 'a' + 1; + + findhandle = FindFirstFile(name, &findbuf); + if (findhandle == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + + findbuf.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + findbuf.nFileSizeHigh = 0; + findbuf.nFileSizeLow = 0; + findbuf.cFileName[0] = '\0'; + + pInfo->modifyTime.year = 1980; + pInfo->modifyTime.month = 1; + pInfo->modifyTime.day = 1; + pInfo->modifyTime.hour = 0; + pInfo->modifyTime.minute = 0; + pInfo->modifyTime.second = 0; + + pInfo->accessTime = pInfo->modifyTime; + } else { + SYSTEMTIME SystemTime; + FILETIME LocalFTime; + +#define GET_TIME(dst, src) \ +if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \ + !FileTimeToSystemTime(&LocalFTime, &SystemTime)) { \ + return set_error(errInfo); \ +} \ +(dst).year = SystemTime.wYear; \ +(dst).month = SystemTime.wMonth; \ +(dst).day = SystemTime.wDay; \ +(dst).hour = SystemTime.wHour; \ +(dst).minute = SystemTime.wMinute; \ +(dst).second = SystemTime.wSecond; + + GET_TIME(pInfo->modifyTime, ftLastWriteTime); + + if (findbuf.ftLastAccessTime.dwLowDateTime == 0 && + findbuf.ftLastAccessTime.dwHighDateTime == 0) { + pInfo->accessTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->accessTime, ftLastAccessTime); + } + + if (findbuf.ftCreationTime.dwLowDateTime == 0 && + findbuf.ftCreationTime.dwHighDateTime == 0) { + pInfo->cTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->cTime, ftCreationTime); + } +#undef GET_TIME + FindClose(findhandle); + } + + pInfo->size_low = findbuf.nFileSizeLow; + pInfo->size_high = findbuf.nFileSizeHigh; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + pInfo->type = FT_DIRECTORY; + else + pInfo->type = FT_REGULAR; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + pInfo->access = FA_READ; + else + pInfo->access = FA_READ|FA_WRITE; + + pInfo->mode = dos_to_posix_mode(findbuf.dwFileAttributes, name); + pInfo->links = 1; + pInfo->major_device = drive; + pInfo->minor_device = 0; + pInfo->inode = 0; + pInfo->uid = 0; + pInfo->gid = 0; + + return 1; +} + +int +efile_write_info(errInfo, pInfo, name) +Efile_error* errInfo; +Efile_info* pInfo; +char* name; +{ + SYSTEMTIME timebuf; + FILETIME LocalFileTime; + FILETIME ModifyFileTime; + FILETIME AccessFileTime; + FILETIME CreationFileTime; + HANDLE fd; + FILETIME* mtime = NULL; + FILETIME* atime = NULL; + FILETIME* ctime = NULL; + DWORD attr; + DWORD tempAttr; + BOOL modifyTime = FALSE; + + /* + * Get the attributes for the file. + */ + + tempAttr = attr = GetFileAttributes((LPTSTR)name); + if (attr == 0xffffffff) { + return set_error(errInfo); + } + if (pInfo->mode != -1) { + if (pInfo->mode & _S_IWRITE) { + /* clear read only bit */ + attr &= ~FILE_ATTRIBUTE_READONLY; + } else { + /* set read only bit */ + attr |= FILE_ATTRIBUTE_READONLY; + } + } + + /* + * Construct all file times. + */ + +#define MKTIME(tb, ts, ptr) \ + timebuf.wYear = ts.year; \ + timebuf.wMonth = ts.month; \ + timebuf.wDay = ts.day; \ + timebuf.wHour = ts.hour; \ + timebuf.wMinute = ts.minute; \ + timebuf.wSecond = ts.second; \ + timebuf.wMilliseconds = 0; \ + if (ts.year != -1) { \ + modifyTime = TRUE; \ + ptr = &tb; \ + if (!SystemTimeToFileTime(&timebuf, &LocalFileTime ) || \ + !LocalFileTimeToFileTime(&LocalFileTime, &tb)) { \ + errno = EINVAL; \ + return check_error(-1, errInfo); \ + } \ + } + + MKTIME(ModifyFileTime, pInfo->accessTime, mtime); + MKTIME(AccessFileTime, pInfo->modifyTime, atime); + MKTIME(CreationFileTime, pInfo->cTime, ctime); +#undef MKTIME + + /* + * If necessary, set the file times. + */ + + if (modifyTime) { + /* + * If the has read only access, we must temporarily turn on + * write access (this is necessary for native filesystems, + * but not for NFS filesystems). + */ + + if (tempAttr & FILE_ATTRIBUTE_READONLY) { + tempAttr &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributes((LPTSTR) name, tempAttr)) { + return set_error(errInfo); + } + } + + fd = CreateFile(name, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd != INVALID_HANDLE_VALUE) { + BOOL result = SetFileTime(fd, ctime, atime, mtime); + if (!result) { + return set_error(errInfo); + } + CloseHandle(fd); + } + } + + /* + * If the file doesn't have the correct attributes, set them now. + * (It could have been done before setting the file times, above). + */ + + if (tempAttr != attr) { + if (!SetFileAttributes((LPTSTR) name, attr)) { + return set_error(errInfo); + } + } + return 1; +} + + +int +efile_pwrite(errInfo, fd, buf, count, offset) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +Sint64 offset; /* where to write it */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_write(errInfo, EFILE_MODE_WRITE, fd, buf, count); + } else { + return res; + } +} + +/* position and read/write as a single atomic op */ +int +efile_pread(errInfo, fd, offset, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to read from. */ +Sint64 offset; /* Offset in bytes from BOF. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_read(errInfo, EFILE_MODE_READ, fd, buf, count, pBytesRead); + } else { + return res; + } +} + + + +int +efile_write(errInfo, flags, fd, buf, count) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +{ + DWORD written; /* Bytes written in last operation. */ + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + while (count > 0) { + if (!WriteFile((HANDLE) fd, buf, count, &written, NULL)) + return set_error(errInfo); + buf += written; + count -= written; + } + return 1; +} + +int +efile_writev(Efile_error* errInfo, /* Where to return error codes */ + int flags, /* Flags given when file was + * opened */ + int fd, /* File descriptor to write to */ + SysIOVec* iov, /* Vector of buffer structs. + * The structs are unchanged + * after the call */ + int iovcnt, /* Number of structs in vector */ + size_t size) /* Number of bytes to write */ +{ + int cnt; /* Buffers so far written */ + + ASSERT(iovcnt >= 0); + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + for (cnt = 0; cnt < iovcnt; cnt++) { + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + /* Non-empty buffer */ + int p; /* Position in buffer */ + int w = iov[cnt].iov_len;/* Bytes written in this call */ + for (p = 0; p < iov[cnt].iov_len; p += w) { + if (!WriteFile((HANDLE) fd, + iov[cnt].iov_base + p, + iov[cnt].iov_len - p, + &w, + NULL)) + return set_error(errInfo); + } + } + } + return 1; +} + +int +efile_read(errInfo, flags, fd, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to read from. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + if (!ReadFile((HANDLE) fd, buf, count, (DWORD *) pBytesRead, NULL)) + return set_error(errInfo); + return 1; +} + +int +efile_seek(errInfo, fd, offset, origin, new_location) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to do the seek on. */ +Sint64 offset; /* Offset in bytes from the given origin. */ +int origin; /* Origin of seek (SEEK_SET, SEEK_CUR, + * SEEK_END). + */ +Sint64* new_location; /* Resulting new location in file. */ +{ + LARGE_INTEGER off, new_loc; + + switch (origin) { + case EFILE_SEEK_SET: origin = FILE_BEGIN; break; + case EFILE_SEEK_CUR: origin = FILE_CURRENT; break; + case EFILE_SEEK_END: origin = FILE_END; break; + default: + errno = EINVAL; + check_error(-1, errInfo); + break; + } + + off.QuadPart = offset; + if (! SetFilePointerEx((HANDLE) fd, off, + new_location ? &new_loc : NULL, origin)) { + return set_error(errInfo); + } + if (new_location) { + *new_location = new_loc.QuadPart; + DEBUGF(("efile_seek(offset=%ld, origin=%d) -> %ld\n", + (long) offset, origin, (long) *new_location)); + } else { + DEBUGF(("efile_seek(offset=%ld, origin=%d)\n", (long) offset, origin)); + } + return 1; +} + +int +efile_truncate_file(errInfo, fd, flags) +Efile_error* errInfo; /* Where to return error codes. */ +int *fd; /* File descriptor for file to truncate. */ +int flags; +{ + if (!SetEndOfFile((HANDLE) (*fd))) + return set_error(errInfo); + return 1; +} + + +/* + * IsRootUNCName - returns TRUE if the argument is a UNC name specifying + * a root share. That is, if it is of the form \\server\share\. + * This routine will also return true if the argument is of the + * form \\server\share (no trailing slash) but Win32 currently + * does not like that form. + * + * Forward slashes ('/') may be used instead of backslashes ('\'). + */ + +static int +IsRootUNCName(const char* path) +{ + /* + * If a root UNC name, path will start with 2 (but not 3) slashes + */ + + if ((strlen(path) >= 5) /* minimum string is "//x/y" */ + && ISSLASH(path[0]) && ISSLASH(path[1])) + { + const char * p = path + 2 ; + + /* + * find the slash between the server name and share name + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + if ( *p && p[1] ) + { + /* + * is there a further slash? + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + /* + * just final slash (or no final slash) + */ + if ( !*p || !p[1]) + return 1; + } + } + + return 0 ; +} + +/* + * Extracts the root part of an absolute filename (by modifying the string + * pointed to by the name argument). The name can start + * with either a driver letter (for example, C:\), or a UNC name + * (for example, \\guinness\bjorn). + * + * If the name is invalid, the buffer will be modified to point to + * an empty string. + * + * Returns: 1 if the name consists of just the root part, 0 if + * the name was longer. + */ + +static int +extract_root(char* name) +{ + int len = strlen(name); + + if (isalpha(name[0]) && name[1] == ':' && ISSLASH(name[2])) { + int c = name[3]; + name[3] = '\0'; + return c == '\0'; + } else if (len < 5 || !ISSLASH(name[0]) || !ISSLASH(name[1])) { + goto error; + } else { /* Try to find the end of the UNC name. */ + char* p; + int c; + + /* + * Find the slash between the server name and share name. + */ + + for (p = name + 2; *p; p++) + if (ISSLASH(*p)) + break; + if (*p == '\0') + goto error; + + /* + * Find the slash after the share name. + */ + + for (p++; *p; p++) + if (ISSLASH(*p)) + break; + c = *p; + *p = '\0'; + return c == '\0' || p[1] == '\0'; + } + + error: + *name = '\0'; + return 1; +} + +static unsigned short +dos_to_posix_mode(int attr, const char *name) +{ + register unsigned short uxmode; + unsigned dosmode; + register const char *p; + + dosmode = attr & 0xff; + if ((p = name)[1] == ':') + p += 2; + + /* check to see if this is a directory - note we must make a special + * check for the root, which DOS thinks is not a directory + */ + + uxmode = (unsigned short) + (((ISSLASH(*p) && !p[1]) || (dosmode & FILE_ATTRIBUTE_DIRECTORY) || + *p == '\0') ? _S_IFDIR|_S_IEXEC : _S_IFREG); + + /* If attribute byte does not have read-only bit, it is read-write */ + + uxmode |= (dosmode & FILE_ATTRIBUTE_READONLY) ? + _S_IREAD : (_S_IREAD|_S_IWRITE); + + /* see if file appears to be executable - check extension of name */ + + if (p = strrchr(name, '.')) { + if (!stricmp(p, ".exe") || + !stricmp(p, ".cmd") || + !stricmp(p, ".bat") || + !stricmp(p, ".com")) + uxmode |= _S_IEXEC; + } + + /* propagate user read/write/execute bits to group/other fields */ + + uxmode |= (uxmode & 0700) >> 3; + uxmode |= (uxmode & 0700) >> 6; + + return uxmode; +} + +int +efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + + +int +efile_altname(Efile_error* errInfo, char* orig_name, char* buffer, size_t size) +{ + WIN32_FIND_DATA wfd; + HANDLE fh; + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + goto enoent; + } else { + drive = tolower(*name) - 'a' + 1; + } + fh = FindFirstFile(name,&wfd); + if (fh == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errno = errno_map(GetLastError()); + return check_error(-1, errInfo); + } + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + strcpy(buffer,name); + return 1; + } + + strcpy(buffer,wfd.cAlternateFileName); + if (!*buffer) { + strcpy(buffer,wfd.cFileName); + } + + return 1; +} + +int +efile_link(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_symlink(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} diff --git a/erts/emulator/drivers/win32/winsock_func.h b/erts/emulator/drivers/win32/winsock_func.h new file mode 100644 index 0000000000..9d2c099c4d --- /dev/null +++ b/erts/emulator/drivers/win32/winsock_func.h @@ -0,0 +1,102 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +typedef struct _WinSockFuncs { + int (WSAAPI *WSAStartup)(WORD wVersionRequired, LPWSADATA lpWSAData); + int (WSAAPI *WSACleanup)(void); + int (WSAAPI *WSAGetLastError)(void); + DWORD (WSAAPI *WSAWaitForMultipleEvents) (DWORD cEvents, + const WSAEVENT FAR * lphEvents, + BOOL fWaitAll, + DWORD dwTimeout, + BOOL fAlertable); + WSAEVENT (WSAAPI *WSACreateEvent)(void); + BOOL (WSAAPI *WSACloseEvent)(WSAEVENT hEvent); + + BOOL (WSAAPI *WSASetEvent)(WSAEVENT hEvent); + BOOL (WSAAPI *WSAResetEvent)(WSAEVENT hEvent); + int (WSAAPI *WSAEventSelect)(SOCKET s, WSAEVENT hEventObject, + long lNetworkEvents); + int (WSAAPI *WSAEnumNetworkEvents)(SOCKET s, + WSAEVENT hEventObject, + LPWSANETWORKEVENTS lpNetworkEvents); + int (WSAAPI *WSAIoctl)(SOCKET s, + DWORD dwIoControlCode, + LPVOID lpvInBuffer, + DWORD cbInBuffer, + LPVOID lpvOUTBuffer, + DWORD cbOUTBuffer, + LPDWORD lpcbBytesReturned, + LPWSAOVERLAPPED lpOverlapped, + LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE + ); + SOCKET (WSAAPI *accept)(SOCKET s, struct sockaddr FAR *addr, + int FAR *addrlen); + int (WSAAPI *bind)(SOCKET s, const struct sockaddr FAR *addr, + int namelen); + int (WSAAPI *closesocket)(SOCKET s); + int (WSAAPI *connect)(SOCKET s, const struct sockaddr FAR *name, + int namelen); + int (WSAAPI *ioctlsocket)(SOCKET s, long cmd, u_long FAR *argp); + int (WSAAPI *getsockopt)(SOCKET s, int level, int optname, + char FAR * optval, int FAR *optlen); + u_long (WSAAPI *htonl)(u_long hostlong); + u_short (WSAAPI *htons)(u_short hostshort); + unsigned long (WSAAPI *inet_addr)(const char FAR * cp); + char FAR * (WSAAPI *inet_ntoa)(struct in_addr in); + int (WSAAPI *listen)(SOCKET s, int backlog); + u_short (WSAAPI *ntohs)(u_short netshort); + int (WSAAPI *recv)(SOCKET s, char FAR * buf, int len, int flags); + int (WSAAPI *send)(SOCKET s, const char FAR * buf, int len, int flags); + int (WSAAPI *setsockopt)(SOCKET s, int level, int optname, + const char FAR * optval, int optlen); + int (WSAAPI *shutdown)(SOCKET s, int how); + SOCKET (WSAAPI *socket)(int af, int type, int protocol); + struct hostent FAR * (WSAAPI *gethostbyname)(const char FAR * name); + struct hostent FAR * (WSAAPI *gethostbyaddr)(const char FAR *addr, + int addrlen, int addrtype); + int (WSAAPI *gethostname)(char FAR * name, int namelen); + struct servent FAR * (WSAAPI *getservbyname)(const char FAR * name, + const char FAR * proto); + struct servent FAR * (WSAAPI *getservbyport)(int port, + const char FAR * proto); + int (WSAAPI *getsockname)(SOCKET sock, struct sockaddr FAR *name, + int FAR *namelen); + + /* + * New, added for inet_drv. + */ + + int (WSAAPI *getpeername)(SOCKET s, struct sockaddr FAR * name, + int FAR * namelen); + u_long (WSAAPI *ntohl)(u_long netlong); + int (WSAAPI *WSASend)(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, + LPDWORD lpNumberOfBytesSent, DWORD dwFlags, + LPWSAOVERLAPPED lpOverlapped, + LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + int (WSAAPI *sendto)(SOCKET s, const char FAR * buf, int len, + int flags, const struct sockaddr FAR * to, int tolen); + int (WSAAPI *recvfrom)(SOCKET s, char FAR * buf, int len, int flags, + struct sockaddr FAR * from, int FAR * fromlen); +} WinSockFuncs; + + +extern WinSockFuncs winSock; + +extern int tcp_lookup_functions(void); |