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 | |
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.
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | ext/dba/libinifile/inifile.c | 6 | ||||
-rw-r--r-- | ext/openssl/config.m4 | 2 | ||||
-rw-r--r-- | ext/openssl/openssl.c | 14 | ||||
-rw-r--r-- | ext/openssl/php_openssl.h | 2 | ||||
-rw-r--r-- | ext/openssl/xp_ssl.c | 498 | ||||
-rw-r--r-- | ext/standard/basic_functions.c | 10 | ||||
-rw-r--r-- | ext/standard/file.c | 17 | ||||
-rw-r--r-- | ext/standard/fsock.c | 218 | ||||
-rw-r--r-- | ext/standard/fsock.h | 4 | ||||
-rw-r--r-- | ext/standard/ftp_fopen_wrapper.c | 16 | ||||
-rw-r--r-- | ext/standard/http_fopen_wrapper.c | 29 | ||||
-rw-r--r-- | main/network.c | 846 | ||||
-rw-r--r-- | main/php_network.h | 31 | ||||
-rwxr-xr-x | main/php_streams.h | 20 | ||||
-rw-r--r-- | main/streams/cast.c | 22 | ||||
-rw-r--r-- | main/streams/mmap.c | 52 | ||||
-rw-r--r-- | main/streams/php_stream_mmap.h | 82 | ||||
-rw-r--r-- | main/streams/php_stream_transport.h | 153 | ||||
-rw-r--r-- | main/streams/php_streams_int.h | 2 | ||||
-rw-r--r-- | main/streams/plain_wrapper.c | 90 | ||||
-rwxr-xr-x | main/streams/streams.c | 200 | ||||
-rw-r--r-- | main/streams/transports.c | 341 | ||||
-rw-r--r-- | main/streams/xp_socket.c | 502 |
24 files changed, 2232 insertions, 927 deletions
diff --git a/configure.in b/configure.in index 87be5ece14..7232d34eab 100644 --- a/configure.in +++ b/configure.in @@ -1113,7 +1113,7 @@ PHP_ADD_SOURCES(main, main.c snprintf.c spprintf.c php_sprintf.c \ output.c ) PHP_ADD_SOURCES(main/streams, streams.c cast.c memory.c filter.c \ - plain_wrapper.c userspace.c) + plain_wrapper.c userspace.c transports.c xp_socket.c mmap.c) PHP_ADD_SOURCES(/main, internal_functions.c,, sapi) PHP_ADD_SOURCES(/main, internal_functions_cli.c,, cli) diff --git a/ext/dba/libinifile/inifile.c b/ext/dba/libinifile/inifile.c index 2a50c9e884..6e3d94dc4a 100644 --- a/ext/dba/libinifile/inifile.c +++ b/ext/dba/libinifile/inifile.c @@ -87,8 +87,8 @@ inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC) int fd = 0; if (!readonly) { - if (php_stream_is(fp, PHP_STREAM_IS_SOCKET)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate sockets"); + if (!php_stream_truncate_supported(fp)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream"); return NULL; } if (SUCCESS != php_stream_cast(fp, PHP_STREAM_AS_FD, (void*)&fd, 1)) { @@ -320,7 +320,7 @@ static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC) { int res; - if ((res=ftruncate(dba->fd, size)) != 0) { + if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res); return FAILURE; } diff --git a/ext/openssl/config.m4 b/ext/openssl/config.m4 index 93c5d052fd..6cc9568428 100644 --- a/ext/openssl/config.m4 +++ b/ext/openssl/config.m4 @@ -3,7 +3,7 @@ dnl $Id$ dnl if test "$PHP_OPENSSL" != "no"; then - PHP_NEW_EXTENSION(openssl, openssl.c, $ext_openssl_shared) + PHP_NEW_EXTENSION(openssl, openssl.c xp_ssl.c, $ext_openssl_shared) OPENSSL_SHARED_LIBADD="-lcrypto -lssl" PHP_SUBST(OPENSSL_SHARED_LIBADD) AC_DEFINE(HAVE_OPENSSL_EXT,1,[ ]) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 976023e329..7fc9da3f5d 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -608,6 +608,13 @@ PHP_MINIT_FUNCTION(openssl) } else { strlcpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename)); } + + php_stream_xport_register("ssl", php_openssl_ssl_socket_factory TSRMLS_CC); + php_stream_xport_register("tls", php_openssl_ssl_socket_factory TSRMLS_CC); + + /* override the default tcp socket provider */ + php_stream_xport_register("tcp", php_openssl_ssl_socket_factory TSRMLS_CC); + return SUCCESS; } /* }}} */ @@ -628,6 +635,13 @@ PHP_MINFO_FUNCTION(openssl) PHP_MSHUTDOWN_FUNCTION(openssl) { EVP_cleanup(); + + php_stream_xport_unregister("ssl" TSRMLS_CC); + php_stream_xport_unregister("tls" TSRMLS_CC); + + /* reinstate the default tcp handler */ + php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC); + return SUCCESS; } /* }}} */ diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 89da3167e1..2c7dde6bb6 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -26,6 +26,8 @@ extern zend_module_entry openssl_module_entry; #define phpext_openssl_ptr &openssl_module_entry +php_stream_transport_factory_func php_openssl_ssl_socket_factory; + PHP_MINIT_FUNCTION(openssl); PHP_MSHUTDOWN_FUNCTION(openssl); PHP_MINFO_FUNCTION(openssl); diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c new file mode 100644 index 0000000000..eb449274d9 --- /dev/null +++ b/ext/openssl/xp_ssl.c @@ -0,0 +1,498 @@ +/* + +----------------------------------------------------------------------+ + | 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" +#include "php_openssl.h" +#include <openssl/err.h> + + +/* This implementation is very closely tied to the that of the native + * sockets implemented in the core. + * Don't try this technique in other extensions! + * */ + +typedef struct _php_openssl_netstream_data_t { + php_netstream_data_t s; + SSL *ssl_handle; + int enable_on_connect; + int is_client; + int ssl_active; + php_stream_xport_crypt_method_t method; +} php_openssl_netstream_data_t; + +php_stream_ops php_openssl_socket_ops; + +static int handle_ssl_error(php_stream *stream, int nr_bytes TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int err = SSL_get_error(sslsock->ssl_handle, nr_bytes); + char esbuf[512]; + char *ebuf = NULL, *wptr = NULL; + size_t ebuf_size = 0; + unsigned long code; + int retry = 1; + + switch(err) { + case SSL_ERROR_ZERO_RETURN: + /* SSL terminated (but socket may still be active) */ + retry = 0; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* re-negotiation, or perhaps the SSL layer needs more + * packets: retry in next iteration */ + break; + case SSL_ERROR_SYSCALL: + if (ERR_peek_error() == 0) { + if (nr_bytes == 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: fatal protocol error"); + stream->eof = 1; + retry = 0; + } else { + char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: %s", estr); + + efree(estr); + retry = 0; + } + break; + } + /* fall through */ + default: + /* some other error */ + while ((code = ERR_get_error()) != 0) { + /* allow room for a NUL and an optional \n */ + if (ebuf) { + esbuf[0] = '\n'; + esbuf[1] = '\0'; + ERR_error_string_n(code, esbuf + 1, sizeof(esbuf) - 2); + } else { + esbuf[0] = '\0'; + ERR_error_string_n(code, esbuf, sizeof(esbuf) - 1); + } + code = strlen(esbuf); + esbuf[code] = '\0'; + + ebuf = erealloc(ebuf, ebuf_size + code + 1); + if (wptr == NULL) { + wptr = ebuf; + } + + /* also copies the NUL */ + memcpy(wptr, esbuf, code + 1); + wptr += code; + } + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL operation failed with code %d.%s%s", + err, + ebuf ? "OpenSSL Error messages:\n" : "", + ebuf ? ebuf : ""); + + retry = 0; + } + return retry; +} + + +static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + size_t didwrite; + + if (sslsock->ssl_active) { + int retry = 1; + + do { + didwrite = SSL_write(sslsock->ssl_handle, buf, count); + + if (didwrite <= 0) { + retry = handle_ssl_error(stream, didwrite TSRMLS_CC); + } else { + break; + } + } while(retry); + + } else { + didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + } + + if (didwrite > 0) + php_stream_notify_progress_increment(stream->context, didwrite, 0); + + return didwrite; +} + +static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + int nr_bytes = 0; + + if (sslsock->ssl_active) { + int retry = 1; + + do { + nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + + if (nr_bytes <= 0) { + retry = handle_ssl_error(stream, nr_bytes TSRMLS_CC); + if (retry == 0 && !SSL_pending(sslsock->ssl_handle)) { + stream->eof = 1; + } + } else { + /* we got the data */ + break; + } + } while (retry); + } + else + { + nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + } + + if (nr_bytes > 0) + php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + + return nr_bytes; +} + + +static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + fd_set wrfds, efds; + int n; + struct timeval timeout; + + if (close_handle) { + if (sslsock->ssl_active) { + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; + } + if (sslsock->ssl_handle) { + SSL_free(sslsock->ssl_handle); + sslsock->ssl_handle = NULL; + } + if (sslsock->s.socket != -1) { + /* prevent more data from coming in */ + shutdown(sslsock->s.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(sslsock->s.socket, &wrfds); + efds = wrfds; + + timeout.tv_sec = 0; + timeout.tv_usec = 5000; /* arbitrary */ + + n = select(sslsock->s.socket + 1, NULL, &wrfds, &efds, &timeout); + } while (n == -1 && php_socket_errno() == EINTR); + + closesocket(sslsock->s.socket); + sslsock->s.socket = -1; + } + } + + pefree(sslsock, php_stream_is_persistent(stream)); + + return 0; +} + +static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) +{ + return php_stream_socket_ops.flush(stream TSRMLS_CC); +} + +static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) +{ + return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); +} + +static inline int php_openssl_setup_crypto(php_stream *stream, + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) +{ + SSL_CTX *ctx; + SSL_METHOD *method; + + if (sslsock->ssl_handle) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); + return -1; + } + + /* need to do slightly different things, based on client/server method, + * so lets remember which method was selected */ + + switch (cparam->inputs.method) { + case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: + sslsock->is_client = 1; + method = SSLv23_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: + sslsock->is_client = 1; + method = SSLv2_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: + sslsock->is_client = 1; + method = SSLv3_client_method(); + break; + case STREAM_CRYPTO_METHOD_TLS_CLIENT: + sslsock->is_client = 1; + method = TLSv1_client_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv23_SERVER: + sslsock->is_client = 0; + method = SSLv23_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv3_SERVER: + sslsock->is_client = 0; + method = SSLv3_server_method(); + break; + case STREAM_CRYPTO_METHOD_SSLv2_SERVER: + sslsock->is_client = 0; + method = SSLv2_server_method(); + break; + case STREAM_CRYPTO_METHOD_TLS_SERVER: + sslsock->is_client = 0; + method = TLSv1_server_method(); + break; + default: + printf("unknown method\n"); + return -1; + + } + + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); + return -1; + } + + sslsock->ssl_handle = SSL_new(ctx); + if (sslsock->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); + SSL_CTX_free(ctx); + return -1; + } + + if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { + printf("failed to set fd %d\n", sslsock->s.socket); + handle_ssl_error(stream, 0 TSRMLS_CC); + } + + if (cparam->inputs.session) { + printf("sess=%p\n", cparam->inputs.session); + if (cparam->inputs.session->ops != &php_openssl_socket_ops) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); + } else { + SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); + } + } +printf("crypto prepared for fd=%d\n", sslsock->s.socket); + return 0; +} + +static inline int php_openssl_enable_crypto(php_stream *stream, + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) +{ + int n, retry = 1; + + if (cparam->inputs.activate) { + if (sslsock->is_client) { + do { + n = SSL_connect(sslsock->ssl_handle); + + if (n <= 0) { + retry = handle_ssl_error(stream, n TSRMLS_CC); + printf("error; retry = %d\n", retry); + } else { + break; + } + } while (retry); + + printf("enabled_crypto: n=%d\n", n); + if (n == 1) { + sslsock->ssl_active = 1; + } + + return n; + + } else { + + } + } else { + /* deactivate - common for server/client */ + } + return -1; +} + +static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam; + php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; + + switch (option) { + case PHP_STREAM_OPTION_CRYPTO_API: + + switch(cparam->op) { + + case STREAM_XPORT_CRYPTO_OP_SETUP: + cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + case STREAM_XPORT_CRYPTO_OP_ENABLE: + cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + default: + /* fall through */ + } + + break; + + case PHP_STREAM_OPTION_XPORT_API: + switch(xparam->op) { + + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + /* TODO: Async connects need to check the enable_on_connect option when + * we notice that the connect has actually been established */ + php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); + + if (xparam->outputs.returncode == 0 && sslsock->enable_on_connect) { + if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto"); + xparam->outputs.returncode = -1; + } + } + return PHP_STREAM_OPTION_RETURN_OK; + default: + /* fall through */ + } + } + + return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC); +} + +static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC) +{ + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *ret = fdopen(sslsock->s.socket, stream->mode); + if (*ret) { + return SUCCESS; + } + return FAILURE; + } + return SUCCESS; + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (sslsock->ssl_active) { + return FAILURE; + } + if (ret) { + *ret = (void*)sslsock->s.socket; + } + return SUCCESS; + default: + return FAILURE; + } +} + +php_stream_ops php_openssl_socket_ops = { + php_openssl_sockop_write, php_openssl_sockop_read, + php_openssl_sockop_close, php_openssl_sockop_flush, + "tcp_socket/ssl", + NULL, /* seek */ + php_openssl_sockop_cast, + php_openssl_sockop_stat, + php_openssl_sockop_set_option, +}; + + +PHPAPI php_stream *php_openssl_ssl_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_openssl_netstream_data_t *sslsock = NULL; + + sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0); + memset(sslsock, 0, sizeof(php_openssl_netstream_data_t)); + + sslsock->s.is_blocked = 1; + sslsock->s.timeout.tv_sec = FG(default_socket_timeout); + sslsock->s.timeout.tv_usec = 0; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sslsock->s.socket = -1; + + stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sslsock, persistent_id ? 1 : 0); + return NULL; + } + + if (strncmp(proto, "ssl", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + } else if (strncmp(proto, "tls", protolen) == 0) { + sslsock->enable_on_connect = 1; + sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + 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 + */ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 9a14905b3a..84c1347f50 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1166,9 +1166,6 @@ PHP_RINIT_FUNCTION(basic) BG(locale_string) = NULL; BG(user_compare_func_name) = NULL; BG(array_walk_func_name) = NULL; -#ifdef HAVE_MMAP - BG(mmap_file) = NULL; -#endif BG(page_uid) = -1; BG(page_gid) = -1; BG(page_inode) = -1; @@ -1219,7 +1216,6 @@ PHP_RSHUTDOWN_FUNCTION(basic) } STR_FREE(BG(locale_string)); - PHP_RSHUTDOWN(fsock)(SHUTDOWN_FUNC_ARGS_PASSTHRU); PHP_RSHUTDOWN(filestat)(SHUTDOWN_FUNC_ARGS_PASSTHRU); #ifdef HAVE_SYSLOG_H PHP_RSHUTDOWN(syslog)(SHUTDOWN_FUNC_ARGS_PASSTHRU); @@ -1246,12 +1242,6 @@ PHP_RSHUTDOWN_FUNCTION(basic) BG(user_filter_map) = NULL; } -#ifdef HAVE_MMAP - if (BG(mmap_file)) { - munmap(BG(mmap_file), BG(mmap_len)); - } -#endif - return SUCCESS; } diff --git a/ext/standard/file.c b/ext/standard/file.c index 0674f4b3fd..9da076e08a 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -647,6 +647,7 @@ PHP_FUNCTION(stream_get_meta_data) add_assoc_long(return_value, "unread_bytes", stream->writepos - stream->readpos); +#if 0 if (php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { php_netstream_data_t *sock = PHP_NETSTREAM_DATA_FROM_STREAM(stream); @@ -654,10 +655,13 @@ PHP_FUNCTION(stream_get_meta_data) add_assoc_bool(return_value, "blocked", sock->is_blocked); add_assoc_bool(return_value, "eof", stream->eof); } else { +#endif add_assoc_bool(return_value, "timed_out", 0); add_assoc_bool(return_value, "blocked", 1); add_assoc_bool(return_value, "eof", php_stream_eof(stream)); +#if 0 } +#endif } /* }}} */ @@ -2237,7 +2241,7 @@ PHP_FUNCTION(unlink) } /* }}} */ -/* {{{ proto int ftruncate(resource fp, int size) +/* {{{ proto bool ftruncate(resource fp, int size) Truncate file to 'size' length */ PHP_NAMED_FUNCTION(php_if_ftruncate) { @@ -2254,15 +2258,12 @@ PHP_NAMED_FUNCTION(php_if_ftruncate) convert_to_long_ex(size); - if (php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate sockets!"); + if (!php_stream_truncate_supported(stream)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream!"); RETURN_FALSE; } - if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fd, 1)) { - ret = ftruncate(fd, Z_LVAL_PP(size)); - RETURN_LONG(ret + 1); - } - RETURN_FALSE; + + RETURN_BOOL(0 == php_stream_truncate_set_size(stream, Z_LVAL_PP(size))); } /* }}} */ diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index bbe6d7debb..8f41cf4d3f 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -20,122 +20,19 @@ /* $Id$ */ -/* converted to PHP Streams and moved much code to main/network.c [wez] */ - -/* Synced with php 3.0 revision 1.121 1999-06-18 [ssb] */ -/* Synced with php 3.0 revision 1.133 1999-07-21 [sas] */ - #include "php.h" #include "php_globals.h" #include <stdlib.h> #include <stddef.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#ifdef HAVE_FCNTL_H -# include <fcntl.h> -#endif - -#ifdef HAVE_SYS_TIME_H -# include <sys/time.h> -#endif - -#include <sys/types.h> -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> -#endif -#ifdef PHP_WIN32 -#include <winsock2.h> -#elif defined(NETWARE) -#ifdef NEW_LIBC -#ifdef USE_WINSOCK -#include <novsock2.h> -#else -#include <netinet/in.h> -#include <netdb.h> -/*#include <sys/socket.h>*/ -#include <sys/select.h> -/*#else -#include <sys/socket.h>*/ -#endif -#endif -#else -#include <netinet/in.h> -#include <netdb.h> -#if HAVE_ARPA_INET_H -#include <arpa/inet.h> -#endif -#endif -#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) -#undef AF_UNIX -#endif -#if defined(AF_UNIX) -#include <sys/un.h> -#endif -#ifdef HAVE_SYS_SELECT_H -#include <sys/select.h> -#endif - -#ifndef PF_INET -#define PF_INET AF_INET -#endif - -#ifndef PF_UNIX -#define PF_UNIX AF_UNIX -#endif - -#include <string.h> -#include <errno.h> - -#include "base64.h" -#include "file.h" -#include "url.h" -#include "fsock.h" - #include "php_network.h" - -#ifdef ZTS -static int fsock_globals_id; -#endif - -#ifdef PHP_WIN32 -#define EWOULDBLOCK WSAEWOULDBLOCK -#elif defined(NETWARE) -#ifdef USE_WINSOCK -#define EWOULDBLOCK WSAEWOULDBLOCK -#endif -#endif - -/* {{{ php_lookup_hostname */ - -/* - * Converts a host name to an IP address. - * TODO: This looks like unused code suitable for nuking. - */ -PHPAPI int php_lookup_hostname(const char *addr, struct in_addr *in) -{ - struct hostent *host_info; - - if (!inet_aton(addr, in)) { - /* XXX NOT THREAD SAFE */ - host_info = gethostbyname(addr); - if (host_info == 0) { - /* Error: unknown host */ - return -1; - } - *in = *((struct in_addr *) host_info->h_addr); - } - return 0; -} -/* }}} */ +#include "file.h" /* {{{ php_fsockopen() */ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) { char *host; - int host_len; + long host_len; int port = -1; zval *zerrno = NULL, *zerrstr = NULL; double timeout = FG(default_socket_timeout); @@ -144,6 +41,9 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) char *hashkey = NULL; php_stream *stream = NULL; int err; + char *hostname = NULL; + long hostname_len; + char *errstr = NULL; RETVAL_FALSE; @@ -151,22 +51,17 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) RETURN_FALSE; } - if (persistent) { spprintf(&hashkey, 0, "pfsockopen__%s:%d", host, port); - - switch(php_stream_from_persistent_id(hashkey, &stream TSRMLS_CC)) { - case PHP_STREAM_PERSISTENT_SUCCESS: - /* TODO: could check if the socket is still alive here */ - php_stream_to_zval(stream, return_value); - - /* fall through */ - case PHP_STREAM_PERSISTENT_FAILURE: - efree(hashkey); - return; - } } + if (port > 0) { + hostname_len = spprintf(&hostname, 0, "%s:%d", host, port); + } else { + hostname_len = host_len; + hostname = host; + } + /* prepare the timeout value for use */ conv = (unsigned long) (timeout * 1000000.0); tv.tv_sec = conv / 1000000; @@ -181,88 +76,29 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) ZVAL_STRING(zerrstr, "", 1); } - if (port > 0) { /* connect to a host */ - enum php_sslflags_t { php_ssl_none, php_ssl_v23, php_ssl_tls }; - enum php_sslflags_t ssl_flags = php_ssl_none; - struct { - char *proto; - int protolen; - int socktype; - enum php_sslflags_t ssl_flags; - /* more flags to be added here */ - } sockmodes[] = { - { "udp://", 6, SOCK_DGRAM, php_ssl_none }, - { "tcp://", 6, SOCK_STREAM, php_ssl_none }, - { "ssl://", 6, SOCK_STREAM, php_ssl_v23 }, - { "tls://", 6, SOCK_STREAM, php_ssl_tls }, - /* more modes to be added here */ - { NULL, 0, 0 } - }; - int socktype = SOCK_STREAM; - int i; - - for (i = 0; sockmodes[i].proto != NULL; i++) { - if (strncmp(host, sockmodes[i].proto, sockmodes[i].protolen) == 0) { - ssl_flags = sockmodes[i].ssl_flags; - socktype = sockmodes[i].socktype; - host += sockmodes[i].protolen; - break; - } - } -#if !HAVE_OPENSSL_EXT - if (ssl_flags != php_ssl_none) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "no SSL support in this build"); - } - else -#endif - stream = php_stream_sock_open_host(host, (unsigned short)port, socktype, &tv, hashkey); - - /* Preserve error */ - err = php_socket_errno(); + stream = php_stream_xport_create(hostname, hostname_len, ENFORCE_SAFE_MODE | REPORT_ERRORS, + STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, hashkey, &tv, NULL, &errstr, &err); - if (stream == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to connect to %s:%d", host, port); - } - -#if HAVE_OPENSSL_EXT - if (stream && ssl_flags != php_ssl_none) { - int ssl_ret = FAILURE; - switch(ssl_flags) { - case php_ssl_v23: - ssl_ret = php_stream_sock_ssl_activate_with_method(stream, 1, SSLv23_client_method(), NULL TSRMLS_CC); - break; - case php_ssl_tls: - ssl_ret = php_stream_sock_ssl_activate_with_method(stream, 1, TLSv1_client_method(), NULL TSRMLS_CC); - break; - default: - /* unknown ?? */ - break; - } - if (ssl_ret == FAILURE) - php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to activate SSL mode %d", ssl_flags); - } -#endif - - } else { - /* FIXME: Win32 - this probably does not return sensible errno and errstr */ - stream = php_stream_sock_open_unix(host, host_len, hashkey, &tv); - err = php_socket_errno(); + if (port > 0) { + efree(hostname); + } + if (stream == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to connect to %s:%d", host, port); } - if (hashkey) + if (hashkey) { efree(hashkey); + } if (stream == NULL) { if (zerrno) { zval_dtor(zerrno); ZVAL_LONG(zerrno, err); } - if (zerrstr) { - char *buf = php_socket_strerror(err, NULL, 0); - - /* no need to dup; we would only need to efree buf anyway */ + if (zerrstr && errstr) { + /* no need to dup; we need to efree buf anyway */ zval_dtor(zerrstr); - ZVAL_STRING(zerrstr, buf, 0); + ZVAL_STRING(zerrstr, errstr, 0); } RETURN_FALSE; } @@ -287,12 +123,6 @@ PHP_FUNCTION(pfsockopen) } /* }}} */ -/* {{{ RSHUTDOWN_FUNCTION(fsock) */ -PHP_RSHUTDOWN_FUNCTION(fsock) -{ - return SUCCESS; -} -/* }}} */ /* * Local variables: * tab-width: 4 diff --git a/ext/standard/fsock.h b/ext/standard/fsock.h index 966fc1ecec..7d263c31e1 100644 --- a/ext/standard/fsock.h +++ b/ext/standard/fsock.h @@ -40,10 +40,6 @@ PHP_FUNCTION(fsockopen); PHP_FUNCTION(pfsockopen); -PHPAPI int php_lookup_hostname(const char *addr, struct in_addr *in); - -PHP_RSHUTDOWN_FUNCTION(fsock); - /* * Local variables: * tab-width: 4 diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 98b5a27892..192786058d 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -142,10 +142,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch char *scratch; int result; int i, use_ssl; -#if HAVE_OPENSSL_EXT int use_ssl_on_data=0; php_stream *reuseid=NULL; -#endif char *tpath, *ttpath, *hoststart=NULL; size_t file_size = 0; @@ -182,7 +180,6 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch goto errexit; } -#if HAVE_OPENSSL_EXT if (use_ssl) { /* send the AUTH TLS request name */ @@ -212,7 +209,9 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch } if (use_ssl) { - if (use_ssl && php_stream_sock_ssl_activate(stream, 1) == FAILURE) { + if (php_stream_xport_crypto_setup(stream, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 + || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); php_stream_close(stream); stream = NULL; @@ -240,8 +239,6 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch #endif } -#endif - /* send the user name */ php_stream_write_string(stream, "USER "); if (resource->user != NULL) { @@ -433,14 +430,15 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch php_stream_context_set(datastream, context); php_stream_notify_progress_init(context, 0, file_size); -#if HAVE_OPENSSL_EXT - if (use_ssl_on_data && php_stream_sock_ssl_activate_with_method(datastream, 1, SSLv23_method(), reuseid TSRMLS_CC) == FAILURE) { + if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) { + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; goto errexit; } -#endif /* remember control stream */ datastream->wrapperdata = (zval *)stream; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index e6da657882..dbe7340a9a 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -99,6 +99,8 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, char *path, ch char tmp_line[128]; size_t chunk_size = 0, file_size = 0; int eol_detect; + char *transport_string, *errstr = NULL; + int transport_len; if (strpbrk(mode, "awx+")) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP wrapper does not support writeable connections."); @@ -110,14 +112,26 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, char *path, ch return NULL; use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's'; - /* choose default ports */ if (use_ssl && resource->port == 0) resource->port = 443; else if (resource->port == 0) resource->port = 80; - stream = php_stream_sock_open_host(resource->host, resource->port, SOCK_STREAM, NULL, 0); + transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port); + + stream = php_stream_xport_create(transport_string, transport_len, options, + STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, + NULL, NULL, context, &errstr, NULL); + + if (errstr) { + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", errstr); + efree(errstr); + errstr = NULL; + } + + efree(transport_string); + if (stream == NULL) goto out; @@ -133,17 +147,6 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, char *path, ch php_stream_context_set(stream, context); php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); - -#if HAVE_OPENSSL_EXT - if (use_ssl) { - if (php_stream_sock_ssl_activate(stream, 1) == FAILURE) { - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); - php_stream_close(stream); - stream = NULL; - goto out; - } - } -#endif scratch_len = strlen(path) + 32; scratch = emalloc(scratch_len); diff --git a/main/network.c b/main/network.c index 8a7f3da006..905af64813 100644 --- a/main/network.c +++ b/main/network.c @@ -48,10 +48,6 @@ #include <fcntl.h> #endif -#ifdef HAVE_OPENSSL_EXT -#include <openssl/err.h> -#endif - #ifdef HAVE_SYS_SELECT_H #include <sys/select.h> #endif @@ -167,100 +163,228 @@ static void php_network_freeaddresses(struct sockaddr **sal) /* {{{ php_network_getaddresses * Returns number of addresses, 0 for none/error */ -static int php_network_getaddresses(const char *host, struct sockaddr ***sal TSRMLS_DC) +static int php_network_getaddresses(const char *host, struct sockaddr ***sal, char **error_string TSRMLS_DC) { struct sockaddr **sap; int n; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints, *res, *sai; +#else + struct hostent *host_info; + struct in_addr in; +#endif if (host == NULL) { return 0; } - { #ifdef HAVE_GETADDRINFO - struct addrinfo hints, *res, *sai; + memset(&hints, '\0', sizeof(hints)); - memset(&hints, '\0', sizeof(hints)); -# ifdef HAVE_IPV6 - hints.ai_family = AF_UNSPEC; -# else - hints.ai_family = AF_INET; -# endif - if ((n = getaddrinfo(host, NULL, &hints, &res))) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed: %s", PHP_GAI_STRERROR(n)); - return 0; - } else if (res == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed (null result pointer)"); - return 0; +# ifdef HAVE_IPV6 + hints.ai_family = AF_UNSPEC; +# else + hints.ai_family = AF_INET; +# endif + + if ((n = getaddrinfo(host, NULL, &hints, &res)) || res == NULL) { + char *str = res == NULL ? "null result pointer" : PHP_GAI_STRERROR(n); + + if (error_string) { + spprintf(error_string, 0, "getaddrinfo: %s", str); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed: %s", str); } + return 0; + } - sai = res; - for (n = 1; (sai = sai->ai_next) != NULL; n++); - *sal = emalloc((n + 1) * sizeof(*sal)); - sai = res; - sap = *sal; - do { - switch (sai->ai_family) { -# ifdef HAVE_IPV6 + sai = res; + + for (n = 1; (sai = sai->ai_next) != NULL; n++) { + ; + } + + *sal = emalloc((n + 1) * sizeof(*sal)); + sai = res; + sap = *sal; + do { + switch (sai->ai_family) { +# if HAVE_IPV6 case AF_INET6: *sap = emalloc(sizeof(struct sockaddr_in6)); *(struct sockaddr_in6 *)*sap = *((struct sockaddr_in6 *)sai->ai_addr); sap++; break; -# endif +# endif case AF_INET: *sap = emalloc(sizeof(struct sockaddr_in)); *(struct sockaddr_in *)*sap = *((struct sockaddr_in *)sai->ai_addr); sap++; break; - } - } while ((sai = sai->ai_next) != NULL); - freeaddrinfo(res); + } + } while ((sai = sai->ai_next) != NULL); + freeaddrinfo(res); #else - struct hostent *host_info; - struct in_addr in; - - if (!inet_aton(host, &in)) { - /* XXX NOT THREAD SAFE - * (but it *is* thread safe under win32) - */ - host_info = gethostbyname(host); - if (host_info == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: gethostbyname failed"); - return 0; - } - in = *((struct in_addr *) host_info->h_addr); + + if (!inet_aton(host, &in)) { + /* XXX NOT THREAD SAFE + * (but it *is* thread safe under win32) + */ + host_info = gethostbyname(host); + if (host_info == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: gethostbyname failed"); + return 0; } + in = *((struct in_addr *) host_info->h_addr); + } - *sal = emalloc(2 * sizeof(*sal)); - sap = *sal; - *sap = emalloc(sizeof(struct sockaddr_in)); - (*sap)->sa_family = AF_INET; - ((struct sockaddr_in *)*sap)->sin_addr = in; - sap++; - n = 1; + *sal = emalloc(2 * sizeof(*sal)); + sap = *sal; + *sap = emalloc(sizeof(struct sockaddr_in)); + (*sap)->sa_family = AF_INET; + ((struct sockaddr_in *)*sap)->sin_addr = in; + sap++; + n = 1; #endif - } + *sap = NULL; return n; } /* }}} */ +#ifndef O_NONBLOCK +#define O_NONBLOCK O_NDELAY +#endif + +#if !defined(__BEOS__) +# define HAVE_NON_BLOCKING_CONNECT 1 +# ifdef PHP_WIN32 +typedef u_long php_non_blocking_flags_t; +# define SET_SOCKET_BLOCKING_MODE(sock, save) \ + save = TRUE; ioctlsocket(sock, FIONBIO, &save) +# define RESTORE_SOCKET_BLOCKING_MODE(sock, save) \ + ioctlsocket(sock, FIONBIO, &save) +# else +typedef int php_non_blocking_flags_t; +# define SET_SOCKET_BLOCKING_MODE(sock, save) \ + save = fcntl(sock, F_GETFL, 0); \ + fcntl(sock, F_SETFL, save | O_NONBLOCK) +# define RESTORE_SOCKET_BLOCKING_MODE(sock, save) \ + fcntl(sock, F_SETFL, save) +# endif +#endif + + + + +/* Connect to a socket using an interruptible connect with optional timeout. + * Optionally, the connect can be made asynchronously, which will implicitly + * enable non-blocking mode on the socket. + * */ +/* {{{ php_network_connect_socket */ +PHPAPI int php_network_connect_socket(int sockfd, + const struct sockaddr *addr, + socklen_t addrlen, + int asynchronous, + struct timeval *timeout, + char **error_string, + int *error_code) +{ +#if HAVE_NON_BLOCKING_CONNECT + php_non_blocking_flags_t orig_flags; + int n; + int error = 0; + socklen_t len; + int ret = 0; + fd_set rset; + fd_set wset; + fd_set eset; + + SET_SOCKET_BLOCKING_MODE(sockfd, orig_flags); + + if ((n = connect(sockfd, addr, addrlen)) < 0) { + error = php_socket_errno(); + + if (error_code) { + *error_code = error; + } + + if (error != EINPROGRESS) { + if (error_string) { + *error_string = php_socket_strerror(error, NULL, 0); + } + + return -1; + } + if (asynchronous && error == EINPROGRESS) { + /* this is fine by us */ + return 0; + } + } + + if (n == 0) { + goto ok; + } + + FD_ZERO(&rset); + FD_ZERO(&eset); + FD_SET(sockfd, &rset); + FD_SET(sockfd, &eset); + + wset = rset; + + if ((n = select(sockfd + 1, &rset, &wset, &eset, timeout)) == 0) { + error = ETIMEDOUT; + } + + if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { + len = sizeof(error); + /* + BSD-derived systems set errno correctly + Solaris returns -1 from getsockopt in case of error + */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + ret = -1; + } + } else { + /* whoops: sockfd has disappeared */ + ret = -1; + } + +ok: + if (!asynchronous) { + /* back to blocking mode */ + RESTORE_SOCKET_BLOCKING_MODE(sockfd, orig_flags); + } + + if (error_code) { + *error_code = error; + } + + if (error && error_string) { + *error_string = php_socket_strerror(error, NULL, 0); + ret = -1; + } + return ret; +#else + if (asynchronous) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Asynchronous connect() not supported on this platform"); + } + return connect(sockfd, addr, addrlen); +#endif +} +/* }}} */ + /* {{{ php_connect_nonb */ PHPAPI int php_connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) { - /* probably won't work on Win32, someone else might try it (read: fix it ;) */ - #if (!defined(__BEOS__) && !defined(PHP_WIN32)) && (defined(O_NONBLOCK) || defined(O_NDELAY)) -#ifndef O_NONBLOCK -#define O_NONBLOCK O_NDELAY -#endif int flags; int n; @@ -395,6 +519,144 @@ PHPAPI int php_connect_nonb_win32(SOCKET sockfd, /* }}} */ #endif +/* {{{ sub_times */ +static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result) +{ + result->tv_usec = a.tv_usec - b.tv_usec; + if (result->tv_usec < 0L) { + a.tv_sec--; + result->tv_usec += 1000000L; + } + result->tv_sec = a.tv_sec - b.tv_sec; + if (result->tv_sec < 0L) { + result->tv_sec++; + result->tv_usec -= 1000000L; + } +} +/* }}} */ + +/* Connect to a remote host using an interruptible connect with optional timeout. + * Optionally, the connect can be made asynchronously, which will implicitly + * enable non-blocking mode on the socket. + * Returns the connected (or connecting) socket, or -1 on failure. + * */ + +/* {{{ php_network_connect_socket_to_host */ +int php_network_connect_socket_to_host(const char *host, unsigned short port, + int socktype, int asynchronous, struct timeval *timeout, char **error_string, + int *error_code + TSRMLS_DC) +{ + int num_addrs, sock, n, fatal = 0; + struct sockaddr **sal, **psal, *sa; + struct timeval working_timeout; + socklen_t socklen; +#if HAVE_GETTIMEOFDAY + struct timeval limit_time, time_now; +#endif + + num_addrs = php_network_getaddresses(host, &psal, error_string TSRMLS_CC); + + if (num_addrs == 0) { + /* could not resolve address(es) */ + return -1; + } + + if (timeout) { + memcpy(&working_timeout, timeout, sizeof(working_timeout)); +#if HAVE_GETTIMEOFDAY + gettimeofday(&limit_time, NULL); + limit_time.tv_sec += working_timeout.tv_sec; + limit_time.tv_usec += working_timeout.tv_usec; + if (limit_time.tv_usec >= 1000000) { + limit_time.tv_usec -= 1000000; + limit_time.tv_sec++; + } +#endif + } + + for (sal = psal; !fatal && *sal != NULL; sal++) { + sa = *sal; + + /* create a socket for this address */ + sock = socket(sa->sa_family, socktype, 0); + + if (sock == SOCK_ERR) { + continue; + } + + switch (sa->sa_family) { +#if HAVE_GETADDRINFO && HAVE_IPV6 + case AF_INET6: + ((struct sockaddr_in6 *)sa)->sin6_family = sa->sa_family; + ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); + socklen = sizeof(struct sockaddr_in6); + break; +#endif + case AF_INET: + ((struct sockaddr_in *)sa)->sin_family = sa->sa_family; + ((struct sockaddr_in *)sa)->sin_port = htons(port); + socklen = sizeof(struct sockaddr_in); + break; + default: + /* Unknown family */ + sa = NULL; + } + + if (sa) { + /* make a connection attempt */ + + n = php_network_connect_socket(sock, sa, socklen, asynchronous, + timeout ? &working_timeout : NULL, + error_string, error_code); + + if (n != SOCK_CONN_ERR) { + goto connected; + } + + /* adjust timeout for next attempt */ +#if HAVE_GETTIMEOFDAY + if (timeout) { + gettimeofday(&time_now, NULL); + + if (timercmp(&time_now, &limit_time, >=)) { + /* time limit expired; don't attempt any further connections */ + fatal = 1; + } else { + /* work out remaining time */ + sub_times(limit_time, time_now, &working_timeout); + } + } +#else + if (err == PHP_TIMEOUT_ERROR_VALUE) { + /* Don't even bother trying to connect to the next alternative; + * we have no way to determine how long we have already taken + * and it is quite likely that the next attempt will fail too. */ + fatal = 1; + } else { + /* re-use the same initial timeout. + * Not the best thing, but in practice it should be good-enough */ + if (timeout) { + memcpy(&working_timeout, timeout, sizeof(working_timeout)); + } + } +#endif + } + + close(sock); + } + sock = -1; + +connected: + + php_network_freeaddresses(psal); + + return sock; +} +/* }}} */ + + + /* {{{ php_hostconnect * Creates a socket of type socktype and connects to the given host and * port, returns the created socket on success, else returns -1. @@ -408,7 +670,7 @@ int php_hostconnect(const char *host, unsigned short port, int socktype, struct int set_timeout = 0; int err; - n = php_network_getaddresses(host, &sal TSRMLS_CC); + n = php_network_getaddresses(host, &sal, NULL TSRMLS_CC); if (n == 0) return -1; @@ -546,6 +808,12 @@ int php_sockaddr_size(php_sockaddr_storage *addr) } /* }}} */ +/* Given a socket error code, if buf == NULL: + * emallocs storage for the error message and returns + * else + * sprintf message into provided buffer and returns buf + */ +/* {{{ php_socket_strerror */ PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize) { #ifndef PHP_WIN32 @@ -589,7 +857,9 @@ PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize) return buf; #endif } +/* }}} */ +/* deprecated */ PHPAPI php_stream *_php_stream_sock_open_from_socket(int socket, const char *persistent_id STREAMS_DC TSRMLS_DC) { php_stream *stream; @@ -603,7 +873,7 @@ PHPAPI php_stream *_php_stream_sock_open_from_socket(int socket, const char *per sock->timeout.tv_usec = 0; sock->socket = socket; - stream = php_stream_alloc_rel(&php_stream_socket_ops, sock, persistent_id, "r+"); + stream = php_stream_alloc_rel(&php_stream_generic_socket_ops, sock, persistent_id, "r+"); stream->flags |= PHP_STREAM_FLAG_AVOID_BLOCKING; if (stream == NULL) @@ -615,125 +885,20 @@ PHPAPI php_stream *_php_stream_sock_open_from_socket(int socket, const char *per PHPAPI php_stream *_php_stream_sock_open_host(const char *host, unsigned short port, int socktype, struct timeval *timeout, const char *persistent_id STREAMS_DC TSRMLS_DC) { - int socket; + char *res; + long reslen; php_stream *stream; - socket = php_hostconnect(host, port, socktype, timeout TSRMLS_CC); - - if (socket == -1) - return NULL; - - stream = php_stream_sock_open_from_socket_rel(socket, persistent_id); - - if (stream == NULL) - closesocket(socket); + reslen = spprintf(&res, 0, "tcp://%s:%d", host, port); - return stream; -} - -PHPAPI php_stream *_php_stream_sock_open_unix(const char *path, int pathlen, const char *persistent_id, - struct timeval *timeout STREAMS_DC TSRMLS_DC) -{ -#if defined(AF_UNIX) - int socketd; - struct sockaddr_un unix_addr; - php_stream *stream; + stream = php_stream_xport_create(res, reslen, ENFORCE_SAFE_MODE | REPORT_ERRORS, + STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, persistent_id, timeout, NULL, NULL, NULL); - socketd = socket(PF_UNIX, SOCK_STREAM, 0); - if (socketd == SOCK_ERR) - return NULL; - - 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 (pathlen >= 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. */ - pathlen = sizeof(unix_addr.sun_path) - 1; - } - - memcpy(unix_addr.sun_path, path, pathlen); + efree(res); - if (php_connect_nonb(socketd, (struct sockaddr *) &unix_addr, sizeof(unix_addr), timeout) == SOCK_CONN_ERR) - return NULL; - - stream = php_stream_sock_open_from_socket_rel(socketd, persistent_id); - if (stream == NULL) - closesocket(socketd); return stream; -#else - return NULL; -#endif -} - -#if HAVE_OPENSSL_EXT -PHPAPI int php_stream_sock_ssl_activate_with_method(php_stream *stream, int activate, SSL_METHOD *method, php_stream *session_stream TSRMLS_DC) -{ - php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; - php_netstream_data_t *psock = NULL; - SSL_CTX *ctx = NULL; - - - if (!php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_stream_sock_ssl_activate_with_method: stream is not a network stream"); - return FAILURE; - } - - if (session_stream) { - if (!php_stream_is(session_stream, PHP_STREAM_IS_SOCKET)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_stream_sock_ssl_activate_with_method: session_stream is not a network stream"); - return FAILURE; - } - psock = (php_netstream_data_t*)session_stream->abstract; - } - - if (activate == sock->ssl_active) - return SUCCESS; /* already in desired mode */ - - if (activate && sock->ssl_handle == NULL) { - ctx = SSL_CTX_new(method); - if (ctx == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_stream_sock_ssl_activate_with_method: failed to create an SSL context"); - return FAILURE; - } - - sock->ssl_handle = SSL_new(ctx); - if (sock->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_stream_sock_ssl_activate_with_method: failed to create an SSL handle"); - SSL_CTX_free(ctx); - return FAILURE; - } - - SSL_set_fd(sock->ssl_handle, sock->socket); - - if (psock) { - SSL_copy_session_id(sock->ssl_handle, psock->ssl_handle); - } - } - - if (activate) { - if (SSL_connect(sock->ssl_handle) <= 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_stream_sock_ssl_activate_with_method: SSL handshake/connection failed"); - SSL_shutdown(sock->ssl_handle); - return FAILURE; - } - sock->ssl_active = activate; - } - else { - SSL_shutdown(sock->ssl_handle); - sock->ssl_active = 0; - } - return SUCCESS; } -#endif - - PHPAPI int php_set_sock_blocking(int socketd, int block TSRMLS_DC) { int ret = SUCCESS; @@ -764,347 +929,6 @@ PHPAPI int php_set_sock_blocking(int socketd, int block TSRMLS_DC) return ret; } -#if HAVE_OPENSSL_EXT -static int handle_ssl_error(php_stream *stream, int nr_bytes TSRMLS_DC) -{ - php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; - int err = SSL_get_error(sock->ssl_handle, nr_bytes); - char esbuf[512]; - char *ebuf = NULL, *wptr = NULL; - size_t ebuf_size = 0; - unsigned long code; - int retry = 1; - - switch(err) { - case SSL_ERROR_ZERO_RETURN: - /* SSL terminated (but socket may still be active) */ - retry = 0; - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* re-negotiation, or perhaps the SSL layer needs more - * packets: retry in next iteration */ - break; - case SSL_ERROR_SYSCALL: - if (ERR_peek_error() == 0) { - if (nr_bytes == 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: fatal protocol error"); - stream->eof = 1; - retry = 0; - } else { - char *estr = php_socket_strerror(php_socket_errno(), NULL, 0); - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: %s", estr); - - efree(estr); - retry = 0; - } - break; - } - /* fall through */ - default: - /* some other error */ - while ((code = ERR_get_error()) != 0) { - /* allow room for a NUL and an optional \n */ - if (ebuf) { - esbuf[0] = '\n'; - esbuf[1] = '\0'; - ERR_error_string_n(code, esbuf + 1, sizeof(esbuf) - 2); - } else { - esbuf[0] = '\0'; - ERR_error_string_n(code, esbuf, sizeof(esbuf) - 1); - } - code = strlen(esbuf); - esbuf[code] = '\0'; - - ebuf = erealloc(ebuf, ebuf_size + code + 1); - if (wptr == NULL) { - wptr = ebuf; - } - - /* also copies the NUL */ - memcpy(wptr, esbuf, code + 1); - wptr += code; - } - - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL operation failed with code %d.%s%s", - err, - ebuf ? "OpenSSL Error messages:\n" : "", - ebuf ? ebuf : ""); - - retry = 0; - } - return retry; -} -#endif - - - -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 HAVE_OPENSSL_EXT - if (sock->ssl_active) { - int retry = 1; - - do { - didwrite = SSL_write(sock->ssl_handle, buf, count); - - if (didwrite <= 0) { - retry = handle_ssl_error(stream, didwrite TSRMLS_CC); - } else { - break; - } - } while(retry); - - } else -#endif - { - 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; -} - -#if ZEND_DEBUG && DEBUG_MAIN_NETWORK -static inline void dump_sock_state(char *msg, php_netstream_data_t *sock TSRMLS_DC) -{ - printf("%s: blocked=%d timeout_event=%d eof=%d inbuf=%d timeout=%d\n", msg, sock->is_blocked, sock->timeout_event, sock->eof, TOREAD(sock), sock->timeout); -} -# define DUMP_SOCK_STATE(msg, sock) dump_sock_state(msg, sock TSRMLS_CC) -#else -# define DUMP_SOCK_STATE(msg, sock) /* nothing */ -#endif - -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; - - 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; - -DUMP_SOCK_STATE("wait_for_data", sock); - - retval = select(sock->socket + 1, &tfdr, NULL, NULL, ptimeout); - - if (retval == 0) - sock->timeout_event = 1; - - if (retval >= 0) - break; - } -DUMP_SOCK_STATE("wait_for_data: done", sock); -} - -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; - size_t nr_bytes = 0; - -#if HAVE_OPENSSL_EXT - if (sock->ssl_active) { - int retry = 1; - - do { - nr_bytes = SSL_read(sock->ssl_handle, buf, count); - - if (nr_bytes <= 0) { - retry = handle_ssl_error(stream, nr_bytes TSRMLS_CC); - if (retry == 0 && !SSL_pending(sock->ssl_handle)) { - stream->eof = 1; - } - } else { - /* we got the data */ - break; - } - } while (retry); - } - else -#endif - { - 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 HAVE_OPENSSL_EXT - if (sock->ssl_active) { - SSL_shutdown(sock->ssl_handle); - sock->ssl_active = 0; - } - if (sock->ssl_handle) { - SSL_free(sock->ssl_handle); - sock->ssl_handle = NULL; - } -#endif - - /* 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); - - } - - 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; - - 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; - - 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 HAVE_OPENSSL_EXT - if (sock->ssl_active) - return FAILURE; -#endif - 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 HAVE_OPENSSL_EXT - if (sock->ssl_active) - return FAILURE; -#endif - if (ret) - *ret = (void*)sock->socket; - return SUCCESS; - default: - return FAILURE; - } -} - -php_stream_ops php_stream_socket_ops = { - php_sockop_write, php_sockop_read, - php_sockop_close, php_sockop_flush, - "socket", - NULL, /* seek */ - php_sockop_cast, - php_sockop_stat, - php_sockop_set_option, -}; - - - /* * Local variables: diff --git a/main/php_network.h b/main/php_network.h index fc322d93e6..57dc41f96c 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -40,6 +40,7 @@ #ifdef PHP_WIN32 #define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEWOULDBLOCK # define fsync _commit # define ftruncate(a, b) chsize(a, b) #endif /* defined(PHP_WIN32) */ @@ -109,6 +110,18 @@ typedef struct { } php_sockaddr_storage; #endif +PHPAPI int php_network_connect_socket_to_host(const char *host, unsigned short port, + int socktype, int asynchronous, struct timeval *timeout, char **error_string, + int *error_code + TSRMLS_DC); + +PHPAPI int php_network_connect_socket(int sockfd, + const struct sockaddr *addr, + socklen_t addrlen, + int asynchronous, + struct timeval *timeout, + char **error_string, + int *error_code); int php_hostconnect(const char *host, unsigned short port, int socktype, struct timeval *timeout TSRMLS_DC); PHPAPI int php_connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout); @@ -125,29 +138,19 @@ struct _php_netstream_data_t { char is_blocked; struct timeval timeout; char timeout_event; -#if HAVE_OPENSSL_EXT - /* openssl specific bits here */ - SSL *ssl_handle; - int ssl_active; -#endif }; typedef struct _php_netstream_data_t php_netstream_data_t; - -#define PHP_NETSTREAM_DATA_FROM_STREAM(stream) (php_netstream_data_t*)(stream)->abstract - extern php_stream_ops php_stream_socket_ops; +extern php_stream_ops php_stream_generic_socket_ops; #define PHP_STREAM_IS_SOCKET (&php_stream_socket_ops) PHPAPI php_stream *_php_stream_sock_open_from_socket(int socket, const char *persistent_id STREAMS_DC TSRMLS_DC ); /* open a connection to a host using php_hostconnect and return a stream */ PHPAPI php_stream *_php_stream_sock_open_host(const char *host, unsigned short port, int socktype, struct timeval *timeout, const char *persistent_id STREAMS_DC TSRMLS_DC); -PHPAPI php_stream *_php_stream_sock_open_unix(const char *path, int pathlen, const char *persistent_id, - struct timeval *timeout STREAMS_DC TSRMLS_DC); #define php_stream_sock_open_from_socket(socket, persistent) _php_stream_sock_open_from_socket((socket), (persistent) STREAMS_CC TSRMLS_CC) #define php_stream_sock_open_host(host, port, socktype, timeout, persistent) _php_stream_sock_open_host((host), (port), (socktype), (timeout), (persistent) STREAMS_CC TSRMLS_CC) -#define php_stream_sock_open_unix(path, pathlen, persistent, timeval) _php_stream_sock_open_unix((path), (pathlen), (persistent), (timeval) STREAMS_CC TSRMLS_CC) /* {{{ memory debug */ #define php_stream_sock_open_from_socket_rel(socket, persistent) _php_stream_sock_open_from_socket((socket), (persistent) STREAMS_REL_CC TSRMLS_CC) @@ -156,12 +159,6 @@ PHPAPI php_stream *_php_stream_sock_open_unix(const char *path, int pathlen, con /* }}} */ -#if HAVE_OPENSSL_EXT -PHPAPI int php_stream_sock_ssl_activate_with_method(php_stream *stream, int activate, SSL_METHOD *method, php_stream *session_stream TSRMLS_DC); -#define php_stream_sock_ssl_activate(stream, activate) php_stream_sock_ssl_activate_with_method((stream), (activate), SSLv23_client_method(), NULL TSRMLS_CC) - -#endif - #endif /* _PHP_NETWORK_H */ /* diff --git a/main/php_streams.h b/main/php_streams.h index bcb6c33f47..008bcdb1a4 100755 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -332,8 +332,22 @@ PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, voi /* whether or not locking is supported */ #define PHP_STREAM_LOCK_SUPPORTED 1 -#define php_stream_supports_lock(stream) php_stream_set_option((stream), PHP_STREAM_OPTION_LOCKING, 0, (void *) PHP_STREAM_LOCK_SUPPORTED TSRMLS_CC) == 0 ? 1 : 0 -#define php_stream_lock(stream, mode) php_stream_set_option((stream), PHP_STREAM_OPTION_LOCKING, (mode), (void *) NULL TSRMLS_CC) +#define php_stream_supports_lock(stream) _php_stream_set_option((stream), PHP_STREAM_OPTION_LOCKING, 0, (void *) PHP_STREAM_LOCK_SUPPORTED TSRMLS_CC) == 0 ? 1 : 0 +#define php_stream_lock(stream, mode) _php_stream_set_option((stream), PHP_STREAM_OPTION_LOCKING, (mode), (void *) NULL TSRMLS_CC) + +/* option code used by the php_stream_xport_XXX api */ +#define PHP_STREAM_OPTION_XPORT_API 7 /* see php_stream_transport.h */ +#define PHP_STREAM_OPTION_CRYPTO_API 8 /* see php_stream_transport.h */ +#define PHP_STREAM_OPTION_MMAP_API 9 /* see php_stream_mmap.h */ +#define PHP_STREAM_OPTION_TRUNCATE_API 10 + +#define PHP_STREAM_TRUNCATE_SUPPORTED 0 +#define PHP_STREAM_TRUNCATE_SET_SIZE 1 /* ptrparam is a pointer to a size_t */ + +#define php_stream_truncate_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SUPPORTED, NULL TSRMLS_CC) == PHP_STREAM_OPTION_RETURN_OK ? 1 : 0) + +PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC); +#define php_stream_truncate_set_size(stream, size) _php_stream_truncate_set_size((stream), (size) TSRMLS_CC) #define PHP_STREAM_OPTION_RETURN_OK 0 /* option set OK */ #define PHP_STREAM_OPTION_RETURN_ERR -1 /* problem setting option */ @@ -356,8 +370,10 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen PHPAPI size_t _php_stream_passthru(php_stream * src STREAMS_DC TSRMLS_DC); #define php_stream_passthru(stream) _php_stream_passthru((stream) STREAMS_CC TSRMLS_CC) +#include "streams/php_stream_transport.h" #include "streams/php_stream_plain_wrapper.h" #include "streams/php_stream_userspace.h" +#include "streams/php_stream_mmap.h" /* coerce the stream into some other form */ /* cast as a stdio FILE * */ diff --git a/main/streams/cast.c b/main/streams/cast.c index aa6e6d12b2..1ebc6af877 100644 --- a/main/streams/cast.c +++ b/main/streams/cast.c @@ -296,28 +296,6 @@ PHPAPI FILE * _php_stream_open_wrapper_as_file(char *path, char *mode, int optio if (stream == NULL) return NULL; - -#ifdef PHP_WIN32 - /* Avoid possible strange problems when working with socket based streams */ - if ((options & STREAM_OPEN_FOR_INCLUDE) && php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { - char buf[CHUNK_SIZE]; - - fp = php_open_temporary_file(NULL, "php", NULL TSRMLS_CC); - if (fp) { - while (!php_stream_eof(stream)) { - size_t didread = php_stream_read(stream, buf, sizeof(buf)); - if (didread > 0) { - fwrite(buf, 1, didread, fp); - } else { - break; - } - } - php_stream_close(stream); - rewind(fp); - return fp; - } - } -#endif if (php_stream_cast(stream, PHP_STREAM_AS_STDIO|PHP_STREAM_CAST_TRY_HARD|PHP_STREAM_CAST_RELEASE, (void**)&fp, REPORT_ERRORS) == FAILURE) diff --git a/main/streams/mmap.c b/main/streams/mmap.c new file mode 100644 index 0000000000..791b96436f --- /dev/null +++ b/main/streams/mmap.c @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | 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$ */ + +/* Memory Mapping interface for streams */ +#include "php.h" +#include "php_streams_int.h" + +PHPAPI char *_php_stream_mmap_range(php_stream *stream, size_t offset, size_t length, php_stream_mmap_operation_t mode, size_t *mapped_len TSRMLS_DC) +{ + php_stream_mmap_range range = { offset, length, mode, NULL }; + + /* TODO: Enforce system policy and limits for mmap sizes ? */ + + if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_MAP_RANGE, &range)) { + if (mapped_len) { + *mapped_len = range.length; + } + return range.mapped; + } + return NULL; +} + +PHPAPI int _php_stream_mmap_unmap(php_stream *stream TSRMLS_DC) +{ + return php_stream_set_option(stream, PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_UNMAP, NULL) == PHP_STREAM_OPTION_RETURN_OK ? 1 : 0; +} + +/* + * 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 + */ diff --git a/main/streams/php_stream_mmap.h b/main/streams/php_stream_mmap.h new file mode 100644 index 0000000000..ccfc767246 --- /dev/null +++ b/main/streams/php_stream_mmap.h @@ -0,0 +1,82 @@ +/* + +----------------------------------------------------------------------+ + | 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$ */ + +/* Memory Mapping interface for streams. + * The intention is to provide a uniform interface over the most common + * operations that are used within PHP itself, rather than a complete + * API for all memory mapping needs. + * + * ATM, we support only mmap(), but win32 memory mapping support will + * follow soon. + * */ + +typedef enum { + /* Does the stream support mmap ? */ + PHP_STREAM_MMAP_SUPPORTED, + /* Request a range and offset to be mapped; + * while mapped, you MUST NOT use any read/write functions + * on the stream (win9x compatibility) */ + PHP_STREAM_MMAP_MAP_RANGE, + /* Unmap the last range that was mapped for the stream */ + PHP_STREAM_MMAP_UNMAP +} php_stream_mmap_operation_t; + +typedef enum { + PHP_STREAM_MAP_MODE_READONLY, + PHP_STREAM_MAP_MODE_READWRITE, + PHP_STREAM_MAP_MODE_SHARED_READONLY, + PHP_STREAM_MAP_MODE_SHARED_READWRITE +} php_stream_mmap_access_t; + +typedef struct { + /* requested offset and length. + * If length is 0, the whole file is mapped */ + size_t offset; + size_t length; + + php_stream_mmap_access_t mode; + + /* returned mapped address */ + char *mapped; + +} php_stream_mmap_range; + +#define php_stream_mmap_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_SUPPORTED, NULL TSRMLS_CC) == 0 ? 1 : 0) + +/* Returns 1 if the stream in its current state can be memory mapped, + * 0 otherwise */ +#define php_stream_mmap_possible(stream) (!php_stream_is_filtered((stream)) && php_stream_mmap_supported((stream))) + +PHPAPI char *_php_stream_mmap_range(php_stream *stream, size_t offset, size_t length, php_stream_mmap_operation_t mode, size_t *mapped_len TSRMLS_DC); +#define php_stream_mmap_range(stream, offset, length, mode, mapped_len) _php_stream_mmap_range((stream), (offset), (length), (mode), (mapped_len) TSRMLS_CC) + +/* un-maps the last mapped range */ +PHPAPI int _php_stream_mmap_unmap(php_stream *stream TSRMLS_DC); +#define php_stream_mmap_unmap(stream) _php_stream_mmap_unmap((stream) TSRMLS_CC) + + +/* + * 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 + */ diff --git a/main/streams/php_stream_transport.h b/main/streams/php_stream_transport.h new file mode 100644 index 0000000000..89642b5652 --- /dev/null +++ b/main/streams/php_stream_transport.h @@ -0,0 +1,153 @@ +/* + +----------------------------------------------------------------------+ + | 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$ */ + +typedef php_stream *(php_stream_transport_factory_func)(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); +typedef php_stream_transport_factory_func *php_stream_transport_factory; + +PHPAPI int php_stream_xport_register(char *protocol, php_stream_transport_factory factory TSRMLS_DC); +PHPAPI int php_stream_xport_unregister(char *protocol TSRMLS_DC); + +#define STREAM_XPORT_CLIENT 0 +#define STREAM_XPORT_SERVER 1 + +#define STREAM_XPORT_CONNECT 2 +#define STREAM_XPORT_BIND 4 +#define STREAM_XPORT_LISTEN 8 +#define STREAM_XPORT_CONNECT_ASYNC 16 + +/* Open a client or server socket connection */ +PHPAPI php_stream *_php_stream_xport_create(const char *name, long namelen, int options, + int flags, const char *persistent_id, + struct timeval *timeout, + php_stream_context *context, + char **error_string, + int *error_code + STREAMS_DC TSRMLS_DC); + +#define php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode) \ + _php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode STREAMS_CC TSRMLS_CC) + +/* Bind the stream to a local address */ +PHPAPI int php_stream_xport_bind(php_stream *stream, + const char *name, long namelen, + char **error_text + TSRMLS_DC); + +/* Connect to a remote address */ +PHPAPI int php_stream_xport_connect(php_stream *stream, + const char *name, long namelen, + int asynchronous, + struct timeval *timeout, + char **error_text, + int *error_code + TSRMLS_DC); + +/* Prepare to listen */ +PHPAPI int php_stream_xport_listen(php_stream *stream, + int backlog, + char **error_text + TSRMLS_DC); + +/* Get the next client and their address as a string, or the underlying address + * structure. You must efree either of these if you request them */ +PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client, + char **textaddr, long *textaddrlen, + void **addr, size_t *addrlen, + struct timeval *timeout, + char **error_text + TSRMLS_DC); + +/* Structure definition for the set_option interface that the above functions wrap */ + +typedef struct _php_stream_xport_param { + enum { + STREAM_XPORT_OP_BIND, STREAM_XPORT_OP_CONNECT, + STREAM_XPORT_OP_LISTEN, STREAM_XPORT_OP_ACCEPT, + STREAM_XPORT_OP_CONNECT_ASYNC + } op; + int want_addr:1; + int want_textaddr:1; + int want_errortext:1; + + struct { + char *name; + long namelen; + int backlog; + struct timeval *timeout; + } inputs; + struct { + php_stream *client; + int returncode; + void *addr; + size_t addrlen; + char *textaddr; + long textaddrlen; + + char *error_text; + int error_code; + } outputs; +} php_stream_xport_param; + + +/* These functions provide crypto support on the underlying transport */ +typedef enum { + STREAM_CRYPTO_METHOD_SSLv2_CLIENT, + STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + STREAM_CRYPTO_METHOD_TLS_CLIENT, + STREAM_CRYPTO_METHOD_SSLv2_SERVER, + STREAM_CRYPTO_METHOD_SSLv3_SERVER, + STREAM_CRYPTO_METHOD_SSLv23_SERVER, + STREAM_CRYPTO_METHOD_TLS_SERVER +} php_stream_xport_crypt_method_t; + +PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream TSRMLS_DC); +PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate TSRMLS_DC); + +typedef struct _php_stream_xport_crypto_param { + enum { + STREAM_XPORT_CRYPTO_OP_SETUP, + STREAM_XPORT_CRYPTO_OP_ENABLE + } op; + struct { + int activate; + php_stream_xport_crypt_method_t method; + php_stream *session; + } inputs; + struct { + int returncode; + } outputs; +} php_stream_xport_crypto_param; + +PHPAPI HashTable *php_stream_xport_get_hash(void); +PHPAPI php_stream_transport_factory_func php_stream_generic_socket_factory; + +/* + * 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 + */ diff --git a/main/streams/php_streams_int.h b/main/streams/php_streams_int.h index 05ad1a0dc8..ddf569399d 100644 --- a/main/streams/php_streams_int.h +++ b/main/streams/php_streams_int.h @@ -56,4 +56,6 @@ extern php_stream_wrapper php_plain_files_wrapper; #define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) #endif +void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC); +void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC); diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index f73c2642aa..f121b8ce15 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -32,6 +32,9 @@ #if HAVE_SYS_FILE_H #include <sys/file.h> #endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif #include "php_streams_int.h" @@ -135,12 +138,17 @@ typedef struct { int fd; /* underlying file descriptor */ int is_process_pipe; /* use pclose instead of fclose */ int is_pipe; /* don't try and seek */ - int lock_flag; /* stores the lock state */ + int lock_flag; /* stores the lock state */ char *temp_file_name; /* if non-null, this is the path to a temporary file that * is to be deleted when the stream is closed */ #if HAVE_FLUSHIO char last_op; #endif + +#if HAVE_MMAP + char *last_mapped_addr; + size_t last_mapped_len; +#endif } php_stdio_stream_data; PHPAPI php_stream *_php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path STREAMS_DC TSRMLS_DC) @@ -348,6 +356,13 @@ static int php_stdiop_close(php_stream *stream, int close_handle TSRMLS_DC) php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract; assert(data != NULL); + +#if HAVE_MMAP + if (data->last_mapped_addr) { + munmap(data->last_mapped_addr, data->last_mapped_len); + data->last_mapped_addr = NULL; + } +#endif if (close_handle) { if (data->lock_flag != LOCK_UN) { @@ -572,8 +587,76 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void } break; + case PHP_STREAM_OPTION_MMAP_API: +#if HAVE_MMAP + { + php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam; + struct stat sbuf; + int prot, flags; + + switch (value) { + case PHP_STREAM_MMAP_SUPPORTED: + return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_MMAP_MAP_RANGE: + fstat(fd, &sbuf); + if (range->length == 0 || range->length > sbuf.st_size) { + range->length = sbuf.st_size; + } + switch (range->mode) { + case PHP_STREAM_MAP_MODE_READONLY: + prot = PROT_READ; + flags = MAP_PRIVATE; + break; + case PHP_STREAM_MAP_MODE_READWRITE: + prot = PROT_READ | PROT_WRITE; + flags = MAP_PRIVATE; + break; + case PHP_STREAM_MAP_MODE_SHARED_READONLY: + prot = PROT_READ; + flags = MAP_SHARED; + break; + case PHP_STREAM_MAP_MODE_SHARED_READWRITE: + prot = PROT_READ | PROT_WRITE; + flags = MAP_SHARED; + break; + default: + return PHP_STREAM_OPTION_RETURN_ERR; + } + range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset); + if (range->mapped == (char*)MAP_FAILED) { + range->mapped = NULL; + return PHP_STREAM_OPTION_RETURN_ERR; + } + /* remember the mapping */ + data->last_mapped_addr = range->mapped; + data->last_mapped_len = range->length; + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_MMAP_UNMAP: + if (data->last_mapped_addr) { + munmap(data->last_mapped_addr, data->last_mapped_len); + data->last_mapped_addr = NULL; + + return PHP_STREAM_OPTION_RETURN_OK; + } + return PHP_STREAM_OPTION_RETURN_ERR; + } + } +#endif + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + + case PHP_STREAM_OPTION_TRUNCATE_API: + switch (value) { + case PHP_STREAM_TRUNCATE_SUPPORTED: + return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_TRUNCATE_SET_SIZE: + return ftruncate(fd, *(size_t*)ptrparam) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + default: - return -1; + return PHP_STREAM_OPTION_RETURN_NOTIMPL; } } @@ -588,6 +671,7 @@ PHPAPI php_stream_ops php_stream_stdio_ops = { }; /* }}} */ +/* {{{ plain files opendir/readdir implementation */ static size_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { DIR *dir = (DIR*)stream->abstract; @@ -658,7 +742,7 @@ static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, char return stream; } - +/* }}} */ static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, char *path, char *mode, diff --git a/main/streams/streams.c b/main/streams/streams.c index e798684d18..b46dcc97aa 100755 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -29,9 +29,6 @@ #include "ext/standard/file.h" #include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */ #include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */ -#ifdef HAVE_SYS_MMAN_H -#include <sys/mman.h> -#endif #include <stddef.h> #include <fcntl.h> #include "php_streams_int.h" @@ -103,7 +100,7 @@ PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream * /* }}} */ /* {{{ wrapper error reporting */ -static void display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC) +void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC) { char *tmp = estrdup(path); char *msg; @@ -152,7 +149,7 @@ static void display_wrapper_errors(php_stream_wrapper *wrapper, const char *path efree(msg); } -static void tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) +void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC) { if (wrapper) { /* tidy up the error stack */ @@ -1061,52 +1058,37 @@ PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, voi return ret; } +PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC) +{ + return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize); +} + PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC) { size_t bcount = 0; - int ready = 0; char buf[8192]; -#ifdef HAVE_MMAP - int fd; -#endif + int b; -#ifdef HAVE_MMAP - if (!php_stream_is(stream, PHP_STREAM_IS_SOCKET) - && !php_stream_is_filtered(stream) - && php_stream_tell(stream) == 0 - && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fd, 0)) - { - struct stat sbuf; - off_t off; - void *p; - size_t len; - - fstat(fd, &sbuf); - - if (sbuf.st_size > sizeof(buf)) { - off = php_stream_tell(stream); - len = sbuf.st_size - off; - p = mmap(0, len, PROT_READ, MAP_SHARED, fd, off); - if (p != (void *) MAP_FAILED) { - BG(mmap_file) = p; - BG(mmap_len) = len; - PHPWRITE(p, len); - BG(mmap_file) = NULL; - munmap(p, len); - bcount += len; - ready = 1; - } + if (php_stream_mmap_possible(stream)) { + char *p; + size_t mapped; + + p = php_stream_mmap_range(stream, php_stream_tell(stream), 0, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); + + if (p) { + PHPWRITE(p, mapped); + + php_stream_mmap_unmap(stream); + + return mapped; } } -#endif - if(!ready) { - int b; - while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) { - PHPWRITE(buf, b); - bcount += b; - } + while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) { + PHPWRITE(buf, b); + bcount += b; } + return bcount; } @@ -1118,9 +1100,6 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen size_t len = 0, max_len; int step = CHUNK_SIZE; int min_room = CHUNK_SIZE / 4; -#if HAVE_MMAP - int srcfd; -#endif if (buf) *buf = NULL; @@ -1131,50 +1110,25 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen if (maxlen == PHP_STREAM_COPY_ALL) maxlen = 0; -#if HAVE_MMAP - /* try and optimize the case where we are copying from the start of a plain file. - * We could probably make this work in more situations, but I don't trust the stdio - * buffering layer. - * */ - if ( php_stream_is(src, PHP_STREAM_IS_STDIO) && - !php_stream_is_filtered(src) && - php_stream_tell(src) == 0 && - SUCCESS == php_stream_cast(src, PHP_STREAM_AS_FD, (void**)&srcfd, 0)) - { - struct stat sbuf; + if (php_stream_mmap_possible(src)) { + char *p; + size_t mapped; - if (fstat(srcfd, &sbuf) == 0) { - void *srcfile; + p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); -#if STREAM_DEBUG - fprintf(stderr, "mmap attempt: maxlen=%d filesize=%ld\n", maxlen, sbuf.st_size); -#endif - - if (maxlen > sbuf.st_size || maxlen == 0) - maxlen = sbuf.st_size; -#if STREAM_DEBUG - fprintf(stderr, "mmap attempt: will map maxlen=%d\n", maxlen); -#endif - - srcfile = mmap(NULL, maxlen, PROT_READ, MAP_SHARED, srcfd, 0); - if (srcfile != (void*)MAP_FAILED) { + if (p) { + *buf = pemalloc_rel_orig(mapped + 1, persistent); - *buf = pemalloc_rel_orig(maxlen + 1, persistent); - - if (*buf) { - memcpy(*buf, srcfile, maxlen); - (*buf)[maxlen] = '\0'; - ret = maxlen; - } + if (*buf) { + memcpy(*buf, p, mapped); + (*buf)[mapped] = '\0'; + } - munmap(srcfile, maxlen); + php_stream_mmap_unmap(src); - return ret; - } + return mapped; } - /* fall through - we might be able to copy in smaller chunks */ } -#endif ptr = *buf = pemalloc_rel_orig(step, persistent); max_len = step; @@ -1204,9 +1158,6 @@ PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size size_t haveread = 0; size_t didread; php_stream_statbuf ssbuf; -#if HAVE_MMAP - int srcfd; -#endif if (maxlen == 0) return 0; @@ -1214,41 +1165,6 @@ PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size if (maxlen == PHP_STREAM_COPY_ALL) maxlen = 0; -#if HAVE_MMAP - /* try and optimize the case where we are copying from the start of a plain file. - * We could probably make this work in more situations, but I don't trust the stdio - * buffering layer. - * */ - if ( php_stream_is(src, PHP_STREAM_IS_STDIO) && - !php_stream_is_filtered(src) && - php_stream_tell(src) == 0 && - SUCCESS == php_stream_cast(src, PHP_STREAM_AS_FD, (void**)&srcfd, 0)) - { - struct stat sbuf; - - if (fstat(srcfd, &sbuf) == 0) { - void *srcfile; - - /* in the event that the source file is 0 bytes, return 1 to indicate success - * because opening the file to write had already created a copy */ - - if(sbuf.st_size ==0) - return 1; - - if (maxlen > sbuf.st_size || maxlen == 0) - maxlen = sbuf.st_size; - - srcfile = mmap(NULL, maxlen, PROT_READ, MAP_SHARED, srcfd, 0); - if (srcfile != (void*)MAP_FAILED) { - haveread = php_stream_write(dest, srcfile, maxlen); - munmap(srcfile, maxlen); - return haveread; - } - } - /* fall through - we might be able to copy in smaller chunks */ - } -#endif - if (php_stream_stat(src, &ssbuf) == 0) { /* in the event that the source file is 0 bytes, return 1 to indicate success * because opening the file to write had already created a copy */ @@ -1257,6 +1173,21 @@ PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size } } + if (php_stream_mmap_possible(src)) { + char *p; + size_t mapped; + + p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); + + if (p) { + haveread = php_stream_write(dest, p, mapped); + + php_stream_mmap_unmap(src); + + return mapped; + } + } + while(1) { readchunk = sizeof(buf); @@ -1323,6 +1254,18 @@ int php_init_stream_wrappers(int module_number TSRMLS_DC) zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS && zend_hash_init(php_get_stream_filters_hash(), 0, NULL, NULL, 1) == SUCCESS + && + zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS + && + php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS + && + php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS +#ifdef AF_UNIX + && + php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS + && + php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS +#endif ) ? SUCCESS : FAILURE; } @@ -1330,6 +1273,7 @@ int php_shutdown_stream_wrappers(int module_number TSRMLS_DC) { zend_hash_destroy(&url_stream_wrappers_hash); zend_hash_destroy(php_get_stream_filters_hash()); + zend_hash_destroy(php_stream_xport_get_hash()); return SUCCESS; } @@ -1451,9 +1395,9 @@ PHPAPI php_stream *_php_stream_opendir(char *path, int options, php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented"); } if (stream == NULL && (options & REPORT_ERRORS)) { - display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC); + php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC); } - tidy_wrapper_error_log(wrapper TSRMLS_CC); + php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); return stream; } @@ -1498,15 +1442,13 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio if (wrapper) { - /* prepare error stack */ - wrapper->err_count = 0; - wrapper->err_stack = NULL; - stream = wrapper->wops->stream_opener(wrapper, path_to_open, mode, options ^ REPORT_ERRORS, opened_path, context STREAMS_REL_CC TSRMLS_CC); - if (stream) + + if (stream) { stream->wrapper = wrapper; + } } #if ZEND_DEBUG @@ -1554,9 +1496,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio } if (stream == NULL && (options & REPORT_ERRORS)) { - display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC); + php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC); } - tidy_wrapper_error_log(wrapper TSRMLS_CC); + php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC); #if ZEND_DEBUG if (stream == NULL && copy_of_path != NULL) { efree(copy_of_path); diff --git a/main/streams/transports.c b/main/streams/transports.c new file mode 100644 index 0000000000..dc8c5a942e --- /dev/null +++ b/main/streams/transports.c @@ -0,0 +1,341 @@ +/* + +----------------------------------------------------------------------+ + | 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 "php_streams_int.h" +#include "ext/standard/file.h" + +static HashTable xport_hash; + +PHPAPI HashTable *php_stream_xport_get_hash(void) +{ + return &xport_hash; +} + +PHPAPI int php_stream_xport_register(char *protocol, php_stream_transport_factory factory TSRMLS_DC) +{ + return zend_hash_update(&xport_hash, protocol, strlen(protocol), &factory, sizeof(factory), NULL); +} + +PHPAPI int php_stream_xport_unregister(char *protocol TSRMLS_DC) +{ + return zend_hash_del(&xport_hash, protocol, strlen(protocol)); +} + +#define ERR_REPORT(out_err, fmt, arg) \ + if (out_err) { spprintf(out_err, 0, fmt, arg); } \ + else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, arg); } + +#define ERR_RETURN(out_err, local_err, fmt) \ + if (out_err) { *out_err = local_err; } \ + else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, local_err ? local_err : "Unspecified error"); \ + if (local_err) { efree(local_err); local_err = NULL; } \ + } + +PHPAPI php_stream *_php_stream_xport_create(const char *name, long namelen, int options, + int flags, const char *persistent_id, + struct timeval *timeout, + php_stream_context *context, + char **error_string, + int *error_code + STREAMS_DC TSRMLS_DC) +{ + php_stream *stream = NULL; + php_stream_transport_factory *factory = NULL; + const char *p, *protocol = NULL; + int n = 0, failed = 0; + char *error_text = NULL; + struct timeval default_timeout = { FG(default_socket_timeout), 0 }; + + if (timeout == NULL) { + timeout = &default_timeout; + } + + /* check for a cached persistent socket */ + if (persistent_id) { + switch(php_stream_from_persistent_id(persistent_id, &stream TSRMLS_CC)) { + case PHP_STREAM_PERSISTENT_SUCCESS: + /* TODO: check if the socket is still live */ + return stream; + + case PHP_STREAM_PERSISTENT_FAILURE: + default: + /* failed; get a new one */ + } + } + + for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { + n++; + } + + if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) { + protocol = name; + name = p + 3; + namelen -= n + 3; + } else { + protocol = "tcp"; + n = 3; + } + + if (protocol) { + if (FAILURE == zend_hash_find(&xport_hash, (char*)protocol, n, (void**)&factory)) { + char wrapper_name[32]; + + if (n >= sizeof(wrapper_name)) + n = sizeof(wrapper_name) - 1; + PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n); + + ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?", + wrapper_name); + + return NULL; + } + } + + if (factory == NULL) { + /* should never happen */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not find a factory !?"); + return NULL; + } + + stream = (*factory)(protocol, n, + (char*)name, namelen, persistent_id, options, flags, timeout, + context STREAMS_REL_CC TSRMLS_CC); + + if (stream) { + stream->context = context; + + if ((flags & STREAM_XPORT_SERVER) == 0) { + /* client */ + + if (flags & STREAM_XPORT_CONNECT) { + if (0 != php_stream_xport_connect(stream, name, namelen, + flags & STREAM_XPORT_OP_CONNECT_ASYNC ? 1 : 0, + timeout, &error_text, error_code TSRMLS_CC)) { + + ERR_RETURN(error_string, error_text, "connect() failed: %s"); + + failed = 1; + } + } + + } else { + /* server */ + if (flags & STREAM_XPORT_BIND) { + if (0 != php_stream_xport_bind(stream, name, namelen, &error_text TSRMLS_CC)) { + ERR_RETURN(error_string, error_text, "bind() failed: %s"); + failed = 1; + } else if (flags & STREAM_XPORT_LISTEN) { + if (0 != php_stream_xport_listen(stream, 5, &error_text TSRMLS_CC)) { + ERR_RETURN(error_string, error_text, "listen() failed: %s"); + failed = 1; + } + } + } + } + } + + if (failed) { + /* failure means that they don't get a stream to play with */ + php_stream_close(stream); + stream = NULL; + } + + return stream; +} + +/* Bind the stream to a local address */ +PHPAPI int php_stream_xport_bind(php_stream *stream, + const char *name, long namelen, + char **error_text + TSRMLS_DC) +{ + php_stream_xport_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + param.op = STREAM_XPORT_OP_BIND; + param.inputs.name = (char*)name; + param.inputs.namelen = namelen; + param.want_errortext = error_text ? 1 : 0; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + if (error_text) { + *error_text = param.outputs.error_text; + } + + return param.outputs.returncode; + } + + return ret; +} + +/* Connect to a remote address */ +PHPAPI int php_stream_xport_connect(php_stream *stream, + const char *name, long namelen, + int asynchronous, + struct timeval *timeout, + char **error_text, + int *error_code + TSRMLS_DC) +{ + php_stream_xport_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT; + param.inputs.name = (char*)name; + param.inputs.namelen = namelen; + param.inputs.timeout = timeout; + + param.want_errortext = error_text ? 1 : 0; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + if (error_text) { + *error_text = param.outputs.error_text; + } + if (error_code) { + *error_code = param.outputs.error_code; + } + return param.outputs.returncode; + } + + return ret; + +} + +/* Prepare to listen */ +PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, char **error_text TSRMLS_DC) +{ + php_stream_xport_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + param.op = STREAM_XPORT_OP_LISTEN; + param.inputs.backlog = backlog; + param.want_errortext = error_text ? 1 : 0; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + if (error_text) { + *error_text = param.outputs.error_text; + } + + return param.outputs.returncode; + } + + return ret; +} + +/* Get the next client and their address (as a string) */ +PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client, + char **textaddr, long *textaddrlen, + void **addr, size_t *addrlen, + struct timeval *timeout, + char **error_text + TSRMLS_DC) +{ + php_stream_xport_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + + param.op = STREAM_XPORT_OP_BIND; + param.inputs.timeout = timeout; + param.want_addr = addr ? 1 : 0; + param.want_textaddr = textaddr ? 1 : 0; + param.want_errortext = error_text ? 1 : 0; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + *client = param.outputs.client; + if (addr) { + *addr = param.outputs.addr; + *addrlen = param.outputs.addrlen; + } + if (textaddr) { + *textaddr = param.outputs.textaddr; + *textaddrlen = param.outputs.textaddrlen; + } + if (error_text) { + *error_text = param.outputs.error_text; + } + + return param.outputs.returncode; + } + return ret; +} + +PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream TSRMLS_DC) +{ + php_stream_xport_crypto_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + param.op = STREAM_XPORT_CRYPTO_OP_SETUP; + param.inputs.method = crypto_method; + param.inputs.session = session_stream; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + return param.outputs.returncode; + } + + php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto"); + + return ret; +} + +PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate TSRMLS_DC) +{ + php_stream_xport_crypto_param param; + int ret; + + memset(¶m, 0, sizeof(param)); + param.op = STREAM_XPORT_CRYPTO_OP_ENABLE; + param.inputs.activate = activate; + + ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); + + if (ret == PHP_STREAM_OPTION_RETURN_OK) { + return param.outputs.returncode; + } + + php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto"); + + return ret; +} + + +/* + * 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 + */ 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 + */ |