Files
evolution/camel/camel-tcp-stream-raw.c
Jeffrey Stedfast a11414daf6 Updated to use the new API from a fe commits ago for the NSS stream. This
2003-03-03  Jeffrey Stedfast  <fejj@ximian.com>

	* camel-tcp-stream-openssl.c (camel_tcp_stream_ssl_new): Updated
	to use the new API from a fe commits ago for the NSS stream. This
	is just to make it compile, but does not update the behaviour to
	act like the NSS stream. Note that people shouldn't be using
	OpenSSL anyway.
	(camel_tcp_stream_ssl_new_raw): Same.

	* camel-process.[c,h]: New source file containing convenience
	functions for process creation/termination mainly for use with
	Pipe filters but should be usable for anything we want.

	* camel-io.[c,h]: New source files implementing read/write system
	calls with proper error checking and cancellation
	(ie. StreamFs::read/write and CamelTcpStreamRaw::read/write). No
	sense duplicating the same code over and over. Now I can use this
	same code easily in other i/o code (such as Pipe filters and gpg
	code?).

svn path=/trunk/; revision=20132
2003-03-03 22:53:15 +00:00

675 lines
16 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright 2001 Ximian, Inc. (www.ximian.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "camel-tcp-stream-raw.h"
#include "camel-operation.h"
static CamelTcpStreamClass *parent_class = NULL;
/* Returns the class for a CamelTcpStreamRaw */
#define CTSR_CLASS(so) CAMEL_TCP_STREAM_RAW_CLASS (CAMEL_OBJECT_GET_CLASS (so))
static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
static int stream_flush (CamelStream *stream);
static int stream_close (CamelStream *stream);
static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port);
static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
static CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream);
static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream);
static void
camel_tcp_stream_raw_class_init (CamelTcpStreamRawClass *camel_tcp_stream_raw_class)
{
CamelTcpStreamClass *camel_tcp_stream_class =
CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_raw_class);
CamelStreamClass *camel_stream_class =
CAMEL_STREAM_CLASS (camel_tcp_stream_raw_class);
parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ()));
/* virtual method overload */
camel_stream_class->read = stream_read;
camel_stream_class->write = stream_write;
camel_stream_class->flush = stream_flush;
camel_stream_class->close = stream_close;
camel_tcp_stream_class->connect = stream_connect;
camel_tcp_stream_class->getsockopt = stream_getsockopt;
camel_tcp_stream_class->setsockopt = stream_setsockopt;
camel_tcp_stream_class->get_local_address = stream_get_local_address;
camel_tcp_stream_class->get_remote_address = stream_get_remote_address;
}
static void
camel_tcp_stream_raw_init (gpointer object, gpointer klass)
{
CamelTcpStreamRaw *stream = CAMEL_TCP_STREAM_RAW (object);
stream->sockfd = -1;
}
static void
camel_tcp_stream_raw_finalize (CamelObject *object)
{
CamelTcpStreamRaw *stream = CAMEL_TCP_STREAM_RAW (object);
if (stream->sockfd != -1)
close (stream->sockfd);
}
CamelType
camel_tcp_stream_raw_get_type (void)
{
static CamelType type = CAMEL_INVALID_TYPE;
if (type == CAMEL_INVALID_TYPE) {
type = camel_type_register (camel_tcp_stream_get_type (),
"CamelTcpStreamRaw",
sizeof (CamelTcpStreamRaw),
sizeof (CamelTcpStreamRawClass),
(CamelObjectClassInitFunc) camel_tcp_stream_raw_class_init,
NULL,
(CamelObjectInitFunc) camel_tcp_stream_raw_init,
(CamelObjectFinalizeFunc) camel_tcp_stream_raw_finalize);
}
return type;
}
#ifdef SIMULATE_FLAKY_NETWORK
static ssize_t
flaky_tcp_write (int fd, const char *buffer, size_t buflen)
{
size_t len = buflen;
ssize_t nwritten;
int val;
if (buflen == 0)
return 0;
val = 1 + (int) (10.0 * rand () / (RAND_MAX + 1.0));
switch (val) {
case 1:
printf ("flaky_tcp_write (%d, ..., %d): (-1) EINTR\n", fd, buflen);
errno = EINTR;
return -1;
case 2:
printf ("flaky_tcp_write (%d, ..., %d): (-1) EAGAIN\n", fd, buflen);
errno = EAGAIN;
return -1;
case 3:
printf ("flaky_tcp_write (%d, ..., %d): (-1) EWOULDBLOCK\n", fd, buflen);
errno = EWOULDBLOCK;
return -1;
case 4:
case 5:
case 6:
len = 1 + (size_t) (buflen * rand () / (RAND_MAX + 1.0));
len = MIN (len, buflen);
/* fall through... */
default:
printf ("flaky_tcp_write (%d, ..., %d): (%d) '%.*s'", fd, buflen, len, (int) len, buffer);
nwritten = write (fd, buffer, len);
if (nwritten < 0)
printf (" errno => %s\n", strerror (errno));
else if (nwritten < len)
printf (" only wrote %d bytes\n", nwritten);
else
printf ("\n");
return nwritten;
}
}
#define write(fd, buffer, buflen) flaky_tcp_write (fd, buffer, buflen)
static ssize_t
flaky_tcp_read (int fd, char *buffer, size_t buflen)
{
size_t len = buflen;
ssize_t nread;
int val;
if (buflen == 0)
return 0;
val = 1 + (int) (10.0 * rand () / (RAND_MAX + 1.0));
switch (val) {
case 1:
printf ("flaky_tcp_read (%d, ..., %d): (-1) EINTR\n", fd, buflen);
errno = EINTR;
return -1;
case 2:
printf ("flaky_tcp_read (%d, ..., %d): (-1) EAGAIN\n", fd, buflen);
errno = EAGAIN;
return -1;
case 3:
printf ("flaky_tcp_read (%d, ..., %d): (-1) EWOULDBLOCK\n", fd, buflen);
errno = EWOULDBLOCK;
return -1;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
len = 1 + (size_t) (10.0 * rand () / (RAND_MAX + 1.0));
len = MIN (len, buflen);
/* fall through... */
default:
printf ("flaky_tcp_read (%d, ..., %d): (%d)", fd, buflen, len);
nread = read (fd, buffer, len);
if (nread < 0)
printf (" errno => %s\n", strerror (errno));
else if (nread < len)
printf (" only read %d bytes\n", nread);
else
printf ("\n");
return nread;
}
}
#define read(fd, buffer, buflen) flaky_tcp_read (fd, buffer, buflen)
#endif /* SIMULATE_FLAKY_NETWORK */
/**
* camel_tcp_stream_raw_new:
*
* Return value: a tcp stream
**/
CamelStream *
camel_tcp_stream_raw_new ()
{
CamelTcpStreamRaw *stream;
stream = CAMEL_TCP_STREAM_RAW (camel_object_new (camel_tcp_stream_raw_get_type ()));
return CAMEL_STREAM (stream);
}
static ssize_t
stream_read (CamelStream *stream, char *buffer, size_t n)
{
CamelTcpStreamRaw *tcp_stream_raw = CAMEL_TCP_STREAM_RAW (stream);
ssize_t nread;
int cancel_fd;
if (camel_operation_cancel_check (NULL)) {
errno = EINTR;
return -1;
}
cancel_fd = camel_operation_cancel_fd (NULL);
if (cancel_fd == -1) {
do {
nread = read (tcp_stream_raw->sockfd, buffer, n);
} while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
} else {
int error, flags, fdmax;
fd_set rdset;
flags = fcntl (tcp_stream_raw->sockfd, F_GETFL);
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags | O_NONBLOCK);
do {
FD_ZERO (&rdset);
FD_SET (tcp_stream_raw->sockfd, &rdset);
FD_SET (cancel_fd, &rdset);
fdmax = MAX (tcp_stream_raw->sockfd, cancel_fd) + 1;
nread = -1;
if (select (fdmax, &rdset, 0, 0, NULL) != -1) {
if (FD_ISSET (cancel_fd, &rdset)) {
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags);
errno = EINTR;
return -1;
}
do {
nread = read (tcp_stream_raw->sockfd, buffer, n);
} while (nread == -1 && errno == EINTR);
} else if (errno == EINTR) {
errno = EAGAIN;
}
} while (nread == -1 && (errno == EAGAIN || errno == EWOULDBLOCK));
error = errno;
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags);
errno = error;
}
return nread;
}
static ssize_t
stream_write (CamelStream *stream, const char *buffer, size_t n)
{
CamelTcpStreamRaw *tcp_stream_raw = CAMEL_TCP_STREAM_RAW (stream);
ssize_t w, written = 0;
int cancel_fd;
if (camel_operation_cancel_check (NULL)) {
errno = EINTR;
return -1;
}
cancel_fd = camel_operation_cancel_fd (NULL);
if (cancel_fd == -1) {
do {
do {
w = write (tcp_stream_raw->sockfd, buffer + written, n - written);
} while (w == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
if (w > 0)
written += w;
} while (w != -1 && written < n);
} else {
int error, flags, fdmax;
fd_set rdset, wrset;
flags = fcntl (tcp_stream_raw->sockfd, F_GETFL);
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags | O_NONBLOCK);
fdmax = MAX (tcp_stream_raw->sockfd, cancel_fd) + 1;
do {
FD_ZERO (&rdset);
FD_ZERO (&wrset);
FD_SET (tcp_stream_raw->sockfd, &wrset);
FD_SET (cancel_fd, &rdset);
w = -1;
if (select (fdmax, &rdset, &wrset, 0, NULL) != -1) {
if (FD_ISSET (cancel_fd, &rdset)) {
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags);
errno = EINTR;
return -1;
}
do {
w = write (tcp_stream_raw->sockfd, buffer + written, n - written);
} while (w == -1 && errno == EINTR);
if (w == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
w = 0;
} else {
error = errno;
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags);
errno = error;
return -1;
}
} else
written += w;
} else if (errno == EINTR) {
w = 0;
}
} while (w != -1 && written < n);
error = errno;
fcntl (tcp_stream_raw->sockfd, F_SETFL, flags);
errno = error;
}
if (w == -1)
return -1;
return written;
}
static int
stream_flush (CamelStream *stream)
{
return 0;
}
static int
stream_close (CamelStream *stream)
{
if (close (((CamelTcpStreamRaw *)stream)->sockfd) == -1)
return -1;
((CamelTcpStreamRaw *)stream)->sockfd = -1;
return 0;
}
/* this is a 'cancellable' connect, cancellable from camel_operation_cancel etc */
/* returns -1 & errno == EINTR if the connection was cancelled */
static int
socket_connect (struct hostent *h, int port)
{
#ifdef ENABLE_IPv6
struct sockaddr_in6 sin6;
#endif
struct sockaddr_in sin;
struct sockaddr *saddr;
struct timeval tv;
socklen_t len;
int cancel_fd;
int ret, fd;
/* see if we're cancelled yet */
if (camel_operation_cancel_check (NULL)) {
errno = EINTR;
return -1;
}
/* setup connect, we do it using a nonblocking socket so we can poll it */
#ifdef ENABLE_IPv6
if (h->h_addrtype == AF_INET6) {
sin6.sin6_port = htons (port);
sin6.sin6_family = h->h_addrtype;
memcpy (&sin6.sin6_addr, h->h_addr, sizeof (sin6.sin6_addr));
saddr = (struct sockaddr *) &sin6;
len = sizeof (sin6);
} else {
#endif
sin.sin_port = htons (port);
sin.sin_family = h->h_addrtype;
memcpy (&sin.sin_addr, h->h_addr, sizeof (sin.sin_addr));
saddr = (struct sockaddr *) &sin;
len = sizeof (sin);
#ifdef ENABLE_IPv6
}
#endif
fd = socket (h->h_addrtype, SOCK_STREAM, 0);
cancel_fd = camel_operation_cancel_fd (NULL);
if (cancel_fd == -1) {
ret = connect (fd, saddr, len);
if (ret == -1) {
close (fd);
return -1;
}
return fd;
} else {
int flags, fdmax, status;
fd_set rdset, wrset;
flags = fcntl (fd, F_GETFL);
fcntl (fd, F_SETFL, flags | O_NONBLOCK);
ret = connect (fd, saddr, len);
if (ret == 0) {
fcntl (fd, F_SETFL, flags);
return fd;
}
if (errno != EINPROGRESS) {
close (fd);
return -1;
}
do {
FD_ZERO (&rdset);
FD_ZERO (&wrset);
FD_SET (fd, &wrset);
FD_SET (cancel_fd, &rdset);
fdmax = MAX (fd, cancel_fd) + 1;
tv.tv_sec = 60 * 4;
tv.tv_usec = 0;
status = select (fdmax, &rdset, &wrset, 0, &tv);
} while (status == -1 && errno == EINTR);
if (status <= 0) {
close (fd);
errno = ETIMEDOUT;
return -1;
}
if (cancel_fd != -1 && FD_ISSET (cancel_fd, &rdset)) {
close (fd);
errno = EINTR;
return -1;
} else {
len = sizeof (int);
if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &ret, &len) == -1) {
close (fd);
return -1;
}
if (ret != 0) {
close (fd);
errno = ret;
return -1;
}
}
fcntl (fd, F_SETFL, flags);
}
return fd;
}
static int
stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
{
CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream);
int fd;
g_return_val_if_fail (host != NULL, -1);
fd = socket_connect (host, port);
if (fd == -1)
return -1;
raw->sockfd = fd;
return 0;
}
static int
get_sockopt_level (const CamelSockOptData *data)
{
switch (data->option) {
case CAMEL_SOCKOPT_MAXSEGMENT:
case CAMEL_SOCKOPT_NODELAY:
return IPPROTO_TCP;
default:
return SOL_SOCKET;
}
}
static int
get_sockopt_optname (const CamelSockOptData *data)
{
switch (data->option) {
case CAMEL_SOCKOPT_MAXSEGMENT:
return TCP_MAXSEG;
case CAMEL_SOCKOPT_NODELAY:
return TCP_NODELAY;
case CAMEL_SOCKOPT_BROADCAST:
return SO_BROADCAST;
case CAMEL_SOCKOPT_KEEPALIVE:
return SO_KEEPALIVE;
case CAMEL_SOCKOPT_LINGER:
return SO_LINGER;
case CAMEL_SOCKOPT_RECVBUFFERSIZE:
return SO_RCVBUF;
case CAMEL_SOCKOPT_SENDBUFFERSIZE:
return SO_SNDBUF;
case CAMEL_SOCKOPT_REUSEADDR:
return SO_REUSEADDR;
case CAMEL_SOCKOPT_IPTYPEOFSERVICE:
return SO_TYPE;
default:
return -1;
}
}
static int
stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
{
int optname, optlen;
if ((optname = get_sockopt_optname (data)) == -1)
return -1;
if (data->option == CAMEL_SOCKOPT_NONBLOCKING) {
int flags;
flags = fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_GETFL);
if (flags == -1)
return -1;
data->value.non_blocking = flags & O_NONBLOCK ? TRUE : FALSE;
return 0;
}
return getsockopt (((CamelTcpStreamRaw *)stream)->sockfd,
get_sockopt_level (data),
optname,
(void *) &data->value,
&optlen);
}
static int
stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
{
int optname;
if ((optname = get_sockopt_optname (data)) == -1)
return -1;
if (data->option == CAMEL_SOCKOPT_NONBLOCKING) {
int flags, set;
flags = fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_GETFL);
if (flags == -1)
return -1;
set = data->value.non_blocking ? O_NONBLOCK : 0;
flags = (flags & ~O_NONBLOCK) | set;
if (fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_SETFL, flags) == -1)
return -1;
return 0;
}
return setsockopt (((CamelTcpStreamRaw *)stream)->sockfd,
get_sockopt_level (data),
optname,
(void *) &data->value,
sizeof (data->value));
}
#ifdef ENABLE_IPv6
#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in6))
#else
#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in))
#endif
static CamelTcpAddress *
stream_get_local_address (CamelTcpStream *stream)
{
unsigned char buf[MIN_SOCKADDR_BUFLEN];
#ifdef ENABLE_IPv6
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf;
#endif
struct sockaddr_in *sin = (struct sockaddr_in *) buf;
struct sockaddr *saddr = (struct sockaddr *) buf;
gpointer address;
socklen_t len;
int family;
len = MIN_SOCKADDR_BUFLEN;
if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1)
return NULL;
if (saddr->sa_family == AF_INET) {
family = CAMEL_TCP_ADDRESS_IPv4;
address = &sin->sin_addr;
#ifdef ENABLE_IPv6
} else if (saddr->sa_family == AF_INET6) {
family = CAMEL_TCP_ADDRESS_IPv6;
address = &sin6->sin6_addr;
#endif
} else
return NULL;
return camel_tcp_address_new (family, sin->sin_port, len, address);
}
static CamelTcpAddress *
stream_get_remote_address (CamelTcpStream *stream)
{
unsigned char buf[MIN_SOCKADDR_BUFLEN];
#ifdef ENABLE_IPv6
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf;
#endif
struct sockaddr_in *sin = (struct sockaddr_in *) buf;
struct sockaddr *saddr = (struct sockaddr *) buf;
gpointer address;
socklen_t len;
int family;
len = MIN_SOCKADDR_BUFLEN;
if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1)
return NULL;
if (saddr->sa_family == AF_INET) {
family = CAMEL_TCP_ADDRESS_IPv4;
address = &sin->sin_addr;
#ifdef ENABLE_IPv6
} else if (saddr->sa_family == AF_INET6) {
family = CAMEL_TCP_ADDRESS_IPv6;
address = &sin6->sin6_addr;
#endif
} else
return NULL;
return camel_tcp_address_new (family, sin->sin_port, len, address);
}