ossp-pkg/l2/l2_ut_sa.c
1.24
/*
** OSSP sa - Socket Abstraction
** Copyright (c) 2001-2003 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 2001-2003 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2003 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
** This file is part of OSSP sa, a socket abstraction library which
** can be found at http://www.ossp.org/pkg/lib/sa/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
** sa.c: socket abstraction library
*/
/* include optional Autoconf header */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* include system API headers */
#include <stdio.h> /* for "s[n]printf()" */
#include <stdlib.h> /* for "malloc()" & friends */
#include <stdarg.h> /* for "va_XXX()" and "va_list" */
#include <string.h> /* for "strXXX()" and "size_t" */
#include <sys/types.h> /* for general prerequisites */
#include <ctype.h> /* for "isXXX()" */
#include <errno.h> /* for "EXXX" */
#include <fcntl.h> /* for "F_XXX" and "O_XXX" */
#include <unistd.h> /* for standard Unix stuff */
#include <netdb.h> /* for "struct prototent" */
#include <sys/time.h> /* for "struct timeval" */
#include <sys/un.h> /* for "struct sockaddr_un" */
#include <netinet/in.h> /* for "struct sockaddr_in[6]" */
#include <sys/socket.h> /* for "PF_XXX", "AF_XXX", "SOCK_XXX" and "SHUT_XX" */
#include <arpa/inet.h> /* for "inet_XtoX" */
/* include own API header */
#include "l2_ut_sa.h"
/* unique library identifier */
const char sa_id[] = "OSSP sa";
/* support for OSSP ex based exception throwing */
#ifdef WITH_EX
#include "ex.h"
#define SA_RC(rv) \
( (rv) != SA_OK && (ex_catching && !ex_shielding) \
? (ex_throw(sa_id, NULL, (rv)), (rv)) : (rv) )
#else
#define SA_RC(rv) (rv)
#endif /* WITH_EX */
/* boolean values */
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
/* backward compatibility for AF_LOCAL */
#if !defined(AF_LOCAL) && defined(AF_UNIX)
#define AF_LOCAL AF_UNIX
#endif
/* backward compatibility for PF_XXX (still unused) */
#if !defined(PF_LOCAL) && defined(AF_LOCAL)
#define PF_LOCAL AF_LOCAL
#endif
#if !defined(PF_INET) && defined(AF_INET)
#define PF_INET AF_INET
#endif
#if !defined(PF_INET6) && defined(AF_INET6)
#define PF_INET6 AF_INET6
#endif
/* backward compatibility for SHUT_XX. Some platforms (like brain-dead
OpenUNIX) define those only if _XOPEN_SOURCE is defined, but unfortunately
then fail in their other vendor includes due to internal inconsistencies. */
#if !defined(SHUT_RD)
#define SHUT_RD 0
#endif
#if !defined(SHUT_WR)
#define SHUT_WR 1
#endif
#if !defined(SHUT_RDWR)
#define SHUT_RDWR 2
#endif
/* backward compatibility for ssize_t */
#if defined(HAVE_CONFIG_H) && !defined(HAVE_SSIZE_T)
#define ssize_t long
#endif
/* backward compatibility for O_NONBLOCK */
#if !defined(O_NONBLOCK) && defined(O_NDELAY)
#define O_NONBLOCK O_NDELAY
#endif
/* system call structure declaration macros */
#define SA_SC_DECLARE_0(rc_t, fn) \
struct { \
union { void (*any)(void); \
rc_t (*std)(void); \
rc_t (*ctx)(void *); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_1(rc_t, fn, a1_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t); \
rc_t (*ctx)(void *, a1_t); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_2(rc_t, fn, a1_t, a2_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t, a2_t); \
rc_t (*ctx)(void *, a1_t, a2_t); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_3(rc_t, fn, a1_t, a2_t, a3_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t, a2_t, a3_t); \
rc_t (*ctx)(void *, a1_t, a2_t, a3_t); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_4(rc_t, fn, a1_t, a2_t, a3_t, a4_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t, a2_t, a3_t, a4_t); \
rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_5(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t); \
rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t); } fptr; \
void *fctx; \
} sc_##fn;
#define SA_SC_DECLARE_6(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t) \
struct { \
union { void (*any)(void); \
rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); \
rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); } fptr; \
void *fctx; \
} sc_##fn;
/* system call structure assignment macro */
#define SA_SC_ASSIGN(sa, fn, ptr, ctx) \
do { \
(sa)->scSysCall.sc_##fn.fptr.any = (void (*)(void))(ptr); \
(sa)->scSysCall.sc_##fn.fctx = (ctx); \
} while (0)
/* system call structure assignment macro */
#define SA_SC_COPY(sa1, sa2, fn) \
do { \
(sa1)->scSysCall.sc_##fn.fptr.any = (sa2)->scSysCall.sc_##fn.fptr.any; \
(sa1)->scSysCall.sc_##fn.fctx = (sa2)->scSysCall.sc_##fn.fctx; \
} while (0)
/* system call function call macros */
#define SA_SC_CALL_0(sa, fn) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(void) )
#define SA_SC_CALL_1(sa, fn, a1) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1) )
#define SA_SC_CALL_2(sa, fn, a1, a2) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2) )
#define SA_SC_CALL_3(sa, fn, a1, a2, a3) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3) )
#define SA_SC_CALL_4(sa, fn, a1, a2, a3, a4) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4) )
#define SA_SC_CALL_5(sa, fn, a1, a2, a3, a4, a5) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5) )
#define SA_SC_CALL_6(sa, fn, a1, a2, a3, a4, a5, a6) \
( (sa)->scSysCall.sc_##fn.fctx != NULL \
? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5, a6) \
: ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5, a6) )
/* system call table */
typedef struct {
SA_SC_DECLARE_3(int, connect, int, const struct sockaddr *, socklen_t)
SA_SC_DECLARE_3(int, accept, int, struct sockaddr *, socklen_t *)
SA_SC_DECLARE_5(int, select, int, fd_set *, fd_set *, fd_set *, struct timeval *)
SA_SC_DECLARE_3(ssize_t, read, int, void *, size_t)
SA_SC_DECLARE_3(ssize_t, write, int, const void *, size_t)
SA_SC_DECLARE_6(ssize_t, recvfrom, int, void *, size_t, int, struct sockaddr *, socklen_t *)
SA_SC_DECLARE_6(ssize_t, sendto, int, const void *, size_t, int, const struct sockaddr *, socklen_t)
} sa_syscall_tab_t;
/* socket option information */
typedef struct {
int todo;
int value;
} sa_optinfo_t;
/* socket abstraction object */
struct sa_st {
sa_type_t eType; /* socket type (stream or datagram) */
int fdSocket; /* socket file descriptor */
struct timeval tvTimeout[4]; /* timeout values (sec, usec) */
int nReadLen; /* read buffer current length */
int nReadSize; /* read buffer current size */
char *cpReadBuf; /* read buffer memory chunk */
int nWriteLen; /* write buffer current length */
int nWriteSize; /* write buffer current size */
char *cpWriteBuf; /* write buffer memory chunk */
sa_syscall_tab_t scSysCall; /* table of system calls */
sa_optinfo_t optInfo[5]; /* option storage */
};
/* socket address abstraction object */
struct sa_addr_st {
int nFamily; /* the socket family (AF_XXX) */
struct sockaddr *saBuf; /* the "struct sockaddr_xx" actually */
socklen_t slBuf; /* the length of "struct sockaddr_xx" */
};
/* handy struct timeval check */
#define SA_TVISZERO(tv) \
((tv).tv_sec == 0 && (tv).tv_usec == 0)
/* convert Internet address from presentation to network format */
#ifndef HAVE_GETADDRINFO
static int sa_inet_pton(int family, const char *strptr, void *addrptr)
{
#ifdef HAVE_INET_PTON
return inet_pton(family, strptr, addrptr);
#else
struct in_addr in_val;
if (family == AF_INET) {
#if defined(HAVE_INET_ATON)
/* at least for IPv4 we can rely on the old inet_aton(3)
and for IPv6 inet_pton(3) would exist anyway */
if (inet_aton(strptr, &in_val) == 0)
return 0;
#elif defined(HAVE_INET_ADDR)
/* at least for IPv4 try to rely on the even older inet_addr(3) */
memset(&in_val, '\0', sizeof(in_val));
if ((in_val.s_addr = inet_addr(strptr)) == ((in_addr_t)-1))
return 0;
#endif
memcpy(addrptr, &in_val, sizeof(struct in_addr));
return 1;
}
errno = EAFNOSUPPORT;
return 0;
#endif
}
#endif /* !HAVE_GETADDRINFO */
/* convert Internet address from network to presentation format */
static const char *sa_inet_ntop(int family, const void *src, char *dst, size_t size)
{
#ifdef HAVE_INET_NTOP
return inet_ntop(family, src, dst, size);
#else
#ifdef HAVE_INET_NTOA
char *cp;
int n;
#endif
if (family == AF_INET) {
#ifdef HAVE_INET_NTOA
/* at least for IPv4 we can rely on the old inet_ntoa(3)
and for IPv6 inet_ntop(3) would exist anyway */
if ((cp = inet_ntoa(*((struct in_addr *)src))) == NULL)
return NULL;
n = strlen(cp);
if (n > size-1)
n = size-1;
memcpy(dst, cp, n);
dst[n] = '\0';
return dst;
#endif
}
errno = EAFNOSUPPORT;
return NULL;
#endif
}
/* minimal output-independent vprintf(3) variant which supports %{c,s,d,%} only */
static int sa_mvxprintf(int (*output)(void *ctx, const char *buffer, size_t bufsize), void *ctx, const char *format, va_list ap)
{
/* sufficient integer buffer: <available-bits> x log_10(2) + safety */
char ibuf[((sizeof(int)*8)/3)+10];
char *cp;
char c;
int d;
int n;
int bytes;
if (format == NULL)
return -1;
bytes = 0;
while (*format != '\0') {
if (*format == '%') {
c = *(format+1);
if (c == '%') {
/* expand "%%" */
cp = &c;
n = (int)sizeof(char);
}
else if (c == 'c') {
/* expand "%c" */
c = (char)va_arg(ap, int);
cp = &c;
n = (int)sizeof(char);
}
else if (c == 's') {
/* expand "%s" */
if ((cp = (char *)va_arg(ap, char *)) == NULL)
cp = "(null)";
n = (int)strlen(cp);
}
else if (c == 'd') {
/* expand "%d" */
d = (int)va_arg(ap, int);
#ifdef HAVE_SNPRINTF
snprintf(ibuf, sizeof(ibuf), "%d", d); /* explicitly secure */
#else
sprintf(ibuf, "%d", d); /* implicitly secure */
#endif
cp = ibuf;
n = (int)strlen(cp);
}
else {
/* any other "%X" */
cp = (char *)format;
n = 2;
}
format += 2;
}
else {
/* plain text */
cp = (char *)format;
if ((format = strchr(cp, '%')) == NULL)
format = strchr(cp, '\0');
n = (int)(format - cp);
}
/* perform output operation */
if (output != NULL)
if ((n = output(ctx, cp, (size_t)n)) == -1)
break;
bytes += n;
}
return bytes;
}
/* output callback function context for sa_mvsnprintf() */
typedef struct {
char *bufptr;
size_t buflen;
} sa_mvsnprintf_cb_t;
/* output callback function for sa_mvsnprintf() */
static int sa_mvsnprintf_cb(void *_ctx, const char *buffer, size_t bufsize)
{
sa_mvsnprintf_cb_t *ctx = (sa_mvsnprintf_cb_t *)_ctx;
if (bufsize > ctx->buflen)
return -1;
memcpy(ctx->bufptr, buffer, bufsize);
ctx->bufptr += bufsize;
ctx->buflen -= bufsize;
return (int)bufsize;
}
/* minimal vsnprintf(3) variant which supports %{c,s,d} only */
static int sa_mvsnprintf(char *buffer, size_t bufsize, const char *format, va_list ap)
{
int n;
sa_mvsnprintf_cb_t ctx;
if (format == NULL)
return -1;
if (buffer != NULL && bufsize == 0)
return -1;
if (buffer == NULL)
/* just determine output length */
n = sa_mvxprintf(NULL, NULL, format, ap);
else {
/* perform real output */
ctx.bufptr = buffer;
ctx.buflen = bufsize;
n = sa_mvxprintf(sa_mvsnprintf_cb, &ctx, format, ap);
if (n != -1 && ctx.buflen == 0)
n = -1;
if (n != -1)
*(ctx.bufptr) = '\0';
}
return n;
}
/* minimal snprintf(3) variant which supports %{c,s,d} only */
static int sa_msnprintf(char *buffer, size_t bufsize, const char *format, ...)
{
int chars;
va_list ap;
/* pass through to va_list based variant */
va_start(ap, format);
chars = sa_mvsnprintf(buffer, bufsize, format, ap);
va_end(ap);
return chars;
}
/* create address object */
sa_rc_t sa_addr_create(sa_addr_t **saap)
{
sa_addr_t *saa;
/* argument sanity check(s) */
if (saap == NULL)
return SA_RC(SA_ERR_ARG);
/* allocate and initialize new address object */
if ((saa = (sa_addr_t *)malloc(sizeof(sa_addr_t))) == NULL)
return SA_RC(SA_ERR_MEM);
saa->nFamily = 0;
saa->saBuf = NULL;
saa->slBuf = 0;
/* pass object to caller */
*saap = saa;
return SA_OK;
}
/* destroy address object */
sa_rc_t sa_addr_destroy(sa_addr_t *saa)
{
/* argument sanity check(s) */
if (saa == NULL)
return SA_RC(SA_ERR_ARG);
/* free address objects and sub-object(s) */
if (saa->saBuf != NULL)
free(saa->saBuf);
free(saa);
return SA_OK;
}
/* import URI into address object */
sa_rc_t sa_addr_u2a(sa_addr_t *saa, const char *uri, ...)
{
va_list ap;
int sf;
socklen_t sl;
struct sockaddr *sa;
struct sockaddr_un un;
#ifdef HAVE_GETADDRINFO
struct addrinfo ai_hints;
struct addrinfo *ai = NULL;
int err;
#else
struct sockaddr_in sa4;
#ifdef AF_INET6
struct sockaddr_in6 sa6;
#endif
struct hostent *he;
#endif
struct servent *se;
int bIPv6;
int bNumeric;
char *cpHost;
char *cpPort;
char *cpProto;
unsigned int nPort;
const char *cpPath;
char uribuf[1024];
char *cp;
int i;
size_t n;
int k;
/* argument sanity check(s) */
if (saa == NULL || uri == NULL)
return SA_RC(SA_ERR_ARG);
/* on-the-fly create or just take over URI */
va_start(ap, uri);
k = sa_mvsnprintf(uribuf, sizeof(uribuf), uri, ap);
va_end(ap);
if (k == -1)
return SA_RC(SA_ERR_MEM);
/* initialize result variables */
sa = NULL;
sl = 0;
sf = 0;
/* parse URI and resolve contents.
The following syntax is recognized:
- unix:<path>
- inet://<host>:<port>[#(tcp|udp)] */
uri = uribuf;
if (strncmp(uri, "unix:", 5) == 0) {
/* parse URI */
cpPath = uri+5;
/* mandatory(!) socket address structure initialization */
memset(&un, 0, sizeof(un));
/* fill-in socket address structure */
n = strlen(cpPath);
if (n == 0)
return SA_RC(SA_ERR_ARG);
if ((n+1) > sizeof(un.sun_path))
return SA_RC(SA_ERR_MEM);
if (cpPath[0] != '/') {
if (getcwd(un.sun_path, sizeof(un.sun_path)-(n+1)) == NULL)
return SA_RC(SA_ERR_MEM);
cp = un.sun_path + strlen(un.sun_path);
}
else
cp = un.sun_path;
memcpy(cp, cpPath, n+1);
un.sun_family = AF_LOCAL;
/* provide results */
sa = (struct sockaddr *)&un;
sl = (socklen_t)sizeof(un);
sf = AF_LOCAL;
}
else if (strncmp(uri, "inet://", 7) == 0) {
/* parse URI into host, port and protocol parts */
cpHost = (char *)(uri+7);
bIPv6 = FALSE;
if (cpHost[0] == '[') {
/* IPv6 address (see RFC2732) */
#ifndef AF_INET6
return SA_RC(SA_ERR_IMP);
#else
bIPv6 = TRUE;
cpHost++;
if ((cp = strchr(cpHost, ']')) == NULL)
return SA_RC(SA_ERR_ARG);
*cp++ = '\0';
if (*cp != ':')
return SA_RC(SA_ERR_ARG);
cp++;
#endif
}
else {
/* IPv4 address or hostname */
if ((cp = strrchr(cpHost, ':')) == NULL)
return SA_RC(SA_ERR_ARG);
*cp++ = '\0';
}
cpPort = cp;
cpProto = "tcp";
if ((cp = strchr(cpPort, '#')) != NULL) {
*cp++ = '\0';
cpProto = cp;
}
/* resolve port */
nPort = 0;
bNumeric = 1;
for (i = 0; cpPort[i] != '\0'; i++) {
if (!isdigit((int)cpPort[i])) {
bNumeric = 0;
break;
}
}
if (bNumeric)
nPort = (unsigned int)atoi(cpPort);
else {
if ((se = getservbyname(cpPort, cpProto)) == NULL)
return SA_RC(SA_ERR_SYS);
nPort = ntohs(se->s_port);
}
#ifdef HAVE_GETADDRINFO
memset(&ai_hints, 0, sizeof(ai_hints));
ai_hints.ai_family = PF_UNSPEC;
if ((err = getaddrinfo(cpHost, NULL, &ai_hints, &ai)) != 0) {
if (err == EAI_MEMORY)
return SA_RC(SA_ERR_MEM);
else if (err == EAI_SYSTEM)
return SA_RC(SA_ERR_SYS);
else
return SA_RC(SA_ERR_ARG);
}
sa = ai->ai_addr;
sl = ai->ai_addrlen;
sf = ai->ai_family;
if (sf == AF_INET)
((struct sockaddr_in *)sa)->sin_port = htons(nPort);
else if (sf == AF_INET6)
((struct sockaddr_in6 *)sa)->sin6_port = htons(nPort);
else
return SA_RC(SA_ERR_ARG);
#else /* !HAVE_GETADDRINFO */
/* mandatory(!) socket address structure initialization */
memset(&sa4, 0, sizeof(sa4));
#ifdef AF_INET6
memset(&sa6, 0, sizeof(sa6));
#endif
/* resolve host by trying to parse it as either directly an IPv4 or
IPv6 address or by resolving it to either an IPv4 or IPv6 address */
if (!bIPv6 && sa_inet_pton(AF_INET, cpHost, &sa4.sin_addr.s_addr) == 1) {
sa4.sin_family = AF_INET;
sa4.sin_port = htons(nPort);
sa = (struct sockaddr *)&sa4;
sl = (socklen_t)sizeof(sa4);
sf = AF_INET;
}
#ifdef AF_INET6
else if (bIPv6 && sa_inet_pton(AF_INET6, cpHost, &sa6.sin6_addr.s6_addr) == 1) {
sa6.sin6_family = AF_INET6;
sa6.sin6_port = htons(nPort);
sa = (struct sockaddr *)&sa6;
sl = (socklen_t)sizeof(sa6);
sf = AF_INET6;
}
#endif
else if ((he = gethostbyname(cpHost)) != NULL) {
if (he->h_addrtype == AF_INET) {
sa4.sin_family = AF_INET;
sa4.sin_port = htons(nPort);
memcpy(&sa4.sin_addr.s_addr, he->h_addr_list[0],
sizeof(sa4.sin_addr.s_addr));
sa = (struct sockaddr *)&sa4;
sl = (socklen_t)sizeof(sa4);
sf = AF_INET;
}
#ifdef AF_INET6
else if (he->h_addrtype == AF_INET6) {
sa6.sin6_family = AF_INET6;
sa6.sin6_port = htons(nPort);
memcpy(&sa6.sin6_addr.s6_addr, he->h_addr_list[0],
sizeof(sa6.sin6_addr.s6_addr));
sa = (struct sockaddr *)&sa6;
sl = (socklen_t)sizeof(sa6);
sf = AF_INET6;
}
#endif
else
return SA_RC(SA_ERR_ARG);
}
else
return SA_RC(SA_ERR_ARG);
#endif /* !HAVE_GETADDRINFO */
}
else
return SA_RC(SA_ERR_ARG);
/* fill-in result address structure */
if (saa->saBuf != NULL)
free(saa->saBuf);
if ((saa->saBuf = (struct sockaddr *)malloc((size_t)sl)) == NULL)
return SA_RC(SA_ERR_MEM);
memcpy(saa->saBuf, sa, (size_t)sl);
saa->slBuf = sl;
saa->nFamily = (int)sf;
#ifdef HAVE_GETADDRINFO
if (ai != NULL)
freeaddrinfo(ai);
#endif
return SA_OK;
}
/* import "struct sockaddr" into address object */
sa_rc_t sa_addr_s2a(sa_addr_t *saa, const struct sockaddr *sabuf, socklen_t salen)
{
/* argument sanity check(s) */
if (saa == NULL || sabuf == NULL || salen == 0)
return SA_RC(SA_ERR_ARG);
/* make sure we import only supported addresses */
if (!( sabuf->sa_family == AF_LOCAL
|| sabuf->sa_family == AF_INET
#ifdef AF_INET6
|| sabuf->sa_family == AF_INET6
#endif
))
return SA_RC(SA_ERR_USE);
/* create result address structure */
if (saa->saBuf != NULL)
free(saa->saBuf);
if ((saa->saBuf = (struct sockaddr *)malloc((size_t)salen)) == NULL)
return SA_RC(SA_ERR_MEM);
memcpy(saa->saBuf, sabuf, (size_t)salen);
saa->slBuf = salen;
/* remember family */
saa->nFamily = (int)(sabuf->sa_family);
return SA_OK;
}
/* export address object into URI */
sa_rc_t sa_addr_a2u(sa_addr_t *saa, char **uri)
{
char uribuf[1024];
struct sockaddr_un *un;
struct sockaddr_in *sa4;
#ifdef AF_INET6
struct sockaddr_in6 *sa6;
#endif
char caHost[512];
unsigned int nPort;
/* argument sanity check(s) */
if (saa == NULL || uri == NULL)
return SA_RC(SA_ERR_ARG);
/* export object contents */
if (saa->nFamily == AF_LOCAL) {
un = (struct sockaddr_un *)((void *)saa->saBuf);
if ( ( saa->slBuf >= (socklen_t)(&(((struct sockaddr_un *)0)->sun_path[0]))
&& un->sun_path[0] == '\0')
|| (size_t)(saa->slBuf) < sizeof(struct sockaddr_un)) {
/* in case the remote side of a Unix Domain socket was not
bound, a "struct sockaddr_un" can occur with a length less
than the expected one. Then there is actually no path at all.
This has been verified under FreeBSD, Linux and Solaris. */
if (sa_msnprintf(uribuf, sizeof(uribuf), "unix:/NOT-BOUND") == -1)
return SA_RC(SA_ERR_FMT);
}
else {
if (sa_msnprintf(uribuf, sizeof(uribuf), "unix:%s", un->sun_path) == -1)
return SA_RC(SA_ERR_FMT);
}
}
else if (saa->nFamily == AF_INET) {
sa4 = (struct sockaddr_in *)((void *)saa->saBuf);
if (sa_inet_ntop(AF_INET, &sa4->sin_addr.s_addr, caHost, sizeof(caHost)) == NULL)
return SA_RC(SA_ERR_NET);
nPort = ntohs(sa4->sin_port);
if (sa_msnprintf(uribuf, sizeof(uribuf), "inet://%s:%d", caHost, nPort) == -1)
return SA_RC(SA_ERR_FMT);
}
#ifdef AF_INET6
else if (saa->nFamily == AF_INET6) {
sa6 = (struct sockaddr_in6 *)((void *)saa->saBuf);
if (sa_inet_ntop(AF_INET6, &sa6->sin6_addr.s6_addr, caHost, sizeof(caHost)) == NULL)
return SA_RC(SA_ERR_NET);
nPort = ntohs(sa6->sin6_port);
if (sa_msnprintf(uribuf, sizeof(uribuf), "inet://[%s]:%d", caHost, nPort) == -1)
return SA_RC(SA_ERR_FMT);
}
#endif
else
return SA_RC(SA_ERR_INT);
/* pass result to caller */
*uri = strdup(uribuf);
return SA_OK;
}
/* export address object into "struct sockaddr" */
sa_rc_t sa_addr_a2s(sa_addr_t *saa, struct sockaddr **sabuf, socklen_t *salen)
{
/* argument sanity check(s) */
if (saa == NULL || sabuf == NULL || salen == 0)
return SA_RC(SA_ERR_ARG);
/* export underlying address structure */
if ((*sabuf = (struct sockaddr *)malloc((size_t)saa->slBuf)) == NULL)
return SA_RC(SA_ERR_MEM);
memmove(*sabuf, saa->saBuf, (size_t)saa->slBuf);
*salen = saa->slBuf;
return SA_OK;
}
sa_rc_t sa_addr_match(sa_addr_t *saa1, sa_addr_t *saa2, int prefixlen)
{
const unsigned char *ucp1, *ucp2;
unsigned int uc1, uc2, mask;
size_t l1, l2;
int nBytes;
int nBits;
#ifdef AF_INET6
int i;
const unsigned char *ucp0;
#endif
unsigned int np1, np2;
int bMatchPort;
/* argument sanity check(s) */
if (saa1 == NULL || saa2 == NULL)
return SA_RC(SA_ERR_ARG);
/* short circuiting for wildcard matching */
if (prefixlen == 0)
return SA_OK;
/* determine address representation pointer and size */
if (saa1->nFamily == AF_LOCAL) {
np1 = 0;
np2 = 0;
ucp1 = (const unsigned char *)(((struct sockaddr_un *)saa1->saBuf)->sun_path);
ucp2 = (const unsigned char *)(((struct sockaddr_un *)saa2->saBuf)->sun_path);
l1 = strlen(((struct sockaddr_un *)saa1->saBuf)->sun_path) * 8;
l2 = strlen(((struct sockaddr_un *)saa2->saBuf)->sun_path) * 8;
if (prefixlen < 0) {
if (l1 != l2)
return SA_RC(SA_ERR_MTC);
nBits = (int)l1;
}
else {
if ((int)l1 < prefixlen || (int)l2 < prefixlen)
return SA_RC(SA_ERR_MTC);
nBits = prefixlen;
}
}
#ifdef AF_INET6
else if ( (saa1->nFamily == AF_INET && saa2->nFamily == AF_INET6)
|| (saa1->nFamily == AF_INET6 && saa2->nFamily == AF_INET )) {
/* special case of comparing a regular IPv4 address (1.2.3.4) with an
"IPv4-mapped IPv6 address" (::ffff:1.2.3.4). For details see RFC 2373. */
if (saa1->nFamily == AF_INET6) {
np1 = (unsigned int)(((struct sockaddr_in6 *)saa1->saBuf)->sin6_port);
np2 = (unsigned int)(((struct sockaddr_in *)saa2->saBuf)->sin_port);
ucp1 = (const unsigned char *)&(((struct sockaddr_in6 *)saa1->saBuf)->sin6_addr);
ucp2 = (const unsigned char *)&(((struct sockaddr_in *)saa2->saBuf)->sin_addr);
ucp0 = ucp1;
ucp1 += 12;
}
else {
np1 = (unsigned int)(((struct sockaddr_in *)saa1->saBuf)->sin_port);
np2 = (unsigned int)(((struct sockaddr_in6 *)saa2->saBuf)->sin6_port);
ucp1 = (const unsigned char *)&(((struct sockaddr_in *)saa1->saBuf)->sin_addr);
ucp2 = (const unsigned char *)&(((struct sockaddr_in6 *)saa2->saBuf)->sin6_addr);
ucp0 = ucp2;
ucp2 += 12;
}
for (i = 0; i < 10; i++)
if ((int)ucp0[i] != 0x00)
return SA_RC(SA_ERR_MTC);
if (!((int)ucp0[10] == 0xFF && (int)ucp0[11] == 0xFF))
return SA_RC(SA_ERR_MTC);
nBits = 32;
}
#endif
else if (saa1->nFamily == AF_INET) {
np1 = (unsigned int)(((struct sockaddr_in *)saa1->saBuf)->sin_port);
np2 = (unsigned int)(((struct sockaddr_in *)saa2->saBuf)->sin_port);
ucp1 = (const unsigned char *)&(((struct sockaddr_in *)saa1->saBuf)->sin_addr);
ucp2 = (const unsigned char *)&(((struct sockaddr_in *)saa2->saBuf)->sin_addr);
nBits = 32;
}
#ifdef AF_INET6
else if (saa1->nFamily == AF_INET6) {
np1 = (unsigned int)(((struct sockaddr_in6 *)saa1->saBuf)->sin6_port);
np2 = (unsigned int)(((struct sockaddr_in6 *)saa2->saBuf)->sin6_port);
ucp1 = (const unsigned char *)&(((struct sockaddr_in6 *)saa1->saBuf)->sin6_addr);
ucp2 = (const unsigned char *)&(((struct sockaddr_in6 *)saa2->saBuf)->sin6_addr);
nBits = 128;
}
#endif
else
return SA_RC(SA_ERR_INT);
/* make sure we do not compare than possible */
if (prefixlen > (nBits+1))
return SA_RC(SA_ERR_ARG);
/* support equal matching (= all bits plus optionally port) */
bMatchPort = FALSE;
if (prefixlen < 0) {
if (prefixlen < -1)
bMatchPort = TRUE;
prefixlen = nBits;
}
/* perform address representation comparison
(assumption guaranteed by API: network byte order is used) */
nBytes = (prefixlen / 8);
nBits = (prefixlen % 8);
if (nBytes > 0) {
if (memcmp(ucp1, ucp2, (size_t)nBytes) != 0)
return SA_RC(SA_ERR_MTC);
}
if (nBits > 0) {
uc1 = (unsigned int)ucp1[nBytes];
uc2 = (unsigned int)ucp2[nBytes];
mask = ((unsigned int)0xFF << (8-nBits)) & (unsigned int)0xFF;
if ((uc1 & mask) != (uc2 & mask))
return SA_RC(SA_ERR_MTC);
}
/* optionally perform additional port matching */
if (bMatchPort)
if (np1 != np2)
return SA_RC(SA_ERR_MTC);
return SA_OK;
}
/* set timeouts if timeouts or done in kernel */
static sa_rc_t sa_socket_settimeouts(sa_t *sa)
{
/* stop processing if socket is still not allocated */
if (sa->fdSocket == -1)
return SA_OK;
#if defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO)
if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_RCVTIMEO,
(const void *)(&sa->tvTimeout[SA_TIMEOUT_READ]),
(socklen_t)(sizeof(sa->tvTimeout[SA_TIMEOUT_READ]))) < 0)
return SA_RC(SA_ERR_SYS);
}
if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_SNDTIMEO,
(const void *)(&sa->tvTimeout[SA_TIMEOUT_WRITE]),
(socklen_t)(sizeof(sa->tvTimeout[SA_TIMEOUT_WRITE]))) < 0)
return SA_RC(SA_ERR_SYS);
}
#endif
return SA_OK;
}
/* set socket options */
static sa_rc_t sa_socket_setoptions(sa_t *sa)
{
int i;
sa_rc_t rv;
/* stop processing if socket is still not allocated */
if (sa->fdSocket == -1)
return SA_OK;
/* check for pending options */
rv = SA_OK;
for (i = 0; i < (int)(sizeof(sa->optInfo)/sizeof(sa->optInfo[0])); i++) {
if (sa->optInfo[i].todo) {
switch (i) {
/* enable/disable Nagle's Algorithm (see RFC898) */
case SA_OPTION_NAGLE: {
#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
int mode = sa->optInfo[i].value;
if (setsockopt(sa->fdSocket, IPPROTO_TCP, TCP_NODELAY,
(const void *)&mode,
(socklen_t)sizeof(mode)) < 0)
rv = SA_ERR_SYS;
else
sa->optInfo[i].todo = FALSE;
#endif
break;
}
/* enable/disable linger close semantics */
case SA_OPTION_LINGER: {
#if defined(SO_LINGER)
struct linger linger;
linger.l_onoff = (sa->optInfo[i].value == 0 ? 0 : 1);
linger.l_linger = sa->optInfo[i].value;
if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_LINGER,
(const void *)&linger,
(socklen_t)sizeof(struct linger)) < 0)
rv = SA_ERR_SYS;
else
sa->optInfo[i].todo = FALSE;
#endif
break;
}
/* enable/disable reusability of binding to address */
case SA_OPTION_REUSEADDR: {
#if defined(SO_REUSEADDR)
int mode = sa->optInfo[i].value;
if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_REUSEADDR,
(const void *)&mode, (socklen_t)sizeof(mode)) < 0)
rv = SA_ERR_SYS;
else
sa->optInfo[i].todo = FALSE;
#endif
break;
}
/* enable/disable reusability of binding to port */
case SA_OPTION_REUSEPORT: {
#if defined(SO_REUSEPORT)
int mode = sa->optInfo[i].value;
if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_REUSEPORT,
(const void *)&mode, (socklen_t)sizeof(mode)) < 0)
rv = SA_ERR_SYS;
else
sa->optInfo[i].todo = FALSE;
#endif
break;
}
/* enable/disable non-blocking I/O mode */
case SA_OPTION_NONBLOCK: {
int mode = sa->optInfo[i].value;
int flags;
if ((flags = fcntl(sa->fdSocket, F_GETFL, 0)) < 0) {
rv = SA_ERR_SYS;
break;
}
if (mode == 0)
flags &= ~(O_NONBLOCK);
else
flags |= O_NONBLOCK;
if (fcntl(sa->fdSocket, F_SETFL, flags) < 0)
rv = SA_ERR_SYS;
else
sa->optInfo[i].todo = FALSE;
break;
}
default: /* NOTREACHED */ break;
}
}
}
return SA_RC(rv);
}
/* internal lazy/delayed initialization of underlying socket */
static sa_rc_t sa_socket_init(sa_t *sa, int nFamily)
{
int nType;
int nProto;
#if !(defined(IPPROTO_TCP) && defined(IPPROTO_UDP))
struct protoent *pe;
#endif
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* only perform operation if socket still does not exist */
if (sa->fdSocket != -1)
return SA_RC(SA_ERR_USE);
/* determine socket type */
if (sa->eType == SA_TYPE_STREAM)
nType = SOCK_STREAM;
else if (sa->eType == SA_TYPE_DATAGRAM)
nType = SOCK_DGRAM;
else
return SA_RC(SA_ERR_INT);
/* determine socket protocol */
if (nFamily == AF_LOCAL)
nProto = 0;
#ifdef AF_INET6
else if (nFamily == AF_INET || nFamily == AF_INET6) {
#else
else if (nFamily == AF_INET) {
#endif
#if defined(IPPROTO_TCP) && defined(IPPROTO_UDP)
if (nType == SOCK_STREAM)
nProto = IPPROTO_TCP;
else if (nType == SOCK_DGRAM)
nProto = IPPROTO_UDP;
else
return SA_RC(SA_ERR_INT);
#else
if (nType == SOCK_STREAM)
pe = getprotobyname("tcp");
else if (nType == SOCK_DGRAM)
pe = getprotobyname("udp");
else
return SA_RC(SA_ERR_INT);
if (pe == NULL)
return SA_RC(SA_ERR_SYS);
nProto = pe->p_proto;
#endif
}
else
return SA_RC(SA_ERR_INT);
/* create the underlying socket */
if ((sa->fdSocket = socket(nFamily, nType, nProto)) == -1)
return SA_RC(SA_ERR_SYS);
/* optionally set kernel timeouts */
if ((rv = sa_socket_settimeouts(sa)) != SA_OK)
return SA_RC(rv);
/* optionally configure changed options */
if ((rv = sa_socket_setoptions(sa)) != SA_OK)
return SA_RC(rv);
return SA_OK;
}
/* internal destruction of underlying socket */
static sa_rc_t sa_socket_kill(sa_t *sa)
{
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* check context */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* close socket */
(void)close(sa->fdSocket);
sa->fdSocket = -1;
return SA_OK;
}
/* create abstract socket object */
sa_rc_t sa_create(sa_t **sap)
{
sa_t *sa;
int i;
/* argument sanity check(s) */
if (sap == NULL)
return SA_RC(SA_ERR_ARG);
/* allocate and initialize socket object */
if ((sa = (sa_t *)malloc(sizeof(sa_t))) == NULL)
return SA_RC(SA_ERR_MEM);
/* init object attributes */
sa->eType = SA_TYPE_STREAM;
sa->fdSocket = -1;
sa->nReadLen = 0;
sa->nReadSize = 0;
sa->cpReadBuf = NULL;
sa->nWriteLen = 0;
sa->nWriteSize = 0;
sa->cpWriteBuf = NULL;
/* init timeval object attributes */
for (i = 0; i < (int)(sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
sa->tvTimeout[i].tv_sec = 0;
sa->tvTimeout[i].tv_usec = 0;
}
/* init options object attributes */
for (i = 0; i < (int)(sizeof(sa->optInfo)/sizeof(sa->optInfo[0])); i++) {
sa->optInfo[i].todo = FALSE;
sa->optInfo[i].value = 0;
}
/* init syscall object attributes */
SA_SC_ASSIGN(sa, connect, connect, NULL);
SA_SC_ASSIGN(sa, accept, accept, NULL);
SA_SC_ASSIGN(sa, select, select, NULL);
SA_SC_ASSIGN(sa, read, read, NULL);
SA_SC_ASSIGN(sa, write, write, NULL);
SA_SC_ASSIGN(sa, recvfrom, recvfrom, NULL);
SA_SC_ASSIGN(sa, sendto, sendto, NULL);
/* pass object to caller */
*sap = sa;
return SA_OK;
}
/* destroy abstract socket object */
sa_rc_t sa_destroy(sa_t *sa)
{
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* kill underlying socket */
(void)sa_socket_kill(sa);
/* free object and sub-objects */
if (sa->cpReadBuf != NULL)
free(sa->cpReadBuf);
if (sa->cpWriteBuf != NULL)
free(sa->cpWriteBuf);
free(sa);
return SA_OK;
}
/* switch communication type of socket */
sa_rc_t sa_type(sa_t *sa, sa_type_t type)
{
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
if (!(type == SA_TYPE_STREAM || type == SA_TYPE_DATAGRAM))
return SA_RC(SA_ERR_ARG);
/* kill underlying socket if type changes */
if (sa->eType != type)
sa_socket_kill(sa);
/* set new type */
sa->eType = type;
return SA_OK;
}
/* configure I/O timeout */
sa_rc_t sa_timeout(sa_t *sa, sa_timeout_t id, long sec, long usec)
{
int i;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
if (id == SA_TIMEOUT_ALL) {
for (i = 0; i < (int)(sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
sa->tvTimeout[i].tv_sec = sec;
sa->tvTimeout[i].tv_usec = usec;
}
}
else {
sa->tvTimeout[id].tv_sec = sec;
sa->tvTimeout[id].tv_usec = usec;
}
/* try to already set timeouts */
if ((rv = sa_socket_settimeouts(sa)) != SA_OK)
return SA_RC(rv);
return SA_OK;
}
/* configure I/O buffers */
sa_rc_t sa_buffer(sa_t *sa, sa_buffer_t id, size_t size)
{
char *cp;
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
if (id == SA_BUFFER_READ) {
/* configure read/incoming buffer */
if (sa->nReadLen > (int)size)
return SA_RC(SA_ERR_USE);
if (size > 0) {
if (sa->cpReadBuf == NULL)
cp = (char *)malloc(size);
else
cp = (char *)realloc(sa->cpReadBuf, size);
if (cp == NULL)
return SA_RC(SA_ERR_MEM);
sa->cpReadBuf = cp;
sa->nReadSize = (int)size;
}
else {
if (sa->cpReadBuf != NULL)
free(sa->cpReadBuf);
sa->cpReadBuf = NULL;
sa->nReadSize = 0;
}
}
else if (id == SA_BUFFER_WRITE) {
/* configure write/outgoing buffer */
if (sa->nWriteLen > (int)size)
return SA_RC(SA_ERR_USE);
if (size > 0) {
if (sa->cpWriteBuf == NULL)
cp = (char *)malloc(size);
else
cp = (char *)realloc(sa->cpWriteBuf, size);
if (cp == NULL)
return SA_RC(SA_ERR_MEM);
sa->cpWriteBuf = cp;
sa->nWriteSize = (int)size;
}
else {
if (sa->cpWriteBuf != NULL)
free(sa->cpWriteBuf);
sa->cpWriteBuf = NULL;
sa->nWriteSize = 0;
}
}
else
return SA_RC(SA_ERR_ARG);
return SA_OK;
}
/* configure socket option */
sa_rc_t sa_option(sa_t *sa, sa_option_t id, ...)
{
sa_rc_t rv;
va_list ap;
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* process option */
rv = SA_OK;
va_start(ap, id);
switch (id) {
case SA_OPTION_NAGLE: {
#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
int mode = ((int)va_arg(ap, int) ? 1 : 0);
sa->optInfo[SA_OPTION_NAGLE].value = mode;
sa->optInfo[SA_OPTION_NAGLE].todo = TRUE;
#else
rv = SA_ERR_IMP;
#endif
break;
}
case SA_OPTION_LINGER: {
#if defined(SO_LINGER)
int amount = ((int)va_arg(ap, int) ? 1 : 0);
sa->optInfo[SA_OPTION_LINGER].value = amount;
sa->optInfo[SA_OPTION_LINGER].todo = TRUE;
#else
rv = SA_ERR_IMP;
#endif
break;
}
case SA_OPTION_REUSEADDR:
{
#if defined(SO_REUSEADDR)
int mode = ((int)va_arg(ap, int) ? 1 : 0);
sa->optInfo[SA_OPTION_REUSEADDR].value = mode;
sa->optInfo[SA_OPTION_REUSEADDR].todo = TRUE;
#else
rv = SA_ERR_IMP;
#endif
break;
}
case SA_OPTION_REUSEPORT:
{
#if defined(SO_REUSEPORT)
int mode = ((int)va_arg(ap, int) ? 1 : 0);
sa->optInfo[SA_OPTION_REUSEPORT].value = mode;
sa->optInfo[SA_OPTION_REUSEPORT].todo = TRUE;
#else
rv = SA_ERR_IMP;
#endif
break;
}
case SA_OPTION_NONBLOCK: {
int mode = (int)va_arg(ap, int);
sa->optInfo[SA_OPTION_NONBLOCK].value = mode;
sa->optInfo[SA_OPTION_NONBLOCK].todo = TRUE;
break;
}
default: {
rv = SA_ERR_ARG;
}
}
va_end(ap);
/* if an error already occured, stop here */
if (rv != SA_OK)
return SA_RC(rv);
/* else already (re)set) options (if possible) */
if ((rv = sa_socket_setoptions(sa)) != SA_OK)
return SA_RC(rv);
return SA_OK;
}
/* override system call */
sa_rc_t sa_syscall(sa_t *sa, sa_syscall_t id, void (*fptr)(void), void *fctx)
{
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL || fptr == NULL)
return SA_RC(SA_ERR_ARG);
/* assign system call */
rv = SA_OK;
switch (id) {
case SA_SYSCALL_CONNECT: SA_SC_ASSIGN(sa, connect, fptr, fctx); break;
case SA_SYSCALL_ACCEPT: SA_SC_ASSIGN(sa, accept, fptr, fctx); break;
case SA_SYSCALL_SELECT: SA_SC_ASSIGN(sa, select, fptr, fctx); break;
case SA_SYSCALL_READ: SA_SC_ASSIGN(sa, read, fptr, fctx); break;
case SA_SYSCALL_WRITE: SA_SC_ASSIGN(sa, write, fptr, fctx); break;
case SA_SYSCALL_RECVFROM: SA_SC_ASSIGN(sa, recvfrom, fptr, fctx); break;
case SA_SYSCALL_SENDTO: SA_SC_ASSIGN(sa, sendto, fptr, fctx); break;
default: rv = SA_ERR_ARG;
}
return SA_RC(rv);
}
/* bind socket to a local address */
sa_rc_t sa_bind(sa_t *sa, sa_addr_t *laddr)
{
sa_rc_t rv;
struct sockaddr_un *un;
/* argument sanity check(s) */
if (sa == NULL || laddr == NULL)
return SA_RC(SA_ERR_ARG);
/* lazy creation of underlying socket */
if (sa->fdSocket == -1)
if ((rv = sa_socket_init(sa, laddr->nFamily)) != SA_OK)
return SA_RC(rv);
/* remove a possibly existing old Unix Domain socket on filesystem */
if (laddr->nFamily == AF_LOCAL) {
un = (struct sockaddr_un *)((void *)laddr->saBuf);
(void)unlink(un->sun_path);
}
/* perform bind operation on underlying socket */
if (bind(sa->fdSocket, laddr->saBuf, laddr->slBuf) == -1)
return SA_RC(SA_ERR_SYS);
return SA_OK;
}
/* connect socket to a remote address */
sa_rc_t sa_connect(sa_t *sa, sa_addr_t *raddr)
{
int flags, n, error;
fd_set rset, wset;
socklen_t len;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL || raddr == NULL)
return SA_RC(SA_ERR_ARG);
/* connecting is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* lazy creation of underlying socket */
if (sa->fdSocket == -1)
if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK)
return SA_RC(rv);
rv = SA_OK;
if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_CONNECT])) {
/* standard/non-timeout-aware connect operation */
if (SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf) < 0)
rv = SA_ERR_SYS;
}
else {
/* emulated/timeout-aware connect operation */
error = 0;
/* temporarily switch underlying socket to non-blocking mode */
flags = fcntl(sa->fdSocket, F_GETFL, 0);
(void)fcntl(sa->fdSocket, F_SETFL, flags|O_NONBLOCK);
/* perform the connect operation */
if ((n = SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf)) < 0) {
if (errno != EINPROGRESS) {
error = errno;
goto done;
}
}
/* ok if connect completed immediately */
if (n == 0)
goto done;
/* wait for read or write possibility */
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(sa->fdSocket, &rset);
FD_SET(sa->fdSocket, &wset);
do {
n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &rset, &wset, NULL,
&sa->tvTimeout[SA_TIMEOUT_CONNECT]);
} while (n == -1 && errno == EINTR);
/* decide on return semantic */
if (n < 0) {
error = errno;
goto done;
}
else if (n == 0) {
(void)close(sa->fdSocket); /* stop TCP three-way handshake */
sa->fdSocket = -1;
rv = SA_ERR_TMT;
goto done;
}
/* fetch pending error */
len = (socklen_t)sizeof(error);
if (getsockopt(sa->fdSocket, SOL_SOCKET, SO_ERROR, (void *)&error, &len) < 0)
error = errno;
done:
/* reset socket flags */
(void)fcntl(sa->fdSocket, F_SETFL, flags);
/* optionally set errno */
if (error != 0) {
(void)close(sa->fdSocket); /* just in case */
sa->fdSocket = -1;
errno = error;
rv = SA_ERR_SYS;
}
}
return SA_RC(rv);
}
/* listen on socket for connections */
sa_rc_t sa_listen(sa_t *sa, int backlog)
{
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* listening is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least sa_bind() has to be already performed */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* perform listen operation on underlying socket */
if (listen(sa->fdSocket, backlog) == -1)
return SA_RC(SA_ERR_SYS);
return SA_OK;
}
/* accept a connection on socket */
sa_rc_t sa_accept(sa_t *sa, sa_addr_t **caddr, sa_t **csa)
{
sa_rc_t rv;
int n;
fd_set fds;
union {
struct sockaddr_un un;
struct sockaddr_in sa4;
#ifdef AF_INET6
struct sockaddr_in6 sa6;
#endif
} sa_buf;
socklen_t sa_size;
int s;
int i;
/* argument sanity check(s) */
if (sa == NULL || caddr == NULL || csa == NULL)
return SA_RC(SA_ERR_ARG);
/* accepting connections is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least sa_listen() has to be already performed */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* if timeout is enabled, perform a smart-blocking wait */
if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_ACCEPT])) {
FD_ZERO(&fds);
FD_SET(sa->fdSocket, &fds);
do {
n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL,
&sa->tvTimeout[SA_TIMEOUT_ACCEPT]);
} while (n == -1 && errno == EINTR);
if (n == 0)
return SA_RC(SA_ERR_TMT);
if (n <= 0)
return SA_RC(SA_ERR_SYS);
}
/* perform accept operation on underlying socket */
sa_size = (socklen_t)sizeof(sa_buf);
do {
s = SA_SC_CALL_3(sa, accept, sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size);
} while (s == -1 && errno == EINTR);
if (s == -1)
return SA_RC(SA_ERR_SYS);
/* create result address object */
if ((rv = sa_addr_create(caddr)) != SA_OK)
return SA_RC(rv);
if ((rv = sa_addr_s2a(*caddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) {
(void)sa_addr_destroy(*caddr);
return SA_RC(rv);
}
/* create result socket object */
if ((rv = sa_create(csa)) != SA_OK) {
(void)sa_addr_destroy(*caddr);
return SA_RC(rv);
}
/* fill-in child socket */
(*csa)->fdSocket = s;
/* copy-over original system calls */
SA_SC_COPY((*csa), sa, connect);
SA_SC_COPY((*csa), sa, accept);
SA_SC_COPY((*csa), sa, select);
SA_SC_COPY((*csa), sa, read);
SA_SC_COPY((*csa), sa, write);
SA_SC_COPY((*csa), sa, recvfrom);
SA_SC_COPY((*csa), sa, sendto);
/* copy-over original timeout values */
for (i = 0; i < (int)(sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
(*csa)->tvTimeout[i].tv_sec = sa->tvTimeout[i].tv_sec;
(*csa)->tvTimeout[i].tv_usec = sa->tvTimeout[i].tv_usec;
}
return SA_OK;
}
/* determine remote address */
sa_rc_t sa_getremote(sa_t *sa, sa_addr_t **raddr)
{
sa_rc_t rv;
union {
struct sockaddr_in sa4;
#ifdef AF_INET6
struct sockaddr_in6 sa6;
#endif
} sa_buf;
socklen_t sa_size;
/* argument sanity check(s) */
if (sa == NULL || raddr == NULL)
return SA_RC(SA_ERR_ARG);
/* peers exist only for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least sa_connect() or sa_accept() has to be already performed */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* determine remote address of underlying socket */
sa_size = (socklen_t)sizeof(sa_buf);
if (getpeername(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size) < 0)
return SA_RC(SA_ERR_SYS);
/* create result address object */
if ((rv = sa_addr_create(raddr)) != SA_OK)
return SA_RC(rv);
if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) {
(void)sa_addr_destroy(*raddr);
return SA_RC(rv);
}
return SA_OK;
}
/* determine local address */
sa_rc_t sa_getlocal(sa_t *sa, sa_addr_t **laddr)
{
sa_rc_t rv;
union {
struct sockaddr_in sa4;
#ifdef AF_INET6
struct sockaddr_in6 sa6;
#endif
} sa_buf;
socklen_t sa_size;
/* argument sanity check(s) */
if (sa == NULL || laddr == NULL)
return SA_RC(SA_ERR_ARG);
/* at least sa_bind() has to be already performed */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* determine local address of underlying socket */
sa_size = (socklen_t)sizeof(sa_buf);
if (getsockname(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size) < 0)
return SA_RC(SA_ERR_SYS);
/* create result address object */
if ((rv = sa_addr_create(laddr)) != SA_OK)
return SA_RC(rv);
if ((rv = sa_addr_s2a(*laddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) {
(void)sa_addr_destroy(*laddr);
return SA_RC(rv);
}
return SA_OK;
}
/* peek at underlying socket */
sa_rc_t sa_getfd(sa_t *sa, int *fd)
{
/* argument sanity check(s) */
if (sa == NULL || fd == NULL)
return SA_RC(SA_ERR_ARG);
/* if still no socket exists, say this explicitly */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* pass socket to caller */
*fd = sa->fdSocket;
return SA_OK;
}
/* internal raw read operation */
static int sa_read_raw(sa_t *sa, char *cpBuf, int nBufLen)
{
int rv;
#if !(defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO))
fd_set fds;
#endif
/* if timeout is enabled, perform explicit/smart blocking instead
of implicitly/hard blocking in the read(2) system call */
#if !(defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO))
if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
FD_ZERO(&fds);
FD_SET(sa->fdSocket, &fds);
do {
rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL,
&sa->tvTimeout[SA_TIMEOUT_READ]);
} while (rv == -1 && errno == EINTR);
if (rv == 0) {
errno = ETIMEDOUT;
return -1;
}
}
#endif
/* perform read operation on underlying socket */
do {
rv = (int)SA_SC_CALL_3(sa, read, sa->fdSocket, cpBuf, (size_t)nBufLen);
} while (rv == -1 && errno == EINTR);
#if defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO)
if (rv == -1 && errno == EWOULDBLOCK)
errno = ETIMEDOUT;
#endif
return rv;
}
/* read data from socket */
sa_rc_t sa_read(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
int n;
sa_rc_t rv;
int res;
/* argument sanity check(s) */
if (sa == NULL || cpBuf == NULL || nBufReq == 0)
return SA_RC(SA_ERR_ARG);
/* reading is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* perform read operation */
rv = SA_OK;
if (sa->nReadSize == 0) {
/* user-space unbuffered I/O */
if (sa->nWriteLen > 0)
(void)sa_flush(sa);
res = sa_read_raw(sa, cpBuf, (int)nBufReq);
if (res == 0)
rv = SA_ERR_EOF;
else if (res < 0 && errno == ETIMEDOUT)
rv = SA_ERR_TMT;
else if (res < 0)
rv = SA_ERR_SYS;
}
else {
/* user-space buffered I/O */
res = 0;
for (;;) {
if ((int)nBufReq <= sa->nReadLen) {
/* buffer holds enough data, so just use this */
memmove(cpBuf, sa->cpReadBuf, nBufReq);
memmove(sa->cpReadBuf, sa->cpReadBuf+nBufReq, sa->nReadLen-nBufReq);
sa->nReadLen -= nBufReq;
res += nBufReq;
}
else {
if (sa->nReadLen > 0) {
/* fetch already existing buffer contents as a start */
memmove(cpBuf, sa->cpReadBuf, (size_t)sa->nReadLen);
nBufReq -= sa->nReadLen;
cpBuf += sa->nReadLen;
res += sa->nReadLen;
sa->nReadLen = 0;
}
if (sa->nWriteLen > 0)
(void)sa_flush(sa);
if ((int)nBufReq >= sa->nReadSize) {
/* buffer is too small at all, so read directly */
n = sa_read_raw(sa, cpBuf, (int)nBufReq);
if (n > 0)
res += n;
else if (n == 0)
rv = (res == 0 ? SA_ERR_EOF : SA_OK);
else if (n < 0 && errno == ETIMEDOUT)
rv = (res == 0 ? SA_ERR_TMT : SA_OK);
else if (n < 0)
rv = (res == 0 ? SA_ERR_SYS : SA_OK);
}
else {
/* fill buffer with new data */
n = sa_read_raw(sa, sa->cpReadBuf, sa->nReadSize);
if (n < 0 && errno == ETIMEDOUT)
/* timeout on this read, but perhaps ok as a whole */
rv = (res == 0 ? SA_ERR_TMT : SA_OK);
else if (n < 0)
/* error on this read, but perhaps ok as a whole */
rv = (res == 0 ? SA_ERR_SYS : SA_OK);
else if (n == 0)
/* EOF on this read, but perhaps ok as a whole */
rv = (res == 0 ? SA_ERR_EOF : SA_OK);
else {
sa->nReadLen = n;
continue; /* REPEAT OPERATION! */
}
}
}
break;
}
}
/* pass number of actually read bytes to caller */
if (nBufRes != NULL)
*nBufRes = (size_t)res;
return SA_RC(rv);
}
/* read data from socket until [CR]LF (convenience function) */
sa_rc_t sa_readln(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
char c;
size_t n;
size_t res;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL || cpBuf == NULL || nBufReq == 0)
return SA_RC(SA_ERR_ARG);
/* reading is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* we just perform a plain sa_read() per character, because if
buffers are enabled, this is not as stupid as it looks at the first
hand and if buffers are disabled, there is no better solution
anyway. */
rv = SA_OK;
res = 0;
while (res < (nBufReq-1)) {
rv = sa_read(sa, &c, 1, &n);
if (rv != SA_OK)
break;
if (n == 0)
break;
cpBuf[res++] = c;
if (c == '\n')
break;
}
cpBuf[res] = '\0';
/* pass number of actually read characters to caller */
if (nBufRes != NULL)
*nBufRes = res;
return SA_RC(rv);
}
/* internal raw write operation */
static int sa_write_raw(sa_t *sa, const char *cpBuf, int nBufLen)
{
int rv;
#if !(defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO))
fd_set fds;
#endif
/* if timeout is enabled, perform explicit/smart blocking instead
of implicitly/hard blocking in the write(2) system call */
#if !(defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO))
if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
FD_ZERO(&fds);
FD_SET(sa->fdSocket, &fds);
do {
rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL,
&sa->tvTimeout[SA_TIMEOUT_WRITE]);
} while (rv == -1 && errno == EINTR);
if (rv == 0) {
errno = ETIMEDOUT;
return -1;
}
}
#endif
/* perform write operation on underlying socket */
do {
rv = (int)SA_SC_CALL_3(sa, write, sa->fdSocket, cpBuf, (size_t)nBufLen);
} while (rv == -1 && errno == EINTR);
#if defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO)
if (rv == -1 && errno == EWOULDBLOCK)
errno = ETIMEDOUT;
#endif
return rv;
}
/* write data to socket */
sa_rc_t sa_write(sa_t *sa, const char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
int n;
int res;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL || cpBuf == NULL || nBufReq == 0)
return SA_RC(SA_ERR_ARG);
/* writing is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
rv = SA_OK;
if (sa->nWriteSize == 0) {
/* user-space unbuffered I/O */
res = sa_write_raw(sa, cpBuf, (int)nBufReq);
if (res < 0 && errno == ETIMEDOUT)
rv = SA_ERR_TMT;
else if (res < 0)
rv = SA_ERR_SYS;
}
else {
/* user-space buffered I/O */
if ((int)nBufReq > (sa->nWriteSize - sa->nWriteLen)) {
/* not enough space in buffer, so flush buffer first */
(void)sa_flush(sa);
}
res = 0;
if ((int)nBufReq >= sa->nWriteSize) {
/* buffer too small at all, so write immediately */
while (nBufReq > 0) {
n = sa_write_raw(sa, cpBuf, (int)nBufReq);
if (n < 0 && errno == ETIMEDOUT)
rv = (res == 0 ? SA_ERR_TMT : SA_OK);
else if (n < 0)
rv = (res == 0 ? SA_ERR_SYS : SA_OK);
if (n <= 0)
break;
nBufReq -= n;
cpBuf += n;
res += n;
}
}
else {
/* (again) enough sprace in buffer, so store data */
memmove(sa->cpWriteBuf+sa->nWriteLen, cpBuf, nBufReq);
sa->nWriteLen += nBufReq;
res = (int)nBufReq;
}
}
/* pass number of actually written bytes to caller */
if (nBufRes != NULL)
*nBufRes = (size_t)res;
return SA_RC(rv);
}
/* output callback function context for sa_writef() */
typedef struct {
sa_t *sa;
sa_rc_t rv;
} sa_writef_cb_t;
/* output callback function for sa_writef() */
static int sa_writef_cb(void *_ctx, const char *buffer, size_t bufsize)
{
size_t n;
sa_writef_cb_t *ctx = (sa_writef_cb_t *)_ctx;
if ((ctx->rv = sa_write(ctx->sa, buffer, bufsize, &n)) != SA_OK)
return -1;
return (int)n;
}
/* write formatted string to socket (convenience function) */
sa_rc_t sa_writef(sa_t *sa, const char *cpFmt, ...)
{
va_list ap;
int n;
sa_writef_cb_t ctx;
/* argument sanity check(s) */
if (sa == NULL || cpFmt == NULL)
return SA_RC(SA_ERR_ARG);
/* writing is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* format string into temporary buffer */
va_start(ap, cpFmt);
ctx.sa = sa;
ctx.rv = SA_OK;
n = sa_mvxprintf(sa_writef_cb, &ctx, cpFmt, ap);
if (n == -1 && ctx.rv == SA_OK)
ctx.rv = SA_ERR_FMT;
va_end(ap);
return ctx.rv;
}
/* flush write/outgoing I/O buffer */
sa_rc_t sa_flush(sa_t *sa)
{
int n;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL)
return SA_RC(SA_ERR_ARG);
/* flushing is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* try to flush buffer */
rv = SA_OK;
if (sa->nWriteSize > 0) {
while (sa->nWriteLen > 0) {
n = sa_write_raw(sa, sa->cpWriteBuf, sa->nWriteLen);
if (n < 0 && errno == ETIMEDOUT)
rv = SA_ERR_TMT;
else if (n < 0)
rv = SA_ERR_SYS;
if (n <= 0)
break;
memmove(sa->cpWriteBuf, sa->cpWriteBuf+n, (size_t)(sa->nWriteLen-n));
sa->nWriteLen -= n;
}
sa->nWriteLen = 0;
}
return SA_RC(rv);
}
/* shutdown a socket connection */
sa_rc_t sa_shutdown(sa_t *sa, char *flags)
{
int how;
/* argument sanity check(s) */
if (sa == NULL || flags == NULL)
return SA_RC(SA_ERR_ARG);
/* shutdown is only possible for stream communication */
if (sa->eType != SA_TYPE_STREAM)
return SA_RC(SA_ERR_USE);
/* at least a connection has to exist */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* determine flags for shutdown(2) */
how = 0;
if (strcmp(flags, "r") == 0)
how = SHUT_RD;
else if (strcmp(flags, "w") == 0)
how = SHUT_WR;
else if (strcmp(flags, "rw") == 0 || strcmp(flags, "wr") == 0)
how = SHUT_RDWR;
else
return SA_RC(SA_ERR_ARG);
/* flush write buffers */
if ((how & SHUT_WR) || (how & SHUT_RDWR))
(void)sa_flush(sa);
/* perform shutdown operation on underlying socket */
if (shutdown(sa->fdSocket, how) == -1)
return SA_RC(SA_ERR_SYS);
return SA_OK;
}
/* receive data via socket */
sa_rc_t sa_recv(sa_t *sa, sa_addr_t **raddr, char *buf, size_t buflen, size_t *bufdone)
{
sa_rc_t rv;
union {
struct sockaddr_in sa4;
#ifdef AF_INET6
struct sockaddr_in6 sa6;
#endif
} sa_buf;
socklen_t sa_size;
ssize_t n;
int k;
fd_set fds;
/* argument sanity check(s) */
if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL)
return SA_RC(SA_ERR_ARG);
/* receiving is only possible for datagram communication */
if (sa->eType != SA_TYPE_DATAGRAM)
return SA_RC(SA_ERR_USE);
/* at least a sa_bind() has to be performed */
if (sa->fdSocket == -1)
return SA_RC(SA_ERR_USE);
/* if timeout is enabled, perform explicit/smart blocking instead
of implicitly/hard blocking in the recvfrom(2) system call */
if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
FD_ZERO(&fds);
FD_SET(sa->fdSocket, &fds);
do {
k = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL,
&sa->tvTimeout[SA_TIMEOUT_READ]);
} while (k == -1 && errno == EINTR);
if (k == 0)
errno = ETIMEDOUT;
if (k <= 0)
return SA_RC(SA_ERR_SYS);
}
/* perform receive operation on underlying socket */
sa_size = (socklen_t)sizeof(sa_buf);
if ((n = SA_SC_CALL_6(sa, recvfrom, sa->fdSocket, buf, buflen, 0,
(struct sockaddr *)&sa_buf, &sa_size)) == -1)
return SA_RC(SA_ERR_SYS);
/* create result address object */
if ((rv = sa_addr_create(raddr)) != SA_OK)
return rv;
if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) {
(void)sa_addr_destroy(*raddr);
return rv;
}
/* pass actual number of received bytes to caller */
if (bufdone != NULL)
*bufdone = (size_t)n;
return SA_OK;
}
/* send data via socket */
sa_rc_t sa_send(sa_t *sa, sa_addr_t *raddr, const char *buf, size_t buflen, size_t *bufdone)
{
ssize_t n;
int k;
fd_set fds;
sa_rc_t rv;
/* argument sanity check(s) */
if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL)
return SA_RC(SA_ERR_ARG);
/* sending is only possible for datagram communication */
if (sa->eType != SA_TYPE_DATAGRAM)
return SA_RC(SA_ERR_USE);
/* lazy creation of underlying socket */
if (sa->fdSocket == -1)
if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK)
return rv;
/* if timeout is enabled, perform explicit/smart blocking instead
of implicitly/hard blocking in the sendto(2) system call */
if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
FD_ZERO(&fds);
FD_SET(sa->fdSocket, &fds);
do {
k = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL,
&sa->tvTimeout[SA_TIMEOUT_WRITE]);
} while (k == -1 && errno == EINTR);
if (k == 0)
errno = ETIMEDOUT;
if (k <= 0)
return SA_RC(SA_ERR_SYS);
}
/* perform send operation on underlying socket */
if ((n = SA_SC_CALL_6(sa, sendto, sa->fdSocket, buf, buflen, 0, raddr->saBuf, raddr->slBuf)) == -1)
return SA_RC(SA_ERR_SYS);
/* pass actual number of sent bytes to caller */
if (bufdone != NULL)
*bufdone = (size_t)n;
return SA_OK;
}
/* send formatted string to socket (convenience function) */
sa_rc_t sa_sendf(sa_t *sa, sa_addr_t *raddr, const char *cpFmt, ...)
{
va_list ap;
int nBuf;
char *cpBuf;
sa_rc_t rv;
char caBuf[1024];
/* argument sanity check(s) */
if (sa == NULL || raddr == NULL || cpFmt == NULL)
return SA_RC(SA_ERR_ARG);
/* format string into temporary buffer */
va_start(ap, cpFmt);
if ((nBuf = sa_mvsnprintf(NULL, 0, cpFmt, ap)) == -1)
return SA_RC(SA_ERR_FMT);
va_end(ap);
if ((nBuf+1) > (int)sizeof(caBuf)) {
/* requires a larger buffer, so allocate dynamically */
if ((cpBuf = (char *)malloc((size_t)(nBuf+1))) == NULL)
return SA_RC(SA_ERR_MEM);
}
else {
/* fits into small buffer, so allocate statically */
cpBuf = caBuf;
}
va_start(ap, cpFmt);
rv = SA_OK;
if (sa_mvsnprintf(cpBuf, (size_t)(nBuf+1), cpFmt, ap) == -1)
rv = SA_ERR_FMT;
va_end(ap);
/* pass-through to sa_send() */
if (rv == SA_OK)
rv = sa_send(sa, raddr, cpBuf, (size_t)nBuf, NULL);
/* cleanup dynamically allocated buffer */
if ((nBuf+1) > (int)sizeof(caBuf))
free(cpBuf);
return rv;
}
/* return error string */
char *sa_error(sa_rc_t rv)
{
char *sz;
/* translate result value into corresponding string */
if (rv == SA_OK) sz = "Everything Ok";
else if (rv == SA_ERR_ARG) sz = "Invalid Argument";
else if (rv == SA_ERR_USE) sz = "Invalid Use Or Context";
else if (rv == SA_ERR_MEM) sz = "Not Enough Memory";
else if (rv == SA_ERR_MTC) sz = "Matching Failed";
else if (rv == SA_ERR_EOF) sz = "End Of Communication";
else if (rv == SA_ERR_TMT) sz = "Communication Timeout";
else if (rv == SA_ERR_SYS) sz = "Operating System Error";
else if (rv == SA_ERR_NET) sz = "Networking Error";
else if (rv == SA_ERR_FMT) sz = "Formatting Error";
else if (rv == SA_ERR_IMP) sz = "Implementation Not Available";
else if (rv == SA_ERR_INT) sz = "Internal Error";
else sz = "Invalid Result Code";
return sz;
}