proc.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  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. #define INCL_DOS
  17. #define INCL_DOSERRORS
  18. #include "apr_arch_threadproc.h"
  19. #include "apr_arch_file_io.h"
  20. #include "apr_private.h"
  21. #include "apr_thread_proc.h"
  22. #include "apr_file_io.h"
  23. #include "apr_general.h"
  24. #include "apr_lib.h"
  25. #include "apr_portable.h"
  26. #include "apr_strings.h"
  27. #include "apr_signal.h"
  28. #include <signal.h>
  29. #include <string.h>
  30. #include <sys/wait.h>
  31. #include <unistd.h>
  32. #include <process.h>
  33. #include <stdlib.h>
  34. /* Heavy on no'ops, here's what we want to pass if there is APR_NO_FILE
  35. * requested for a specific child handle;
  36. */
  37. static apr_file_t no_file = { NULL, -1, };
  38. APR_DECLARE(apr_status_t) apr_procattr_create(apr_procattr_t **new, apr_pool_t *pool)
  39. {
  40. (*new) = (apr_procattr_t *)apr_palloc(pool,
  41. sizeof(apr_procattr_t));
  42. if ((*new) == NULL) {
  43. return APR_ENOMEM;
  44. }
  45. (*new)->pool = pool;
  46. (*new)->parent_in = NULL;
  47. (*new)->child_in = NULL;
  48. (*new)->parent_out = NULL;
  49. (*new)->child_out = NULL;
  50. (*new)->parent_err = NULL;
  51. (*new)->child_err = NULL;
  52. (*new)->currdir = NULL;
  53. (*new)->cmdtype = APR_PROGRAM;
  54. (*new)->detached = FALSE;
  55. return APR_SUCCESS;
  56. }
  57. APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr,
  58. apr_int32_t in,
  59. apr_int32_t out,
  60. apr_int32_t err)
  61. {
  62. apr_status_t rv;
  63. if ((in != APR_NO_PIPE) && (in != APR_NO_FILE)) {
  64. /* APR_CHILD_BLOCK maps to APR_WRITE_BLOCK, while
  65. * APR_PARENT_BLOCK maps to APR_READ_BLOCK, so transpose
  66. * the CHILD/PARENT blocking flags for the stdin pipe.
  67. * stdout/stderr map to the correct mode by default.
  68. */
  69. if (in == APR_CHILD_BLOCK)
  70. in = APR_READ_BLOCK;
  71. else if (in == APR_PARENT_BLOCK)
  72. in = APR_WRITE_BLOCK;
  73. if ((rv = apr_file_pipe_create_ex(&attr->child_in, &attr->parent_in,
  74. in, attr->pool)) == APR_SUCCESS)
  75. rv = apr_file_inherit_unset(attr->parent_in);
  76. if (rv != APR_SUCCESS)
  77. return rv;
  78. }
  79. else if (in == APR_NO_FILE)
  80. attr->child_in = &no_file;
  81. if ((out != APR_NO_PIPE) && (out != APR_NO_FILE)) {
  82. if ((rv = apr_file_pipe_create_ex(&attr->parent_out, &attr->child_out,
  83. out, attr->pool)) == APR_SUCCESS)
  84. rv = apr_file_inherit_unset(attr->parent_out);
  85. if (rv != APR_SUCCESS)
  86. return rv;
  87. }
  88. else if (out == APR_NO_FILE)
  89. attr->child_out = &no_file;
  90. if ((err != APR_NO_PIPE) && (err != APR_NO_FILE)) {
  91. if ((rv = apr_file_pipe_create_ex(&attr->parent_err, &attr->child_err,
  92. err, attr->pool)) == APR_SUCCESS)
  93. rv = apr_file_inherit_unset(attr->parent_err);
  94. if (rv != APR_SUCCESS)
  95. return rv;
  96. }
  97. else if (err == APR_NO_FILE)
  98. attr->child_err = &no_file;
  99. return APR_SUCCESS;
  100. }
  101. APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr, apr_file_t *child_in,
  102. apr_file_t *parent_in)
  103. {
  104. apr_status_t rv;
  105. if (attr->child_in == NULL && attr->parent_in == NULL
  106. && child_in == NULL && parent_in == NULL)
  107. if ((rv = apr_file_pipe_create(&attr->child_in, &attr->parent_in,
  108. attr->pool)) == APR_SUCCESS)
  109. rv = apr_file_inherit_unset(attr->parent_in);
  110. if (child_in != NULL && rv == APR_SUCCESS) {
  111. if (attr->child_in && (attr->child_in->filedes != -1))
  112. rv = apr_file_dup2(attr->child_in, child_in, attr->pool);
  113. else {
  114. attr->child_in = NULL;
  115. if ((rv = apr_file_dup(&attr->child_in, child_in, attr->pool))
  116. == APR_SUCCESS)
  117. rv = apr_file_inherit_set(attr->child_in);
  118. }
  119. }
  120. if (parent_in != NULL && rv == APR_SUCCESS) {
  121. rv = apr_file_dup(&attr->parent_in, parent_in, attr->pool);
  122. }
  123. return rv;
  124. }
  125. APR_DECLARE(apr_status_t) apr_procattr_child_out_set(apr_procattr_t *attr, apr_file_t *child_out,
  126. apr_file_t *parent_out)
  127. {
  128. apr_status_t rv;
  129. if (attr->child_out == NULL && attr->parent_out == NULL
  130. && child_out == NULL && parent_out == NULL)
  131. if ((rv = apr_file_pipe_create(&attr->parent_out, &attr->child_out,
  132. attr->pool)) == APR_SUCCESS)
  133. rv = apr_file_inherit_unset(attr->parent_out);
  134. if (child_out != NULL && rv == APR_SUCCESS) {
  135. if (attr->child_out && (attr->child_out->filedes != -1))
  136. rv = apr_file_dup2(attr->child_out, child_out, attr->pool);
  137. else {
  138. attr->child_out = NULL;
  139. if ((rv = apr_file_dup(&attr->child_out, child_out, attr->pool))
  140. == APR_SUCCESS)
  141. rv = apr_file_inherit_set(attr->child_out);
  142. }
  143. }
  144. if (parent_out != NULL && rv == APR_SUCCESS) {
  145. rv = apr_file_dup(&attr->parent_out, parent_out, attr->pool);
  146. }
  147. return rv;
  148. }
  149. APR_DECLARE(apr_status_t) apr_procattr_child_err_set(apr_procattr_t *attr, apr_file_t *child_err,
  150. apr_file_t *parent_err)
  151. {
  152. apr_status_t rv;
  153. if (attr->child_err == NULL && attr->parent_err == NULL
  154. && child_err == NULL && parent_err == NULL)
  155. if ((rv = apr_file_pipe_create(&attr->parent_err, &attr->child_err,
  156. attr->pool)) == APR_SUCCESS)
  157. rv = apr_file_inherit_unset(attr->parent_err);
  158. if (child_err != NULL && rv == APR_SUCCESS) {
  159. if (attr->child_err && (attr->child_err->filedes != -1))
  160. rv = apr_file_dup2(attr->child_err, child_err, attr->pool);
  161. else {
  162. attr->child_err = NULL;
  163. if ((rv = apr_file_dup(&attr->child_err, child_err, attr->pool))
  164. == APR_SUCCESS)
  165. rv = apr_file_inherit_set(attr->child_err);
  166. }
  167. }
  168. if (parent_err != NULL && rv == APR_SUCCESS) {
  169. rv = apr_file_dup(&attr->parent_err, parent_err, attr->pool);
  170. }
  171. return rv;
  172. }
  173. APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr, const char *dir)
  174. {
  175. attr->currdir = apr_pstrdup(attr->pool, dir);
  176. if (attr->currdir) {
  177. return APR_SUCCESS;
  178. }
  179. return APR_ENOMEM;
  180. }
  181. APR_DECLARE(apr_status_t) apr_procattr_cmdtype_set(apr_procattr_t *attr,
  182. apr_cmdtype_e cmd)
  183. {
  184. attr->cmdtype = cmd;
  185. return APR_SUCCESS;
  186. }
  187. APR_DECLARE(apr_status_t) apr_procattr_detach_set(apr_procattr_t *attr, apr_int32_t detach)
  188. {
  189. attr->detached = detach;
  190. return APR_SUCCESS;
  191. }
  192. APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool)
  193. {
  194. int pid;
  195. if ((pid = fork()) < 0) {
  196. return errno;
  197. }
  198. else if (pid == 0) {
  199. proc->pid = pid;
  200. proc->in = NULL;
  201. proc->out = NULL;
  202. proc->err = NULL;
  203. return APR_INCHILD;
  204. }
  205. proc->pid = pid;
  206. proc->in = NULL;
  207. proc->out = NULL;
  208. proc->err = NULL;
  209. return APR_INPARENT;
  210. }
  211. /* quotes in the string are doubled up.
  212. * Used to escape quotes in args passed to OS/2's cmd.exe
  213. */
  214. static char *double_quotes(apr_pool_t *pool, const char *str)
  215. {
  216. int num_quotes = 0;
  217. int len = 0;
  218. char *quote_doubled_str, *dest;
  219. while (str[len]) {
  220. num_quotes += str[len++] == '\"';
  221. }
  222. quote_doubled_str = apr_palloc(pool, len + num_quotes + 1);
  223. dest = quote_doubled_str;
  224. while (*str) {
  225. if (*str == '\"')
  226. *(dest++) = '\"';
  227. *(dest++) = *(str++);
  228. }
  229. *dest = 0;
  230. return quote_doubled_str;
  231. }
  232. APR_DECLARE(apr_status_t) apr_procattr_child_errfn_set(apr_procattr_t *attr,
  233. apr_child_errfn_t *errfn)
  234. {
  235. /* won't ever be called on this platform, so don't save the function pointer */
  236. return APR_SUCCESS;
  237. }
  238. APR_DECLARE(apr_status_t) apr_procattr_error_check_set(apr_procattr_t *attr,
  239. apr_int32_t chk)
  240. {
  241. /* won't ever be used on this platform, so don't save the flag */
  242. return APR_SUCCESS;
  243. }
  244. APR_DECLARE(apr_status_t) apr_procattr_addrspace_set(apr_procattr_t *attr,
  245. apr_int32_t addrspace)
  246. {
  247. /* won't ever be used on this platform, so don't save the flag */
  248. return APR_SUCCESS;
  249. }
  250. APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *proc, const char *progname,
  251. const char * const *args,
  252. const char * const *env,
  253. apr_procattr_t *attr, apr_pool_t *pool)
  254. {
  255. int i, arg, numargs, cmdlen;
  256. apr_status_t status;
  257. const char **newargs;
  258. char savedir[300];
  259. HFILE save_in, save_out, save_err, dup;
  260. int criticalsection = FALSE;
  261. char *extension, *newprogname, *extra_arg = NULL, *cmdline, *cmdline_pos;
  262. char interpreter[1024];
  263. char error_object[260];
  264. apr_file_t *progfile;
  265. int env_len, e;
  266. char *env_block, *env_block_pos;
  267. RESULTCODES rescodes;
  268. proc->in = attr->parent_in;
  269. proc->err = attr->parent_err;
  270. proc->out = attr->parent_out;
  271. /* Prevent other threads from running while these process-wide resources are modified */
  272. if (attr->child_in || attr->child_out || attr->child_err || attr->currdir) {
  273. criticalsection = TRUE;
  274. DosEnterCritSec();
  275. }
  276. if (attr->child_in) {
  277. save_in = -1;
  278. DosDupHandle(STDIN_FILENO, &save_in);
  279. dup = STDIN_FILENO;
  280. if (attr->child_in->filedes == -1)
  281. DosClose(dup);
  282. else
  283. DosDupHandle(attr->child_in->filedes, &dup);
  284. }
  285. if (attr->child_out) {
  286. save_out = -1;
  287. DosDupHandle(STDOUT_FILENO, &save_out);
  288. dup = STDOUT_FILENO;
  289. if (attr->child_out->filedes == -1)
  290. DosClose(dup);
  291. else
  292. DosDupHandle(attr->child_out->filedes, &dup);
  293. }
  294. if (attr->child_err) {
  295. save_err = -1;
  296. DosDupHandle(STDERR_FILENO, &save_err);
  297. dup = STDERR_FILENO;
  298. if (attr->child_err->filedes == -1)
  299. DosClose(dup);
  300. else
  301. DosDupHandle(attr->child_err->filedes, &dup);
  302. }
  303. apr_signal(SIGCHLD, SIG_DFL); /*not sure if this is needed or not */
  304. if (attr->currdir != NULL) {
  305. _getcwd2(savedir, sizeof(savedir));
  306. if (_chdir2(attr->currdir) < 0) {
  307. if (criticalsection)
  308. DosExitCritSec();
  309. return errno;
  310. }
  311. }
  312. interpreter[0] = 0;
  313. extension = strrchr(progname, '.');
  314. if (extension == NULL || strchr(extension, '/') || strchr(extension, '\\'))
  315. extension = "";
  316. /* ### how to handle APR_PROGRAM_ENV and APR_PROGRAM_PATH? */
  317. if (attr->cmdtype == APR_SHELLCMD ||
  318. attr->cmdtype == APR_SHELLCMD_ENV ||
  319. strcasecmp(extension, ".cmd") == 0) {
  320. strcpy(interpreter, "#!" SHELL_PATH);
  321. extra_arg = "/C";
  322. } else if (stricmp(extension, ".exe") != 0) {
  323. status = apr_file_open(&progfile, progname, APR_READ|APR_BUFFERED, 0, pool);
  324. if (status != APR_SUCCESS && APR_STATUS_IS_ENOENT(status)) {
  325. progname = apr_pstrcat(pool, progname, ".exe", NULL);
  326. }
  327. if (status == APR_SUCCESS) {
  328. status = apr_file_gets(interpreter, sizeof(interpreter), progfile);
  329. if (status == APR_SUCCESS) {
  330. if (interpreter[0] == '#' && interpreter[1] == '!') {
  331. /* delete CR/LF & any other whitespace off the end */
  332. int end = strlen(interpreter) - 1;
  333. while (end >= 0 && apr_isspace(interpreter[end])) {
  334. interpreter[end] = '\0';
  335. end--;
  336. }
  337. if (interpreter[2] != '/' && interpreter[2] != '\\' && interpreter[3] != ':') {
  338. char buffer[300];
  339. if (DosSearchPath(SEARCH_ENVIRONMENT, "PATH", interpreter+2, buffer, sizeof(buffer)) == 0) {
  340. strcpy(interpreter+2, buffer);
  341. } else {
  342. strcat(interpreter, ".exe");
  343. if (DosSearchPath(SEARCH_ENVIRONMENT, "PATH", interpreter+2, buffer, sizeof(buffer)) == 0) {
  344. strcpy(interpreter+2, buffer);
  345. }
  346. }
  347. }
  348. } else {
  349. interpreter[0] = 0;
  350. }
  351. }
  352. apr_file_close(progfile);
  353. }
  354. }
  355. i = 0;
  356. while (args && args[i]) {
  357. i++;
  358. }
  359. newargs = (const char **)apr_palloc(pool, sizeof (char *) * (i + 4));
  360. numargs = 0;
  361. if (interpreter[0])
  362. newargs[numargs++] = interpreter + 2;
  363. if (extra_arg)
  364. newargs[numargs++] = "/c";
  365. newargs[numargs++] = newprogname = apr_pstrdup(pool, progname);
  366. arg = 1;
  367. while (args && args[arg]) {
  368. newargs[numargs++] = args[arg++];
  369. }
  370. newargs[numargs] = NULL;
  371. for (i=0; newprogname[i]; i++)
  372. if (newprogname[i] == '/')
  373. newprogname[i] = '\\';
  374. cmdlen = 0;
  375. for (i=0; i<numargs; i++)
  376. cmdlen += strlen(newargs[i]) + 3;
  377. cmdline = apr_palloc(pool, cmdlen + 2);
  378. cmdline_pos = cmdline;
  379. for (i=0; i<numargs; i++) {
  380. const char *a = newargs[i];
  381. if (strpbrk(a, "&|<>\" "))
  382. a = apr_pstrcat(pool, "\"", double_quotes(pool, a), "\"", NULL);
  383. if (i)
  384. *(cmdline_pos++) = ' ';
  385. strcpy(cmdline_pos, a);
  386. cmdline_pos += strlen(cmdline_pos);
  387. }
  388. *(++cmdline_pos) = 0; /* Add required second terminator */
  389. cmdline_pos = strchr(cmdline, ' ');
  390. if (cmdline_pos) {
  391. *cmdline_pos = 0;
  392. cmdline_pos++;
  393. }
  394. /* Create environment block from list of envariables */
  395. if (env) {
  396. for (env_len=1, e=0; env[e]; e++)
  397. env_len += strlen(env[e]) + 1;
  398. env_block = apr_palloc(pool, env_len);
  399. env_block_pos = env_block;
  400. for (e=0; env[e]; e++) {
  401. strcpy(env_block_pos, env[e]);
  402. env_block_pos += strlen(env_block_pos) + 1;
  403. }
  404. *env_block_pos = 0; /* environment block is terminated by a double null */
  405. } else
  406. env_block = NULL;
  407. status = DosExecPgm(error_object, sizeof(error_object),
  408. attr->detached ? EXEC_BACKGROUND : EXEC_ASYNCRESULT,
  409. cmdline, env_block, &rescodes, cmdline);
  410. proc->pid = rescodes.codeTerminate;
  411. if (attr->currdir != NULL) {
  412. chdir(savedir);
  413. }
  414. if (attr->child_in) {
  415. if (attr->child_in->filedes != -1) {
  416. apr_file_close(attr->child_in);
  417. }
  418. dup = STDIN_FILENO;
  419. DosDupHandle(save_in, &dup);
  420. DosClose(save_in);
  421. }
  422. if (attr->child_out) {
  423. if (attr->child_out->filedes != -1) {
  424. apr_file_close(attr->child_out);
  425. }
  426. dup = STDOUT_FILENO;
  427. DosDupHandle(save_out, &dup);
  428. DosClose(save_out);
  429. }
  430. if (attr->child_err) {
  431. if (attr->child_err->filedes != -1) {
  432. apr_file_close(attr->child_err);
  433. }
  434. dup = STDERR_FILENO;
  435. DosDupHandle(save_err, &dup);
  436. DosClose(save_err);
  437. }
  438. if (criticalsection)
  439. DosExitCritSec();
  440. return status;
  441. }
  442. static void proces_result_codes(RESULTCODES codes,
  443. int *exitcode,
  444. apr_exit_why_e *exitwhy)
  445. {
  446. int result = 0;
  447. apr_exit_why_e why = APR_PROC_EXIT;
  448. switch (codes.codeTerminate) {
  449. case TC_EXIT: /* Normal exit */
  450. why = APR_PROC_EXIT;
  451. result = codes.codeResult;
  452. break;
  453. case TC_HARDERROR: /* Hard error halt */
  454. why = APR_PROC_SIGNAL;
  455. result = SIGSYS;
  456. break;
  457. case TC_KILLPROCESS: /* Was killed by a DosKillProcess() */
  458. why = APR_PROC_SIGNAL;
  459. result = SIGKILL;
  460. break;
  461. case TC_TRAP: /* TRAP in 16 bit code */
  462. case TC_EXCEPTION: /* Threw an exception (32 bit code) */
  463. why = APR_PROC_SIGNAL;
  464. switch (codes.codeResult | XCPT_FATAL_EXCEPTION) {
  465. case XCPT_ACCESS_VIOLATION:
  466. result = SIGSEGV;
  467. break;
  468. case XCPT_ILLEGAL_INSTRUCTION:
  469. result = SIGILL;
  470. break;
  471. case XCPT_FLOAT_DIVIDE_BY_ZERO:
  472. case XCPT_INTEGER_DIVIDE_BY_ZERO:
  473. result = SIGFPE;
  474. break;
  475. default:
  476. result = codes.codeResult;
  477. break;
  478. }
  479. }
  480. if (exitcode) {
  481. *exitcode = result;
  482. }
  483. if (exitwhy) {
  484. *exitwhy = why;
  485. }
  486. }
  487. APR_DECLARE(apr_status_t) apr_proc_wait_all_procs(apr_proc_t *proc,
  488. int *exitcode,
  489. apr_exit_why_e *exitwhy,
  490. apr_wait_how_e waithow,
  491. apr_pool_t *p)
  492. {
  493. RESULTCODES codes;
  494. ULONG rc;
  495. PID pid;
  496. rc = DosWaitChild(DCWA_PROCESSTREE, waithow == APR_WAIT ? DCWW_WAIT : DCWW_NOWAIT, &codes, &pid, 0);
  497. if (rc == 0) {
  498. proc->pid = pid;
  499. proces_result_codes(codes, exitcode, exitwhy);
  500. return APR_CHILD_DONE;
  501. } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
  502. return APR_CHILD_NOTDONE;
  503. }
  504. return APR_OS2_STATUS(rc);
  505. }
  506. APR_DECLARE(apr_status_t) apr_proc_wait(apr_proc_t *proc,
  507. int *exitcode, apr_exit_why_e *exitwhy,
  508. apr_wait_how_e waithow)
  509. {
  510. RESULTCODES codes;
  511. ULONG rc;
  512. PID pid;
  513. rc = DosWaitChild(DCWA_PROCESS, waithow == APR_WAIT ? DCWW_WAIT : DCWW_NOWAIT, &codes, &pid, proc->pid);
  514. if (rc == 0) {
  515. proces_result_codes(codes, exitcode, exitwhy);
  516. return APR_CHILD_DONE;
  517. } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
  518. return APR_CHILD_NOTDONE;
  519. }
  520. return APR_OS2_STATUS(rc);
  521. }
  522. APR_DECLARE(apr_status_t) apr_proc_detach(int daemonize)
  523. {
  524. return APR_ENOTIMPL;
  525. }
  526. APR_DECLARE(apr_status_t) apr_procattr_user_set(apr_procattr_t *attr,
  527. const char *username,
  528. const char *password)
  529. {
  530. return APR_ENOTIMPL;
  531. }
  532. APR_DECLARE(apr_status_t) apr_procattr_group_set(apr_procattr_t *attr,
  533. const char *groupname)
  534. {
  535. return APR_ENOTIMPL;
  536. }