]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/apr/threadproc/unix/proc.c
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.git] / contrib / apr / threadproc / unix / proc.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "apr_arch_threadproc.h"
18 #include "apr_strings.h"
19 #include "apr_portable.h"
20 #include "apr_signal.h"
21 #include "apr_random.h"
22
23 /* Heavy on no'ops, here's what we want to pass if there is APR_NO_FILE
24  * requested for a specific child handle;
25  */
26 static apr_file_t no_file = { NULL, -1, };
27
28 APR_DECLARE(apr_status_t) apr_procattr_create(apr_procattr_t **new,
29                                               apr_pool_t *pool)
30 {
31     (*new) = (apr_procattr_t *)apr_pcalloc(pool, sizeof(apr_procattr_t));
32
33     if ((*new) == NULL) {
34         return APR_ENOMEM;
35     }
36     (*new)->pool = pool;
37     (*new)->cmdtype = APR_PROGRAM;
38     (*new)->uid = (*new)->gid = -1;
39     return APR_SUCCESS;
40 }
41
42 APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr,
43                                               apr_int32_t in,
44                                               apr_int32_t out,
45                                               apr_int32_t err)
46 {
47     apr_status_t rv;
48
49     if ((in != APR_NO_PIPE) && (in != APR_NO_FILE)) {
50         /* APR_CHILD_BLOCK maps to APR_WRITE_BLOCK, while
51          * APR_PARENT_BLOCK maps to APR_READ_BLOCK, so transpose 
52          * the CHILD/PARENT blocking flags for the stdin pipe.
53          * stdout/stderr map to the correct mode by default.
54          */
55         if (in == APR_CHILD_BLOCK)
56             in = APR_READ_BLOCK;
57         else if (in == APR_PARENT_BLOCK)
58             in = APR_WRITE_BLOCK;
59
60         if ((rv = apr_file_pipe_create_ex(&attr->child_in, &attr->parent_in,
61                                           in, attr->pool)) == APR_SUCCESS)
62             rv = apr_file_inherit_unset(attr->parent_in);
63         if (rv != APR_SUCCESS)
64             return rv;
65     }
66     else if (in == APR_NO_FILE)
67         attr->child_in = &no_file;
68
69     if ((out != APR_NO_PIPE) && (out != APR_NO_FILE)) {
70         if ((rv = apr_file_pipe_create_ex(&attr->parent_out, &attr->child_out,
71                                           out, attr->pool)) == APR_SUCCESS)
72             rv = apr_file_inherit_unset(attr->parent_out);
73         if (rv != APR_SUCCESS)
74             return rv;
75     }
76     else if (out == APR_NO_FILE)
77         attr->child_out = &no_file;
78
79     if ((err != APR_NO_PIPE) && (err != APR_NO_FILE)) {
80         if ((rv = apr_file_pipe_create_ex(&attr->parent_err, &attr->child_err,
81                                           err, attr->pool)) == APR_SUCCESS)
82             rv = apr_file_inherit_unset(attr->parent_err);
83         if (rv != APR_SUCCESS)
84             return rv;
85     }
86     else if (err == APR_NO_FILE)
87         attr->child_err = &no_file;
88
89     return APR_SUCCESS;
90 }
91
92
93 APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr,
94                                                     apr_file_t *child_in,
95                                                     apr_file_t *parent_in)
96 {
97     apr_status_t rv = APR_SUCCESS;
98
99     if (attr->child_in == NULL && attr->parent_in == NULL
100            && child_in == NULL && parent_in == NULL)
101         if ((rv = apr_file_pipe_create(&attr->child_in, &attr->parent_in,
102                                        attr->pool)) == APR_SUCCESS)
103             rv = apr_file_inherit_unset(attr->parent_in);
104
105     if (child_in != NULL && rv == APR_SUCCESS) {
106         if (attr->child_in && (attr->child_in->filedes != -1))
107             rv = apr_file_dup2(attr->child_in, child_in, attr->pool);
108         else {
109             attr->child_in = NULL;
110             if ((rv = apr_file_dup(&attr->child_in, child_in, attr->pool))
111                     == APR_SUCCESS)
112                 rv = apr_file_inherit_set(attr->child_in);
113         }
114     }
115
116     if (parent_in != NULL && rv == APR_SUCCESS) {
117         if (attr->parent_in)
118             rv = apr_file_dup2(attr->parent_in, parent_in, attr->pool);
119         else
120             rv = apr_file_dup(&attr->parent_in, parent_in, attr->pool);
121     }
122
123     return rv;
124 }
125
126
127 APR_DECLARE(apr_status_t) apr_procattr_child_out_set(apr_procattr_t *attr,
128                                                      apr_file_t *child_out,
129                                                      apr_file_t *parent_out)
130 {
131     apr_status_t rv = APR_SUCCESS;
132
133     if (attr->child_out == NULL && attr->parent_out == NULL
134            && child_out == NULL && parent_out == NULL)
135         if ((rv = apr_file_pipe_create(&attr->parent_out, &attr->child_out,
136                                        attr->pool)) == APR_SUCCESS)
137             rv = apr_file_inherit_unset(attr->parent_out);
138
139     if (child_out != NULL && rv == APR_SUCCESS) {
140         if (attr->child_out && (attr->child_out->filedes != -1))
141             rv = apr_file_dup2(attr->child_out, child_out, attr->pool);
142         else {
143             attr->child_out = NULL;
144             if ((rv = apr_file_dup(&attr->child_out, child_out, attr->pool))
145                     == APR_SUCCESS)
146                 rv = apr_file_inherit_set(attr->child_out);
147         }
148     }
149
150     if (parent_out != NULL && rv == APR_SUCCESS) {
151         if (attr->parent_out)
152             rv = apr_file_dup2(attr->parent_out, parent_out, attr->pool);
153         else
154             rv = apr_file_dup(&attr->parent_out, parent_out, attr->pool);
155     }
156
157     return rv;
158 }
159
160
161 APR_DECLARE(apr_status_t) apr_procattr_child_err_set(apr_procattr_t *attr,
162                                                      apr_file_t *child_err,
163                                                      apr_file_t *parent_err)
164 {
165     apr_status_t rv = APR_SUCCESS;
166
167     if (attr->child_err == NULL && attr->parent_err == NULL
168            && child_err == NULL && parent_err == NULL)
169         if ((rv = apr_file_pipe_create(&attr->parent_err, &attr->child_err,
170                                       attr->pool)) == APR_SUCCESS)
171             rv = apr_file_inherit_unset(attr->parent_err);
172
173     if (child_err != NULL && rv == APR_SUCCESS) {
174         if (attr->child_err && (attr->child_err->filedes != -1))
175             rv = apr_file_dup2(attr->child_err, child_err, attr->pool);
176         else {
177             attr->child_err = NULL;
178             if ((rv = apr_file_dup(&attr->child_err, child_err, attr->pool))
179                     == APR_SUCCESS)
180                 rv = apr_file_inherit_set(attr->child_err);
181         }
182     }
183     if (parent_err != NULL && rv == APR_SUCCESS) {
184         if (attr->parent_err)
185             rv = apr_file_dup2(attr->parent_err, parent_err, attr->pool);
186         else
187             rv = apr_file_dup(&attr->parent_err, parent_err, attr->pool);
188     }
189
190     return rv;
191 }
192
193
194 APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr,
195                                                const char *dir)
196 {
197     attr->currdir = apr_pstrdup(attr->pool, dir);
198     if (attr->currdir) {
199         return APR_SUCCESS;
200     }
201
202     return APR_ENOMEM;
203 }
204
205 APR_DECLARE(apr_status_t) apr_procattr_cmdtype_set(apr_procattr_t *attr,
206                                                    apr_cmdtype_e cmd)
207 {
208     attr->cmdtype = cmd;
209     return APR_SUCCESS;
210 }
211
212 APR_DECLARE(apr_status_t) apr_procattr_detach_set(apr_procattr_t *attr,
213                                                   apr_int32_t detach)
214 {
215     attr->detached = detach;
216     return APR_SUCCESS;
217 }
218
219 APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool)
220 {
221     int pid;
222     
223     memset(proc, 0, sizeof(apr_proc_t));
224
225     if ((pid = fork()) < 0) {
226         return errno;
227     }
228     else if (pid == 0) {
229         proc->pid = getpid();
230
231         apr_random_after_fork(proc);
232
233         return APR_INCHILD;
234     }
235
236     proc->pid = pid;
237
238     return APR_INPARENT;
239 }
240
241 static apr_status_t limit_proc(apr_procattr_t *attr)
242 {
243 #if APR_HAVE_STRUCT_RLIMIT && APR_HAVE_SETRLIMIT
244 #ifdef RLIMIT_CPU
245     if (attr->limit_cpu != NULL) {
246         if ((setrlimit(RLIMIT_CPU, attr->limit_cpu)) != 0) {
247             return errno;
248         }
249     }
250 #endif
251 #ifdef RLIMIT_NPROC
252     if (attr->limit_nproc != NULL) {
253         if ((setrlimit(RLIMIT_NPROC, attr->limit_nproc)) != 0) {
254             return errno;
255         }
256     }
257 #endif
258 #ifdef RLIMIT_NOFILE
259     if (attr->limit_nofile != NULL) {
260         if ((setrlimit(RLIMIT_NOFILE, attr->limit_nofile)) != 0) {
261             return errno;
262         }
263     }
264 #endif
265 #if defined(RLIMIT_AS)
266     if (attr->limit_mem != NULL) {
267         if ((setrlimit(RLIMIT_AS, attr->limit_mem)) != 0) {
268             return errno;
269         }
270     }
271 #elif defined(RLIMIT_DATA)
272     if (attr->limit_mem != NULL) {
273         if ((setrlimit(RLIMIT_DATA, attr->limit_mem)) != 0) {
274             return errno;
275         }
276     }
277 #elif defined(RLIMIT_VMEM)
278     if (attr->limit_mem != NULL) {
279         if ((setrlimit(RLIMIT_VMEM, attr->limit_mem)) != 0) {
280             return errno;
281         }
282     }
283 #endif
284 #else
285     /*
286      * Maybe make a note in error_log that setrlimit isn't supported??
287      */
288
289 #endif
290     return APR_SUCCESS;
291 }
292
293 APR_DECLARE(apr_status_t) apr_procattr_child_errfn_set(apr_procattr_t *attr,
294                                                        apr_child_errfn_t *errfn)
295 {
296     attr->errfn = errfn;
297     return APR_SUCCESS;
298 }
299
300 APR_DECLARE(apr_status_t) apr_procattr_error_check_set(apr_procattr_t *attr,
301                                                        apr_int32_t chk)
302 {
303     attr->errchk = chk;
304     return APR_SUCCESS;
305 }
306
307 APR_DECLARE(apr_status_t) apr_procattr_addrspace_set(apr_procattr_t *attr,
308                                                        apr_int32_t addrspace)
309 {
310     /* won't ever be used on this platform, so don't save the flag */
311     return APR_SUCCESS;
312 }
313
314 APR_DECLARE(apr_status_t) apr_procattr_user_set(apr_procattr_t *attr,
315                                                 const char *username,
316                                                 const char *password)
317 {
318     apr_status_t rv;
319     apr_gid_t    gid;
320
321     if ((rv = apr_uid_get(&attr->uid, &gid, username,
322                           attr->pool)) != APR_SUCCESS) {
323         attr->uid = -1;
324         return rv;
325     }
326     
327     /* Use default user group if not already set */
328     if (attr->gid == -1) {
329         attr->gid = gid;
330     }
331     return APR_SUCCESS;
332 }
333
334 APR_DECLARE(apr_status_t) apr_procattr_group_set(apr_procattr_t *attr,
335                                                  const char *groupname)
336 {
337     apr_status_t rv;
338
339     if ((rv = apr_gid_get(&attr->gid, groupname, attr->pool)) != APR_SUCCESS)
340         attr->gid = -1;
341     return rv;
342 }
343
344 APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new,
345                                           const char *progname,
346                                           const char * const *args,
347                                           const char * const *env,
348                                           apr_procattr_t *attr,
349                                           apr_pool_t *pool)
350 {
351     int i;
352     const char * const empty_envp[] = {NULL};
353
354     if (!env) { /* Specs require an empty array instead of NULL;
355                  * Purify will trigger a failure, even if many
356                  * implementations don't.
357                  */
358         env = empty_envp;
359     }
360
361     new->in = attr->parent_in;
362     new->err = attr->parent_err;
363     new->out = attr->parent_out;
364
365     if (attr->errchk) {
366         if (attr->currdir) {
367             if (access(attr->currdir, X_OK) == -1) {
368                 /* chdir() in child wouldn't have worked */
369                 return errno;
370             }
371         }
372
373         if (attr->cmdtype == APR_PROGRAM ||
374             attr->cmdtype == APR_PROGRAM_ENV ||
375             *progname == '/') {
376             /* for both of these values of cmdtype, caller must pass
377              * full path, so it is easy to check;
378              * caller can choose to pass full path for other
379              * values of cmdtype
380              */
381             if (access(progname, X_OK) == -1) {
382                 /* exec*() in child wouldn't have worked */
383                 return errno;
384             }
385         }
386         else {
387             /* todo: search PATH for progname then try to access it */
388         }
389     }
390
391     if ((new->pid = fork()) < 0) {
392         return errno;
393     }
394     else if (new->pid == 0) {
395         /* child process */
396
397         /*
398          * If we do exec cleanup before the dup2() calls to set up pipes
399          * on 0-2, we accidentally close the pipes used by programs like
400          * mod_cgid.
401          *
402          * If we do exec cleanup after the dup2() calls, cleanup can accidentally
403          * close our pipes which replaced any files which previously had
404          * descriptors 0-2.
405          *
406          * The solution is to kill the cleanup for the pipes, then do
407          * exec cleanup, then do the dup2() calls.
408          */
409
410         if (attr->child_in) {
411             apr_pool_cleanup_kill(apr_file_pool_get(attr->child_in),
412                                   attr->child_in, apr_unix_file_cleanup);
413         }
414
415         if (attr->child_out) {
416             apr_pool_cleanup_kill(apr_file_pool_get(attr->child_out),
417                                   attr->child_out, apr_unix_file_cleanup);
418         }
419
420         if (attr->child_err) {
421             apr_pool_cleanup_kill(apr_file_pool_get(attr->child_err),
422                                   attr->child_err, apr_unix_file_cleanup);
423         }
424
425         apr_pool_cleanup_for_exec();
426
427         if ((attr->child_in) && (attr->child_in->filedes == -1)) {
428             close(STDIN_FILENO);
429         }
430         else if (attr->child_in &&
431                  attr->child_in->filedes != STDIN_FILENO) {
432             dup2(attr->child_in->filedes, STDIN_FILENO);
433             apr_file_close(attr->child_in);
434         }
435
436         if ((attr->child_out) && (attr->child_out->filedes == -1)) {
437             close(STDOUT_FILENO);
438         }
439         else if (attr->child_out &&
440                  attr->child_out->filedes != STDOUT_FILENO) {
441             dup2(attr->child_out->filedes, STDOUT_FILENO);
442             apr_file_close(attr->child_out);
443         }
444
445         if ((attr->child_err) && (attr->child_err->filedes == -1)) {
446             close(STDERR_FILENO);
447         }
448         else if (attr->child_err &&
449                  attr->child_err->filedes != STDERR_FILENO) {
450             dup2(attr->child_err->filedes, STDERR_FILENO);
451             apr_file_close(attr->child_err);
452         }
453
454         apr_signal(SIGCHLD, SIG_DFL); /* not sure if this is needed or not */
455
456         if (attr->currdir != NULL) {
457             if (chdir(attr->currdir) == -1) {
458                 if (attr->errfn) {
459                     attr->errfn(pool, errno, "change of working directory failed");
460                 }
461                 _exit(-1);   /* We have big problems, the child should exit. */
462             }
463         }
464         if (!geteuid()) {
465             apr_procattr_pscb_t *c = attr->perms_set_callbacks;
466
467             while (c) {
468                 apr_status_t r;
469                 r = (*c->perms_set_fn)((void *)c->data, c->perms,
470                                        attr->uid, attr->gid);
471                 if (r != APR_SUCCESS && r != APR_ENOTIMPL) {
472                     _exit(-1);
473                 }
474                 c = c->next;
475             }
476         }
477         /* Only try to switch if we are running as root */
478         if (attr->gid != -1 && !geteuid()) {
479             if (setgid(attr->gid)) {
480                 if (attr->errfn) {
481                     attr->errfn(pool, errno, "setting of group failed");
482                 }
483                 _exit(-1);   /* We have big problems, the child should exit. */
484             }
485         }
486
487         if (attr->uid != -1 && !geteuid()) {
488             if (setuid(attr->uid)) {
489                 if (attr->errfn) {
490                     attr->errfn(pool, errno, "setting of user failed");
491                 }
492                 _exit(-1);   /* We have big problems, the child should exit. */
493             }
494         }
495
496         if (limit_proc(attr) != APR_SUCCESS) {
497             if (attr->errfn) {
498                 attr->errfn(pool, errno, "setting of resource limits failed");
499             }
500             _exit(-1);   /* We have big problems, the child should exit. */
501         }
502
503         if (attr->cmdtype == APR_SHELLCMD ||
504             attr->cmdtype == APR_SHELLCMD_ENV) {
505             int onearg_len = 0;
506             const char *newargs[4];
507
508             newargs[0] = SHELL_PATH;
509             newargs[1] = "-c";
510
511             i = 0;
512             while (args[i]) {
513                 onearg_len += strlen(args[i]);
514                 onearg_len++; /* for space delimiter */
515                 i++;
516             }
517
518             switch(i) {
519             case 0:
520                 /* bad parameters; we're doomed */
521                 break;
522             case 1:
523                 /* no args, or caller already built a single string from
524                  * progname and args
525                  */
526                 newargs[2] = args[0];
527                 break;
528             default:
529             {
530                 char *ch, *onearg;
531                 
532                 ch = onearg = apr_palloc(pool, onearg_len);
533                 i = 0;
534                 while (args[i]) {
535                     size_t len = strlen(args[i]);
536
537                     memcpy(ch, args[i], len);
538                     ch += len;
539                     *ch = ' ';
540                     ++ch;
541                     ++i;
542                 }
543                 --ch; /* back up to trailing blank */
544                 *ch = '\0';
545                 newargs[2] = onearg;
546             }
547             }
548
549             newargs[3] = NULL;
550
551             if (attr->detached) {
552                 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
553             }
554
555             if (attr->cmdtype == APR_SHELLCMD) {
556                 execve(SHELL_PATH, (char * const *) newargs, (char * const *)env);
557             }
558             else {
559                 execv(SHELL_PATH, (char * const *)newargs);
560             }
561         }
562         else if (attr->cmdtype == APR_PROGRAM) {
563             if (attr->detached) {
564                 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
565             }
566
567             execve(progname, (char * const *)args, (char * const *)env);
568         }
569         else if (attr->cmdtype == APR_PROGRAM_ENV) {
570             if (attr->detached) {
571                 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
572             }
573
574             execv(progname, (char * const *)args);
575         }
576         else {
577             /* APR_PROGRAM_PATH */
578             if (attr->detached) {
579                 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
580             }
581
582             execvp(progname, (char * const *)args);
583         }
584         if (attr->errfn) {
585             char *desc;
586
587             desc = apr_psprintf(pool, "exec of '%s' failed",
588                                 progname);
589             attr->errfn(pool, errno, desc);
590         }
591
592         _exit(-1);  /* if we get here, there is a problem, so exit with an
593                      * error code. */
594     }
595
596     /* Parent process */
597     if (attr->child_in && (attr->child_in->filedes != -1)) {
598         apr_file_close(attr->child_in);
599     }
600
601     if (attr->child_out && (attr->child_out->filedes != -1)) {
602         apr_file_close(attr->child_out);
603     }
604
605     if (attr->child_err && (attr->child_err->filedes != -1)) {
606         apr_file_close(attr->child_err);
607     }
608
609     return APR_SUCCESS;
610 }
611
612 APR_DECLARE(apr_status_t) apr_proc_wait_all_procs(apr_proc_t *proc,
613                                                   int *exitcode,
614                                                   apr_exit_why_e *exitwhy,
615                                                   apr_wait_how_e waithow,
616                                                   apr_pool_t *p)
617 {
618     proc->pid = -1;
619     return apr_proc_wait(proc, exitcode, exitwhy, waithow);
620 }
621
622 APR_DECLARE(apr_status_t) apr_proc_wait(apr_proc_t *proc,
623                                         int *exitcode, apr_exit_why_e *exitwhy,
624                                         apr_wait_how_e waithow)
625 {
626     pid_t pstatus;
627     int waitpid_options = WUNTRACED;
628     int exit_int;
629     int ignore;
630     apr_exit_why_e ignorewhy;
631
632     if (exitcode == NULL) {
633         exitcode = &ignore;
634     }
635
636     if (exitwhy == NULL) {
637         exitwhy = &ignorewhy;
638     }
639
640     if (waithow != APR_WAIT) {
641         waitpid_options |= WNOHANG;
642     }
643
644     do {
645         pstatus = waitpid(proc->pid, &exit_int, waitpid_options);
646     } while (pstatus < 0 && errno == EINTR);
647
648     if (pstatus > 0) {
649         proc->pid = pstatus;
650
651         if (WIFEXITED(exit_int)) {
652             *exitwhy = APR_PROC_EXIT;
653             *exitcode = WEXITSTATUS(exit_int);
654         }
655         else if (WIFSIGNALED(exit_int)) {
656             *exitwhy = APR_PROC_SIGNAL;
657
658 #ifdef WCOREDUMP
659             if (WCOREDUMP(exit_int)) {
660                 *exitwhy |= APR_PROC_SIGNAL_CORE;
661             }
662 #endif
663
664             *exitcode = WTERMSIG(exit_int);
665         }
666         else {
667             /* unexpected condition */
668             return APR_EGENERAL;
669         }
670
671         return APR_CHILD_DONE;
672     }
673     else if (pstatus == 0) {
674         return APR_CHILD_NOTDONE;
675     }
676
677     return errno;
678 }
679
680 #if APR_HAVE_STRUCT_RLIMIT
681 APR_DECLARE(apr_status_t) apr_procattr_limit_set(apr_procattr_t *attr,
682                                                  apr_int32_t what,
683                                                  struct rlimit *limit)
684 {
685     switch(what) {
686         case APR_LIMIT_CPU:
687 #ifdef RLIMIT_CPU
688             attr->limit_cpu = limit;
689             break;
690 #else
691             return APR_ENOTIMPL;
692 #endif
693
694         case APR_LIMIT_MEM:
695 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
696             attr->limit_mem = limit;
697             break;
698 #else
699             return APR_ENOTIMPL;
700 #endif
701
702         case APR_LIMIT_NPROC:
703 #ifdef RLIMIT_NPROC
704             attr->limit_nproc = limit;
705             break;
706 #else
707             return APR_ENOTIMPL;
708 #endif
709
710         case APR_LIMIT_NOFILE:
711 #ifdef RLIMIT_NOFILE
712             attr->limit_nofile = limit;
713             break;
714 #else
715             return APR_ENOTIMPL;
716 #endif
717
718     }
719
720     return APR_SUCCESS;
721 }
722 #endif /* APR_HAVE_STRUCT_RLIMIT */
723
724 APR_DECLARE(apr_status_t) apr_procattr_perms_set_register(apr_procattr_t *attr,
725                                                  apr_perms_setfn_t *perms_set_fn,
726                                                  void *data,
727                                                  apr_fileperms_t perms)
728 {
729     apr_procattr_pscb_t *c;
730
731     c = apr_palloc(attr->pool, sizeof(apr_procattr_pscb_t));
732     c->data = data;
733     c->perms = perms;
734     c->perms_set_fn = perms_set_fn;
735     c->next = attr->perms_set_callbacks;
736     attr->perms_set_callbacks = c;
737
738     return APR_SUCCESS;
739 }