int php_mongo_io_stream_send(mongo_connection *con, mongo_server_options *options, void *data, int size, char **error_message) { int retval; ERROR_HANDLER_DECLARATION(error_handler) TSRMLS_FETCH(); php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_WRITE, 0, size TSRMLS_CC); ERROR_HANDLER_REPLACE(error_handler, mongo_ce_ConnectionException); retval = php_stream_write(con->socket, (char *) data, size); ERROR_HANDLER_RESTORE(error_handler); if (retval >= size) { php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_COMPLETED, size, size TSRMLS_CC); } return retval; }
void* php_mongo_io_stream_connect(mongo_con_manager *manager, mongo_server_def *server, mongo_server_options *options, char **error_message) { char *errmsg; int errcode; php_stream *stream; char *hash = mongo_server_create_hash(server); struct timeval ctimeout = {0, 0}; char *dsn; int dsn_len; int tcp_socket = 1; ERROR_HANDLER_DECLARATION(error_handler) TSRMLS_FETCH(); if (server->host[0] == '/') { dsn_len = spprintf(&dsn, 0, "unix://%s", server->host); tcp_socket = 0; } else { dsn_len = spprintf(&dsn, 0, "tcp://%s:%d", server->host, server->port); } /* Connection timeout behavior varies based on the following: * - Negative => no timeout (i.e. block indefinitely) * - Zero => not specified (PHP will use default_socket_timeout) * - Positive => used specified timeout */ if (options->connectTimeoutMS) { /* Convert negative value to -1 second, which implies no timeout */ int connectTimeoutMS = options->connectTimeoutMS < 0 ? -1000 : options->connectTimeoutMS; ctimeout.tv_sec = connectTimeoutMS / 1000; ctimeout.tv_usec = (connectTimeoutMS % 1000) * 1000; mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "Connecting to %s (%s) with connection timeout: %d.%06d", dsn, hash, ctimeout.tv_sec, ctimeout.tv_usec); } else { mongo_manager_log(manager, MLOG_CON, MLOG_FINE, "Connecting to %s (%s) without connection timeout (default_socket_timeout will be used)", dsn, hash); } ERROR_HANDLER_REPLACE(error_handler, mongo_ce_ConnectionException); stream = php_stream_xport_create(dsn, dsn_len, 0, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, hash, options->connectTimeoutMS > 0 ? &ctimeout : NULL, (php_stream_context *)options->ctx, &errmsg, &errcode); ERROR_HANDLER_RESTORE(error_handler); efree(dsn); free(hash); if (!stream) { /* error_message will be free()d, but errmsg was allocated by PHP and needs efree() */ *error_message = strdup(errmsg); efree(errmsg); return NULL; } if (tcp_socket) { int socket = ((php_netstream_data_t*)stream->abstract)->socket; int flag = 1; setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); } if (options->ssl) { int crypto_enabled; ERROR_HANDLER_REPLACE(error_handler, mongo_ce_ConnectionException); if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0) { ERROR_HANDLER_RESTORE(error_handler); *error_message = strdup("Cannot setup SSL, is ext/openssl loaded?"); php_stream_close(stream); return NULL; } crypto_enabled = php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC); ERROR_HANDLER_RESTORE(error_handler); if (crypto_enabled < 0) { /* Setting up crypto failed. Thats only OK if we only preferred it */ if (options->ssl == MONGO_SSL_PREFER) { /* FIXME: We can't actually get here because we reject setting * this option to prefer in mcon/parse.c. This is however * probably what we need to do in the future when mongod starts * actually supporting this! :) */ mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "stream_connect: Failed establishing SSL for %s:%d", server->host, server->port); php_stream_xport_crypto_enable(stream, 0 TSRMLS_CC); } else { *error_message = strdup("Can't connect over SSL, is mongod running with SSL?"); php_stream_close(stream); return NULL; } } else { mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "stream_connect: Establish SSL for %s:%d", server->host, server->port); } } else { mongo_manager_log(manager, MLOG_CON, MLOG_INFO, "stream_connect: Not establishing SSL for %s:%d", server->host, server->port); } /* Socket timeout behavior uses the same logic as connectTimeoutMS */ if (options->socketTimeoutMS) { struct timeval rtimeout = {0, 0}; /* Convert negative value to -1 second, which implies no timeout */ int socketTimeoutMS = options->socketTimeoutMS < 0 ? -1000 : options->socketTimeoutMS; rtimeout.tv_sec = socketTimeoutMS / 1000; rtimeout.tv_usec = (socketTimeoutMS % 1000) * 1000; php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &rtimeout); mongo_manager_log(MonGlo(manager), MLOG_CON, MLOG_FINE, "Setting stream timeout to %d.%06d", rtimeout.tv_sec, rtimeout.tv_usec); } /* Avoid a weird leak warning in debug mode when freeing the stream */ #if ZEND_DEBUG stream->__exposed = 1; #endif return stream; }
/* Returns the bytes read on success * Returns -31 on unknown failure * Returns -80 on timeout * Returns -32 when remote server closes the connection */ int php_mongo_io_stream_read(mongo_connection *con, mongo_server_options *options, int timeout, void *data, int size, char **error_message) { int num = 1, received = 0; TSRMLS_FETCH(); int socketTimeoutMS = options->socketTimeoutMS ? options->socketTimeoutMS : FG(default_socket_timeout) * 1000; /* Convert negative values to -1 second, which implies no timeout */ socketTimeoutMS = socketTimeoutMS < 0 ? -1000 : socketTimeoutMS; timeout = timeout < 0 ? -1000 : timeout; /* Socket timeout behavior varies based on the following: * - Negative => no timeout (i.e. block indefinitely) * - Zero => not specified (no changes to existing configuration) * - Positive => used specified timeout (revert to previous value later) */ if (timeout && timeout != socketTimeoutMS) { struct timeval rtimeout = {0, 0}; rtimeout.tv_sec = timeout / 1000; rtimeout.tv_usec = (timeout % 1000) * 1000; php_stream_set_option(con->socket, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &rtimeout); mongo_manager_log(MonGlo(manager), MLOG_CON, MLOG_FINE, "Setting the stream timeout to %d.%06d", rtimeout.tv_sec, rtimeout.tv_usec); } else { mongo_manager_log(MonGlo(manager), MLOG_CON, MLOG_FINE, "No timeout changes for %s", con->hash); } php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_READ, 0, size TSRMLS_CC); /* this can return FAILED if there is just no more data from db */ while (received < size && num > 0) { int len = 4096 < (size - received) ? 4096 : size - received; ERROR_HANDLER_DECLARATION(error_handler) ERROR_HANDLER_REPLACE(error_handler, mongo_ce_ConnectionException); num = php_stream_read(con->socket, (char *) data, len); ERROR_HANDLER_RESTORE(error_handler); if (num < 0) { /* Doesn't look like this can happen, php_sockop_read overwrites * the failure from recv() to return 0 */ *error_message = strdup("Read from socket failed"); return -31; } /* It *may* have failed. It also may simply have no data */ if (num == 0) { zval *metadata; MAKE_STD_ZVAL(metadata); array_init(metadata); if (php_stream_populate_meta_data(con->socket, metadata)) { zval **tmp; if (zend_hash_find(Z_ARRVAL_P(metadata), "timed_out", sizeof("timed_out"), (void**)&tmp) == SUCCESS) { convert_to_boolean_ex(tmp); if (Z_BVAL_PP(tmp)) { struct timeval rtimeout = {0, 0}; if (timeout > 0 && options->socketTimeoutMS != timeout) { rtimeout.tv_sec = timeout / 1000; rtimeout.tv_usec = (timeout % 1000) * 1000; } else { /* Convert timeout=-1 to -1second, which PHP interprets as no timeout */ int socketTimeoutMS = options->socketTimeoutMS == -1 ? -1000 : options->socketTimeoutMS; rtimeout.tv_sec = socketTimeoutMS / 1000; rtimeout.tv_usec = (socketTimeoutMS % 1000) * 1000; } *error_message = malloc(256); snprintf(*error_message, 256, "Read timed out after reading %d bytes, waited for %d.%06d seconds", num, rtimeout.tv_sec, rtimeout.tv_usec); zval_ptr_dtor(&metadata); return -80; } } if (zend_hash_find(Z_ARRVAL_P(metadata), "eof", sizeof("eof"), (void**)&tmp) == SUCCESS) { convert_to_boolean_ex(tmp); if (Z_BVAL_PP(tmp)) { *error_message = strdup("Remote server has closed the connection"); zval_ptr_dtor(&metadata); return -32; } } } zval_ptr_dtor(&metadata); } data = (char*)data + num; received += num; } /* PHP may have sent notify-progress of *more then* 'received' in some * cases. * PHP will read 8192 byte chunks at a time, but if we request less data * then that PHP will just buffer the rest, which is fine. It could * confuse users a little, why their progress update was higher then the * max-bytes-expected though... */ php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_COMPLETED, received, size TSRMLS_CC); /* If the timeout was changed, revert to the previous value now */ if (timeout && timeout != socketTimeoutMS) { struct timeval rtimeout = {0, 0}; /* If socketTimeoutMS was never specified, revert to default_socket_timeout */ if (options->socketTimeoutMS == 0) { mongo_manager_log(MonGlo(manager), MLOG_CON, MLOG_FINE, "Stream timeout will be reverted to default_socket_timeout (%d)", FG(default_socket_timeout)); } rtimeout.tv_sec = socketTimeoutMS / 1000; rtimeout.tv_usec = (socketTimeoutMS % 1000) * 1000; php_stream_set_option(con->socket, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &rtimeout); mongo_manager_log(MonGlo(manager), MLOG_CON, MLOG_FINE, "Now setting stream timeout back to %d.%06d", rtimeout.tv_sec, rtimeout.tv_usec); } return received; }
/* Returns the bytes read on success * Returns -31 on unknown failure * Returns -80 on timeout * Returns -32 when remote server closes the connection */ int php_mongo_io_stream_read(mongo_connection *con, mongo_server_options *options, int timeout, void *data, int size, char **error_message) { int num = 1, received = 0; TSRMLS_FETCH(); if (timeout > 0 && options->socketTimeoutMS != timeout) { struct timeval rtimeout = {0, 0}; rtimeout.tv_sec = timeout / 1000; rtimeout.tv_usec = (timeout % 1000) * 1000; php_stream_set_option(con->socket, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &rtimeout); } php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_READ, 0, size TSRMLS_CC); /* this can return FAILED if there is just no more data from db */ while (received < size && num > 0) { int len = 4096 < (size - received) ? 4096 : size - received; ERROR_HANDLER_DECLARATION(error_handler) ERROR_HANDLER_REPLACE(error_handler, mongo_ce_ConnectionException); num = php_stream_read(con->socket, (char *) data, len); ERROR_HANDLER_RESTORE(error_handler); if (num < 0) { /* Doesn't look like this can happen, php_sockop_read overwrites * the failure from recv() to return 0 */ *error_message = strdup("Read from socket failed"); return -31; } /* It *may* have failed. It also may simply have no data */ if (num == 0) { zval *metadata; MAKE_STD_ZVAL(metadata); array_init(metadata); if (php_stream_populate_meta_data(con->socket, metadata)) { zval **tmp; if (zend_hash_find(Z_ARRVAL_P(metadata), "timed_out", sizeof("timed_out"), (void**)&tmp) == SUCCESS) { convert_to_boolean_ex(tmp); if (Z_BVAL_PP(tmp)) { struct timeval rtimeout = {0, 0}; if (timeout > 0 && options->socketTimeoutMS != timeout) { rtimeout.tv_sec = timeout / 1000; rtimeout.tv_usec = (timeout % 1000) * 1000; } else { rtimeout.tv_sec = options->socketTimeoutMS / 1000; rtimeout.tv_usec = (options->socketTimeoutMS % 1000) * 1000; } *error_message = malloc(256); snprintf(*error_message, 256, "Read timed out after reading %d bytes, waited for %d.%06d seconds", num, rtimeout.tv_sec, rtimeout.tv_usec); zval_ptr_dtor(&metadata); return -80; } } if (zend_hash_find(Z_ARRVAL_P(metadata), "eof", sizeof("eof"), (void**)&tmp) == SUCCESS) { convert_to_boolean_ex(tmp); if (Z_BVAL_PP(tmp)) { *error_message = strdup("Remote server has closed the connection"); zval_ptr_dtor(&metadata); return -32; } } } zval_ptr_dtor(&metadata); } data = (char*)data + num; received += num; } /* PHP may have sent notify-progress of *more then* 'received' in some * cases. * PHP will read 8192 byte chunks at a time, but if we request less data * then that PHP will just buffer the rest, which is fine. It could * confuse users a little, why their progress update was higher then the * max-bytes-expected though... */ php_mongo_stream_notify_io(options, MONGO_STREAM_NOTIFY_IO_COMPLETED, received, size TSRMLS_CC); if (timeout > 0 && options->socketTimeoutMS != timeout) { struct timeval rtimeout = {0, 0}; rtimeout.tv_sec = options->socketTimeoutMS / 1000; rtimeout.tv_usec = (options->socketTimeoutMS % 1000) * 1000; php_stream_set_option(con->socket, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &rtimeout); } return received; }