xref: /PHP-7.4/sapi/fpm/fpm/fpm_stdio.c (revision 0bc6a66a)
1 	/* (c) 2007,2008 Andrei Nigmatulin */
2 
3 #include "fpm_config.h"
4 
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <string.h>
8 #include <fcntl.h>
9 #include <unistd.h>
10 #include <errno.h>
11 
12 #include "php_syslog.h"
13 
14 #include "fpm.h"
15 #include "fpm_children.h"
16 #include "fpm_cleanup.h"
17 #include "fpm_events.h"
18 #include "fpm_sockets.h"
19 #include "fpm_stdio.h"
20 #include "zlog.h"
21 
22 static int fd_stdout[2];
23 static int fd_stderr[2];
24 
fpm_stdio_init_main()25 int fpm_stdio_init_main() /* {{{ */
26 {
27 	int fd = open("/dev/null", O_RDWR);
28 
29 	if (0 > fd) {
30 		zlog(ZLOG_SYSERROR, "failed to init stdio: open(\"/dev/null\")");
31 		return -1;
32 	}
33 
34 	if (0 > dup2(fd, STDIN_FILENO) || 0 > dup2(fd, STDOUT_FILENO)) {
35 		zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()");
36 		close(fd);
37 		return -1;
38 	}
39 	close(fd);
40 	return 0;
41 }
42 /* }}} */
43 
fpm_use_error_log()44 static inline int fpm_use_error_log() {  /* {{{ */
45 	/*
46 	 * the error_log is NOT used when running in foreground
47 	 * and from a tty (user looking at output).
48 	 * So, error_log is used by
49 	 * - SysV init launch php-fpm as a daemon
50 	 * - Systemd launch php-fpm in foreground
51 	 */
52 #if HAVE_UNISTD_H
53 	if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
54 #else
55 	if (fpm_global_config.daemonize) {
56 #endif
57 		return 1;
58 	}
59 	return 0;
60 }
61 
62 /* }}} */
63 int fpm_stdio_init_final() /* {{{ */
64 {
65 	if (fpm_use_error_log()) {
66 		/* prevent duping if logging to syslog */
67 		if (fpm_globals.error_log_fd > 0 && fpm_globals.error_log_fd != STDERR_FILENO) {
68 
69 			/* there might be messages to stderr from other parts of the code, we need to log them all */
70 			if (0 > dup2(fpm_globals.error_log_fd, STDERR_FILENO)) {
71 				zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()");
72 				return -1;
73 			}
74 		}
75 #ifdef HAVE_SYSLOG_H
76 		else if (fpm_globals.error_log_fd == ZLOG_SYSLOG) {
77 			/* dup to /dev/null when using syslog */
78 			dup2(STDOUT_FILENO, STDERR_FILENO);
79 		}
80 #endif
81 	}
82 	zlog_set_launched();
83 	return 0;
84 }
85 /* }}} */
86 
87 int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
88 {
89 #ifdef HAVE_SYSLOG_H
90 	if (fpm_globals.error_log_fd == ZLOG_SYSLOG) {
91 		closelog(); /* ensure to close syslog not to interrupt with PHP syslog code */
92 	} else
93 #endif
94 
95 	/* Notice: child cannot use master error_log
96 	 * because not aware when being reopen
97 	 * else, should use if (!fpm_use_error_log())
98 	 */
99 	if (fpm_globals.error_log_fd > 0) {
100 		close(fpm_globals.error_log_fd);
101 	}
102 	fpm_globals.error_log_fd = -1;
103 	zlog_set_fd(-1);
104 
105 	return 0;
106 }
107 /* }}} */
108 
109 #define FPM_STDIO_CMD_FLUSH "\0fscf"
110 
111 int fpm_stdio_flush_child() /* {{{ */
112 {
113 	return write(STDERR_FILENO, FPM_STDIO_CMD_FLUSH, sizeof(FPM_STDIO_CMD_FLUSH));
114 }
115 /* }}} */
116 
117 static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
118 {
119 	static const int max_buf_size = 1024;
120 	int fd = ev->fd;
121 	char buf[max_buf_size];
122 	struct fpm_child_s *child;
123 	int is_stdout;
124 	struct fpm_event_s *event;
125 	int in_buf = 0, cmd_pos = 0, pos, start;
126 	int read_fail = 0, create_log_stream;
127 	struct zlog_stream *log_stream;
128 
129 	if (!arg) {
130 		return;
131 	}
132 	child = (struct fpm_child_s *)arg;
133 
134 	is_stdout = (fd == child->fd_stdout);
135 	if (is_stdout) {
136 		event = &child->ev_stdout;
137 	} else {
138 		event = &child->ev_stderr;
139 	}
140 
141 	create_log_stream = !child->log_stream;
142 	if (create_log_stream) {
143 		log_stream = child->log_stream = malloc(sizeof(struct zlog_stream));
144 		zlog_stream_init_ex(log_stream, ZLOG_WARNING, STDERR_FILENO);
145 		zlog_stream_set_decorating(log_stream, child->wp->config->decorate_workers_output);
146 		zlog_stream_set_wrapping(log_stream, ZLOG_TRUE);
147 		zlog_stream_set_msg_prefix(log_stream, STREAM_SET_MSG_PREFIX_FMT,
148 				child->wp->config->name, (int) child->pid, is_stdout ? "stdout" : "stderr");
149 		zlog_stream_set_msg_quoting(log_stream, ZLOG_TRUE);
150 		zlog_stream_set_is_stdout(log_stream, is_stdout);
151 		zlog_stream_set_child_pid(log_stream, (int)child->pid);
152 	} else {
153 		log_stream = child->log_stream;
154 		// if fd type (stdout/stderr) or child's pid is changed,
155 		// then the stream will be finished and msg's prefix will be reinitialized
156 		if (log_stream->is_stdout != (unsigned int)is_stdout || log_stream->child_pid != (int)child->pid) {
157 			zlog_stream_finish(log_stream);
158 			zlog_stream_set_msg_prefix(log_stream, STREAM_SET_MSG_PREFIX_FMT,
159 					child->wp->config->name, (int) child->pid, is_stdout ? "stdout" : "stderr");
160 			zlog_stream_set_is_stdout(log_stream, is_stdout);
161 			zlog_stream_set_child_pid(log_stream, (int)child->pid);
162 		}
163 	}
164 
165 	while (1) {
166 stdio_read:
167 		in_buf = read(fd, buf, max_buf_size - 1);
168 		if (in_buf <= 0) { /* no data */
169 			if (in_buf == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
170 				/* pipe is closed or error */
171 				read_fail = (in_buf < 0) ? in_buf : 1;
172 			}
173 			break;
174 		}
175 		start = 0;
176 		if (cmd_pos > 0) {
177 			if 	((sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos) <= in_buf &&
178 					!memcmp(buf, &FPM_STDIO_CMD_FLUSH[cmd_pos], sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos)) {
179 				zlog_stream_finish(log_stream);
180 				start = cmd_pos;
181 			} else {
182 				zlog_stream_str(log_stream, &FPM_STDIO_CMD_FLUSH[0], cmd_pos);
183 			}
184 			cmd_pos = 0;
185 		}
186 		for (pos = start; pos < in_buf; pos++) {
187 			switch (buf[pos]) {
188 				case '\n':
189 					zlog_stream_str(log_stream, buf + start, pos - start);
190 					zlog_stream_finish(log_stream);
191 					start = pos + 1;
192 					break;
193 				case '\0':
194 					if (pos + sizeof(FPM_STDIO_CMD_FLUSH) <= in_buf) {
195 						if (!memcmp(buf + pos, FPM_STDIO_CMD_FLUSH, sizeof(FPM_STDIO_CMD_FLUSH))) {
196 							zlog_stream_str(log_stream, buf + start, pos - start);
197 							zlog_stream_finish(log_stream);
198 							start = pos + sizeof(FPM_STDIO_CMD_FLUSH);
199 							pos = start - 1;
200 						}
201 					} else if (!memcmp(buf + pos, FPM_STDIO_CMD_FLUSH, in_buf - pos)) {
202 						cmd_pos = in_buf - pos;
203 						zlog_stream_str(log_stream, buf + start, pos - start);
204 						goto stdio_read;
205 					}
206 					break;
207 			}
208 		}
209 		if (start < pos) {
210 			zlog_stream_str(log_stream, buf + start, pos - start);
211 		}
212 	}
213 
214 	if (read_fail) {
215 		if (create_log_stream) {
216 			zlog_stream_set_msg_suffix(log_stream, NULL, ", pipe is closed");
217 			zlog_stream_finish(log_stream);
218 		}
219 		if (read_fail < 0) {
220 			zlog(ZLOG_SYSERROR, "unable to read what child say");
221 		}
222 
223 		fpm_event_del(event);
224 
225 		if (is_stdout) {
226 			close(child->fd_stdout);
227 			child->fd_stdout = -1;
228 		} else {
229 			close(child->fd_stderr);
230 			child->fd_stderr = -1;
231 		}
232 	}
233 }
234 /* }}} */
235 
236 int fpm_stdio_prepare_pipes(struct fpm_child_s *child) /* {{{ */
237 {
238 	if (0 == child->wp->config->catch_workers_output) { /* not required */
239 		return 0;
240 	}
241 
242 	if (0 > pipe(fd_stdout)) {
243 		zlog(ZLOG_SYSERROR, "failed to prepare the stdout pipe");
244 		return -1;
245 	}
246 
247 	if (0 > pipe(fd_stderr)) {
248 		zlog(ZLOG_SYSERROR, "failed to prepare the stderr pipe");
249 		close(fd_stdout[0]);
250 		close(fd_stdout[1]);
251 		return -1;
252 	}
253 
254 	if (0 > fd_set_blocked(fd_stdout[0], 0) || 0 > fd_set_blocked(fd_stderr[0], 0)) {
255 		zlog(ZLOG_SYSERROR, "failed to unblock pipes");
256 		close(fd_stdout[0]);
257 		close(fd_stdout[1]);
258 		close(fd_stderr[0]);
259 		close(fd_stderr[1]);
260 		return -1;
261 	}
262 	return 0;
263 }
264 /* }}} */
265 
266 int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */
267 {
268 	if (0 == child->wp->config->catch_workers_output) { /* not required */
269 		return 0;
270 	}
271 
272 	close(fd_stdout[1]);
273 	close(fd_stderr[1]);
274 
275 	child->fd_stdout = fd_stdout[0];
276 	child->fd_stderr = fd_stderr[0];
277 
278 	fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child);
279 	fpm_event_add(&child->ev_stdout, 0);
280 
281 	fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child);
282 	fpm_event_add(&child->ev_stderr, 0);
283 	return 0;
284 }
285 /* }}} */
286 
287 int fpm_stdio_discard_pipes(struct fpm_child_s *child) /* {{{ */
288 {
289 	if (0 == child->wp->config->catch_workers_output) { /* not required */
290 		return 0;
291 	}
292 
293 	close(fd_stdout[1]);
294 	close(fd_stderr[1]);
295 
296 	close(fd_stdout[0]);
297 	close(fd_stderr[0]);
298 	return 0;
299 }
300 /* }}} */
301 
302 void fpm_stdio_child_use_pipes(struct fpm_child_s *child) /* {{{ */
303 {
304 	if (child->wp->config->catch_workers_output) {
305 		dup2(fd_stdout[1], STDOUT_FILENO);
306 		dup2(fd_stderr[1], STDERR_FILENO);
307 		close(fd_stdout[0]); close(fd_stdout[1]);
308 		close(fd_stderr[0]); close(fd_stderr[1]);
309 	} else {
310 		/* stdout of parent is always /dev/null */
311 		dup2(STDOUT_FILENO, STDERR_FILENO);
312 	}
313 }
314 /* }}} */
315 
316 int fpm_stdio_open_error_log(int reopen) /* {{{ */
317 {
318 	int fd;
319 
320 #ifdef HAVE_SYSLOG_H
321 	if (!strcasecmp(fpm_global_config.error_log, "syslog")) {
322 		php_openlog(fpm_global_config.syslog_ident, LOG_PID | LOG_CONS, fpm_global_config.syslog_facility);
323 		fpm_globals.error_log_fd = ZLOG_SYSLOG;
324 		if (fpm_use_error_log()) {
325 			zlog_set_fd(fpm_globals.error_log_fd);
326 		}
327 		return 0;
328 	}
329 #endif
330 
331 	fd = open(fpm_global_config.error_log, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
332 	if (0 > fd) {
333 		zlog(ZLOG_SYSERROR, "failed to open error_log (%s)", fpm_global_config.error_log);
334 		return -1;
335 	}
336 
337 	if (reopen) {
338 		if (fpm_use_error_log()) {
339 			dup2(fd, STDERR_FILENO);
340 		}
341 
342 		dup2(fd, fpm_globals.error_log_fd);
343 		close(fd);
344 		fd = fpm_globals.error_log_fd; /* for FD_CLOSEXEC to work */
345 	} else {
346 		fpm_globals.error_log_fd = fd;
347 		if (fpm_use_error_log()) {
348 			zlog_set_fd(fpm_globals.error_log_fd);
349 		}
350 	}
351 	if (0 > fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)) {
352 		zlog(ZLOG_WARNING, "failed to change attribute of error_log");
353 	}
354 	return 0;
355 }
356 /* }}} */
357