mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-25 21:53:17 -05:00
3138 lines
78 KiB
C
3138 lines
78 KiB
C
/*
|
|
* $Id: $
|
|
*
|
|
* Generic IO handling for files/sockets/etc
|
|
*/
|
|
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS 1601
|
|
#define _CRT_SECURE_NO_DEPRECATE 1
|
|
#define _CRT_NONSTDC_NO_DEPRECATE 1
|
|
#pragma warning(disable: 4996)
|
|
|
|
#ifdef LARGE_FILE
|
|
# define _LARGEFILE_SOURCE
|
|
# define _LARGEFILE64_SOURCE
|
|
#endif
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#ifndef WIN32
|
|
# include <netdb.h>
|
|
#else
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# include <ws2tcpip.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef WIN32
|
|
# include <netinet/in.h>
|
|
# include <sys/select.h>
|
|
# include <sys/socket.h>
|
|
# include <arpa/inet.h>
|
|
# ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# endif
|
|
#endif
|
|
#include <sys/types.h>
|
|
|
|
#include "io-errors.h"
|
|
#include "io-plugin.h"
|
|
#include "bsd-snprintf.h"
|
|
|
|
#ifdef WIN32
|
|
# define strcasecmp stricmp
|
|
# define SHUT_RDWR SD_BOTH
|
|
# define ssize_t int
|
|
|
|
# define IO_HANDLES_START 1
|
|
# define IO_HANDLES_GROW 1
|
|
#else
|
|
# define closesocket close
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
typedef struct tag_io_waithandle {
|
|
DWORD dwLastResult;
|
|
DWORD dwItemCount;
|
|
DWORD dwMaxItems;
|
|
DWORD dwWhichEvent;
|
|
WAITABLE_T *hWaitItems;
|
|
WSANETWORKEVENTS wsaNetworkEvents;
|
|
IO_PRIVHANDLE **ppHandle;
|
|
} IO_WAITHANDLE;
|
|
#else
|
|
typedef struct tag_io_waithandle {
|
|
int max_fd;
|
|
fd_set read_fds;
|
|
fd_set write_fds;
|
|
fd_set err_fds;
|
|
fd_set result_read;
|
|
fd_set result_write;
|
|
fd_set result_err;
|
|
} IO_WAITHANDLE;
|
|
#endif
|
|
|
|
|
|
/* Public interface */
|
|
int io_init(void);
|
|
int io_deinit(void);
|
|
int io_isproto(IO_PRIVHANDLE *phandle, char *proto);
|
|
|
|
#define IO_WAIT_READ 1
|
|
#define IO_WAIT_WRITE 2
|
|
#define IO_WAIT_ERROR 4
|
|
|
|
#define IO_BUFFER_SIZE 1024
|
|
|
|
IO_WAITHANDLE *io_wait_new(void);
|
|
int io_wait_add(IO_WAITHANDLE *pwait, IO_PRIVHANDLE *phandle, int type);
|
|
int io_wait(IO_WAITHANDLE *pwait, uint32_t *ms);
|
|
int io_wait_status(IO_WAITHANDLE *pwait, IO_PRIVHANDLE *phandle);
|
|
int io_wait_dispose(IO_WAITHANDLE *pwait);
|
|
|
|
IO_PRIVHANDLE *io_new(void);
|
|
int io_open(IO_PRIVHANDLE *phandle, char *fmt, ...);
|
|
int io_close(IO_PRIVHANDLE *phandle);
|
|
int io_read(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len);
|
|
int io_write(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len);
|
|
int io_printf(IO_PRIVHANDLE *phandle, char *fmt, ...);
|
|
int io_size(IO_PRIVHANDLE *phandle, uint64_t *size);
|
|
int io_setpos(IO_PRIVHANDLE *phandle, uint64_t offset, int whence);
|
|
int io_getpos(IO_PRIVHANDLE *phandle, uint64_t *pos);
|
|
int io_buffer(IO_PRIVHANDLE *phandle);
|
|
int io_readline(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len);
|
|
int io_readline_timeout(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len, uint32_t *ms);
|
|
char *io_errstr(IO_PRIVHANDLE *phandle);
|
|
void io_dispose(IO_PRIVHANDLE *phandle);
|
|
|
|
void io_set_errhandler(void(*err_handler)(int, char*));
|
|
void io_set_lockhandler(void(*lock_handler)(int));
|
|
int io_register_handler(char *proto, IO_FNPTR *fnptr);
|
|
|
|
/* Built-ins for file and socket proto */
|
|
static int io_file_open(IO_PRIVHANDLE *phandle, char *uri);
|
|
static int io_file_close(IO_PRIVHANDLE *phandle);
|
|
static int io_file_read(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len);
|
|
static int io_file_write(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len);
|
|
static int io_file_size(IO_PRIVHANDLE *phandle, uint64_t *size);
|
|
static int io_file_setpos(IO_PRIVHANDLE *phandle, uint64_t offset, int whence);
|
|
static int io_file_getpos(IO_PRIVHANDLE *phandle, uint64_t *pos);
|
|
static char* io_file_geterrmsg(IO_PRIVHANDLE *phandle, ERR_T *code, int *is_local);
|
|
static int io_file_getwaitable(IO_PRIVHANDLE *phandle, int mode, WAITABLE_T *retval);
|
|
static int io_file_getfd(IO_PRIVHANDLE *phandle, FILE_T *pfd);
|
|
|
|
|
|
static int io_socket_open(IO_PRIVHANDLE *phandle, char *uri);
|
|
static int io_socket_close(IO_PRIVHANDLE *phandle);
|
|
static int io_socket_read(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len);
|
|
static int io_socket_write(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len);
|
|
static char* io_socket_geterrmsg(IO_PRIVHANDLE *phandle, ERR_T *code, int *is_local);
|
|
static int io_socket_getwaitable(IO_PRIVHANDLE *phandle, int mode, WAITABLE_T *retval);
|
|
static int io_socket_getsocket(IO_PRIVHANDLE *phandle, SOCKET_T *psock);
|
|
|
|
static int io_listen_open(IO_PRIVHANDLE *phandle, char *uri);
|
|
int io_listen_accept(IO_PRIVHANDLE *phandle, IO_PRIVHANDLE *pchild,
|
|
struct in_addr *host);
|
|
int io_udp_recvfrom(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len,
|
|
struct sockaddr_in *si_remote, socklen_t *si_len);
|
|
int io_udp_sendto(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len,
|
|
struct sockaddr_in *si_remote, socklen_t si_len);
|
|
|
|
/* Private functions */
|
|
static void io_err(IO_PRIVHANDLE *phandle, uint32_t errorcode);
|
|
static void io_err_printf(int level, char *fmt, ...);
|
|
static void io_default_errhandler(int level, char *msg);
|
|
static void io_default_lockhandler(int);
|
|
static void io_lock(void);
|
|
static void io_unlock(void);
|
|
static int io_urldecode(char *); /**< do an in-place decode */
|
|
static int io_option_add(IO_PRIVHANDLE *phandle, char *key, char *value);
|
|
static int io_option_dispose(IO_PRIVHANDLE *phandle);
|
|
|
|
static void io_file_seterr(IO_PRIVHANDLE *phandle, ERR_T errcode);
|
|
static void io_socket_seterr(IO_PRIVHANDLE *phandle, ERR_T errcode);
|
|
|
|
/* Defines */
|
|
struct tag_io_optionlist {
|
|
char *key;
|
|
char *value;
|
|
struct tag_io_optionlist *next;
|
|
};
|
|
|
|
typedef struct tag_io_handler_list {
|
|
char *proto;
|
|
IO_FNPTR *fnptr;
|
|
struct tag_io_handler_list *next;
|
|
} IO_HANDLER_LIST;
|
|
|
|
typedef struct tag_io_file_priv {
|
|
FILE_T fd;
|
|
ERR_T err;
|
|
int local_err;
|
|
int opened;
|
|
int open_flags;
|
|
} IO_FILE_PRIV;
|
|
|
|
#define IO_FILE_READ 0x01
|
|
#define IO_FILE_WRITE 0x02
|
|
#define IO_FILE_APPEND 0x04
|
|
#define IO_FILE_TRUNCATE 0x08
|
|
#define IO_FILE_CREATENEW 0x10
|
|
#define IO_FILE_CREATE 0x20
|
|
|
|
typedef struct tag_io_socket_priv {
|
|
SOCKET_T fd;
|
|
ERR_T err;
|
|
unsigned short port;
|
|
int local_err;
|
|
int opened;
|
|
int is_udp;
|
|
struct sockaddr_in si_remote;
|
|
#ifdef WIN32
|
|
WAITABLE_T hEvent;
|
|
int wait_mode;
|
|
#endif
|
|
} IO_SOCKET_PRIV;
|
|
|
|
#ifdef DEBUG
|
|
# ifndef ASSERT
|
|
# define ASSERT(f) \
|
|
if(f) \
|
|
{} \
|
|
else \
|
|
io_err_printf(0,"Assert error in %s, line %d\n",__FILE__,__LINE__)
|
|
# endif /* ndef ASSERT */
|
|
#else /* ndef DEBUG */
|
|
# ifndef ASSERT
|
|
# define ASSERT(f)
|
|
# endif
|
|
#endif
|
|
|
|
|
|
/* Globals */
|
|
static IO_HANDLER_LIST io_handler_list;
|
|
static void(*io_err_handler)(int, char*) = io_default_errhandler;
|
|
static void(*io_lock_handler)(int) = io_default_lockhandler;
|
|
static int io_initialized = 0;
|
|
|
|
static char *io_err_strings[] = {
|
|
"Error passed from proto handler",
|
|
"Invalid or unknown protocol",
|
|
"IO Handle not open",
|
|
"Unsupported IO function",
|
|
"unknown internal error"
|
|
};
|
|
|
|
static char *io_file_err_strings[] = {
|
|
"libc error",
|
|
"Operation on unopened file",
|
|
"Unknown error"
|
|
};
|
|
|
|
static char *io_socket_err_strings[] = {
|
|
"libc error",
|
|
"socket isn't open",
|
|
"unknown internal error",
|
|
"unknown or invalid hostname",
|
|
"child socket not initialized",
|
|
"socket doesn't support function",
|
|
"no multicast group specified",
|
|
"invalid parameter to function call",
|
|
"socket/port in use"
|
|
};
|
|
|
|
static IO_FNPTR io_pfn_file = {
|
|
io_file_open,
|
|
io_file_close,
|
|
io_file_read,
|
|
io_file_write,
|
|
io_file_size,
|
|
io_file_setpos,
|
|
io_file_getpos,
|
|
io_file_geterrmsg,
|
|
io_file_getwaitable,
|
|
io_file_getfd,
|
|
NULL
|
|
};
|
|
|
|
static IO_FNPTR io_pfn_socket = {
|
|
io_socket_open,
|
|
io_socket_close,
|
|
io_socket_read,
|
|
io_socket_write,
|
|
NULL, /* size */
|
|
NULL, /* setpos */
|
|
NULL, /* getpos */
|
|
io_socket_geterrmsg,
|
|
io_socket_getwaitable,
|
|
NULL,
|
|
io_socket_getsocket
|
|
};
|
|
|
|
static IO_FNPTR io_pfn_listen = { /* reuse most of the socket stuff */
|
|
io_listen_open,
|
|
io_socket_close,
|
|
NULL, /* read */
|
|
NULL, /* write */
|
|
NULL, /* size */
|
|
NULL, /* setpos */
|
|
NULL, /* getpos */
|
|
io_socket_geterrmsg,
|
|
io_socket_getwaitable,
|
|
NULL,
|
|
io_socket_getsocket
|
|
};
|
|
|
|
static IO_FNPTR io_pfn_udp = {
|
|
io_socket_open,
|
|
io_socket_close,
|
|
io_socket_read,
|
|
io_socket_write,
|
|
NULL, /* size */
|
|
NULL, /* setpos */
|
|
NULL, /* getpos */
|
|
io_socket_geterrmsg,
|
|
io_socket_getwaitable,
|
|
NULL,
|
|
io_socket_getsocket
|
|
};
|
|
|
|
static IO_FNPTR io_pfn_udplisten = {
|
|
io_listen_open,
|
|
io_socket_close,
|
|
io_socket_read,
|
|
io_socket_write,
|
|
NULL, /* size */
|
|
NULL, /* setpos */
|
|
NULL, /* getpos */
|
|
io_socket_geterrmsg,
|
|
io_socket_getwaitable,
|
|
NULL,
|
|
io_socket_getsocket
|
|
};
|
|
|
|
|
|
/**
|
|
* Windows compatibility functions
|
|
*/
|
|
#ifdef WIN32
|
|
|
|
static WSADATA io_WSAData;
|
|
|
|
#undef gettimeofday /* FIXME: proper gettimeofday fixups */
|
|
|
|
int gettimeofday (struct timeval *tv, void* tz) {
|
|
union {
|
|
uint64_t ns100; /*time since 1 Jan 1601 in 100ns units */
|
|
FILETIME ft;
|
|
} now;
|
|
|
|
GetSystemTimeAsFileTime (&now.ft);
|
|
tv->tv_usec = (long) ((now.ns100 / 10LL) % 1000000LL);
|
|
tv->tv_sec = (long) ((now.ns100 - 116444736000000000LL) / 10000000LL);
|
|
return (0);
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
/**
|
|
* initialize the io handlers
|
|
*
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_init(void) {
|
|
#ifdef WIN32
|
|
WORD wVersionRequested;
|
|
int err;
|
|
#endif
|
|
|
|
ASSERT(!io_initialized); /* only initialize once */
|
|
|
|
io_lock();
|
|
io_handler_list.next = NULL;
|
|
|
|
#ifdef WIN32
|
|
wVersionRequested = MAKEWORD(2,2);
|
|
err = WSAStartup(wVersionRequested, &io_WSAData);
|
|
if(err) {
|
|
io_err_printf(IO_LOG_FATAL,"Could not initialize winsock\n");
|
|
io_unlock();
|
|
return FALSE;
|
|
}
|
|
|
|
if(HIBYTE(io_WSAData.wVersion < 2)) {
|
|
io_err_printf(IO_LOG_FATAL,"This program requires Winsock 2.0 or better\n");
|
|
WSACleanup();
|
|
io_unlock();
|
|
return FALSE;
|
|
|
|
}
|
|
#endif
|
|
io_initialized = TRUE;
|
|
io_unlock();
|
|
|
|
io_register_handler("file",&io_pfn_file);
|
|
io_register_handler("socket",&io_pfn_socket);
|
|
io_register_handler("listen",&io_pfn_listen);
|
|
io_register_handler("udp",&io_pfn_udp);
|
|
io_register_handler("udplisten",&io_pfn_udplisten);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* urldecode a string in-place
|
|
*
|
|
* @param str string to decode in-place
|
|
*/
|
|
int io_urldecode(char *str) {
|
|
char *current,*dst;
|
|
int digit1, digit2;
|
|
char *hexdigits = "0123456789abcdef";
|
|
|
|
current = dst = str;
|
|
while(*current) {
|
|
switch(*current) {
|
|
case '+':
|
|
*dst++ = ' ';
|
|
*current++;
|
|
break;
|
|
case '%':
|
|
/* This is rather brute force. Maybe sscanf? */
|
|
if(strlen(current) < 3) {
|
|
io_err_printf(IO_LOG_DEBUG,"urldecode: unexpected EOL\n");
|
|
return FALSE;
|
|
}
|
|
current++;
|
|
|
|
if(!strchr(hexdigits,tolower(*current))) {
|
|
io_err_printf(IO_LOG_DEBUG,"urldecode: bad hex digit\n");
|
|
return FALSE;
|
|
}
|
|
digit1 = (int)(strchr(hexdigits,tolower(*current)) - hexdigits);
|
|
|
|
current++;
|
|
if(!strchr(hexdigits,tolower(*current))) {
|
|
io_err_printf(IO_LOG_DEBUG,"urldecode: bad hex digit\n");
|
|
return FALSE;
|
|
}
|
|
digit2 = (int)(strchr(hexdigits,tolower(*current)) - hexdigits);
|
|
current++;
|
|
|
|
*dst++ = (char)(((digit1 & 0x0F) << 4) | (digit2 & 0x0F));
|
|
break;
|
|
default:
|
|
*dst++ = *current++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*dst = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* default lock handler (no locking)
|
|
*/
|
|
void io_default_lockhandler(int lock) {
|
|
}
|
|
|
|
|
|
/**
|
|
* handler lock
|
|
*/
|
|
void io_lock(void) {
|
|
io_lock_handler(TRUE);
|
|
}
|
|
|
|
|
|
/**
|
|
* handler unlock
|
|
*/
|
|
void io_unlock(void) {
|
|
io_lock_handler(FALSE);
|
|
}
|
|
|
|
|
|
/**
|
|
* deinitiailize anything required of the io handlers
|
|
*
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_deinit(void) {
|
|
IO_HANDLER_LIST *pcurrent;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
|
|
io_lock();
|
|
pcurrent = io_handler_list.next;
|
|
while(pcurrent) {
|
|
io_handler_list.next = pcurrent->next;
|
|
free(pcurrent->proto);
|
|
free(pcurrent);
|
|
pcurrent = io_handler_list.next;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
WSACleanup();
|
|
#endif
|
|
|
|
io_unlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* set the error handler to something other than default
|
|
*
|
|
* @param err_handler new error handler to use
|
|
*/
|
|
void io_set_errhandler(void(*err_handler)(int, char*)) {
|
|
io_err_handler = err_handler;
|
|
}
|
|
|
|
void io_set_lockhandler(void(*lock_handler)(int)) {
|
|
io_lock_handler = lock_handler;
|
|
}
|
|
|
|
|
|
/**
|
|
* internal function to report errors
|
|
*
|
|
* @param level priority level, 1 - 10.
|
|
* @param fmt printf style format
|
|
*/
|
|
void io_err_printf(int level, char *fmt, ...) {
|
|
char errbuf[4096];
|
|
va_list ap;
|
|
|
|
va_start(ap,fmt);
|
|
vsnprintf(errbuf,sizeof(errbuf),fmt,ap);
|
|
va_end(ap);
|
|
|
|
io_err_handler(level,errbuf);
|
|
}
|
|
|
|
/**
|
|
* internal function for printing error messages. Should
|
|
* probably be overridden using io_set_errhandler
|
|
*
|
|
* @param level errorlevel, 1:fatal, 9:debug
|
|
*/
|
|
void io_default_errhandler(int level, char *msg) {
|
|
fprintf(stderr,"%d: %s", level, msg);
|
|
if(!level) { /* fatal! */
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* register a handler for a particular protocol.
|
|
*
|
|
* @param proto protocol to handle (e.g. http)
|
|
* @param fnptr a IO_FNPTR list of function pointers
|
|
* @returns TRUE if successful
|
|
*/
|
|
int io_register_handler(char *proto, IO_FNPTR *fnptr) {
|
|
IO_HANDLER_LIST *pnew;
|
|
IO_HANDLER_LIST *last;
|
|
|
|
pnew = (IO_HANDLER_LIST*)malloc(sizeof(IO_HANDLER_LIST));
|
|
if(!pnew)
|
|
io_err_printf(IO_LOG_FATAL,"Error in malloc\n");
|
|
|
|
pnew->proto = strdup(proto);
|
|
pnew->fnptr = fnptr;
|
|
pnew->next = NULL;
|
|
|
|
io_lock();
|
|
|
|
last = &io_handler_list;
|
|
while(last->next) {
|
|
last = last->next;
|
|
}
|
|
|
|
last->next = pnew;
|
|
io_unlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* create a new io device, and return it
|
|
*/
|
|
IO_PRIVHANDLE *io_new(void) {
|
|
IO_PRIVHANDLE *pnew;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
|
|
pnew = (IO_PRIVHANDLE*)malloc(sizeof(IO_PRIVHANDLE));
|
|
if(!pnew) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_new\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset(pnew,0x00,sizeof(IO_PRIVHANDLE));
|
|
return pnew;
|
|
}
|
|
|
|
/**
|
|
* set up a new io object with the correct fnptrs, etc
|
|
*
|
|
* @param phandle handle of io device to set up
|
|
* @param proto proto to fill ptrs with
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_assign_handle(IO_PRIVHANDLE *phandle,char *proto) {
|
|
IO_HANDLER_LIST *plist;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
|
|
if(phandle->open) {
|
|
if(!io_close(phandle))
|
|
return FALSE;
|
|
}
|
|
|
|
/* re-assigning an already assigned handle */
|
|
if((phandle->proto) && (strcasecmp(phandle->proto,proto)==0))
|
|
return TRUE;
|
|
|
|
/* walk through the list of items and see what we have */
|
|
io_lock();
|
|
plist = io_handler_list.next;
|
|
while(plist && (strcasecmp(proto,plist->proto) != 0))
|
|
plist = plist->next;
|
|
io_unlock();
|
|
|
|
if(!plist) {
|
|
io_err_printf(IO_LOG_DEBUG,"Couldn't find handler for '%s'\n",proto);
|
|
io_err(phandle,IO_E_BADPROTO);
|
|
return FALSE;
|
|
}
|
|
|
|
phandle->open = TRUE;
|
|
phandle->proto = plist->proto;
|
|
phandle->fnptr = plist->fnptr;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* open an io device, returning the handle necessary
|
|
*
|
|
* @param phandle handle of io device from io_new
|
|
* @param uri URI to open
|
|
* @param mode mode to open (O_RDWR, etc, as open(2))
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_open(IO_PRIVHANDLE *phandle, char *fmt, ...) {
|
|
char *proto_part = NULL;
|
|
char *path_part = NULL;
|
|
char *options_part = NULL;
|
|
char *key, *value;
|
|
int result;
|
|
char uri_copy[4096];
|
|
va_list ap;
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
|
|
va_start(ap, fmt);
|
|
io_util_vsnprintf(uri_copy, sizeof(uri_copy), fmt, ap);
|
|
va_end(ap);
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Opening %s\n", uri_copy);
|
|
|
|
if((proto_part = strstr(uri_copy,"://"))) {
|
|
*proto_part = '\0';
|
|
path_part = proto_part + 3;
|
|
proto_part = uri_copy;
|
|
}
|
|
|
|
if(path_part)
|
|
io_urldecode(path_part);
|
|
|
|
/* find the start of the options */
|
|
options_part = strchr(path_part,'?');
|
|
if(options_part) {
|
|
*options_part = '\0';
|
|
options_part++;
|
|
}
|
|
|
|
/* see if we can generate a list of options */
|
|
while(options_part) {
|
|
key = options_part;
|
|
options_part = strchr(options_part,'&');
|
|
if(options_part) {
|
|
*options_part = '\0';
|
|
options_part++;
|
|
}
|
|
value = strchr(key,'=');
|
|
if(value) {
|
|
*value = '\0';
|
|
value++;
|
|
}
|
|
|
|
if((key) && (value)) {
|
|
io_urldecode(key);
|
|
io_urldecode(value);
|
|
io_option_add(phandle,key,value);
|
|
}
|
|
}
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Checking handler for %s\n",proto_part);
|
|
if(!io_assign_handle(phandle,proto_part)) {
|
|
return FALSE; /* error is already set */
|
|
}
|
|
|
|
phandle->open = FALSE;
|
|
io_err_printf(IO_LOG_DEBUG,"opening %s\n",path_part);
|
|
result = phandle->fnptr->fn_open(phandle,path_part);
|
|
|
|
if(!result) {
|
|
io_err(phandle,IO_E_OTHER);
|
|
return FALSE;
|
|
}
|
|
phandle->open = TRUE;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* close an open device
|
|
*
|
|
* @param phandle handle of device to close
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_close(IO_PRIVHANDLE *phandle) {
|
|
int result=FALSE;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_close);
|
|
|
|
if((!phandle) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_close) { /* not closeable?? */
|
|
io_err(phandle,IO_E_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
if(phandle->open) {
|
|
result = phandle->fnptr->fn_close(phandle);
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
}
|
|
|
|
phandle->open = FALSE;
|
|
io_option_dispose(phandle);
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* read from an io device with a timeout. If the timeout expires, then
|
|
* the function returns FALSE. Otherwise, it returns TRUE, with the
|
|
* _remaining_ time in ms.
|
|
*
|
|
* Pass NULL to ms to read without a timeout
|
|
*
|
|
* @param phandle handle of io device to read from
|
|
* @param buf buffer to read into
|
|
* @param len length of buffer (bytes to read)
|
|
* @param ms timeout in milliseconds
|
|
* @returns TRUE on success, FALSE with ms=0 on timeout, or FALSE
|
|
* on other kind of error
|
|
*/
|
|
int io_read_timeout(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len,
|
|
uint32_t *ms) {
|
|
IO_WAITHANDLE *pwh;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_read);
|
|
|
|
io_err_printf(IO_LOG_SPAM,"entering io_read_timeout\n");
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_read) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
if(ms) {
|
|
/* wait for handle to become readable */
|
|
pwh=io_wait_new();
|
|
if(!pwh) {
|
|
io_err(phandle,IO_E_INTERNAL);
|
|
return FALSE;
|
|
}
|
|
|
|
io_wait_add(pwh,phandle,IO_WAIT_READ | IO_WAIT_ERROR);
|
|
if(!io_wait(pwh,ms)) {
|
|
io_wait_dispose(pwh);
|
|
io_err(phandle,IO_E_INTERNAL);
|
|
return FALSE;
|
|
}
|
|
io_wait_dispose(pwh);
|
|
}
|
|
|
|
return io_read(phandle,buf,len);
|
|
}
|
|
|
|
|
|
/**
|
|
* read from the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_read(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len) {
|
|
int result;
|
|
unsigned char *buf_ptr = buf;
|
|
uint32_t read_size;
|
|
uint32_t max_len = *len;
|
|
|
|
ASSERT(io_initialized);
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_read);
|
|
|
|
io_err_printf(IO_LOG_SPAM,"entering io_read\n");
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_read) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
/* check to see if we are in buffering mode */
|
|
if(phandle->buffering) {
|
|
*len = 0;
|
|
while(max_len) {
|
|
/* fill as much as possible from buffer */
|
|
if(phandle->buffer_offset < phandle->buffer_len) {
|
|
io_err_printf(IO_LOG_SPAM,"Fulfilling from buffer\n");
|
|
read_size = max_len;
|
|
if(read_size > (phandle->buffer_len - phandle->buffer_offset))
|
|
read_size = phandle->buffer_len - phandle->buffer_offset;
|
|
memcpy((void*)buf_ptr,(void*)&phandle->buffer[phandle->buffer_offset], read_size);
|
|
*len += read_size;
|
|
max_len -= read_size;
|
|
buf_ptr += read_size;
|
|
phandle->buffer_offset += read_size;
|
|
} else {
|
|
/* read a new block */
|
|
io_err_printf(IO_LOG_SPAM,"reading new block\n");
|
|
phandle->buffer_len = IO_BUFFER_SIZE;
|
|
if(!phandle->fnptr->fn_read(phandle,phandle->buffer,&phandle->buffer_len)) {
|
|
io_err(phandle,IO_E_OTHER);
|
|
return FALSE;
|
|
}
|
|
phandle->buffer_offset = 0;
|
|
if(phandle->buffer_len == 0)
|
|
return TRUE; /* can't read any more */
|
|
}
|
|
}
|
|
return TRUE; /* filled buffer */
|
|
}
|
|
|
|
result = phandle->fnptr->fn_read(phandle,buf,len);
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Read %d bytes\n",*len);
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* read a line from an io device with a timeout. If the timeout expires,
|
|
* then the function returns FALSE (with *ms=0). On any other error, it
|
|
* returns FALSE, with ms non zero. Otherwise, it returns TRUE, with
|
|
* ms set to the number of ms *left* in the wait.
|
|
*
|
|
* Pass NULL to ms to read without a timeout
|
|
*
|
|
* @param phandle handle of io device to read from
|
|
* @param buf buffer to read into
|
|
* @param len length of buffer (bytes to read)
|
|
* @param ms timeout in milliseconds
|
|
* @returns TRUE on success, FALSE with ms=0 on timeout, or FALSE
|
|
* on other kind of error
|
|
*/
|
|
int io_readline_timeout(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len, uint32_t *ms) {
|
|
uint32_t numread = 0;
|
|
uint32_t to_read;
|
|
int ascii = 0;
|
|
|
|
if(io_option_get(phandle,"ascii",NULL))
|
|
ascii = 1;
|
|
|
|
io_err_printf(IO_LOG_SPAM,"entering readline_timeout\n");
|
|
|
|
while(numread < (*len - 1)) {
|
|
to_read = 1;
|
|
if(io_read_timeout(phandle, buf + numread, &to_read, ms)) {
|
|
if(!to_read) { /* EOF */
|
|
*len = numread;
|
|
buf[numread] = '\0';
|
|
return TRUE;
|
|
}
|
|
if((!ascii) || (to_read != '\r')) {
|
|
if(buf[numread] == '\n') {
|
|
buf[numread+1] = '\0'; /* retain the CR */
|
|
*len = numread + 1;
|
|
return TRUE;
|
|
}
|
|
numread++;
|
|
}
|
|
} else {
|
|
}
|
|
}
|
|
|
|
buf[numread-1] = '\0';
|
|
*len = numread-1;
|
|
|
|
io_err_printf(IO_LOG_LOG,"Buffer too small in io_readline_timeout()\n");
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* read a line from an io device with a timeout. If the timeout expires,
|
|
* then the function returns FALSE (with *ms=0). On any other error, it
|
|
* returns FALSE, with ms non zero. Otherwise, it returns TRUE, with
|
|
* ms set to the number of ms *left* in the wait.
|
|
*
|
|
* Pass NULL to ms to read without a timeout
|
|
*
|
|
* @param phandle handle of io device to read from
|
|
* @param buf buffer to read into
|
|
* @param len length of buffer (bytes to read)
|
|
* @param ms timeout in milliseconds
|
|
* @returns TRUE on success, FALSE with ms=0 on timeout, or FALSE
|
|
* on other kind of error
|
|
*/
|
|
int io_readline(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|
uint32_t *len) {
|
|
|
|
io_err_printf(IO_LOG_SPAM,"entering io_readline\n");
|
|
return io_readline_timeout(phandle, buf, len, NULL);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* write to the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_write(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len) {
|
|
int result;
|
|
int ascii=0;
|
|
int must_free=FALSE;
|
|
unsigned char *real_buffer;
|
|
unsigned char *dst;
|
|
|
|
uint32_t ascii_len;
|
|
uint32_t index;
|
|
uint32_t real_len;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_write);
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_write) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#ifdef WIN32
|
|
// We won't do ascii mode on non-windows platforms
|
|
if(io_option_get(phandle,"ascii",NULL))
|
|
ascii = 1;
|
|
#endif
|
|
|
|
if(ascii) {
|
|
ascii_len = *len;
|
|
for(index = 0; index < *len; index++) {
|
|
if(buf[index] == '\n')
|
|
ascii_len++;
|
|
}
|
|
real_buffer = (unsigned char *)malloc(ascii_len);
|
|
if(!real_buffer) {
|
|
io_err_printf(IO_LOG_FATAL,"Could not alloc buffer in io_printf\n");
|
|
exit(-1);
|
|
}
|
|
|
|
must_free = TRUE;
|
|
dst = real_buffer;
|
|
for(index = 0; index < *len; index++) {
|
|
if(buf[index] == '\n')
|
|
*dst++ = '\r';
|
|
*dst++ = buf[index];
|
|
}
|
|
real_len = ascii_len;
|
|
} else {
|
|
real_buffer = buf; /* just write what was passed */
|
|
real_len = *len;
|
|
}
|
|
|
|
result = phandle->fnptr->fn_write(phandle,real_buffer,&real_len);
|
|
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
|
|
if(must_free) {
|
|
if(real_len == ascii_len) /* lie about how much we wrote */
|
|
real_len = *len;
|
|
free(real_buffer);
|
|
}
|
|
|
|
*len = real_len;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Write a formatted string to an io handle. This deals with
|
|
* versions of vsnprintf that return either the C99 way, or the pre-C99
|
|
* way, by increasing the buffer until it works.
|
|
*
|
|
* @param phandle device to write to
|
|
* @param fmt format string of print (compatible with printf(2))
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_printf(IO_PRIVHANDLE *phandle, char *fmt, ...) {
|
|
char *outbuf;
|
|
char *newbuf;
|
|
va_list ap;
|
|
int size=200;
|
|
int new_size;
|
|
uint32_t len;
|
|
|
|
outbuf = (char*)malloc(size);
|
|
if(!outbuf) {
|
|
io_err_printf(IO_LOG_FATAL,"Could not alloc buffer in io_printf\n");
|
|
exit(1);
|
|
}
|
|
|
|
while(1) {
|
|
va_start(ap,fmt);
|
|
new_size=vsnprintf(outbuf,size,fmt,ap);
|
|
va_end(ap);
|
|
|
|
if(new_size > -1 && new_size < size)
|
|
break;
|
|
|
|
if(new_size > -1)
|
|
size = new_size + 1;
|
|
else
|
|
size *= 2;
|
|
|
|
if((newbuf = realloc(outbuf,size)) == NULL) {
|
|
free(outbuf);
|
|
io_err_printf(IO_LOG_FATAL,"malloc error in io_printf\n");
|
|
exit(1);
|
|
}
|
|
outbuf = newbuf;
|
|
}
|
|
|
|
len = new_size;
|
|
if(!io_write(phandle,(unsigned char *)outbuf,&len) || (len != new_size)) {
|
|
free(outbuf);
|
|
return FALSE;
|
|
}
|
|
|
|
free(outbuf);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* get size (length) of io device
|
|
*
|
|
* @param phandle device to get size for
|
|
* @param size returns size of file (if successful)
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_size(IO_PRIVHANDLE *phandle, uint64_t *size) {
|
|
int result;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_size) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
result = phandle->fnptr->fn_size(phandle,size);
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* set current file position
|
|
*
|
|
* @param phandle device to set position of
|
|
* @param offset offset to set
|
|
* @param whence from whence to set, as in fsetpos
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_setpos(IO_PRIVHANDLE *phandle, uint64_t offset, int whence) {
|
|
int result;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_setpos);
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_setpos) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
result = phandle->fnptr->fn_setpos(phandle, offset, whence);
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* get current file position
|
|
*
|
|
* @param phandle device to get file position for
|
|
* @param pos position variable to fill
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_getpos(IO_PRIVHANDLE *phandle, uint64_t *pos) {
|
|
int result;
|
|
|
|
ASSERT(io_initialized); /* call io_init first */
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->open);
|
|
ASSERT(phandle->fnptr);
|
|
ASSERT(phandle->fnptr->fn_getpos);
|
|
|
|
if((!phandle) || (!phandle->open) || (!phandle->fnptr)) {
|
|
io_err(phandle,IO_E_NOTINIT);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle->fnptr->fn_getpos) {
|
|
io_err(phandle,IO_E_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
result = phandle->fnptr->fn_getpos(phandle, pos);
|
|
if(!result)
|
|
io_err(phandle,IO_E_OTHER);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* set up line buffering mode for the file handle
|
|
*
|
|
* @param phandle device to set line buffering up for
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_buffer(IO_PRIVHANDLE *phandle) {
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return FALSE;
|
|
|
|
if(phandle->buffer)
|
|
return TRUE;
|
|
|
|
phandle->buffer = (unsigned char*)malloc(IO_BUFFER_SIZE);
|
|
if(!phandle->buffer) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_buffer\n");
|
|
exit(-1);
|
|
}
|
|
|
|
phandle->buffering=1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* return the current error string for an io device
|
|
*
|
|
* @param phandle phandle to get error string for
|
|
* @returns error string. Only valid until next io call on this device
|
|
*/
|
|
char *io_errstr(IO_PRIVHANDLE *phandle) {
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->err_str);
|
|
|
|
if(!phandle)
|
|
return "Invalid io handle";
|
|
|
|
if(!phandle->err_str)
|
|
return "Unknown error";
|
|
|
|
return phandle->err_str;
|
|
}
|
|
|
|
/**
|
|
* return the current error code for an io device
|
|
*
|
|
* @param phandle phandle to get error code for
|
|
* @returns error code. Only valid until next io call on this device
|
|
*/
|
|
uint32_t io_errcode(IO_PRIVHANDLE *phandle) {
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return 0;
|
|
|
|
return phandle->err;
|
|
}
|
|
|
|
|
|
/**
|
|
* dispoase of an io handle
|
|
*
|
|
*/
|
|
void io_dispose(IO_PRIVHANDLE *phandle) {
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return;
|
|
|
|
if(phandle->open)
|
|
io_close(phandle);
|
|
|
|
io_option_dispose(phandle);
|
|
if(phandle->err_str)
|
|
free(phandle->err_str);
|
|
|
|
free(phandle);
|
|
}
|
|
|
|
/**
|
|
* set up an error code and error message
|
|
*
|
|
* @param phandle phandle to read from
|
|
* @param errorcode errorcode to return, or IO_E_OTHER if the
|
|
* errorcode should be set from the underlying provider
|
|
*/
|
|
void io_err(IO_PRIVHANDLE *phandle, uint32_t errorcode) {
|
|
ASSERT(phandle);
|
|
ASSERT(errorcode || phandle->fnptr);
|
|
|
|
if((!phandle) || (!errorcode && !phandle->fnptr))
|
|
return;
|
|
|
|
if(phandle->err_str)
|
|
free(phandle->err_str);
|
|
|
|
phandle->err = errorcode;
|
|
|
|
if(errorcode) {
|
|
phandle->err_str = strdup(io_err_strings[errorcode & 0x00FFFFFF]);
|
|
phandle->err = errorcode;
|
|
phandle->is_local = TRUE;
|
|
} else {
|
|
/* underlying provider must provide a free-able error message */
|
|
phandle->err_str = phandle->fnptr->fn_geterrmsg(phandle, &phandle->err, &phandle->is_local);
|
|
}
|
|
|
|
while((phandle->err_str[strlen(phandle->err_str) - 1] == '\n') ||
|
|
(phandle->err_str[strlen(phandle->err_str) -1] == '\r'))
|
|
phandle->err_str[strlen(phandle->err_str) -1] = '\0';
|
|
}
|
|
|
|
/**
|
|
* attach a file device to an existing native file representative
|
|
*
|
|
* @param phandle an existing phandle to attach to
|
|
* @param fd native file handle
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_attach(IO_PRIVHANDLE *phandle, FILE_T fd) {
|
|
IO_FILE_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(!io_assign_handle(phandle,"file")) {
|
|
return FALSE; /* error already set */
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*)malloc(sizeof(IO_FILE_PRIV));
|
|
if(!priv) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_file_attach\n");
|
|
return FALSE;
|
|
}
|
|
phandle->private = (void*)priv;
|
|
memset(priv,0x00,sizeof(IO_FILE_PRIV));
|
|
|
|
priv->fd = fd;
|
|
priv->opened = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* open an file device
|
|
*
|
|
* @param phandle handle of io device from io_new
|
|
* @param uri URI to open (minus the protocol)
|
|
* @param mode mode to open (O_RDWR, etc, as open(2))
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_open(IO_PRIVHANDLE *phandle, char *uri) {
|
|
IO_FILE_PRIV *priv;
|
|
char *mode;
|
|
char *permissions; /* octal */
|
|
uint32_t native_mode=0;
|
|
#ifdef WIN32
|
|
uint32_t native_permissions=0;
|
|
WCHAR *utf16_path; /* the real windows utf16 path */
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
if(!phandle) {
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*)malloc(sizeof(IO_FILE_PRIV));
|
|
if(!priv) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_file_attach\n");
|
|
return FALSE;
|
|
}
|
|
phandle->private = (void*)priv;
|
|
memset(priv,0x00,sizeof(IO_FILE_PRIV));
|
|
|
|
priv->open_flags = 0;
|
|
mode = io_option_get(phandle,"mode","r");
|
|
permissions = io_option_get(phandle,"permissions","0666");
|
|
|
|
if(strcmp(mode,"r")==0)
|
|
priv->open_flags = IO_FILE_READ;
|
|
if(strcmp(mode,"r+")==0)
|
|
priv->open_flags = IO_FILE_READ | IO_FILE_WRITE;
|
|
if(strcmp(mode,"w")==0)
|
|
priv->open_flags = IO_FILE_WRITE | IO_FILE_CREATE | IO_FILE_TRUNCATE;
|
|
if(strcmp(mode,"w+")==0)
|
|
priv->open_flags = IO_FILE_READ | IO_FILE_WRITE | IO_FILE_CREATE | IO_FILE_TRUNCATE;
|
|
if(strcmp(mode,"a")==0)
|
|
priv->open_flags = IO_FILE_WRITE | IO_FILE_APPEND | IO_FILE_CREATE;
|
|
if(strcmp(mode,"a+")==0)
|
|
priv->open_flags = IO_FILE_READ | IO_FILE_WRITE | IO_FILE_APPEND | IO_FILE_CREATE;
|
|
|
|
/* open the file natively, and get native file handle */
|
|
#ifdef WIN32
|
|
if(priv->open_flags & IO_FILE_READ)
|
|
native_permissions |= GENERIC_READ;
|
|
if(priv->open_flags & IO_FILE_WRITE)
|
|
native_permissions |= GENERIC_WRITE;
|
|
|
|
if(priv->open_flags & IO_FILE_CREATE)
|
|
native_mode |= OPEN_ALWAYS;
|
|
else
|
|
native_mode |= OPEN_EXISTING;
|
|
|
|
utf16_path = (WCHAR *)util_utf8toutf16_alloc(uri);
|
|
priv->fd = CreateFileW(utf16_path,native_permissions,FILE_SHARE_READ,NULL,
|
|
native_mode,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
free(utf16_path);
|
|
if(priv->fd == INVALID_HANDLE_VALUE) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
if(priv->open_flags & IO_FILE_TRUNCATE)
|
|
SetEndOfFile(priv->fd);
|
|
#else
|
|
if((priv->open_flags & IO_FILE_READ) && (priv->open_flags & IO_FILE_WRITE))
|
|
native_mode |= O_RDWR;
|
|
else {
|
|
if(priv->open_flags & IO_FILE_READ)
|
|
native_mode |= O_RDONLY;
|
|
if(priv->open_flags & IO_FILE_WRITE)
|
|
native_mode |= O_WRONLY;
|
|
}
|
|
if(priv->open_flags & IO_FILE_TRUNCATE)
|
|
native_mode |= O_TRUNC;
|
|
if(priv->open_flags & IO_FILE_APPEND)
|
|
native_mode |= O_APPEND;
|
|
if(priv->open_flags & IO_FILE_CREATE)
|
|
native_mode |= O_CREAT;
|
|
|
|
priv->fd = open(uri,native_mode,strtol(permissions,(char**)NULL,8));
|
|
if(priv->fd == -1) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
priv->opened = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* close an open device
|
|
*
|
|
* @param phandle handle of device to close
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_close(IO_PRIVHANDLE *phandle) {
|
|
IO_FILE_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_FILE_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
/* close the native file handle */
|
|
#ifdef WIN32
|
|
CloseHandle(priv->fd);
|
|
#else
|
|
close(priv->fd);
|
|
#endif
|
|
|
|
free(priv);
|
|
phandle->private = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* read from the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_file_read(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len) {
|
|
IO_FILE_PRIV *priv;
|
|
int result;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_FILE_NOTOPEN);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
/* read from native file handle */
|
|
#ifdef WIN32
|
|
result = (int) ReadFile(priv->fd,buf,*len,len,NULL);
|
|
if(!result) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
*len = 0;
|
|
result = FALSE;
|
|
} else {
|
|
result = TRUE;
|
|
}
|
|
#else
|
|
while(((result = read(priv->fd, buf, *len)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(result < 0) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
*len = 0;
|
|
result = FALSE;
|
|
} else {
|
|
*len = result;
|
|
result = TRUE;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* write to the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_file_write(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len) {
|
|
IO_FILE_PRIV *priv;
|
|
int result;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_FILE_NOTOPEN);
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
/* write to native file handle */
|
|
#ifdef WIN32
|
|
result = (int) WriteFile(priv->fd,buf,*len,len,NULL);
|
|
if(!result) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
*len = 0;
|
|
result = FALSE;
|
|
} else {
|
|
result = TRUE;
|
|
}
|
|
|
|
#else
|
|
while(((result = write(priv->fd, buf, *len)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(result < 0) {
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
*len = 0;
|
|
result = FALSE;
|
|
} else {
|
|
*len = result;
|
|
result = TRUE;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* get size (length) of io device
|
|
*
|
|
* @param phandle device to get size for
|
|
* @param size returns size of file (if successful)
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_size(IO_PRIVHANDLE *phandle, uint64_t *size) {
|
|
IO_FILE_PRIV *priv;
|
|
int result=FALSE;
|
|
#ifdef WIN32
|
|
LARGE_INTEGER liSize;
|
|
#else
|
|
uint64_t curpos;
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
*size = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
*size = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
result = GetFileSizeEx(priv->fd,&liSize);
|
|
*size = liSize.QuadPart;
|
|
#else
|
|
result = FALSE;
|
|
if(io_file_getpos(phandle,&curpos))
|
|
if(io_file_setpos(phandle,0,SEEK_END))
|
|
if(io_file_getpos(phandle,size))
|
|
if(io_file_setpos(phandle, curpos, SEEK_SET))
|
|
result = TRUE;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* set current file position
|
|
*
|
|
* @param phandle device to set position of
|
|
* @param offset offset to set
|
|
* @param whence from whence to set, as in fsetpos
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_setpos(IO_PRIVHANDLE *phandle, uint64_t offset, int whence) {
|
|
IO_FILE_PRIV *priv;
|
|
int result=FALSE;
|
|
#ifdef WIN32
|
|
int native_position;
|
|
LARGE_INTEGER liSize;
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
switch(whence) {
|
|
case SEEK_SET:
|
|
native_position = FILE_BEGIN;
|
|
break;
|
|
case SEEK_CUR:
|
|
native_position = FILE_CURRENT;
|
|
break;
|
|
case SEEK_END:
|
|
native_position = FILE_END;
|
|
break;
|
|
}
|
|
liSize.QuadPart = offset;
|
|
result = SetFilePointerEx(priv->fd,liSize,NULL,(DWORD)native_position);
|
|
#else
|
|
result = TRUE;
|
|
if(lseek(priv->fd, (off_t)offset, whence) == -1)
|
|
result = FALSE;
|
|
#endif /* WIN32 */
|
|
|
|
if(!result) {
|
|
io_file_seterr(phandle, IO_E_FILE_OTHER);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* get current file position
|
|
*
|
|
* @param phandle device to get file position for
|
|
* @param pos position variable to fill
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_file_getpos(IO_PRIVHANDLE *phandle, uint64_t *pos) {
|
|
IO_FILE_PRIV *priv;
|
|
int result=FALSE;
|
|
|
|
#ifdef WIN32
|
|
LARGE_INTEGER liPos;
|
|
LARGE_INTEGER liResult;
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
liPos.QuadPart = 0;
|
|
result = SetFilePointerEx(priv->fd,liPos,&liResult,FILE_CURRENT);
|
|
*pos = liResult.QuadPart;
|
|
#else
|
|
result = TRUE;
|
|
*pos = lseek(priv->fd, 0, SEEK_CUR);
|
|
if((*pos) == -1)
|
|
result = FALSE;
|
|
#endif
|
|
if(!result)
|
|
io_file_seterr(phandle,IO_E_FILE_OTHER);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* set the file error for a particular file object
|
|
* this should be a local error code, or IO_E_FILE_OTHER
|
|
* if it should come from the libc error (stderr, getlasterror), etc
|
|
*
|
|
* @param phandle file object to set error for
|
|
* @param errcode error code to set
|
|
*/
|
|
void io_file_seterr(IO_PRIVHANDLE *phandle, ERR_T errcode) {
|
|
IO_FILE_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return;
|
|
|
|
priv = (IO_FILE_PRIV*)(phandle->private);
|
|
|
|
if(errcode) {
|
|
priv->err = errcode;
|
|
priv->local_err = TRUE;
|
|
} else {
|
|
priv->err = errno;
|
|
priv->local_err = FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the local error message, whatever it is. This is only
|
|
* guaranteed to be around until the next io call. It may very
|
|
* well dissappear at that point.
|
|
*
|
|
* @param phandle device to get error message for
|
|
* @returns error message
|
|
*/
|
|
char *io_file_geterrmsg(IO_PRIVHANDLE *phandle, ERR_T *code, int *is_local) {
|
|
IO_FILE_PRIV *priv;
|
|
#ifdef WIN32
|
|
char lpErrorBuf[256];
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return NULL;
|
|
|
|
priv = (IO_FILE_PRIV*)(phandle->private);
|
|
ASSERT(priv);
|
|
|
|
if(!priv) {
|
|
return "file not initialized";
|
|
}
|
|
|
|
if(code)
|
|
*code = priv->err;
|
|
|
|
if(priv->local_err) {
|
|
if(is_local)
|
|
*is_local = TRUE;
|
|
return strdup(io_file_err_strings[priv->err & 0x00FFFFFF]);
|
|
} else {
|
|
if(is_local)
|
|
*is_local=FALSE;
|
|
#ifdef WIN32
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,priv->err,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
|
|
(LPTSTR)lpErrorBuf,sizeof(lpErrorBuf),NULL);
|
|
return strdup(lpErrorBuf);
|
|
#else
|
|
return strdup(strerror(priv->err));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the waitable dingus for the io object. This iwll be a
|
|
* selectable fd for unix, or a HANDLE for win32
|
|
*
|
|
* @param phandle io device to get waitable object for
|
|
* @returns waitable object
|
|
*/
|
|
int io_file_getwaitable(IO_PRIVHANDLE *phandle, int mode, WAITABLE_T *retval) {
|
|
IO_FILE_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
ASSERT(retval);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(!retval) {
|
|
io_file_seterr(phandle,IO_E_FILE_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_FILE_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
*retval = priv->fd;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* return the underlying file handle for a file type object
|
|
*/
|
|
int io_file_getfd(IO_PRIVHANDLE *phandle, FILE_T *pfd) {
|
|
IO_FILE_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(!pfd) {
|
|
io_file_seterr(phandle,IO_E_FILE_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_FILE_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_FILE_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
*pfd = priv->fd;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* attach a socket device to an existing native file representative
|
|
*
|
|
* @param phandle an existing phandle to attach to
|
|
* @param fd native file handle
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_attach(IO_PRIVHANDLE *phandle, SOCKET_T fd) {
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(!io_assign_handle(phandle,"socket")) {
|
|
return FALSE; /* error already set */
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)malloc(sizeof(IO_SOCKET_PRIV));
|
|
if(!priv) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_socket_attach\n");
|
|
return FALSE;
|
|
}
|
|
phandle->private = (void*)priv;
|
|
memset(priv,0x00,sizeof(IO_SOCKET_PRIV));
|
|
|
|
priv->fd = fd;
|
|
priv->opened = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* open an socket device
|
|
*
|
|
* @param phandle handle of io device from io_new
|
|
* @param uri URI to open (minus the protocol)
|
|
* @param mode mode to open (O_RDWR, etc, as open(2))
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_open(IO_PRIVHANDLE *phandle, char *uri) {
|
|
char *port_part;
|
|
char *server_part;
|
|
struct hostent *phe;
|
|
unsigned short int port = 80;
|
|
int retval;
|
|
int is_udp;
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
is_udp = io_isproto(phandle,"udp");
|
|
|
|
priv = (IO_SOCKET_PRIV*)malloc(sizeof(IO_SOCKET_PRIV));
|
|
if(!priv) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_socket_attach\n");
|
|
return FALSE;
|
|
}
|
|
phandle->private = (void*)priv;
|
|
memset(priv,0x00,sizeof(IO_SOCKET_PRIV));
|
|
|
|
/* the uri should be server:port */
|
|
server_part = uri;
|
|
port_part = strchr(server_part,':');
|
|
if(port_part) {
|
|
*port_part = '\0';
|
|
port_part++;
|
|
port = atoi(port_part);
|
|
}
|
|
|
|
ASSERT(port_part);
|
|
|
|
if(inet_addr(server_part) == INADDR_NONE) {
|
|
phe=gethostbyname(server_part);
|
|
if(phe == NULL) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_BADHOST);
|
|
return FALSE;
|
|
}
|
|
memcpy((char*)&priv->si_remote.sin_addr,(char*)(phe->h_addr_list[0]),
|
|
sizeof(struct in_addr));
|
|
} else {
|
|
priv->si_remote.sin_addr.s_addr = inet_addr(server_part);
|
|
}
|
|
|
|
priv->si_remote.sin_port = htons((short)port);
|
|
priv->si_remote.sin_family = AF_INET;
|
|
|
|
if(is_udp) {
|
|
priv->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
} else {
|
|
priv->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
}
|
|
if(priv->fd == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
if(is_udp) {
|
|
priv->opened = TRUE;
|
|
priv->is_udp = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* do the connect */
|
|
while(1) {
|
|
retval = connect(priv->fd,(struct sockaddr *)&priv->si_remote,
|
|
sizeof(priv->si_remote));
|
|
if((retval != -1) || (errno != EINTR))
|
|
break;
|
|
}
|
|
|
|
if(retval == -1) {
|
|
closesocket(priv->fd);
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
priv->opened = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* open a listening socket device
|
|
*
|
|
* @param phandle handle of io device from io_new
|
|
* @param uri URI to open (minus the protocol)
|
|
* @param flags (as open(2) - ignored)
|
|
* @param mode mode to open (as open(2) - ignored)
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_listen_open(IO_PRIVHANDLE *phandle, char *uri) {
|
|
IO_SOCKET_PRIV *priv;
|
|
struct sockaddr_in server;
|
|
unsigned short port;
|
|
int retval;
|
|
int opt;
|
|
int backlog;
|
|
int reuse;
|
|
int multicast=0;
|
|
struct ip_mreq mreq;
|
|
char *mcast_group;
|
|
|
|
ASSERT(phandle && uri);
|
|
|
|
io_err_printf(IO_LOG_DEBUG, "Doing io_listen_open\n");
|
|
|
|
if((!phandle) || (!uri)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)malloc(sizeof(IO_SOCKET_PRIV));
|
|
if(!priv) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_listen_open\n");
|
|
return FALSE;
|
|
}
|
|
|
|
phandle->private=(void*)priv;
|
|
memset(priv,0x0,sizeof(IO_SOCKET_PRIV));
|
|
|
|
if(io_isproto(phandle, "udplisten")) {
|
|
priv->is_udp = 1;
|
|
}
|
|
|
|
/* read options, get defaults */
|
|
backlog = atoi(io_option_get(phandle,"backlog","5"));
|
|
reuse = atoi(io_option_get(phandle,"reuseaddr","1"));
|
|
|
|
/* the uri should be simply the port number */
|
|
port = atoi(uri);
|
|
|
|
if(priv->is_udp) {
|
|
/* set up socket for multicast, if required */
|
|
multicast = atoi(io_option_get(phandle,"multicast","0"));
|
|
|
|
priv->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if((priv->fd != -1) && (multicast)) {
|
|
opt = atoi(io_option_get(phandle,"mcast_ttl","4"));
|
|
setsockopt(priv->fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&opt,
|
|
sizeof(opt));
|
|
mcast_group = io_option_get(phandle,"mcast_group",NULL);
|
|
if(!mcast_group) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOMCAST);
|
|
while((closesocket(priv->fd) == -1) && (errno == EINTR));
|
|
return FALSE;
|
|
} else {
|
|
mreq.imr_multiaddr.s_addr = inet_addr(mcast_group);
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
if(setsockopt(priv->fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mreq,
|
|
sizeof(mreq)) < 0) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
while((closesocket(priv->fd) == -1) && (errno == EINTR));
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
priv->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
}
|
|
|
|
if(priv->fd == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
io_err_printf(IO_LOG_DEBUG,"Couldn't open socket\n");
|
|
return FALSE;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Socket opened\n");
|
|
opt = reuse; /* SO_REUSEADDR */
|
|
if(setsockopt(priv->fd,SOL_SOCKET, SO_REUSEADDR,(char*)&opt,
|
|
sizeof(opt)) == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
io_err_printf(IO_LOG_DEBUG,"Error setting SO_REUSEADDR\n");
|
|
while((closesocket(priv->fd) == -1) && (errno == EINTR));
|
|
return FALSE;
|
|
}
|
|
|
|
/* bind and listen */
|
|
server.sin_family = AF_INET;
|
|
server.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
server.sin_port = htons((short)port);
|
|
|
|
while(((retval =
|
|
bind(priv->fd,(struct sockaddr *)&server, sizeof(server))) == -1)
|
|
&& (errno == EINTR));
|
|
|
|
if(retval == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
io_err_printf(IO_LOG_DEBUG,"Error binding socket\n");
|
|
while((closesocket(priv->fd) == -1) && (errno == EINTR));
|
|
return FALSE;
|
|
}
|
|
|
|
if(priv->is_udp) {
|
|
priv->opened = TRUE;
|
|
io_err_printf(IO_LOG_DEBUG,"Set up UDP listener successfully\n");
|
|
return TRUE; /* don't need to listen */
|
|
}
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Preparing to listen with %d backlog on %d\n",
|
|
backlog, port);
|
|
while(((retval = listen(priv->fd,backlog)) == -1) && (errno == EINTR));
|
|
if(retval == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
while((closesocket(priv->fd) == -1) && (errno == EINTR));
|
|
return FALSE;
|
|
}
|
|
|
|
priv->opened = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* accept from a listening socket
|
|
*
|
|
* @param phandle handle to accept
|
|
* @param pchild handle to initialize with child socket
|
|
* @param host network address of remote host
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_listen_accept(IO_PRIVHANDLE *phandle, IO_PRIVHANDLE *pchild,
|
|
struct in_addr *host) {
|
|
socklen_t len = sizeof(struct sockaddr);
|
|
struct sockaddr_in client;
|
|
SOCKET_T child_fd;
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(pchild);
|
|
|
|
if(!phandle)
|
|
return FALSE;
|
|
|
|
if(!pchild) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTINIT);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!io_isproto(phandle,"listen")) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV *)phandle->private;
|
|
|
|
while(((child_fd =
|
|
accept(priv->fd,(struct sockaddr *)(&client),&len)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(child_fd == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Got listen socket %d\n",child_fd);
|
|
|
|
/* copy host, if passed a buffer */
|
|
if(host)
|
|
*host = client.sin_addr;
|
|
|
|
/* FIXME: should return error in parent handle, not child */
|
|
return io_socket_attach(pchild, child_fd);
|
|
}
|
|
|
|
/**
|
|
* close an open socket device
|
|
*
|
|
* @param phandle handle of device to close
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_close(IO_PRIVHANDLE *phandle) {
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle)||(!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
shutdown(priv->fd,SHUT_RDWR);
|
|
closesocket(priv->fd);
|
|
|
|
#ifdef WIN32
|
|
if(priv->hEvent) {
|
|
WSACloseEvent(priv->hEvent);
|
|
priv->hEvent = NULL;
|
|
}
|
|
#endif
|
|
|
|
free(priv);
|
|
phandle->private = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* read from the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_socket_read(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len) {
|
|
ssize_t bytes_read;
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle)||(!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
while(((bytes_read = recv(priv->fd, buf, *len, 0)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(bytes_read == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
*len = bytes_read;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* write to the io device, using the underlying provider
|
|
*
|
|
* @param phandle handle of io device
|
|
* @param buf buffer to read into
|
|
* @param len size of buffer
|
|
* @returns number of bytes read, 0 on EOF, or -1 on error
|
|
*/
|
|
int io_socket_write(IO_PRIVHANDLE *phandle, unsigned char *buf,uint32_t *len) {
|
|
IO_SOCKET_PRIV *priv;
|
|
int slen;
|
|
|
|
uint32_t bytestowrite;
|
|
ssize_t byteswritten=0;
|
|
uint32_t totalbytes;
|
|
unsigned char *bufp;
|
|
long blocking = 0;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle)||(!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
for(bufp = buf, bytestowrite = *len, totalbytes=0;
|
|
bytestowrite > 0;
|
|
bufp += byteswritten, bytestowrite -= byteswritten) {
|
|
if(priv->is_udp) { /* must be sending to */
|
|
slen = sizeof(struct sockaddr_in);
|
|
byteswritten = sendto(priv->fd, bufp, bytestowrite, 0,
|
|
(struct sockaddr *)&priv->si_remote, slen);
|
|
} else {
|
|
byteswritten = send(priv->fd, bufp, bytestowrite, 0);
|
|
}
|
|
|
|
io_err_printf(IO_LOG_SPAM,"wrote %d bytes to socket %d\n",byteswritten,priv->fd);
|
|
|
|
#ifdef WIN32
|
|
if(WSAGetLastError() == WSAEWOULDBLOCK) {
|
|
byteswritten = 0;
|
|
|
|
if(priv->hEvent) {
|
|
WSAEventSelect(priv->fd,(WSAEVENT)priv->hEvent,0);
|
|
}
|
|
|
|
blocking = 0;
|
|
if(ioctlsocket(priv->fd,FIONBIO,&blocking)) {
|
|
io_err_printf(IO_LOG_LOG,"Couldn't set socket to blocking: %ld\n",WSAGetLastError());
|
|
}
|
|
}
|
|
#endif
|
|
if((byteswritten == -1 ) && (errno != EINTR)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
*len = totalbytes;
|
|
return FALSE;
|
|
}
|
|
|
|
if(byteswritten == -1)
|
|
byteswritten = 0;
|
|
totalbytes += byteswritten;
|
|
}
|
|
|
|
*len = totalbytes;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* get size (length) of io device
|
|
*
|
|
* @param phandle device to get size for
|
|
* @param size returns size of file (if successful)
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_size(IO_PRIVHANDLE *phandle, uint64_t *size) {
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* set current file position
|
|
*
|
|
* @param phandle device to set position of
|
|
* @param offset offset to set
|
|
* @param whence from whence to set, as in fsetpos
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_setpos(IO_PRIVHANDLE *phandle, uint64_t offset, int whence) {
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* get current file position
|
|
*
|
|
* @param phandle device to get file position for
|
|
* @param pos position variable to fill
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_socket_getpos(IO_PRIVHANDLE *phandle, uint64_t *pos) {
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* set the file error for a particular file object
|
|
* this should be a local error code, or IO_E_SOCKET_OTHER
|
|
* if it should come from the libc error (stderr, getlasterror), etc
|
|
*
|
|
* @param phandle file object to set error for
|
|
* @param errcode error code to set
|
|
*/
|
|
void io_socket_seterr(IO_PRIVHANDLE *phandle, ERR_T errcode) {
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return;
|
|
|
|
priv = (IO_SOCKET_PRIV*)(phandle->private);
|
|
|
|
if(errcode) {
|
|
priv->err = errcode;
|
|
priv->local_err = TRUE;
|
|
} else {
|
|
priv->local_err = FALSE;
|
|
#ifdef WIN32
|
|
priv->err = WSAGetLastError();
|
|
/* map error codes to exported errors */
|
|
if(priv->err == WSAEADDRINUSE) {
|
|
priv->err = IO_E_SOCKET_INUSE;
|
|
priv->local_err = TRUE;
|
|
}
|
|
#else
|
|
priv->err = errno;
|
|
/* map error codes to exported errors */
|
|
if(priv->err == EADDRINUSE) {
|
|
priv->err = IO_E_SOCKET_INUSE;
|
|
priv->local_err = TRUE;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the local error message, whatever it is. This is only
|
|
* guaranteed to be around until the next io call. It may very
|
|
* well dissappear at that point.
|
|
*
|
|
* @param phandle device to get error message for
|
|
* @returns error message
|
|
*/
|
|
char *io_socket_geterrmsg(IO_PRIVHANDLE *phandle, ERR_T *code, int *is_local) {
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
#ifdef WIN32
|
|
char lpErrorBuf[256];
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
|
|
priv = (IO_SOCKET_PRIV*)(phandle->private);
|
|
if(*code)
|
|
*code = priv->err;
|
|
|
|
if(priv->local_err) {
|
|
*is_local = TRUE;
|
|
return strdup(io_socket_err_strings[priv->err & 0x00FFFFFF]);
|
|
} else {
|
|
*is_local = FALSE;
|
|
#ifdef WIN32
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,priv->err,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
|
|
(LPTSTR)lpErrorBuf,sizeof(lpErrorBuf),NULL);
|
|
return strdup(lpErrorBuf);
|
|
|
|
#else
|
|
return strdup(strerror(priv->err));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the waitable dingus for the io object. This iwll be a
|
|
* selectable fd for unix, or a HANDLE for win32
|
|
*
|
|
* @param phandle io device to get waitable object for
|
|
* @returns waitable object
|
|
*/
|
|
int io_socket_getwaitable(IO_PRIVHANDLE *phandle, int mode, WAITABLE_T *retval) {
|
|
IO_SOCKET_PRIV *priv;
|
|
#ifdef WIN32
|
|
long lEvents=0;
|
|
#endif
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
ASSERT(retval);
|
|
ASSERT(mode);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!retval) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!mode) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_INVALID);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
io_err_printf(IO_LOG_SPAM,"Building synthesized event for socket\n");
|
|
if(priv->hEvent) {
|
|
if(mode == priv->wait_mode) {
|
|
*retval = priv->hEvent;
|
|
return TRUE;
|
|
} else {
|
|
WSACloseEvent(priv->hEvent);
|
|
priv->hEvent = NULL;
|
|
}
|
|
}
|
|
|
|
priv->hEvent = (WAITABLE_T)WSACreateEvent();
|
|
if(!priv->hEvent) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
if(mode & IO_WAIT_READ)
|
|
lEvents = FD_READ | FD_OOB | FD_ACCEPT | FD_CLOSE;
|
|
if(mode & IO_WAIT_WRITE)
|
|
lEvents |= FD_WRITE | FD_CLOSE;
|
|
if(mode & IO_WAIT_ERROR)
|
|
lEvents |= FD_CLOSE;
|
|
|
|
if(WSAEventSelect(priv->fd,(WSAEVENT)priv->hEvent,lEvents) == SOCKET_ERROR) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
*retval = priv->hEvent;
|
|
#else
|
|
*retval = priv->fd;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* return the underlying socket handle for a socket type object
|
|
*/
|
|
int io_socket_getsocket(IO_PRIVHANDLE *phandle, SOCKET_T *psock) {
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle) || (!phandle->private)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(!psock) {
|
|
io_file_seterr(phandle,IO_E_SOCKET_UNKNOWN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*) phandle->private;
|
|
ASSERT(priv->opened);
|
|
|
|
if(!priv->opened) {
|
|
io_file_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
*psock = priv->fd;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* udp function, similar to recvfrom(2)
|
|
*
|
|
* @param phandle io device (of type "udp" or "udplisten") to recv from
|
|
* @param buf buffer to read into
|
|
* @param len length of buffer, returns bytes read
|
|
* @param si_remote address of remote endpoint
|
|
* @param si_len address length
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_udp_recvfrom(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len,
|
|
struct sockaddr_in *si_remote, socklen_t *si_len) {
|
|
ssize_t bytes_read;
|
|
IO_SOCKET_PRIV *priv;
|
|
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle)||(!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)phandle->private;
|
|
ASSERT(priv->opened);
|
|
ASSERT(priv->is_udp);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!priv->is_udp) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
while(((bytes_read = recvfrom(priv->fd, buf, *len, 0,
|
|
(struct sockaddr *)si_remote,
|
|
si_len)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(bytes_read == -1) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
|
|
*len = bytes_read;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* udp function, similar to sendto(2)
|
|
*
|
|
* @param phandle io device (of type "udp" or "udplisten") to recv from
|
|
* @param buf buffer holding data to write
|
|
* @param len length of buffer, returns bytes written
|
|
* @param si_remote address of remote endpoint
|
|
* @param si_len remote address length
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_udp_sendto(IO_PRIVHANDLE *phandle, unsigned char *buf, uint32_t *len,
|
|
struct sockaddr_in *si_remote, socklen_t si_len) {
|
|
IO_SOCKET_PRIV *priv;
|
|
uint32_t bytestowrite;
|
|
ssize_t byteswritten;
|
|
uint32_t totalbytes;
|
|
unsigned char *bufp;
|
|
|
|
ASSERT(phandle);
|
|
ASSERT(phandle->private);
|
|
|
|
if((!phandle)||(!phandle->private)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = (IO_SOCKET_PRIV*)phandle->private;
|
|
ASSERT(priv->opened);
|
|
ASSERT(priv->is_udp);
|
|
|
|
if(!priv->opened) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_NOTOPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
if(!priv->is_udp) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_BADFN);
|
|
return FALSE;
|
|
}
|
|
|
|
for(bufp = buf, bytestowrite = *len, totalbytes=0;
|
|
bytestowrite > 0;
|
|
bufp += byteswritten, bytestowrite -= byteswritten) {
|
|
byteswritten = sendto(priv->fd, buf, *len, 0,
|
|
(struct sockaddr *)si_remote, si_len);
|
|
|
|
if((byteswritten == -1 ) && (errno != EINTR)) {
|
|
io_socket_seterr(phandle,IO_E_SOCKET_OTHER);
|
|
return FALSE;
|
|
}
|
|
if(byteswritten == -1)
|
|
byteswritten = 0;
|
|
totalbytes += byteswritten;
|
|
}
|
|
|
|
*len = totalbytes;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* dispose of an optionlist
|
|
*
|
|
* @param phandle handle for which to dispose optionlist
|
|
*/
|
|
int io_option_dispose(IO_PRIVHANDLE *phandle) {
|
|
IO_OPTIONLIST *pcurrent;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle)
|
|
return TRUE;
|
|
|
|
io_lock();
|
|
while(phandle->pol) {
|
|
pcurrent = phandle->pol;
|
|
if(pcurrent->key) free(pcurrent->key);
|
|
if(pcurrent->value) free(pcurrent->value);
|
|
phandle->pol = pcurrent->next;
|
|
free(pcurrent);
|
|
}
|
|
io_unlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* add an option to an option list
|
|
*
|
|
* @param key option to add
|
|
* @param value value to set option to
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_option_add(IO_PRIVHANDLE *phandle, char *key, char *value) {
|
|
IO_OPTIONLIST *pnew;
|
|
|
|
ASSERT(phandle);
|
|
if(!phandle) return FALSE;
|
|
|
|
pnew = (IO_OPTIONLIST*)malloc(sizeof(IO_OPTIONLIST));
|
|
if(!pnew) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_option_add\n");
|
|
return FALSE;
|
|
}
|
|
|
|
pnew->key = strdup(key);
|
|
pnew->value = strdup(value);
|
|
|
|
if((!pnew->key) || (!pnew->value)) {
|
|
io_err_printf(IO_LOG_FATAL,"Malloc error in io_option_add\n");
|
|
if(pnew->key) free(pnew->key);
|
|
if(pnew->value) free(pnew->value);
|
|
free(pnew);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* this could (should?) be a per-handle lock */
|
|
io_lock();
|
|
pnew->next = phandle->pol;
|
|
phandle->pol = pnew;
|
|
io_unlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fetch an option for a specific handle
|
|
*
|
|
* @param key option to add
|
|
* @param value value to set option to
|
|
* @returns TRUE on success
|
|
*/
|
|
char* io_option_get(IO_PRIVHANDLE *phandle,char *option,char *dflt) {
|
|
IO_OPTIONLIST *pcurrent;
|
|
|
|
ASSERT(phandle);
|
|
|
|
if(!phandle) return dflt;
|
|
|
|
io_lock();
|
|
pcurrent = phandle->pol;
|
|
while(pcurrent && (strcasecmp(pcurrent->key,option) != 0))
|
|
pcurrent = pcurrent->next;
|
|
io_unlock();
|
|
|
|
if(!pcurrent)
|
|
return dflt;
|
|
|
|
return pcurrent->value;
|
|
}
|
|
|
|
/**
|
|
* see if an io device is a specific proto (mostly for proto-specific
|
|
* functions, like io_listen_accept, or io_udp_sendto
|
|
*/
|
|
int io_isproto(IO_PRIVHANDLE *phandle, char *proto) {
|
|
ASSERT(phandle && phandle->proto);
|
|
|
|
if(!phandle || !phandle->proto)
|
|
return FALSE;
|
|
|
|
if(strcasecmp(phandle->proto, proto) == 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* make a new wait object
|
|
*
|
|
* @returns new wait object
|
|
*/
|
|
IO_WAITHANDLE *io_wait_new(void) {
|
|
IO_WAITHANDLE *pnew;
|
|
|
|
pnew = (IO_WAITHANDLE*)malloc(sizeof(IO_WAITHANDLE));
|
|
if(!pnew) {
|
|
io_err_printf(IO_LOG_FATAL,"malloc error in io_wait_new\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset(pnew,0x00,sizeof(IO_WAITHANDLE));
|
|
|
|
#ifdef WIN32
|
|
pnew->hWaitItems = (WAITABLE_T *)malloc(sizeof(WAITABLE_T) * IO_HANDLES_START);
|
|
if(!pnew->hWaitItems) {
|
|
free(pnew);
|
|
return NULL;
|
|
}
|
|
pnew->ppHandle = (IO_PRIVHANDLE **)malloc(sizeof(IO_PRIVHANDLE*) * IO_HANDLES_START);
|
|
if(!pnew->ppHandle) {
|
|
free(pnew->hWaitItems);
|
|
free(pnew);
|
|
return NULL;
|
|
}
|
|
|
|
pnew->dwItemCount = 0;
|
|
pnew->dwMaxItems = IO_HANDLES_START;
|
|
#else
|
|
FD_ZERO(&pnew->read_fds);
|
|
FD_ZERO(&pnew->write_fds);
|
|
FD_ZERO(&pnew->err_fds);
|
|
#endif
|
|
|
|
return pnew;
|
|
}
|
|
|
|
/**
|
|
* Add a io device to be waited on.
|
|
*
|
|
* @param pwait wait object to add the io device to
|
|
* @param phandle io device to add
|
|
* @param type action to wait for (IO_WAIT_READ/WRITE/ERROR)
|
|
* @returns TRUE if device was successfully added
|
|
*/
|
|
int io_wait_add(IO_WAITHANDLE *pwait, IO_PRIVHANDLE *phandle, int type) {
|
|
WAITABLE_T waitable;
|
|
#ifdef WIN32
|
|
void *ptmp;
|
|
#endif
|
|
|
|
ASSERT(pwait);
|
|
ASSERT(phandle && phandle->open && phandle->fnptr &&
|
|
phandle->fnptr->fn_getwaitable);
|
|
|
|
if(!pwait) {
|
|
io_err_printf(IO_LOG_WARN,"io_wait_add: bad IO_WAITHANDLE\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if(!phandle || !phandle->open || !phandle->fnptr ||
|
|
!phandle->fnptr->fn_getwaitable) {
|
|
io_err_printf(IO_LOG_WARN,"io_wait_add: impossible to get waitable\n");
|
|
return FALSE;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Getting io waitable of type %d\n",type);
|
|
if(!phandle->fnptr->fn_getwaitable(phandle, type, &waitable)) {
|
|
io_err_printf(IO_LOG_WARN,"io_wait_add: error getting waitable\n");
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
ASSERT(pwait->dwItemCount <= pwait->dwMaxItems);
|
|
|
|
while (pwait->dwMaxItems <= pwait->dwItemCount) { /* must expand our handle list */
|
|
ptmp = realloc(pwait->hWaitItems,sizeof(WAITABLE_T) * (pwait->dwMaxItems + IO_HANDLES_GROW));
|
|
if(!ptmp) {
|
|
return FALSE;
|
|
}
|
|
pwait->hWaitItems = ptmp;
|
|
ptmp = realloc(pwait->ppHandle,sizeof(IO_PRIVHANDLE *) * (pwait->dwMaxItems + IO_HANDLES_GROW));
|
|
if(!ptmp) {
|
|
return FALSE;
|
|
}
|
|
pwait->ppHandle = ptmp;
|
|
pwait->dwMaxItems += IO_HANDLES_GROW;
|
|
}
|
|
|
|
pwait->hWaitItems[pwait->dwItemCount] = waitable;
|
|
pwait->ppHandle[pwait->dwItemCount] = phandle;
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Added event %08X with pHandle %08X as %d\n",
|
|
waitable,phandle,pwait->dwItemCount);
|
|
|
|
pwait->dwItemCount++;
|
|
#else
|
|
if(waitable > pwait->max_fd)
|
|
pwait->max_fd = waitable;
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Adding %d to waitlist\n",waitable);
|
|
|
|
if(type & IO_WAIT_READ)
|
|
FD_SET(waitable,&pwait->read_fds);
|
|
if(type & IO_WAIT_WRITE)
|
|
FD_SET(waitable,&pwait->write_fds);
|
|
if(type & IO_WAIT_ERROR)
|
|
FD_SET(waitable,&pwait->err_fds);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Actually wait on the objects currently added. Returns time
|
|
* _left_ in the wait if successful.
|
|
n *
|
|
* @param pwait wait device to wait on
|
|
* @param ms milliseconds to wait
|
|
* @returns TRUE on success, FALSE and ms=0 on timeout, FALSE otherwise
|
|
*/
|
|
int io_wait(IO_WAITHANDLE *pwait, uint32_t *ms) {
|
|
struct timeval timeout;
|
|
struct timeval start_time, end_time;
|
|
uint32_t elapsed_ms;
|
|
uint32_t wait_ms;
|
|
#ifdef WIN32
|
|
SOCKET_T sock;
|
|
#endif
|
|
int retval=0;
|
|
|
|
ASSERT(pwait);
|
|
|
|
if(!pwait)
|
|
return FALSE;
|
|
|
|
wait_ms = *ms;
|
|
timeout.tv_sec = wait_ms/1000;
|
|
if(!timeout.tv_sec)
|
|
timeout.tv_sec++;
|
|
timeout.tv_usec = 0;
|
|
|
|
gettimeofday(&start_time,NULL);
|
|
#ifdef WIN32
|
|
ASSERT(pwait->dwItemCount);
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Waiting on %d items for %d sec\n",
|
|
pwait->dwItemCount,timeout.tv_sec);
|
|
|
|
while(1) {
|
|
pwait->dwLastResult = WaitForMultipleObjects(pwait->dwItemCount,pwait->hWaitItems,FALSE,*ms);
|
|
if((pwait->dwLastResult == WAIT_FAILED) || (pwait->dwLastResult == WAIT_TIMEOUT))
|
|
break;
|
|
|
|
pwait->dwWhichEvent = pwait->dwLastResult - WAIT_OBJECT_0;
|
|
if(pwait->dwWhichEvent >= pwait->dwItemCount) { /* error or something */
|
|
pwait->dwWhichEvent = pwait->dwLastResult - WAIT_ABANDONED_0;
|
|
ASSERT(pwait->dwWhichEvent < pwait->dwItemCount);
|
|
if(pwait->dwWhichEvent < pwait->dwItemCount)
|
|
return FALSE;
|
|
break;
|
|
}
|
|
|
|
/* Was definitely a WAIT_OBJECT_x. Make sure it's not spurious */
|
|
io_err_printf(IO_LOG_SPAM,"Got wait on index %d\n",pwait->dwWhichEvent);
|
|
if(!pwait->ppHandle[pwait->dwWhichEvent]->fnptr->fn_getsocket) { /* not a socket, must be a file */
|
|
/* dummy up a result */
|
|
pwait->wsaNetworkEvents.lNetworkEvents = FD_READ;
|
|
break;
|
|
}
|
|
|
|
if(!pwait->ppHandle[pwait->dwWhichEvent]->fnptr->fn_getsocket(pwait->ppHandle[pwait->dwWhichEvent],&sock)) {
|
|
io_err_printf(IO_LOG_SPAM,"Could not get socket handle\n");
|
|
return FALSE;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Getting event details for wait object\n");
|
|
WSAEnumNetworkEvents(sock,pwait->hWaitItems[pwait->dwWhichEvent],&pwait->wsaNetworkEvents);
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents != 0) {
|
|
io_err_printf(IO_LOG_SPAM,"Got %ld\n",pwait->wsaNetworkEvents.lNetworkEvents);
|
|
break;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_SPAM,"Skipping spurious wakeup\n");
|
|
}
|
|
|
|
if(pwait->dwLastResult == WAIT_FAILED)
|
|
return FALSE;
|
|
|
|
/* on timeout, return FALSE, with ms=0 */
|
|
if(WAIT_TIMEOUT == pwait->dwLastResult) {
|
|
*ms = 0;
|
|
return FALSE;
|
|
}
|
|
#else
|
|
ASSERT(pwait->max_fd);
|
|
|
|
memcpy(&pwait->result_read, &pwait->read_fds, sizeof(pwait->read_fds));
|
|
memcpy(&pwait->result_write, &pwait->write_fds, sizeof(pwait->write_fds));
|
|
memcpy(&pwait->result_err, &pwait->err_fds, sizeof(pwait->err_fds));
|
|
|
|
if(!pwait->max_fd) {
|
|
io_err_printf(IO_LOG_WARN,"No fds being monitored in io_wait\n");
|
|
return FALSE;
|
|
}
|
|
|
|
io_err_printf(IO_LOG_SPAM,"selecting on %d nfds, for %d.%d sec\n",
|
|
pwait->max_fd+1,timeout.tv_sec,timeout.tv_usec);
|
|
|
|
while(((retval = select(pwait->max_fd+1,&pwait->result_read,
|
|
&pwait->result_write, &pwait->result_err,
|
|
&timeout)) == -1) &&
|
|
(errno == EINTR));
|
|
|
|
if(retval == -1) {
|
|
io_err_printf(IO_LOG_WARN,"Error in select: %s\n",strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if(retval == 0) {
|
|
io_err_printf(IO_LOG_WARN,"timeout in select\n");
|
|
*ms = 0;
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
gettimeofday(&end_time,NULL);
|
|
elapsed_ms = ((end_time.tv_sec - start_time.tv_sec) * 1000) +
|
|
((end_time.tv_usec - start_time.tv_usec)/1000);
|
|
if(elapsed_ms > wait_ms)
|
|
*ms = 0;
|
|
else
|
|
*ms -= elapsed_ms;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* return a flagset for what events were signalled by an io handle
|
|
*
|
|
* @param pwait wait object that was just io_wait'ed
|
|
* @param phandle io device to get flagset for
|
|
* @returns flagset (IO_WAIT_READ/WRITE/ERROR)
|
|
*/
|
|
int io_wait_status(IO_WAITHANDLE *pwait, IO_PRIVHANDLE *phandle) {
|
|
int retval = 0;
|
|
|
|
#ifndef WIN32
|
|
WAITABLE_T waitable;
|
|
#endif
|
|
|
|
ASSERT(pwait);
|
|
|
|
if(!pwait) {
|
|
io_err_printf(IO_LOG_WARN,"io_wait_status: invalid IO_WAITHANDLE\n");
|
|
return 0;
|
|
}
|
|
|
|
ASSERT(phandle && phandle->open && phandle->fnptr &&
|
|
phandle->fnptr->fn_getwaitable);
|
|
|
|
if(!phandle || !phandle->open || !phandle->fnptr ||
|
|
!phandle->fnptr->fn_getwaitable) {
|
|
io_err_printf(IO_LOG_WARN,"io_wait_status: can't get WAITABLE_T for "
|
|
"io device.\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
io_err_printf(IO_LOG_DEBUG,"lnetwork: %08x\n",pwait->wsaNetworkEvents.lNetworkEvents);
|
|
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents == FD_READ) {
|
|
retval |= IO_WAIT_READ;
|
|
io_err_printf(IO_LOG_DEBUG,"Set: FD_READ\n");
|
|
}
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents == FD_ACCEPT) {
|
|
retval |= IO_WAIT_READ;
|
|
io_err_printf(IO_LOG_DEBUG,"Set: FD_ACCEPT\n");
|
|
}
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents == FD_OOB) {
|
|
retval |= IO_WAIT_READ;
|
|
io_err_printf(IO_LOG_DEBUG,"Set: FD_OOB\n");
|
|
}
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents == FD_WRITE) {
|
|
retval |= IO_WAIT_WRITE;
|
|
io_err_printf(IO_LOG_DEBUG,"Set: FD_WRITE\n");
|
|
}
|
|
if(pwait->wsaNetworkEvents.lNetworkEvents == FD_CLOSE) {
|
|
retval |= IO_WAIT_READ | IO_WAIT_ERROR | IO_WAIT_WRITE;
|
|
io_err_printf(IO_LOG_DEBUG,"Set: FD_CLOSE\n");
|
|
}
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Wait status: %d\n",retval);
|
|
/*
|
|
if(wsaNetworkEvents.iErrorCode[FD_READ_BIT])
|
|
retval |= IO_WAIT_ERROR;
|
|
if(wsaNetworkEvents.iErrorCode[FD_ACCEPT_BIT])
|
|
retval |= IO_WAIT_ERROR;
|
|
if(wsaNetworkEvents.iErrorCode[FD_OOB_BIT])
|
|
retval |= IO_WAIT_ERROR;
|
|
if(wsaNetworkEvents.iErrorCode[FD_WRITE_BIT])
|
|
retval |= IO_WAIT_ERROR;
|
|
if(wsaNetworkEvents.iErrorCode[FD_CLOSE_BIT])
|
|
retval |= IO_WAIT_ERROR;
|
|
*/
|
|
#else
|
|
if(!phandle->fnptr->fn_getwaitable(phandle, 1, &waitable)) /* get underlying fd */
|
|
return 0;
|
|
|
|
io_err_printf(IO_LOG_DEBUG,"Checking status of fd %d\n",waitable);
|
|
if(FD_ISSET(waitable,&pwait->result_read))
|
|
retval |= IO_WAIT_READ;
|
|
if(FD_ISSET(waitable,&pwait->result_write))
|
|
retval |= IO_WAIT_WRITE;
|
|
if(FD_ISSET(waitable,&pwait->result_err))
|
|
retval |= IO_WAIT_ERROR;
|
|
io_err_printf(IO_LOG_DEBUG,"Returning %d\n",retval);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* dispose of a wait object
|
|
*
|
|
* @param pwait object to dispose
|
|
* @returns TRUE on success
|
|
*/
|
|
int io_wait_dispose(IO_WAITHANDLE *pwait) {
|
|
ASSERT(pwait);
|
|
|
|
if(!pwait)
|
|
return TRUE;
|
|
|
|
#ifdef WIN32
|
|
if(pwait->hWaitItems)
|
|
free(pwait->hWaitItems);
|
|
if(pwait->ppHandle)
|
|
free(pwait->ppHandle);
|
|
#endif
|
|
|
|
free(pwait);
|
|
return TRUE;
|
|
}
|