summaryrefslogtreecommitdiff
path: root/sapi/fpm
diff options
context:
space:
mode:
Diffstat (limited to 'sapi/fpm')
-rw-r--r--sapi/fpm/fpm/fastcgi.c100
-rw-r--r--sapi/fpm/fpm/fastcgi.h2
-rw-r--r--sapi/fpm/fpm/fpm_conf.c11
-rw-r--r--sapi/fpm/fpm/fpm_env.c2
-rw-r--r--sapi/fpm/fpm/fpm_log.c4
-rw-r--r--sapi/fpm/fpm/fpm_sockets.c54
-rw-r--r--sapi/fpm/fpm/fpm_stdio.c40
-rw-r--r--sapi/fpm/fpm/fpm_unix.c1
-rw-r--r--sapi/fpm/php-fpm.8.in21
-rw-r--r--sapi/fpm/php-fpm.conf.in9
-rw-r--r--sapi/fpm/tests/002.phpt14
-rw-r--r--sapi/fpm/tests/003.phpt14
-rw-r--r--sapi/fpm/tests/004.phpt59
-rw-r--r--sapi/fpm/tests/005.phpt57
-rw-r--r--sapi/fpm/tests/006.phpt57
-rw-r--r--sapi/fpm/tests/007.phpt68
-rw-r--r--sapi/fpm/tests/008.phpt84
-rw-r--r--sapi/fpm/tests/009.phpt53
-rw-r--r--sapi/fpm/tests/010.phpt84
-rw-r--r--sapi/fpm/tests/011.phpt53
-rw-r--r--sapi/fpm/tests/012.phpt79
-rw-r--r--sapi/fpm/tests/013.phpt54
-rw-r--r--sapi/fpm/tests/014.phpt54
-rw-r--r--sapi/fpm/tests/015.phpt87
-rw-r--r--sapi/fpm/tests/016.phpt87
-rw-r--r--sapi/fpm/tests/017.phpt67
-rw-r--r--sapi/fpm/tests/019.phpt80
-rw-r--r--sapi/fpm/tests/020.phpt76
-rw-r--r--sapi/fpm/tests/fcgi.inc592
-rw-r--r--sapi/fpm/tests/include.inc34
30 files changed, 1912 insertions, 85 deletions
diff --git a/sapi/fpm/fpm/fastcgi.c b/sapi/fpm/fpm/fastcgi.c
index d77b6f8ca..d2764a59b 100644
--- a/sapi/fpm/fpm/fastcgi.c
+++ b/sapi/fpm/fpm/fastcgi.c
@@ -137,13 +137,14 @@ typedef union _sa_t {
struct sockaddr sa;
struct sockaddr_un sa_unix;
struct sockaddr_in sa_inet;
+ struct sockaddr_in6 sa_inet6;
} sa_t;
static HashTable fcgi_mgmt_vars;
static int is_initialized = 0;
static int in_shutdown = 0;
-static in_addr_t *allowed_clients = NULL;
+static sa_t *allowed_clients = NULL;
static sa_t client_sa;
@@ -266,15 +267,23 @@ void fcgi_set_allowed_clients(char *ip)
*end = 0;
end++;
}
- allowed_clients[n] = inet_addr(cur);
- if (allowed_clients[n] == INADDR_NONE) {
+ if (inet_pton(AF_INET, cur, &allowed_clients[n].sa_inet.sin_addr)>0) {
+ allowed_clients[n].sa.sa_family = AF_INET;
+ n++;
+ } else if (inet_pton(AF_INET6, cur, &allowed_clients[n].sa_inet6.sin6_addr)>0) {
+ allowed_clients[n].sa.sa_family = AF_INET6;
+ n++;
+ } else {
zlog(ZLOG_ERROR, "Wrong IP address '%s' in listen.allowed_clients", cur);
}
- n++;
cur = end;
}
- allowed_clients[n] = INADDR_NONE;
+ allowed_clients[n].sa.sa_family = 0;
free(ip);
+ if (!n) {
+ zlog(ZLOG_ERROR, "There are no allowed addresses for this pool");
+ /* don't clear allowed_clients as it will create an "open for all" security issue */
+ }
}
}
@@ -759,6 +768,43 @@ void fcgi_close(fcgi_request *req, int force, int destroy)
}
}
+static int fcgi_is_allowed() {
+ int i;
+
+ if (client_sa.sa.sa_family == AF_UNIX) {
+ return 1;
+ }
+ if (!allowed_clients) {
+ return 1;
+ }
+ if (client_sa.sa.sa_family == AF_INET) {
+ for (i=0 ; allowed_clients[i].sa.sa_family ; i++) {
+ if (allowed_clients[i].sa.sa_family == AF_INET
+ && !memcmp(&client_sa.sa_inet.sin_addr, &allowed_clients[i].sa_inet.sin_addr, 4)) {
+ return 1;
+ }
+ }
+ }
+ if (client_sa.sa.sa_family == AF_INET6) {
+ for (i=0 ; allowed_clients[i].sa.sa_family ; i++) {
+ if (allowed_clients[i].sa.sa_family == AF_INET6
+ && !memcmp(&client_sa.sa_inet6.sin6_addr, &allowed_clients[i].sa_inet6.sin6_addr, 12)) {
+ return 1;
+ }
+#ifdef IN6_IS_ADDR_V4MAPPED
+ if (allowed_clients[i].sa.sa_family == AF_INET
+ && IN6_IS_ADDR_V4MAPPED(&client_sa.sa_inet6.sin6_addr)
+ && !memcmp(((char *)&client_sa.sa_inet6.sin6_addr)+12, &allowed_clients[i].sa_inet.sin_addr, 4)) {
+ return 1;
+ }
+#endif
+ }
+ }
+
+ zlog(ZLOG_ERROR, "Connection disallowed: IP address '%s' has been dropped.", fcgi_get_last_client_ip());
+ return 0;
+}
+
int fcgi_accept_request(fcgi_request *req)
{
#ifdef _WIN32
@@ -809,23 +855,10 @@ int fcgi_accept_request(fcgi_request *req)
FCGI_UNLOCK(req->listen_socket);
client_sa = sa;
- if (sa.sa.sa_family == AF_INET && req->fd >= 0 && allowed_clients) {
- int n = 0;
- int allowed = 0;
-
- while (allowed_clients[n] != INADDR_NONE) {
- if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) {
- allowed = 1;
- break;
- }
- n++;
- }
- if (!allowed) {
- zlog(ZLOG_ERROR, "Connection disallowed: IP address '%s' has been dropped.", inet_ntoa(sa.sa_inet.sin_addr));
- closesocket(req->fd);
- req->fd = -1;
- continue;
- }
+ if (req->fd >= 0 && !fcgi_is_allowed()) {
+ closesocket(req->fd);
+ req->fd = -1;
+ continue;
}
}
@@ -1094,12 +1127,27 @@ void fcgi_free_mgmt_var_cb(void * ptr)
pefree(*var, 1);
}
-char *fcgi_get_last_client_ip() /* {{{ */
+const char *fcgi_get_last_client_ip() /* {{{ */
{
- if (client_sa.sa.sa_family == AF_UNIX) {
- return NULL;
+ static char str[INET6_ADDRSTRLEN];
+
+ /* Ipv4 */
+ if (client_sa.sa.sa_family == AF_INET) {
+ return inet_ntop(client_sa.sa.sa_family, &client_sa.sa_inet.sin_addr, str, INET6_ADDRSTRLEN);
+ }
+#ifdef IN6_IS_ADDR_V4MAPPED
+ /* Ipv4-Mapped-Ipv6 */
+ if (client_sa.sa.sa_family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED(&client_sa.sa_inet6.sin6_addr)) {
+ return inet_ntop(AF_INET, ((char *)&client_sa.sa_inet6.sin6_addr)+12, str, INET6_ADDRSTRLEN);
}
- return inet_ntoa(client_sa.sa_inet.sin_addr);
+#endif
+ /* Ipv6 */
+ if (client_sa.sa.sa_family == AF_INET6) {
+ return inet_ntop(client_sa.sa.sa_family, &client_sa.sa_inet6.sin6_addr, str, INET6_ADDRSTRLEN);
+ }
+ /* Unix socket */
+ return NULL;
}
/* }}} */
/*
diff --git a/sapi/fpm/fpm/fastcgi.h b/sapi/fpm/fpm/fastcgi.h
index 34f9eef9d..f5cfe9f66 100644
--- a/sapi/fpm/fpm/fastcgi.h
+++ b/sapi/fpm/fpm/fastcgi.h
@@ -133,7 +133,7 @@ int fcgi_flush(fcgi_request *req, int close);
void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len);
void fcgi_free_mgmt_var_cb(void * ptr);
-char *fcgi_get_last_client_ip();
+const char *fcgi_get_last_client_ip();
/*
* Local variables:
diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c
index 34e048010..1f73bc915 100644
--- a/sapi/fpm/fpm/fpm_conf.c
+++ b/sapi/fpm/fpm/fpm_conf.c
@@ -819,7 +819,7 @@ static int fpm_conf_process_all_pools() /* {{{ */
if (config->pm_start_servers <= 0) {
config->pm_start_servers = config->pm_min_spare_servers + ((config->pm_max_spare_servers - config->pm_min_spare_servers) / 2);
- zlog(ZLOG_WARNING, "[pool %s] pm.start_servers is not set. It's been set to %d.", wp->config->name, config->pm_start_servers);
+ zlog(ZLOG_NOTICE, "[pool %s] pm.start_servers is not set. It's been set to %d.", wp->config->name, config->pm_start_servers);
} else if (config->pm_start_servers < config->pm_min_spare_servers || config->pm_start_servers > config->pm_max_spare_servers) {
zlog(ZLOG_ALERT, "[pool %s] pm.start_servers(%d) must not be less than pm.min_spare_servers(%d) and not greater than pm.max_spare_servers(%d)", wp->config->name, config->pm_start_servers, config->pm_min_spare_servers, config->pm_max_spare_servers);
@@ -1159,6 +1159,7 @@ static int fpm_conf_post_process(int force_daemon TSRMLS_DC) /* {{{ */
}
fpm_globals.log_level = fpm_global_config.log_level;
+ zlog_set_level(fpm_globals.log_level);
if (fpm_global_config.process_max < 0) {
zlog(ZLOG_ERROR, "process_max can't be negative");
@@ -1199,15 +1200,15 @@ static int fpm_conf_post_process(int force_daemon TSRMLS_DC) /* {{{ */
return -1;
}
- if (0 > fpm_log_open(0)) {
+ if (0 > fpm_event_pre_init(fpm_global_config.events_mechanism)) {
return -1;
}
- if (0 > fpm_event_pre_init(fpm_global_config.events_mechanism)) {
+ if (0 > fpm_conf_process_all_pools()) {
return -1;
}
- if (0 > fpm_conf_process_all_pools()) {
+ if (0 > fpm_log_open(0)) {
return -1;
}
@@ -1256,7 +1257,7 @@ static void fpm_conf_ini_parser_include(char *inc, void *arg TSRMLS_DC) /* {{{ *
#ifdef HAVE_GLOB
{
g.gl_offs = 0;
- if ((i = glob(inc, GLOB_ERR | GLOB_MARK | GLOB_NOSORT, NULL, &g)) != 0) {
+ if ((i = glob(inc, GLOB_ERR | GLOB_MARK, NULL, &g)) != 0) {
#ifdef GLOB_NOMATCH
if (i == GLOB_NOMATCH) {
zlog(ZLOG_WARNING, "Nothing matches the include pattern '%s' from %s at line %d.", inc, filename, ini_lineno);
diff --git a/sapi/fpm/fpm/fpm_env.c b/sapi/fpm/fpm/fpm_env.c
index 2ff0bdc0e..3bdb34634 100644
--- a/sapi/fpm/fpm/fpm_env.c
+++ b/sapi/fpm/fpm/fpm_env.c
@@ -212,7 +212,7 @@ int fpm_env_init_main() /* {{{ */
#ifndef HAVE_SETPROCTITLE
#ifdef __linux__
/*
- * This piece of code has been inspirated from nginx and pureftpd code, whic
+ * This piece of code has been inspirated from nginx and pureftpd code, which
* are under BSD licence.
*
* To change the process title in Linux we have to set argv[1] to NULL
diff --git a/sapi/fpm/fpm/fpm_log.c b/sapi/fpm/fpm/fpm_log.c
index 4e1a057db..b0bf32ac1 100644
--- a/sapi/fpm/fpm/fpm_log.c
+++ b/sapi/fpm/fpm/fpm_log.c
@@ -46,6 +46,8 @@ int fpm_log_open(int reopen) /* {{{ */
if (0 > fd) {
zlog(ZLOG_SYSERROR, "failed to open access log (%s)", wp->config->access_log);
return -1;
+ } else {
+ zlog(ZLOG_DEBUG, "open access log (%s)", wp->config->access_log);
}
if (reopen) {
@@ -367,7 +369,7 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
case 'R': /* remote IP address */
if (!test) {
- char *tmp = fcgi_get_last_client_ip();
+ const char *tmp = fcgi_get_last_client_ip();
len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp ? tmp : "-");
}
break;
diff --git a/sapi/fpm/fpm/fpm_sockets.c b/sapi/fpm/fpm/fpm_sockets.c
index da14d63d8..0286f0eee 100644
--- a/sapi/fpm/fpm/fpm_sockets.c
+++ b/sapi/fpm/fpm/fpm_sockets.c
@@ -85,13 +85,24 @@ static void *fpm_get_in_addr(struct sockaddr *sa) /* {{{ */
}
/* }}} */
+static int fpm_get_in_port(struct sockaddr *sa) /* {{{ */
+{
+ if (sa->sa_family == AF_INET) {
+ return ntohs(((struct sockaddr_in*)sa)->sin_port);
+ }
+
+ return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
+}
+/* }}} */
+
static int fpm_sockets_hash_op(int sock, struct sockaddr *sa, char *key, int type, int op) /* {{{ */
{
if (key == NULL) {
switch (type) {
case FPM_AF_INET : {
- key = alloca(INET6_ADDRSTRLEN);
- inet_ntop(sa->sa_family, fpm_get_in_addr(sa), key, sizeof key);
+ key = alloca(INET6_ADDRSTRLEN+10);
+ inet_ntop(sa->sa_family, fpm_get_in_addr(sa), key, INET6_ADDRSTRLEN);
+ sprintf(key+strlen(key), ":%d", fpm_get_in_port(sa));
break;
}
@@ -244,9 +255,10 @@ static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /*
char *dup_address = strdup(wp->config->listen_address);
char *port_str = strrchr(dup_address, ':');
char *addr = NULL;
+ char tmpbuf[INET6_ADDRSTRLEN];
int addr_len;
int port = 0;
- int sock;
+ int sock = -1;
int status;
if (port_str) { /* this is host:port pair */
@@ -263,13 +275,23 @@ static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /*
return -1;
}
- // strip brackets from address for getaddrinfo
- if (addr != NULL) {
- addr_len = strlen(addr);
- if (addr[0] == '[' && addr[addr_len - 1] == ']') {
- addr[addr_len - 1] = '\0';
- addr++;
- }
+ if (!addr) {
+ /* no address: default documented behavior, all IPv4 addresses */
+ struct sockaddr_in sa_in;
+
+ memset(&sa_in, 0, sizeof(sa_in));
+ sa_in.sin_family = AF_INET;
+ sa_in.sin_port = htons(port);
+ sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ free(dup_address);
+ return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_in, sizeof(struct sockaddr_in));
+ }
+
+ /* strip brackets from address for getaddrinfo */
+ addr_len = strlen(addr);
+ if (addr[0] == '[' && addr[addr_len - 1] == ']') {
+ addr[addr_len - 1] = '\0';
+ addr++;
}
memset(&hints, 0, sizeof hints);
@@ -281,14 +303,18 @@ static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /*
return -1;
}
- free(dup_address);
-
for (p = servinfo; p != NULL; p = p->ai_next) {
- if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) {
- break;
+ inet_ntop(p->ai_family, fpm_get_in_addr(p->ai_addr), tmpbuf, INET6_ADDRSTRLEN);
+ if (sock < 0) {
+ if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) {
+ zlog(ZLOG_DEBUG, "Found address for %s, socket opened on %s", dup_address, tmpbuf);
+ }
+ } else {
+ zlog(ZLOG_WARNING, "Found multiple addresses for %s, %s ignored", dup_address, tmpbuf);
}
}
+ free(dup_address);
freeaddrinfo(servinfo);
return sock;
diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c
index fcec78435..e28c0cbe7 100644
--- a/sapi/fpm/fpm/fpm_stdio.c
+++ b/sapi/fpm/fpm/fpm_stdio.c
@@ -42,9 +42,28 @@ int fpm_stdio_init_main() /* {{{ */
}
/* }}} */
+static inline int fpm_use_error_log() { /* {{{ */
+ /*
+ * the error_log is NOT used when running in foreground
+ * and from a tty (user looking at output).
+ * So, error_log is used by
+ * - SysV init launch php-fpm as a daemon
+ * - Systemd launch php-fpm in foreground
+ */
+#if HAVE_UNISTD_H
+ if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
+#else
+ if (fpm_global_config.daemonize) {
+#endif
+ return 1;
+ }
+ return 0;
+}
+
+/* }}} */
int fpm_stdio_init_final() /* {{{ */
{
- if (fpm_global_config.daemonize) {
+ if (fpm_use_error_log()) {
/* prevent duping if logging to syslog */
if (fpm_globals.error_log_fd > 0 && fpm_globals.error_log_fd != STDERR_FILENO) {
@@ -67,6 +86,11 @@ int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
closelog(); /* ensure to close syslog not to interrupt with PHP syslog code */
} else
#endif
+
+ /* Notice: child cannot use master error_log
+ * because not aware when being reopen
+ * else, should use if (!fpm_use_error_log())
+ */
if (fpm_globals.error_log_fd > 0) {
close(fpm_globals.error_log_fd);
}
@@ -268,11 +292,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
if (!strcasecmp(fpm_global_config.error_log, "syslog")) {
openlog(fpm_global_config.syslog_ident, LOG_PID | LOG_CONS, fpm_global_config.syslog_facility);
fpm_globals.error_log_fd = ZLOG_SYSLOG;
-#if HAVE_UNISTD_H
- if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
-#else
- if (fpm_global_config.daemonize) {
-#endif
+ if (fpm_use_error_log()) {
zlog_set_fd(fpm_globals.error_log_fd);
}
return 0;
@@ -286,7 +306,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
}
if (reopen) {
- if (fpm_global_config.daemonize) {
+ if (fpm_use_error_log()) {
dup2(fd, STDERR_FILENO);
}
@@ -295,11 +315,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
fd = fpm_globals.error_log_fd; /* for FD_CLOSEXEC to work */
} else {
fpm_globals.error_log_fd = fd;
-#if HAVE_UNISTD_H
- if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
-#else
- if (fpm_global_config.daemonize) {
-#endif
+ if (fpm_use_error_log()) {
zlog_set_fd(fpm_globals.error_log_fd);
}
}
diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c
index 68978ee75..32448fc4d 100644
--- a/sapi/fpm/fpm/fpm_unix.c
+++ b/sapi/fpm/fpm/fpm_unix.c
@@ -396,7 +396,6 @@ int fpm_unix_init_main() /* {{{ */
}
}
- zlog_set_level(fpm_globals.log_level);
return 0;
}
/* }}} */
diff --git a/sapi/fpm/php-fpm.8.in b/sapi/fpm/php-fpm.8.in
index b02aa25ba..3a697c6a6 100644
--- a/sapi/fpm/php-fpm.8.in
+++ b/sapi/fpm/php-fpm.8.in
@@ -84,6 +84,13 @@ Version number
Specify alternative prefix path (the default is @php_fpm_prefix@)
.TP
.PD 0
+.B \-\-pid \fIfile\fP
+.TP
+.PD 1
+.B \-g
+Specify the PID file location.
+.TP
+.PD 0
.B \-\-fpm\-config \fIfile\fP
.TP
.PD 1
@@ -113,12 +120,18 @@ Force to run in background and ignore daemonize option from configuration file.
Force to stay in foreground and ignore daemonize option from configuration file.
.TP
.PD 0
-.B \-\-zend\-extension \fIfile\fP
+.B \-\-force-stderr
.TP
.PD 1
-.B \-z \fIfile\fP
-Load Zend extension
-.IR file
+.B \-O
+Force output to stderr in nodaemonize even if stderr is not a TTY.
+.TP
+.PD 0
+.B \-\-allow\-to\-run\-as\-root
+.TP
+.PD 1
+.B \-R
+Allow pool to run as root (disabled by default)
.SH FILES
.TP 15
.B php-fpm.conf
diff --git a/sapi/fpm/php-fpm.conf.in b/sapi/fpm/php-fpm.conf.in
index 631bc46f4..833a4f4f8 100644
--- a/sapi/fpm/php-fpm.conf.in
+++ b/sapi/fpm/php-fpm.conf.in
@@ -131,6 +131,7 @@
; Per pool prefix
; It only applies on the following directives:
+; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
@@ -150,12 +151,14 @@ group = @php_fpm_group@
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
-; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on
+; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
-; 'port' - to listen on a TCP socket to all addresses on a
+; 'port' - to listen on a TCP socket to all IPv4 addresses on a
; specific port;
+; '[::]:port' - to listen on a TCP socket to all addresses
+; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000
@@ -173,7 +176,7 @@ listen = 127.0.0.1:9000
;listen.group = @php_fpm_group@
;listen.mode = 0660
-; List of ipv4 addresses of FastCGI clients which are allowed to connect.
+; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
diff --git a/sapi/fpm/tests/002.phpt b/sapi/fpm/tests/002.phpt
index 2ef6cedc3..89e431849 100644
--- a/sapi/fpm/tests/002.phpt
+++ b/sapi/fpm/tests/002.phpt
@@ -8,12 +8,13 @@ FPM: Startup and connect
include "include.inc";
$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
$cfg = <<<EOT
[global]
error_log = $logfile
[unconfined]
-listen = 127.0.0.1:9000
+listen = 127.0.0.1:$port
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
@@ -23,10 +24,9 @@ EOT;
$fpm = run_fpm($cfg, $tail);
if (is_resource($fpm)) {
- var_dump(fgets($tail));
- var_dump(fgets($tail));
+ fpm_display_log($tail, 2);
$i = 0;
- while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', 9000))) {
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
usleep(10000);
}
if ($fp) {
@@ -41,10 +41,8 @@ if (is_resource($fpm)) {
?>
--EXPECTF--
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-"
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-"
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
Done
--CLEAN--
<?php
diff --git a/sapi/fpm/tests/003.phpt b/sapi/fpm/tests/003.phpt
index 389cb2401..f928626ec 100644
--- a/sapi/fpm/tests/003.phpt
+++ b/sapi/fpm/tests/003.phpt
@@ -8,12 +8,13 @@ FPM: Test IPv6 support
include "include.inc";
$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
$cfg = <<<EOT
[global]
error_log = $logfile
[unconfined]
-listen = [::1]:9000
+listen = [::1]:$port
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
@@ -23,10 +24,9 @@ EOT;
$fpm = run_fpm($cfg, $tail);
if (is_resource($fpm)) {
- var_dump(fgets($tail));
- var_dump(fgets($tail));
+ fpm_display_log($tail, 2);
$i = 0;
- while (($i++ < 30) && !($fp = fsockopen('[::1]', 9000))) {
+ while (($i++ < 30) && !($fp = fsockopen('[::1]', $port))) {
usleep(10000);
}
if ($fp) {
@@ -41,10 +41,8 @@ if (is_resource($fpm)) {
?>
--EXPECTF--
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-"
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-"
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
Done
--CLEAN--
<?php
diff --git a/sapi/fpm/tests/004.phpt b/sapi/fpm/tests/004.phpt
new file mode 100644
index 000000000..2a4d666c6
--- /dev/null
+++ b/sapi/fpm/tests/004.phpt
@@ -0,0 +1,59 @@
+--TEST--
+FPM: Test IPv4/IPv6 support
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done IPv4\n";
+ fclose($fp);
+ }
+ while (($i++ < 30) && !($fp = @fsockopen('[::1]', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done IPv6\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+Done IPv4
+Done IPv6
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/005.phpt b/sapi/fpm/tests/005.phpt
new file mode 100644
index 000000000..c565c2a9e
--- /dev/null
+++ b/sapi/fpm/tests/005.phpt
@@ -0,0 +1,57 @@
+--TEST--
+FPM: Test IPv4 allowed clients
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+listen.allowed_clients = 127.0.0.1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ run_request('[::1]', $port);
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+IPv4 ok
+IPv6 error
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/006.phpt b/sapi/fpm/tests/006.phpt
new file mode 100644
index 000000000..a12ca253d
--- /dev/null
+++ b/sapi/fpm/tests/006.phpt
@@ -0,0 +1,57 @@
+--TEST--
+FPM: Test IPv6 allowed clients (bug #68428)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+listen.allowed_clients = ::1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ run_request('[::1]', $port);
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+IPv4 error
+IPv6 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/007.phpt b/sapi/fpm/tests/007.phpt
new file mode 100644
index 000000000..0d817907c
--- /dev/null
+++ b/sapi/fpm/tests/007.phpt
@@ -0,0 +1,68 @@
+--TEST--
+FPM: Test IPv6 all addresses and access_log (bug #68421)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+access.log = $accfile
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ var_dump(strpos(run_request('[::1]', $port), 'pong'));
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+
+ echo file_get_contents($accfile);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+int(%d)
+IPv6 ok
+127.0.0.1 %s "GET /ping" 200
+::1 %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+ $accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
+ @unlink($accfile);
+?>
diff --git a/sapi/fpm/tests/008.phpt b/sapi/fpm/tests/008.phpt
new file mode 100644
index 000000000..60f1ea6eb
--- /dev/null
+++ b/sapi/fpm/tests/008.phpt
@@ -0,0 +1,84 @@
+--TEST--
+FPM: Test multi pool (dynamic + ondemand + static) (bug #68423)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port1 = 9000+PHP_INT_SIZE;
+$port2 = 9001+PHP_INT_SIZE;
+$port3 = 9002+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[pool_dynamic]
+listen = 127.0.0.1:$port1
+ping.path = /ping
+ping.response = pong-dynamic
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+[poold_ondemand]
+listen = 127.0.0.1:$port2
+ping.path = /ping
+ping.response = pong-on-demand
+pm = ondemand
+pm.max_children = 2
+pm.process_idle_timeout = 10
+[pool_static]
+listen = 127.0.0.1:$port3
+ping.path = /ping
+ping.response = pong-static
+pm = static
+pm.max_children = 2
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port1), 'pong-dynamic'));
+ echo "Dynamic ok\n";
+ } catch (Exception $e) {
+ echo "Dynamic error\n";
+ }
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port2), 'pong-on-demand'));
+ echo "OnDemand ok\n";
+ } catch (Exception $e) {
+ echo "OnDemand error\n";
+ }
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port3), 'pong-static'));
+ echo "Static ok\n";
+ } catch (Exception $e) {
+ echo "Static error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+Dynamic ok
+int(%d)
+OnDemand ok
+int(%d)
+Static ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/009.phpt b/sapi/fpm/tests/009.phpt
new file mode 100644
index 000000000..34cdffcf8
--- /dev/null
+++ b/sapi/fpm/tests/009.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: Test Unix Domain Socket
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$socket = dirname(__FILE__).'/php-fpm.sock';
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $socket
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
+ echo "UDS ok\n";
+ } catch (Exception $e) {
+ echo "UDS error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+UDS ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/010.phpt b/sapi/fpm/tests/010.phpt
new file mode 100644
index 000000000..37c778db5
--- /dev/null
+++ b/sapi/fpm/tests/010.phpt
@@ -0,0 +1,84 @@
+--TEST--
+FPM: Test status page
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = 127.0.0.1:$port
+pm.status_path = /status
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ echo run_request('127.0.0.1', $port, '/status');
+
+ $html = run_request('127.0.0.1', $port, '/status', 'html');
+ var_dump(strpos($html, 'text/html') && strpos($html, 'DOCTYPE') && strpos($html, 'PHP-FPM Status Page'));
+
+ $json = run_request('127.0.0.1', $port, '/status', 'json');
+ var_dump(strpos($json, 'application/json') && strpos($json, '"pool":"unconfined"'));
+
+ $xml = run_request('127.0.0.1', $port, '/status', 'xml');
+ var_dump(strpos($xml, 'text/xml') && strpos($xml, '<?xml'));
+
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+X-Powered-By: PHP/%s
+Expires: %s
+Cache-Control: %s
+Content-type: text/plain%s
+
+pool: unconfined
+process manager: dynamic
+start time: %s
+start since: %d
+accepted conn: 1
+listen queue: 0
+max listen queue: 0
+listen queue len: %d
+idle processes: 1
+active processes: 1
+total processes: 2
+max active processes: 1
+max children reached: 0
+slow requests: 0
+
+bool(true)
+bool(true)
+bool(true)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/011.phpt b/sapi/fpm/tests/011.phpt
new file mode 100644
index 000000000..0b849f873
--- /dev/null
+++ b/sapi/fpm/tests/011.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: Test IPv4 all addresses (bug #68420)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $port
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/012.phpt b/sapi/fpm/tests/012.phpt
new file mode 100644
index 000000000..d96c53081
--- /dev/null
+++ b/sapi/fpm/tests/012.phpt
@@ -0,0 +1,79 @@
+--TEST--
+FPM: Test reload configuration (bug #68442)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$pidfile = dirname(__FILE__).'/php-fpm.pid';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+pid = $pidfile
+[unconfined]
+listen = 127.0.0.1:$port
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ $pid=file_get_contents($pidfile);
+ if ($pid) {
+ exec("kill -USR2 $pid");
+ } else {
+ die("PID not found\n");
+ }
+ fpm_display_log($tail, 5);
+
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+[%d-%s-%d %d:%d:%d] NOTICE: Reloading in progress ...
+[%d-%s-%d %d:%d:%d] NOTICE: reloading: %s
+[%d-%s-%d %d:%d:%d] NOTICE: using inherited socket fd=%d, "127.0.0.1:%d"
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+ $pidfile = dirname(__FILE__).'/php-fpm.pid';
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/013.phpt b/sapi/fpm/tests/013.phpt
new file mode 100644
index 000000000..8d6a9d1d8
--- /dev/null
+++ b/sapi/fpm/tests/013.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Test for log_level in fpm_unix_init_main #68381
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = warning
+[unconfined]
+listen = 127.0.0.1:$port
+user = foo
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/014.phpt b/sapi/fpm/tests/014.phpt
new file mode 100644
index 000000000..ee0e549cc
--- /dev/null
+++ b/sapi/fpm/tests/014.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Test for pm.start_servers default calculation message being a notice and not a warning #68458
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = warning
+[unconfined]
+listen = 127.0.0.1:$port
+user = foo
+pm = dynamic
+pm.max_children = 5
+;pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/015.phpt b/sapi/fpm/tests/015.phpt
new file mode 100644
index 000000000..fba333e25
--- /dev/null
+++ b/sapi/fpm/tests/015.phpt
@@ -0,0 +1,87 @@
+--TEST--
+FPM: Test various messages on start, from master and childs
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port1 = 9000+PHP_INT_SIZE;
+$port2 = 9001+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = notice
+[pool1]
+listen = 127.0.0.1:$port1
+listen.allowed_clients=127.0.0.1
+user = foo
+pm = dynamic
+pm.max_children = 5
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+[pool2]
+listen = 127.0.0.1:$port2
+listen.allowed_clients=xxx
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port1))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ for ($i=0 ; $i<10 ; $i++) {
+ try {
+ run_request('127.0.0.1', $port1);
+ } catch (Exception $e) {
+ echo "Error 1\n";
+ }
+ }
+ try {
+ run_request('127.0.0.1', $port2);
+ } catch (Exception $e) {
+ echo "Error 2\n";
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Error 2
+[%s] NOTICE: [pool pool1] pm.start_servers is not set. It's been set to 2.
+[%s] NOTICE: [pool pool1] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Wrong IP address 'xxx' in listen.allowed_clients"
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: There are no allowed addresses for this pool"
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Connection disallowed: IP address '127.0.0.1' has been dropped."
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/016.phpt b/sapi/fpm/tests/016.phpt
new file mode 100644
index 000000000..1a9e8e757
--- /dev/null
+++ b/sapi/fpm/tests/016.phpt
@@ -0,0 +1,87 @@
+--TEST--
+FPM: Test splited configuration and load order #68391
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$logdir = __DIR__.'/conf.d';
+$port = 9000+PHP_INT_SIZE;
+
+// Main configuration
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = notice
+include = $logdir/*.conf
+EOT;
+
+// Splited configuration
+@mkdir($logdir);
+$i=$port;
+$names = ['cccc', 'aaaa', 'eeee', 'dddd', 'bbbb'];
+foreach($names as $name) {
+ $poolcfg = <<<EOT
+[$name]
+listen = 127.0.0.1:$i
+listen.allowed_clients=127.0.0.1
+user = foo
+pm = ondemand
+pm.max_children = 5
+EOT;
+ file_put_contents("$logdir/$name.conf", $poolcfg);
+ $i++;
+}
+
+// Test
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, count($names)+2);
+ $i=$port;
+ foreach($names as $name) {
+ try {
+ run_request('127.0.0.1', $i++);
+ echo "OK $name\n";
+ } catch (Exception $e) {
+ echo "Error 1\n";
+ }
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+[%s] NOTICE: [pool aaaa] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool bbbb] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool cccc] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool dddd] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool eeee] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+OK cccc
+OK aaaa
+OK eeee
+OK dddd
+OK bbbb
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $logdir = __DIR__.'/conf.d';
+ @unlink($logfile);
+ foreach(glob("$logdir/*.conf") as $name) {
+ unlink($name);
+ }
+ @rmdir($logdir);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/017.phpt b/sapi/fpm/tests/017.phpt
new file mode 100644
index 000000000..b3de089a7
--- /dev/null
+++ b/sapi/fpm/tests/017.phpt
@@ -0,0 +1,67 @@
+--TEST--
+FPM: Test fastcgi_finish_request function
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$srcfile = __DIR__.'/php-fpm.tmp.php';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = 127.0.0.1:$port
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$code = <<<EOT
+<?php
+echo "Test Start\n";
+fastcgi_finish_request();
+echo "Test End\n";
+EOT;
+file_put_contents($srcfile, $code);
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ $req = run_request('127.0.0.1', $port, $srcfile);
+ echo strstr($req, "Test Start");
+ echo "Request ok\n";
+ } catch (Exception $e) {
+ echo "Request error\n";
+ }
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Test Start
+
+Request ok
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $srcfile = __DIR__.'/php-fpm.tmp.php';
+ @unlink($logfile);
+ @unlink($srcfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/019.phpt b/sapi/fpm/tests/019.phpt
new file mode 100644
index 000000000..cdf812624
--- /dev/null
+++ b/sapi/fpm/tests/019.phpt
@@ -0,0 +1,80 @@
+--TEST--
+FPM: Test global prefix
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = 'php-fpm.log.tmp';
+$accfile = 'php-fpm.acc.tmp';
+$slwfile = 'php-fpm.slw.tmp';
+$pidfile = 'php-fpm.pid.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+pid = $pidfile
+[test]
+listen = 127.0.0.1:$port
+access.log = $accfile
+slowlog = $slwfile;
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+EOT;
+
+$fpm = run_fpm($cfg, $tail, '--prefix '.__DIR__);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "Ping ok\n";
+ } catch (Exception $e) {
+ echo "Ping error\n";
+ }
+ printf("File %s %s\n", $logfile, (file_exists(__DIR__.'/'.$logfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "exists" : "missing"));
+
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+ printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "still exists" : "removed"));
+ readfile(__DIR__.'/'.$accfile);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Ping ok
+File php-fpm.log.tmp exists
+File php-fpm.acc.tmp exists
+File php-fpm.slw.tmp exists
+File php-fpm.pid.tmp exists
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+File php-fpm.pid.tmp removed
+127.0.0.1 - %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $accfile = __DIR__.'/php-fpm.acc.tmp';
+ $slwfile = __DIR__.'/php-fpm.slw.tmp';
+ $pidfile = __DIR__.'/php-fpm.pid.tmp';
+ @unlink($logfile);
+ @unlink($accfile);
+ @unlink($slwfile);
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/020.phpt b/sapi/fpm/tests/020.phpt
new file mode 100644
index 000000000..d45eeccff
--- /dev/null
+++ b/sapi/fpm/tests/020.phpt
@@ -0,0 +1,76 @@
+--TEST--
+FPM: Test pool prefix
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$prefix = __DIR__;
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$accfile = 'php-fpm.acc.tmp';
+$slwfile = 'php-fpm.slw.tmp';
+$pidfile = __DIR__.'/php-fpm.pid.tmp';
+$port = 9000+PHP_INT_SIZE;
+$cfg = <<<EOT
+
+[global]
+error_log = $logfile
+pid = $pidfile
+[test]
+prefix = $prefix;
+listen = 127.0.0.1:$port
+access.log = $accfile
+slowlog = $slwfile;
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "Ping ok\n";
+ } catch (Exception $e) {
+ echo "Ping error\n";
+ }
+ printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
+
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+ readfile(__DIR__.'/'.$accfile);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Ping ok
+File php-fpm.acc.tmp exists
+File php-fpm.slw.tmp exists
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+127.0.0.1 - %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $accfile = __DIR__.'/php-fpm.acc.tmp';
+ $slwfile = __DIR__.'/php-fpm.slw.tmp';
+ $pidfile = __DIR__.'/php-fpm.pid.tmp';
+ @unlink($logfile);
+ @unlink($accfile);
+ @unlink($slwfile);
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/fcgi.inc b/sapi/fpm/tests/fcgi.inc
new file mode 100644
index 000000000..b31676260
--- /dev/null
+++ b/sapi/fpm/tests/fcgi.inc
@@ -0,0 +1,592 @@
+<?php
+/*
+ * This file is part of PHP-FastCGI-Client.
+ *
+ * (c) Pierrick Charron <pierrick@adoy.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+namespace Adoy\FastCGI;
+
+class TimedOutException extends \Exception {}
+class ForbiddenException extends \Exception {}
+
+/**
+ * Handles communication with a FastCGI application
+ *
+ * @author Pierrick Charron <pierrick@adoy.net>
+ * @version 1.0
+ */
+class Client
+{
+ const VERSION_1 = 1;
+
+ const BEGIN_REQUEST = 1;
+ const ABORT_REQUEST = 2;
+ const END_REQUEST = 3;
+ const PARAMS = 4;
+ const STDIN = 5;
+ const STDOUT = 6;
+ const STDERR = 7;
+ const DATA = 8;
+ const GET_VALUES = 9;
+ const GET_VALUES_RESULT = 10;
+ const UNKNOWN_TYPE = 11;
+ const MAXTYPE = self::UNKNOWN_TYPE;
+
+ const RESPONDER = 1;
+ const AUTHORIZER = 2;
+ const FILTER = 3;
+
+ const REQUEST_COMPLETE = 0;
+ const CANT_MPX_CONN = 1;
+ const OVERLOADED = 2;
+ const UNKNOWN_ROLE = 3;
+
+ const MAX_CONNS = 'MAX_CONNS';
+ const MAX_REQS = 'MAX_REQS';
+ const MPXS_CONNS = 'MPXS_CONNS';
+
+ const HEADER_LEN = 8;
+
+ const REQ_STATE_WRITTEN = 1;
+ const REQ_STATE_OK = 2;
+ const REQ_STATE_ERR = 3;
+ const REQ_STATE_TIMED_OUT = 4;
+
+ /**
+ * Socket
+ * @var Resource
+ */
+ private $_sock = null;
+
+ /**
+ * Host
+ * @var String
+ */
+ private $_host = null;
+
+ /**
+ * Port
+ * @var Integer
+ */
+ private $_port = null;
+
+ /**
+ * Keep Alive
+ * @var Boolean
+ */
+ private $_keepAlive = false;
+
+ /**
+ * Outstanding request statuses keyed by request id
+ *
+ * Each request is an array with following form:
+ *
+ * array(
+ * 'state' => REQ_STATE_*
+ * 'response' => null | string
+ * )
+ *
+ * @var array
+ */
+ private $_requests = array();
+
+ /**
+ * Use persistent sockets to connect to backend
+ * @var Boolean
+ */
+ private $_persistentSocket = false;
+
+ /**
+ * Connect timeout in milliseconds
+ * @var Integer
+ */
+ private $_connectTimeout = 5000;
+
+ /**
+ * Read/Write timeout in milliseconds
+ * @var Integer
+ */
+ private $_readWriteTimeout = 5000;
+
+ /**
+ * Constructor
+ *
+ * @param String $host Host of the FastCGI application
+ * @param Integer $port Port of the FastCGI application
+ */
+ public function __construct($host, $port)
+ {
+ $this->_host = $host;
+ $this->_port = $port;
+ }
+
+ /**
+ * Define whether or not the FastCGI application should keep the connection
+ * alive at the end of a request
+ *
+ * @param Boolean $b true if the connection should stay alive, false otherwise
+ */
+ public function setKeepAlive($b)
+ {
+ $this->_keepAlive = (boolean)$b;
+ if (!$this->_keepAlive && $this->_sock) {
+ fclose($this->_sock);
+ }
+ }
+
+ /**
+ * Get the keep alive status
+ *
+ * @return Boolean true if the connection should stay alive, false otherwise
+ */
+ public function getKeepAlive()
+ {
+ return $this->_keepAlive;
+ }
+
+ /**
+ * Define whether or not PHP should attempt to re-use sockets opened by previous
+ * request for efficiency
+ *
+ * @param Boolean $b true if persistent socket should be used, false otherwise
+ */
+ public function setPersistentSocket($b)
+ {
+ $was_persistent = ($this->_sock && $this->_persistentSocket);
+ $this->_persistentSocket = (boolean)$b;
+ if (!$this->_persistentSocket && $was_persistent) {
+ fclose($this->_sock);
+ }
+ }
+
+ /**
+ * Get the pesistent socket status
+ *
+ * @return Boolean true if the socket should be persistent, false otherwise
+ */
+ public function getPersistentSocket()
+ {
+ return $this->_persistentSocket;
+ }
+
+
+ /**
+ * Set the connect timeout
+ *
+ * @param Integer number of milliseconds before connect will timeout
+ */
+ public function setConnectTimeout($timeoutMs)
+ {
+ $this->_connectTimeout = $timeoutMs;
+ }
+
+ /**
+ * Get the connect timeout
+ *
+ * @return Integer number of milliseconds before connect will timeout
+ */
+ public function getConnectTimeout()
+ {
+ return $this->_connectTimeout;
+ }
+
+ /**
+ * Set the read/write timeout
+ *
+ * @param Integer number of milliseconds before read or write call will timeout
+ */
+ public function setReadWriteTimeout($timeoutMs)
+ {
+ $this->_readWriteTimeout = $timeoutMs;
+ $this->set_ms_timeout($this->_readWriteTimeout);
+ }
+
+ /**
+ * Get the read timeout
+ *
+ * @return Integer number of milliseconds before read will timeout
+ */
+ public function getReadWriteTimeout()
+ {
+ return $this->_readWriteTimeout;
+ }
+
+ /**
+ * Helper to avoid duplicating milliseconds to secs/usecs in a few places
+ *
+ * @param Integer millisecond timeout
+ * @return Boolean
+ */
+ private function set_ms_timeout($timeoutMs) {
+ if (!$this->_sock) {
+ return false;
+ }
+ return stream_set_timeout($this->_sock, floor($timeoutMs / 1000), ($timeoutMs % 1000) * 1000);
+ }
+
+
+ /**
+ * Create a connection to the FastCGI application
+ */
+ private function connect()
+ {
+ if (!$this->_sock) {
+ if ($this->_persistentSocket) {
+ $this->_sock = pfsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ } else {
+ $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ }
+
+ if (!$this->_sock) {
+ throw new \Exception('Unable to connect to FastCGI application: ' . $errstr);
+ }
+
+ if (!$this->set_ms_timeout($this->_readWriteTimeout)) {
+ throw new \Exception('Unable to set timeout on socket');
+ }
+ }
+ }
+
+ /**
+ * Build a FastCGI packet
+ *
+ * @param Integer $type Type of the packet
+ * @param String $content Content of the packet
+ * @param Integer $requestId RequestId
+ */
+ private function buildPacket($type, $content, $requestId = 1)
+ {
+ $clen = strlen($content);
+ return chr(self::VERSION_1) /* version */
+ . chr($type) /* type */
+ . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
+ . chr($requestId & 0xFF) /* requestIdB0 */
+ . chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
+ . chr($clen & 0xFF) /* contentLengthB0 */
+ . chr(0) /* paddingLength */
+ . chr(0) /* reserved */
+ . $content; /* content */
+ }
+
+ /**
+ * Build an FastCGI Name value pair
+ *
+ * @param String $name Name
+ * @param String $value Value
+ * @return String FastCGI Name value pair
+ */
+ private function buildNvpair($name, $value)
+ {
+ $nlen = strlen($name);
+ $vlen = strlen($value);
+ if ($nlen < 128) {
+ /* nameLengthB0 */
+ $nvpair = chr($nlen);
+ } else {
+ /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
+ $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
+ }
+ if ($vlen < 128) {
+ /* valueLengthB0 */
+ $nvpair .= chr($vlen);
+ } else {
+ /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
+ $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
+ }
+ /* nameData & valueData */
+ return $nvpair . $name . $value;
+ }
+
+ /**
+ * Read a set of FastCGI Name value pairs
+ *
+ * @param String $data Data containing the set of FastCGI NVPair
+ * @return array of NVPair
+ */
+ private function readNvpair($data, $length = null)
+ {
+ $array = array();
+
+ if ($length === null) {
+ $length = strlen($data);
+ }
+
+ $p = 0;
+
+ while ($p != $length) {
+
+ $nlen = ord($data{$p++});
+ if ($nlen >= 128) {
+ $nlen = ($nlen & 0x7F << 24);
+ $nlen |= (ord($data{$p++}) << 16);
+ $nlen |= (ord($data{$p++}) << 8);
+ $nlen |= (ord($data{$p++}));
+ }
+ $vlen = ord($data{$p++});
+ if ($vlen >= 128) {
+ $vlen = ($nlen & 0x7F << 24);
+ $vlen |= (ord($data{$p++}) << 16);
+ $vlen |= (ord($data{$p++}) << 8);
+ $vlen |= (ord($data{$p++}));
+ }
+ $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
+ $p += ($nlen + $vlen);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Decode a FastCGI Packet
+ *
+ * @param String $data String containing all the packet
+ * @return array
+ */
+ private function decodePacketHeader($data)
+ {
+ $ret = array();
+ $ret['version'] = ord($data{0});
+ $ret['type'] = ord($data{1});
+ $ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
+ $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
+ $ret['paddingLength'] = ord($data{6});
+ $ret['reserved'] = ord($data{7});
+ return $ret;
+ }
+
+ /**
+ * Read a FastCGI Packet
+ *
+ * @return array
+ */
+ private function readPacket()
+ {
+ if ($packet = fread($this->_sock, self::HEADER_LEN)) {
+ $resp = $this->decodePacketHeader($packet);
+ $resp['content'] = '';
+ if ($resp['contentLength']) {
+ $len = $resp['contentLength'];
+ while ($len && $buf=fread($this->_sock, $len)) {
+ $len -= strlen($buf);
+ $resp['content'] .= $buf;
+ }
+ }
+ if ($resp['paddingLength']) {
+ $buf = fread($this->_sock, $resp['paddingLength']);
+ }
+ return $resp;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get Informations on the FastCGI application
+ *
+ * @param array $requestedInfo information to retrieve
+ * @return array
+ */
+ public function getValues(array $requestedInfo)
+ {
+ $this->connect();
+
+ $request = '';
+ foreach ($requestedInfo as $info) {
+ $request .= $this->buildNvpair($info, '');
+ }
+ fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
+
+ $resp = $this->readPacket();
+ if ($resp['type'] == self::GET_VALUES_RESULT) {
+ return $this->readNvpair($resp['content'], $resp['length']);
+ } else {
+ throw new \Exception('Unexpected response type, expecting GET_VALUES_RESULT');
+ }
+ }
+
+ /**
+ * Execute a request to the FastCGI application
+ *
+ * @param array $params Array of parameters
+ * @param String $stdin Content
+ * @return String
+ */
+ public function request(array $params, $stdin)
+ {
+ $id = $this->async_request($params, $stdin);
+ return $this->wait_for_response($id);
+ }
+
+ /**
+ * Execute a request to the FastCGI application asyncronously
+ *
+ * This sends request to application and returns the assigned ID for that request.
+ *
+ * You should keep this id for later use with wait_for_response(). Ids are chosen randomly
+ * rather than seqentially to guard against false-positives when using persistent sockets.
+ * In that case it is possible that a delayed response to a request made by a previous script
+ * invocation comes back on this socket and is mistaken for response to request made with same ID
+ * during this request.
+ *
+ * @param array $params Array of parameters
+ * @param String $stdin Content
+ * @return Integer
+ */
+ public function async_request(array $params, $stdin)
+ {
+ $this->connect();
+
+ // Pick random number between 1 and max 16 bit unsigned int 65535
+ $id = mt_rand(1, (1 << 16) - 1);
+
+ // Using persistent sockets implies you want them keept alive by server!
+ $keepAlive = intval($this->_keepAlive || $this->_persistentSocket);
+
+ $request = $this->buildPacket(self::BEGIN_REQUEST
+ ,chr(0) . chr(self::RESPONDER) . chr($keepAlive) . str_repeat(chr(0), 5)
+ ,$id
+ );
+
+ $paramsRequest = '';
+ foreach ($params as $key => $value) {
+ $paramsRequest .= $this->buildNvpair($key, $value, $id);
+ }
+ if ($paramsRequest) {
+ $request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
+ }
+ $request .= $this->buildPacket(self::PARAMS, '', $id);
+
+ if ($stdin) {
+ $request .= $this->buildPacket(self::STDIN, $stdin, $id);
+ }
+ $request .= $this->buildPacket(self::STDIN, '', $id);
+
+ if (fwrite($this->_sock, $request) === false || fflush($this->_sock) === false) {
+
+ $info = stream_get_meta_data($this->_sock);
+
+ if ($info['timed_out']) {
+ throw new TimedOutException('Write timed out');
+ }
+
+ // Broken pipe, tear down so future requests might succeed
+ fclose($this->_sock);
+ throw new \Exception('Failed to write request to socket');
+ }
+
+ $this->_requests[$id] = array(
+ 'state' => self::REQ_STATE_WRITTEN,
+ 'response' => null
+ );
+
+ return $id;
+ }
+
+ /**
+ * Blocking call that waits for response to specific request
+ *
+ * @param Integer $requestId
+ * @param Integer $timeoutMs [optional] the number of milliseconds to wait. Defaults to the ReadWriteTimeout value set.
+ * @return string response body
+ */
+ public function wait_for_response($requestId, $timeoutMs = 0) {
+
+ if (!isset($this->_requests[$requestId])) {
+ throw new \Exception('Invalid request id given');
+ }
+
+ // If we already read the response during an earlier call for different id, just return it
+ if ($this->_requests[$requestId]['state'] == self::REQ_STATE_OK
+ || $this->_requests[$requestId]['state'] == self::REQ_STATE_ERR
+ ) {
+ return $this->_requests[$requestId]['response'];
+ }
+
+ if ($timeoutMs > 0) {
+ // Reset timeout on socket for now
+ $this->set_ms_timeout($timeoutMs);
+ } else {
+ $timeoutMs = $this->_readWriteTimeout;
+ }
+
+ // Need to manually check since we might do several reads none of which timeout themselves
+ // but still not get the response requested
+ $startTime = microtime(true);
+
+ do {
+ $resp = $this->readPacket();
+
+ if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
+ if ($resp['type'] == self::STDERR) {
+ $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_ERR;
+ }
+ $this->_requests[$resp['requestId']]['response'] .= $resp['content'];
+ }
+ if ($resp['type'] == self::END_REQUEST) {
+ $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_OK;
+ if ($resp['requestId'] == $requestId) {
+ break;
+ }
+ }
+ if (microtime(true) - $startTime >= ($timeoutMs * 1000)) {
+ // Reset
+ $this->set_ms_timeout($this->_readWriteTimeout);
+ throw new \Exception('Timed out');
+ }
+ } while ($resp);
+
+ if (!is_array($resp)) {
+ $info = stream_get_meta_data($this->_sock);
+
+ // We must reset timeout but it must be AFTER we get info
+ $this->set_ms_timeout($this->_readWriteTimeout);
+
+ if ($info['timed_out']) {
+ throw new TimedOutException('Read timed out');
+ }
+
+ if ($info['unread_bytes'] == 0
+ && $info['blocked']
+ && $info['eof']) {
+ throw new ForbiddenException('Not in white list. Check listen.allowed_clients.');
+ }
+
+ throw new \Exception('Read failed');
+ }
+
+ // Reset timeout
+ $this->set_ms_timeout($this->_readWriteTimeout);
+
+ switch (ord($resp['content']{4})) {
+ case self::CANT_MPX_CONN:
+ throw new \Exception('This app can\'t multiplex [CANT_MPX_CONN]');
+ break;
+ case self::OVERLOADED:
+ throw new \Exception('New request rejected; too busy [OVERLOADED]');
+ break;
+ case self::UNKNOWN_ROLE:
+ throw new \Exception('Role value not known [UNKNOWN_ROLE]');
+ break;
+ case self::REQUEST_COMPLETE:
+ return $this->_requests[$requestId]['response'];
+ }
+ }
+}
diff --git a/sapi/fpm/tests/include.inc b/sapi/fpm/tests/include.inc
index 983cbd345..b195fad50 100644
--- a/sapi/fpm/tests/include.inc
+++ b/sapi/fpm/tests/include.inc
@@ -76,4 +76,36 @@ function run_fpm_till($needle, $config, $max = 10) /* {{{ */
}
/* }}} */
-?>
+function fpm_display_log($tail, $n=1, $ignore='systemd') {
+ while ($n) {
+ $a = fgets($tail);
+ if (empty($ignore) || !strpos($a, $ignore)) {
+ echo $a;
+ $n--;
+ }
+ }
+}
+
+function run_request($host, $port, $uri='/ping', $query='') {
+ require_once 'fcgi.inc';
+ $client = new Adoy\FastCGI\Client($host, $port);
+ $params = array(
+ 'GATEWAY_INTERFACE' => 'FastCGI/1.0',
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_FILENAME' => $uri,
+ 'SCRIPT_NAME' => $uri,
+ 'QUERY_STRING' => $query,
+ 'REQUEST_URI' => $uri . ($query ? '?'.$query : ""),
+ 'DOCUMENT_URI' => $uri,
+ 'SERVER_SOFTWARE' => 'php/fcgiclient',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'REMOTE_PORT' => '9985',
+ 'SERVER_ADDR' => '127.0.0.1',
+ 'SERVER_PORT' => '80',
+ 'SERVER_NAME' => php_uname('n'),
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'CONTENT_TYPE' => '',
+ 'CONTENT_LENGTH' => 0
+ );
+ return $client->request($params, false)."\n";
+}