]> granicus.if.org Git - apache/blob - server/mpm_common.c
Fix a bug in how we select the IP for the POD to connect to for dummy
[apache] / server / mpm_common.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /* The purpose of this file is to store the code that MOST mpm's will need
60  * this does not mean a function only goes into this file if every MPM needs
61  * it.  It means that if a function is needed by more than one MPM, and
62  * future maintenance would be served by making the code common, then the
63  * function belongs here.
64  *
65  * This is going in src/main because it is not platform specific, it is
66  * specific to multi-process servers, but NOT to Unix.  Which is why it
67  * does not belong in src/os/unix
68  */
69
70 #include "apr.h"
71 #include "apr_thread_proc.h"
72 #include "apr_signal.h"
73 #include "apr_strings.h"
74 #include "apr_lock.h"
75 #define APR_WANT_STRFUNC
76 #include "apr_want.h"
77
78 #include "httpd.h"
79 #include "http_config.h"
80 #include "http_log.h"
81 #include "http_main.h"
82 #include "mpm.h"
83 #include "mpm_common.h"
84 #include "ap_mpm.h"
85 #include "ap_listen.h"
86
87 #ifdef AP_MPM_WANT_SET_SCOREBOARD
88 #include "scoreboard.h"
89 #endif
90
91 #ifdef HAVE_PWD_H
92 #include <pwd.h>
93 #endif
94 #ifdef HAVE_GRP_H
95 #include <grp.h>
96 #endif
97
98 #ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
99 void ap_reclaim_child_processes(int terminate)
100 {
101     int i;
102     long int waittime = 1024 * 16;      /* in usecs */
103     apr_status_t waitret;
104     int tries;
105     int not_dead_yet;
106     int max_daemons;
107
108     ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons);
109     MPM_SYNC_CHILD_TABLE();
110
111     for (tries = terminate ? 4 : 1; tries <= 9; ++tries) {
112         /* don't want to hold up progress any more than
113          * necessary, but we need to allow children a few moments to exit.
114          * Set delay with an exponential backoff.
115          */
116         waittime = waittime * 4;
117         apr_sleep(waittime);
118
119         /* now see who is done */
120         not_dead_yet = 0;
121         for (i = 0; i < max_daemons; ++i) {
122             pid_t pid = MPM_CHILD_PID(i);
123             apr_proc_t proc;
124
125             if (pid == 0)
126                 continue;
127
128             proc.pid = pid;
129             waitret = apr_proc_wait(&proc, NULL, NULL, APR_NOWAIT);
130             if (waitret != APR_CHILD_NOTDONE) {
131                 MPM_NOTE_CHILD_KILLED(i);
132                 continue;
133             }
134             ++not_dead_yet;
135             switch (tries) {
136             case 1:     /*  16ms */
137             case 2:     /*  82ms */
138             case 3:     /* 344ms */
139             case 4:     /*  16ms */
140                 break;
141             case 5:     /*  82ms */
142             case 6:     /* 344ms */
143             case 7:     /* 1.4sec */
144                 /* ok, now it's being annoying */
145                 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING,
146                              0, ap_server_conf,
147                    "child process %ld still did not exit, sending a SIGTERM",
148                              (long)pid);
149                 kill(pid, SIGTERM);
150                 break;
151             case 8:     /*  6 sec */
152                 /* die child scum */
153                 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
154                              0, ap_server_conf,
155                    "child process %ld still did not exit, sending a SIGKILL",
156                              (long)pid);
157 #ifndef BEOS
158                 kill(pid, SIGKILL);
159 #else
160                 /* sending a SIGKILL kills the entire team on BeOS, and as
161                  * httpd thread is part of that team it removes any chance
162                  * of ever doing a restart.  To counter this I'm changing to
163                  * use a kinder, gentler way of killing a specific thread
164                  * that is just as effective.
165                  */
166                 kill_thread(pid);
167 #endif
168                 break;
169             case 9:     /* 14 sec */
170                 /* gave it our best shot, but alas...  If this really
171                  * is a child we are trying to kill and it really hasn't
172                  * exited, we will likely fail to bind to the port
173                  * after the restart.
174                  */
175                 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
176                              0, ap_server_conf,
177                              "could not make child process %ld exit, "
178                              "attempting to continue anyway", (long)pid);
179                 break;
180             }
181         }
182 #if APR_HAS_OTHER_CHILD
183         apr_proc_other_child_check();
184 #endif
185         if (!not_dead_yet) {
186             /* nothing left to wait for */
187             break;
188         }
189     }
190 }
191 #endif /* AP_MPM_WANT_RECLAIM_CHILD_PROCESSES */
192
193 #ifdef AP_MPM_WANT_WAIT_OR_TIMEOUT
194
195 /* number of calls to wait_or_timeout between writable probes */
196 #ifndef INTERVAL_OF_WRITABLE_PROBES
197 #define INTERVAL_OF_WRITABLE_PROBES 10
198 #endif
199 static int wait_or_timeout_counter;
200
201 void ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, apr_proc_t *ret,
202                         apr_pool_t *p)
203 {
204     apr_status_t rv;
205
206     ++wait_or_timeout_counter;
207     if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) {
208         wait_or_timeout_counter = 0;
209     }
210     rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p);
211     if (APR_STATUS_IS_EINTR(rv)) {
212         ret->pid = -1;
213         return;
214     }
215     if (APR_STATUS_IS_CHILD_DONE(rv)) {
216         return;
217     }
218 #ifdef NEED_WAITPID
219     if ((ret = reap_children(exitcode, status)) > 0) {
220         return;
221     }
222 #endif
223     apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
224     ret->pid = -1;
225     return;
226 }
227 #endif /* AP_MPM_WANT_WAIT_OR_TIMEOUT */
228
229 #ifdef AP_MPM_WANT_PROCESS_CHILD_STATUS
230 int ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, int status)
231 {
232     int signum = status;
233     const char *sigdesc = apr_signal_get_description(signum);
234
235     /* Child died... if it died due to a fatal error,
236      * we should simply bail out.  The caller needs to
237      * check for bad rc from us and exit, running any
238      * appropriate cleanups.
239      */
240     if ((APR_PROC_CHECK_EXIT(why)) &&
241         (status == APEXIT_CHILDFATAL)) {
242         ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, 0, ap_server_conf,
243                      "Child %" APR_OS_PROC_T_FMT
244                      " returned a Fatal error..." APR_EOL_STR
245                      "Apache is exiting!",
246                      pid->pid);
247         return APEXIT_CHILDFATAL;
248     }
249
250     if (APR_PROC_CHECK_SIGNALED(why)) {
251         switch (signum) {
252         case SIGTERM:
253         case SIGHUP:
254         case AP_SIG_GRACEFUL:
255         case SIGKILL:
256             break;
257         default:
258             if (APR_PROC_CHECK_CORE_DUMP(why)) {
259                 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE,
260                              0, ap_server_conf,
261                              "child pid %ld exit signal %s (%d), "
262                              "possible coredump in %s",
263                              (long)pid->pid, sigdesc, signum,
264                              ap_coredump_dir);
265             }
266             else {
267                 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE,
268                              0, ap_server_conf,
269                              "child pid %ld exit signal %s (%d)",
270                              (long)pid->pid, sigdesc, signum);
271             }
272         }
273     }
274     return 0;
275 }
276 #endif /* AP_MPM_WANT_PROCESS_CHILD_STATUS */
277
278 #if defined(TCP_NODELAY) && !defined(MPE) && !defined(TPF) && !defined(WIN32)
279 void ap_sock_disable_nagle(apr_socket_t *s)
280 {
281     /* The Nagle algorithm says that we should delay sending partial
282      * packets in hopes of getting more data.  We don't want to do
283      * this; we are not telnet.  There are bad interactions between
284      * persistent connections and Nagle's algorithm that have very severe
285      * performance penalties.  (Failing to disable Nagle is not much of a
286      * problem with simple HTTP.)
287      *
288      * In spite of these problems, failure here is not a shooting offense.
289      */
290     apr_status_t status = apr_setsocketopt(s, APR_TCP_NODELAY, 1);
291
292     if (status != APR_SUCCESS) {
293         ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf,
294                     "setsockopt: (TCP_NODELAY)");
295     }
296 }
297 #endif
298
299 #ifdef HAVE_GETPWNAM
300 AP_DECLARE(uid_t) ap_uname2id(const char *name)
301 {
302     struct passwd *ent;
303
304     if (name[0] == '#')
305         return (atoi(&name[1]));
306
307     if (!(ent = getpwnam(name))) {
308         ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, "%s: bad user name %s", ap_server_argv0, name);
309         exit(1);
310     }
311     return (ent->pw_uid);
312 }
313 #endif
314
315 #ifdef HAVE_GETGRNAM
316 AP_DECLARE(gid_t) ap_gname2id(const char *name)
317 {
318     struct group *ent;
319
320     if (name[0] == '#')
321         return (atoi(&name[1]));
322
323     if (!(ent = getgrnam(name))) {
324         ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, "%s: bad group name %s", ap_server_argv0, name);                                               exit(1);
325     }
326     return (ent->gr_gid);
327 }
328 #endif
329
330 #ifndef HAVE_INITGROUPS
331 int initgroups(const char *name, gid_t basegid)
332 {
333 #if defined(QNX) || defined(MPE) || defined(BEOS) || defined(_OSD_POSIX) || defined(TPF) || defined(__TANDEM) || defined(OS2) || defined(WIN32) || defined(NETWARE)
334 /* QNX, MPE and BeOS do not appear to support supplementary groups. */
335     return 0;
336 #else /* ndef QNX */
337     gid_t groups[NGROUPS_MAX];
338     struct group *g;
339     int index = 0;
340
341     setgrent();
342
343     groups[index++] = basegid;
344
345     while (index < NGROUPS_MAX && ((g = getgrent()) != NULL))
346         if (g->gr_gid != basegid) {
347             char **names;
348
349             for (names = g->gr_mem; *names != NULL; ++names)
350                 if (!strcmp(*names, name))
351                     groups[index++] = g->gr_gid;
352         }
353
354     endgrent();
355
356     return setgroups(index, groups);
357 #endif /* def QNX */
358 }
359 #endif /* def NEED_INITGROUPS */
360
361 #ifdef AP_MPM_USES_POD
362
363 AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod)
364 {
365     apr_status_t rv;
366
367     *pod = apr_palloc(p, sizeof(**pod));
368     rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p);
369     if (rv != APR_SUCCESS) {
370         return rv;
371     }
372     apr_file_pipe_timeout_set((*pod)->pod_in, 0);
373     (*pod)->p = p;
374     
375     apr_sockaddr_info_get(&(*pod)->sa, ap_listeners->bind_addr->hostname,
376                           APR_UNSPEC, ap_listeners->bind_addr->port, 0, p);
377
378     return APR_SUCCESS;
379 }
380
381 AP_DECLARE(apr_status_t) ap_mpm_pod_check(ap_pod_t *pod)
382 {
383     char c;
384     apr_size_t len = 1;
385     apr_status_t rv;
386
387     rv = apr_file_read(pod->pod_in, &c, &len);
388
389     if ((rv == APR_SUCCESS) && (len == 1)) {
390         return APR_SUCCESS;
391     }
392     if (rv != APR_SUCCESS) {
393         return rv;
394     }
395     return AP_NORESTART;
396 }
397
398 AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod)
399 {
400     apr_status_t rv;
401
402     rv = apr_file_close(pod->pod_out);
403     if (rv != APR_SUCCESS) {
404         return rv;
405     }
406
407     rv = apr_file_close(pod->pod_in);
408     if (rv != APR_SUCCESS) {
409         return rv;
410     }
411     return rv;
412 }
413
414 static apr_status_t pod_signal_internal(ap_pod_t *pod)
415 {
416     apr_status_t rv;
417     char char_of_death = '!';
418     apr_size_t one = 1;
419
420     do {
421         rv = apr_file_write(pod->pod_out, &char_of_death, &one);
422     } while (APR_STATUS_IS_EINTR(rv));
423     if (rv != APR_SUCCESS) {
424         ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
425                      "write pipe_of_death");
426     }
427     return rv;
428 }
429
430 /* This function connects to the server, then immediately closes the connection.
431  * This permits the MPM to skip the poll when there is only one listening
432  * socket, because it provides a alternate way to unblock an accept() when
433  * the pod is used.
434  */
435
436 static apr_status_t dummy_connection(ap_pod_t *pod)
437 {
438     apr_status_t rv;
439     apr_socket_t *sock;
440     apr_pool_t *p;
441     
442     /* create a temporary pool for the socket.  pconf stays around too long */
443     rv = apr_pool_create(&p, pod->p);
444     if (rv != APR_SUCCESS) {
445         return rv;
446     }
447     
448     rv = apr_socket_create(&sock, pod->sa->family, SOCK_STREAM, p);
449     if (rv != APR_SUCCESS) {
450         ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
451                      "get socket to connect to listener");
452         return rv;
453     }
454     /* on some platforms (e.g., FreeBSD), the kernel won't accept many
455      * queued connections before it starts blocking local connects...
456      * we need to keep from blocking too long and instead return an error,
457      * because the MPM won't want to hold up a graceful restart for a
458      * long time
459      */
460     rv = apr_setsocketopt(sock, APR_SO_TIMEOUT, 3 * APR_USEC_PER_SEC);
461     if (rv != APR_SUCCESS) {
462         ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
463                      "set timeout on socket to connect to listener");
464         return rv;
465     }
466     
467     rv = apr_connect(sock, pod->sa);    
468     if (rv != APR_SUCCESS) {
469         int log_level = APLOG_WARNING;
470
471         if (APR_STATUS_IS_TIMEUP(rv)) {
472             /* probably some server processes bailed out already and there 
473              * is nobody around to call accept and clear out the kernel 
474              * connection queue; usually this is not worth logging
475              */
476             log_level = APLOG_DEBUG;
477         }
478         
479         ap_log_error(APLOG_MARK, log_level, rv, ap_server_conf,
480                      "connect to listener");
481     }
482
483     apr_socket_close(sock);
484     apr_pool_destroy(p);
485
486     return rv;
487 }
488
489 AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod)
490 {
491     apr_status_t rv;
492
493     rv = pod_signal_internal(pod);
494     if (rv != APR_SUCCESS) {
495         return rv;
496     }
497     return dummy_connection(pod);
498 }
499
500 void ap_mpm_pod_killpg(ap_pod_t *pod, int num)
501 {
502     int i;
503     apr_status_t rv = APR_SUCCESS;
504
505     for (i = 0; i < num && rv == APR_SUCCESS; i++) {
506         rv = pod_signal_internal(pod);
507     }
508     if (rv == APR_SUCCESS) {
509         for (i = 0; i < num && rv == APR_SUCCESS; i++) {
510              rv = dummy_connection(pod);
511         }
512     }
513 }
514 #endif /* #ifdef AP_MPM_USES_POD */
515
516 /* standard mpm configuration handling */
517 #ifdef AP_MPM_WANT_SET_PIDFILE
518 const char *ap_pid_fname = NULL;
519
520 const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy,
521                                const char *arg)
522 {
523     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
524     if (err != NULL) {
525         return err;
526     }
527
528     if (cmd->server->is_virtual) {
529         return "PidFile directive not allowed in <VirtualHost>";
530     }
531     ap_pid_fname = arg;
532     return NULL;
533 }
534 #endif
535
536 #ifdef AP_MPM_WANT_SET_SCOREBOARD
537 AP_DECLARE(const char *) ap_mpm_set_scoreboard(cmd_parms *cmd, void *dummy,
538                                                const char *arg)
539 {
540     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
541     if (err != NULL) {
542         return err;
543     }
544
545     ap_scoreboard_fname = arg;
546     return NULL;
547 }
548 #endif
549
550 #ifdef AP_MPM_WANT_SET_LOCKFILE
551 const char *ap_lock_fname = NULL;
552 AP_DECLARE(const char *) ap_mpm_set_lockfile(cmd_parms *cmd, void *dummy,
553                                              const char *arg)
554 {
555     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
556     if (err != NULL) {
557         return err;
558     }
559
560     ap_lock_fname = arg;
561     return NULL;
562 }
563 #endif
564
565 #ifdef AP_MPM_WANT_SET_MAX_REQUESTS
566 int ap_max_requests_per_child = 0;
567 const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy,
568                                     const char *arg)
569 {
570     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
571     if (err != NULL) {
572         return err;
573     }
574
575     ap_max_requests_per_child = atoi(arg);
576
577     return NULL;
578 }
579 #endif
580
581 #ifdef AP_MPM_WANT_SET_COREDUMPDIR
582 char ap_coredump_dir[MAX_STRING_LEN];
583 const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy,
584                                    const char *arg)
585 {
586     apr_finfo_t finfo;
587     const char *fname;
588     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
589     if (err != NULL) {
590         return err;
591     }
592
593     fname = ap_server_root_relative(cmd->pool, arg);
594     if ((apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool) != APR_SUCCESS)
595         || (finfo.filetype != APR_DIR)) {
596         return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname,
597                            " does not exist or is not a directory", NULL);
598     }
599     apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir));
600     return NULL;
601 }
602 #endif
603
604 #ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
605 apr_lockmech_e_np ap_accept_lock_mech = APR_LOCK_DEFAULT;
606 AP_DECLARE(const char *) ap_mpm_set_accept_lock_mech(cmd_parms *cmd,
607                                                      void *dummy,
608                                                      const char *arg)
609 {
610     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
611     if (err != NULL) {
612         return err;
613     }
614
615     if (!strcasecmp(arg, "default")) {
616         ap_accept_lock_mech = APR_LOCK_DEFAULT;
617     }
618 #if APR_HAS_FLOCK_SERIALIZE
619     else if (!strcasecmp(arg, "flock")) {
620         ap_accept_lock_mech = APR_LOCK_FLOCK;
621     }
622 #endif
623 #if APR_HAS_FCNTL_SERIALIZE
624     else if (!strcasecmp(arg, "fcntl")) {
625         ap_accept_lock_mech = APR_LOCK_FCNTL;
626     }
627 #endif
628     /* perchild can't use SysV sems because the permissions on the accept
629      * mutex can't be set to allow all processes to use the mutex and
630      * at the same time keep all users from being able to dink with the
631      * mutex
632      */
633 #if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
634     else if (!strcasecmp(arg, "sysvsem")) {
635         ap_accept_lock_mech = APR_LOCK_SYSVSEM;
636     }
637 #endif
638 #if APR_HAS_PROC_PTHREAD_SERIALIZE
639     else if (!strcasecmp(arg, "pthread")) {
640         ap_accept_lock_mech = APR_LOCK_PROC_PTHREAD;
641     }
642 #endif
643     else {
644         return apr_pstrcat(cmd->pool, arg, " is an invalid mutex mechanism; valid "
645                            "ones for this platform and MPM are: default"
646 #if APR_HAS_FLOCK_SERIALIZE
647                            ", flock"
648 #endif
649 #if APR_HAS_FCNTL_SERIALIZE
650                            ", fcntl"
651 #endif
652 #if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
653                            ", sysvsem"
654 #endif
655 #if APR_HAS_PROC_PTHREAD_SERIALIZE
656                            ", pthread"
657 #endif
658                            , NULL);
659     }
660     return NULL;
661 }
662 #endif