diff options
| author | Wez Furlong <wez@php.net> | 2003-02-27 17:43:38 +0000 |
|---|---|---|
| committer | Wez Furlong <wez@php.net> | 2003-02-27 17:43:38 +0000 |
| commit | fd61f69077f6156ca71dde60ecfd9ed9765a02db (patch) | |
| tree | 7285ad393cdb5a85107a3329d1ab2bcafe89f051 /main/streams/xp_socket.c | |
| parent | 560e33968de93250377606782949f5004affca83 (diff) | |
| download | php-git-fd61f69077f6156ca71dde60ecfd9ed9765a02db.tar.gz | |
Another big commit (tm).
Main Changes:
- Implement a socket transport layer for use by all code that needs to open
some kind of "special" socket for network or IPC.
- Extensions can register (and override) transports.
- Implement ftruncate() on streams via the ioctl-alike option interface.
- Implement mmap() on streams via the ioctl-alike option interface.
- Implement generic crypto API via the ioctl-alike option interface.
(currently only supports OpenSSL, but could support other SSL toolkits,
and other crypto transport protocols).
Impact:
- tcp sockets can be overloaded by the openssl capable sockets at runtime,
removing the link-time requirement for ssl:// and https:// sockets and
streams.
- checking stream types using PHP_STREAM_IS_SOCKET is deprecated, since
there are now a range of possible socket-type streams.
Working towards:
- socket servers using the new transport layer
- mmap support under win32
- Cleaner code.
# I will be updating the win32 build to add the new files shortly
# after this commit.
Diffstat (limited to 'main/streams/xp_socket.c')
| -rw-r--r-- | main/streams/xp_socket.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c new file mode 100644 index 0000000000..01861701e8 --- /dev/null +++ b/main/streams/xp_socket.c @@ -0,0 +1,502 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 4 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2003 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.02 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available at through the world-wide-web at | + | http://www.php.net/license/2_02.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "ext/standard/file.h" +#include "streams/php_streams_int.h" +#include "php_network.h" + +#if defined(AF_UNIX) +#include <sys/un.h> +#endif + + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC); + +/* {{{ Generic socket stream operations */ +static size_t php_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + size_t didwrite; + + if (sock->socket == -1) { + return 0; + } + + didwrite = send(sock->socket, buf, count, 0); + + if (didwrite <= 0) { + char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); + + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "send of %d bytes failed with errno=%d %s", + count, php_socket_errno(), estr); + efree(estr); + } + + if (didwrite > 0) { + php_stream_notify_progress_increment(stream->context, didwrite, 0); + } + + return didwrite; +} + +static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock TSRMLS_DC) +{ + fd_set fdr, tfdr; + int retval; + struct timeval timeout, *ptimeout; + + if (sock->socket == -1) { + return; + } + + FD_ZERO(&fdr); + FD_SET(sock->socket, &fdr); + sock->timeout_event = 0; + + if (sock->timeout.tv_sec == -1) + ptimeout = NULL; + else + ptimeout = &timeout; + + + while(1) { + tfdr = fdr; + timeout = sock->timeout; + + retval = select(sock->socket + 1, &tfdr, NULL, NULL, ptimeout); + + if (retval == 0) + sock->timeout_event = 1; + + if (retval >= 0) + break; + } +} + +static size_t php_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + int nr_bytes = 0; + + if (sock->socket == -1) { + return 0; + } + + if (sock->is_blocked) { + php_sock_stream_wait_for_data(stream, sock TSRMLS_CC); + if (sock->timeout_event) + return 0; + } + + nr_bytes = recv(sock->socket, buf, count, 0); + + if (nr_bytes == 0 || (nr_bytes == -1 && php_socket_errno() != EWOULDBLOCK)) { + stream->eof = 1; + } + + if (nr_bytes > 0) + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + + return nr_bytes; +} + + +static int php_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + fd_set wrfds, efds; + int n; + struct timeval timeout; + + if (close_handle) { + + if (sock->socket != -1) { + /* prevent more data from coming in */ + shutdown(sock->socket, SHUT_RD); + + /* try to make sure that the OS sends all data before we close the connection. + * Essentially, we are waiting for the socket to become writeable, which means + * that all pending data has been sent. + * We use a small timeout which should encourage the OS to send the data, + * but at the same time avoid hanging indefintely. + * */ + do { + FD_ZERO(&wrfds); + FD_SET(sock->socket, &wrfds); + efds = wrfds; + + timeout.tv_sec = 0; + timeout.tv_usec = 5000; /* arbitrary */ + + n = select(sock->socket + 1, NULL, &wrfds, &efds, &timeout); + } while (n == -1 && php_socket_errno() == EINTR); + + closesocket(sock->socket); + sock->socket = -1; + } + + } + + pefree(sock, php_stream_is_persistent(stream)); + + return 0; +} + +static int php_sockop_flush(php_stream *stream TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + return fsync(sock->socket); +} + +static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + return fstat(sock->socket, &ssb->sb); +} + +static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) +{ + int oldmode; + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + switch(option) { + case PHP_STREAM_OPTION_BLOCKING: + + oldmode = sock->is_blocked; + + /* no need to change anything */ + if (value == oldmode) + return oldmode; + + if (SUCCESS == php_set_sock_blocking(sock->socket, value TSRMLS_CC)) { + sock->is_blocked = value; + return oldmode; + } + + return PHP_STREAM_OPTION_RETURN_ERR; + + case PHP_STREAM_OPTION_READ_TIMEOUT: + sock->timeout = *(struct timeval*)ptrparam; + sock->timeout_event = 0; + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch (xparam->op) { + case STREAM_XPORT_OP_LISTEN: + xparam->outputs.returncode = listen(sock->socket, 5); + return PHP_STREAM_OPTION_RETURN_OK; + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } +} + +static int php_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (ret) { + *ret = fdopen(sock->socket, stream->mode); + if (*ret) + return SUCCESS; + return FAILURE; + } + return SUCCESS; + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (ret) + *ret = (void*)sock->socket; + return SUCCESS; + default: + return FAILURE; + } +} +/* }}} */ + +/* These may look identical, but we need them this way so that + * we can determine which type of socket we are dealing with + * by inspecting stream->ops. + * A "useful" side-effect is that the user's scripts can then + * make similar decisions using stream_get_meta_data. + * */ +php_stream_ops php_stream_generic_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "generic_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_sockop_set_option, +}; + + +php_stream_ops php_stream_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "tcp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +php_stream_ops php_stream_udp_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +#ifdef AF_UNIX +php_stream_ops php_stream_unix_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "unix_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +php_stream_ops php_stream_unixdg_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udg_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +#endif + + +/* network socket operations */ + +static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam TSRMLS_DC) +{ + + return -1; +} + +static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam TSRMLS_DC) +{ + char *colon; + char *host = NULL; + int portno, err; + int ret; + +#ifdef AF_UNIX + if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { + struct sockaddr_un unix_addr; + + sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (sock->socket == SOCK_ERR) { + if (xparam->want_errortext) { + spprintf(&xparam->outputs.error_text, 0, "Failed to create unix socket"); + } + return -1; + } + + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + + /* we need to be binary safe on systems that support an abstract + * namespace */ + if (xparam->inputs.namelen >= sizeof(unix_addr.sun_path)) { + /* On linux, when the path begins with a NUL byte we are + * referring to an abstract namespace. In theory we should + * allow an extra byte below, since we don't need the NULL. + * BUT, to get into this branch of code, the name is too long, + * so we don't care. */ + xparam->inputs.namelen = sizeof(unix_addr.sun_path) - 1; + } + + memcpy(unix_addr.sun_path, xparam->inputs.name, xparam->inputs.namelen); + + ret = php_network_connect_socket(sock->socket, + (const struct sockaddr *)&unix_addr, sizeof(unix_addr), + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err); + + xparam->outputs.error_code = err; + + goto out; + } +#endif + + colon = memchr(xparam->inputs.name, ':', xparam->inputs.namelen); + if (colon) { + portno = atoi(colon + 1); + host = estrndup(xparam->inputs.name, colon - xparam->inputs.name); + } else { + if (xparam->want_errortext) { + spprintf(&xparam->outputs.error_text, 0, "Failed to parse address \"%s\"", xparam->inputs.name); + } + return -1; + } + + /* Note: the test here for php_stream_udp_socket_ops is important, because we + * want the default to be TCP sockets so that the openssl extension can + * re-use this code. */ + + sock->socket = php_network_connect_socket_to_host(host, portno, + stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, + xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err + TSRMLS_CC); + + ret = sock->socket == -1 ? -1 : 0; + xparam->outputs.error_code = err; + + if (host) { + efree(host); + } + +#ifdef AF_UNIX +out: +#endif + + if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { + /* indicates pending connection */ + return 1; + } + + return ret; +} + +static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam TSRMLS_DC) +{ + return -1; +} + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + switch(option) { + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch(xparam->op) { + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_BIND: + xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + + + case STREAM_XPORT_OP_ACCEPT: + xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + default: + /* fall through */ + } + + /* fall through */ + default: + return php_sockop_set_option(stream, option, value, ptrparam TSRMLS_CC); + } +} + + +PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, long protolen, + char *resourcename, long resourcenamelen, + const char *persistent_id, int options, int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC TSRMLS_DC) +{ + php_stream *stream = NULL; + php_netstream_data_t *sock; + php_stream_ops *ops; + + /* which type of socket ? */ + if (strncmp(proto, "tcp", protolen) == 0) { + ops = &php_stream_socket_ops; + } else if (strncmp(proto, "udp", protolen) == 0) { + ops = &php_stream_udp_socket_ops; + } +#ifdef AF_UNIX + else if (strncmp(proto, "unix", protolen) == 0) { + ops = &php_stream_unix_socket_ops; + } else if (strncmp(proto, "udg", protolen) == 0) { + ops = &php_stream_unixdg_socket_ops; + } +#endif + else { + /* should never happen */ + return NULL; + } + + sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); + memset(sock, 0, sizeof(php_netstream_data_t)); + + sock->is_blocked = 1; + sock->timeout.tv_sec = FG(default_socket_timeout); + sock->timeout.tv_usec = 0; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sock->socket = -1; + + stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sock, persistent_id ? 1 : 0); + return NULL; + } + + if (flags == 0) { + return stream; + } + + return stream; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ |
