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