Tests about a simple python3 fastcgi runner using libfcgi and the Python-C API.
python
c
wsgi
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pyworker.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /*
  2. * Copyright (C) 2019 Weber Yann
  3. *
  4. * This file is part of PyFCGI.
  5. *
  6. * PyFCGI is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * any later version.
  10. *
  11. * PyFCGI is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with PyFCGI. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "pyworker.h"
  20. static int worker_piper_sigrcv = 0;
  21. int work(int wrk_id, int semid)
  22. {
  23. PyObject *entry_fun, *pystdout_flush, *pystderr_flush,
  24. *py_osmod;
  25. FCGX_Stream *in_stream, *out_stream, *err_stream;
  26. char **envp;
  27. int count, pipe_out[2], pipe_err[2], pipe_ctl[2], err, piper_status;
  28. int max_reqs;
  29. struct sembuf sop;
  30. struct sigaction act;
  31. struct timeval start, stop;
  32. sigset_t emptyset;
  33. char buf[PIPE_BUF];
  34. size_t rep_sz;
  35. piper_args_t piper_args;
  36. char *piper_stack;
  37. max_reqs = PyFCGI_conf.max_reqs;
  38. piper_args.wrk_id = wrk_id;
  39. piper_args.act = &act;
  40. sop.sem_num = 0;
  41. sop.sem_flg = 0;
  42. // preparing sigaction for piper
  43. if(sigemptyset(&emptyset))
  44. {
  45. err = errno;
  46. pyfcgi_log( LOG_ALERT, "sigemptyset fails in piper : %s",
  47. strerror(err));
  48. exit(err);
  49. }
  50. act.sa_handler = worker_piper_sighandler;
  51. act.sa_mask = emptyset;
  52. //act.sa_flags = SA_RESTART;
  53. act.sa_flags = 0;
  54. act.sa_restorer = NULL;
  55. if( !(piper_stack = malloc(PIPER_STACK_SZ)) )
  56. {
  57. err = errno;
  58. pyfcgi_log(LOG_ALERT, "Error while allocating piper stack : %s",
  59. strerror(err));
  60. exit(err);
  61. }
  62. pyfcgi_log( LOG_INFO,
  63. "Worker %d started", wrk_id);
  64. update_python_path(); // add cwd to python path
  65. Py_Initialize(); // "start" python
  66. update_python_fd(pipe_out, pipe_err);
  67. piper_args.pystdout = pipe_out[0];
  68. piper_args.pystderr = pipe_err[0];
  69. fetch_pyflush(&pystdout_flush, &pystderr_flush);
  70. pyfcgi_log( LOG_INFO,
  71. "Worker[%d] Python started", wrk_id);
  72. //importing os
  73. py_osmod = PyImport_ImportModule("os");
  74. if(!py_osmod)
  75. {
  76. pyfcgi_log(LOG_ALERT, "Unable to import os module");
  77. log_expt(LOG_ALERT);
  78. Py_Exit(EXIT_PYERR);
  79. }
  80. // loading module
  81. entry_fun = import_entrypoint();
  82. pyfcgi_log( LOG_INFO,
  83. "Worker[%d] Waiting request with %s.%s()", wrk_id,
  84. PyFCGI_conf.py_entrymod, PyFCGI_conf.py_entryfun);
  85. sop.sem_op = 1; //indicate worker as ready & idle
  86. if(semop(semid, &sop, 1) < 0)
  87. {
  88. err = errno;
  89. pyfcgi_log(LOG_ERR,
  90. "Worker[%d] error incrementing the semaphore : %s",
  91. wrk_id, strerror(err));
  92. }
  93. if(!entry_fun) //but exit if import failed
  94. {
  95. pyfcgi_log(LOG_ALERT, "Unable to import entrypoint");
  96. exit(PYFCGI_FATAL);
  97. }
  98. if(pipe(pipe_ctl) == -1)
  99. {
  100. err = errno;
  101. pyfcgi_log(LOG_ALERT,
  102. "Worker[%d] Pipe fails for piper ctl : %s",
  103. wrk_id, strerror(err));
  104. exit(err);
  105. }
  106. piper_args.ctl_pipe = pipe_ctl[1];
  107. count = 0;
  108. while ((!count || count != max_reqs) && FCGX_Accept(&in_stream, &out_stream, &err_stream, &envp) >= 0)
  109. {
  110. gettimeofday(&start, NULL);
  111. sop.sem_op = -1; // decrementing sem to show worker busy
  112. if(semop(semid, &sop, 1) < 0)
  113. {
  114. err = errno;
  115. pyfcgi_log(LOG_ERR,
  116. "Worker[%d] error decrementing the semaphore : %s",
  117. wrk_id, strerror(err));
  118. }
  119. count++;
  120. piper_args.out = out_stream;
  121. //piper_args.req_id = count;
  122. worker_piper_sigrcv = 0;
  123. pid_t pid = clone(worker_piper, piper_stack + PIPER_STACK_SZ - 1,
  124. CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | \
  125. SIGCHLD | \
  126. CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_VM,
  127. &piper_args);
  128. if(pid < 0)
  129. {
  130. err = errno;
  131. pyfcgi_log(LOG_ALERT,
  132. "Worker[%d] req #%d Fork failed for piper : %s",
  133. wrk_id, count, strerror(err));
  134. exit(err);
  135. }
  136. update_pyenv(py_osmod, envp);
  137. //close(pipe_ctl[1]);
  138. PyObject_CallObject(entry_fun, NULL);
  139. if(PyErr_Occurred())
  140. {
  141. log_expt(LOG_ERR);
  142. }
  143. else
  144. {
  145. /*
  146. pyfcgi_log(LOG_DEBUG, "Worker[%d] request %d funcall [OK]",
  147. wrk_id, count);
  148. */
  149. }
  150. PyObject_CallObject(pystdout_flush, NULL);
  151. PyObject_CallObject(pystderr_flush, NULL);
  152. read(pipe_ctl[0], &buf, 1); // unblock when child ready
  153. FCGX_FClose(in_stream);
  154. FCGX_FClose(err_stream);
  155. //close(pipe_ctl[0]);
  156. kill(pid, WPIPER_SIG); //indicate child python call ended
  157. /*
  158. gettimeofday(&stop, NULL);
  159. pyfcgi_log(LOG_DEBUG, "W%dR%dtimer KIL %ld.%06ld us", wrk_id, count, stop.tv_sec - start.tv_sec, stop.tv_usec - start.tv_usec);
  160. */
  161. waitpid(pid, &piper_status, 0);
  162. /*
  163. gettimeofday(&stop, NULL);
  164. pyfcgi_log(LOG_DEBUG, "W%dR%dtimer W8 %ld.%06ld us", wrk_id, count, stop.tv_sec - start.tv_sec, stop.tv_usec - start.tv_usec);
  165. */
  166. if(WIFSIGNALED(piper_status))
  167. {
  168. pyfcgi_log(LOG_ERR,
  169. "Woker[%d] req #%d piper terminated by sig %d",
  170. wrk_id, count, WTERMSIG(piper_status));
  171. exit(40);
  172. }
  173. else if(WEXITSTATUS(piper_status))
  174. {
  175. pyfcgi_log(LOG_ERR,
  176. "Woker[%d] req #%d piper exited with error status %d",
  177. wrk_id, count, WEXITSTATUS(piper_status));
  178. exit(41);
  179. }
  180. FCGI_Finish();
  181. if(ctl_get_rep_sz(pipe_ctl[0], &rep_sz))
  182. {
  183. rep_sz = 0;
  184. }
  185. //Increase sem showing the worker is idle
  186. sop.sem_op = 1;
  187. if(semop(semid, &sop, 1) < 0)
  188. {
  189. err = errno;
  190. pyfcgi_log(LOG_ERR,
  191. "Worker[%d] error incrementing the semaphore : %s",
  192. wrk_id, strerror(err));
  193. }
  194. gettimeofday(&stop, NULL);
  195. stop.tv_sec = stop.tv_sec - start.tv_sec;
  196. stop.tv_usec = stop.tv_usec - start.tv_usec;
  197. if(stop.tv_usec < 0)
  198. {
  199. stop.tv_usec += 1000000;
  200. stop.tv_sec -= 1;
  201. }
  202. pyfcgi_log(LOG_DEBUG, "Worker[%d] request %d END [OK] %lu bytes in %ld.%06lds",
  203. wrk_id, count, rep_sz, stop.tv_sec, stop.tv_usec);
  204. }
  205. free(piper_stack);
  206. Py_Exit(count == max_reqs ?0:EXIT_PYERR);
  207. }
  208. /*
  209. void worker_piper(int wrk_id, int req_id, int pystdout, int pystderr,
  210. int ctl_pipe, FCGX_Stream* out)
  211. */
  212. int worker_piper(void *ptr)
  213. {
  214. piper_args_t *args;
  215. struct pollfd fds[2];
  216. int err, ret, cont, poll_ret;
  217. short revents, closed, nfds;
  218. char buf[PIPE_BUF];
  219. size_t out_sz;
  220. args = ptr;
  221. int wrk_id, pystdout, pystderr, ctl_pipe;
  222. //int req_id;
  223. FCGX_Stream *out;
  224. struct sigaction *act;
  225. wrk_id = args->wrk_id;
  226. //req_id = args->req_id;
  227. pystdout = args->pystdout;
  228. pystderr = args->pystderr;
  229. ctl_pipe = args->ctl_pipe;
  230. out = args->out;
  231. act = args->act;
  232. if(sigaction(WPIPER_SIG, act, NULL))
  233. {
  234. err = errno;
  235. pyfcgi_log( LOG_ALERT, "Unable to sigaction in piper : %s",
  236. strerror(err));
  237. exit(err);
  238. }
  239. write(ctl_pipe, " ", 1);
  240. //pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d piper", wrk_id, req_id);
  241. fds[0].fd = pystderr;
  242. fds[1].fd = pystdout;
  243. fds[0].events = fds[1].events = POLLIN;
  244. fds[0].revents = fds[1].revents = 0;
  245. nfds = 2;
  246. closed = out_sz = 0;
  247. cont = 2;
  248. while(cont)
  249. {
  250. poll_ret = poll(fds, nfds, 0);
  251. //pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
  252. if(poll_ret < 0)
  253. {
  254. err = errno;
  255. if(err == EINTR)
  256. {
  257. continue;
  258. }
  259. pyfcgi_log( LOG_WARNING, "Poll error : %s",
  260. strerror(err));
  261. fds[0].revents = fds[1].revents = 0;
  262. continue;
  263. }
  264. if(!poll_ret && worker_piper_sigrcv)
  265. {
  266. cont--;
  267. continue;
  268. }
  269. if(poll_ret && (revents = fds[1].revents))
  270. {
  271. /*
  272. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d STDOUT evt !",
  273. wrk_id, req_id);
  274. */
  275. poll_ret--;
  276. if(revents & POLLIN)
  277. {
  278. /*
  279. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d POLLIN STDOUT !",
  280. wrk_id, req_id);
  281. */
  282. ret = read(pystdout, buf, PIPE_BUF);
  283. /*
  284. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d read(stdout) ret %d",
  285. wrk_id, req_id, ret);
  286. */
  287. if(ret < 0)
  288. {
  289. err = errno;
  290. if(err == EINTR)
  291. {
  292. continue;
  293. }
  294. pyfcgi_log( LOG_ERR,
  295. "Error reading python stdout : %s",
  296. strerror(err));
  297. exit(err);
  298. }
  299. //buf[ret] = '\0';
  300. ret = FCGX_PutStr(buf, ret, out);
  301. out_sz += ret;
  302. if(ret < PIPE_BUF && worker_piper_sigrcv)
  303. {
  304. FCGX_FClose(out);
  305. FCGI_Finish();
  306. write(ctl_pipe, &out_sz, sizeof(size_t));
  307. closed = 1;
  308. nfds = 1;
  309. }
  310. }
  311. //TODO handle other poll events
  312. }
  313. if(poll_ret && (revents = fds[0].revents))
  314. {
  315. /*
  316. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d STDERR evt !",
  317. wrk_id, req_id);
  318. */
  319. if(revents & POLLIN)
  320. {
  321. /*
  322. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d POLLIN STDERR !",
  323. wrk_id, req_id);
  324. */
  325. while(1)
  326. {
  327. ret = read(pystderr, buf, PIPE_BUF);
  328. if(ret < 0)
  329. {
  330. err = errno;
  331. if(err == EINTR)
  332. {
  333. continue;
  334. }
  335. pyfcgi_log( LOG_ERR,
  336. "Error reading python stdout : %s",
  337. strerror(err));
  338. exit(err);
  339. }
  340. buf[ret] = '\0';
  341. pyfcgi_log(LOG_ERR,
  342. "Worker[%d] Python error : %s",
  343. wrk_id, buf);
  344. break;
  345. }
  346. }
  347. //TODO handle other poll events
  348. }
  349. fds[0].revents = fds[1].revents = 0;
  350. }
  351. /*
  352. pyfcgi_log(LOG_DEBUG, "Worker[%d] req #%d piper exiting...",
  353. wrk_id, req_id);
  354. */
  355. if(!closed)
  356. {
  357. FCGX_FClose(out);
  358. FCGI_Finish();
  359. write(ctl_pipe, &out_sz, sizeof(size_t));
  360. }
  361. return 0;
  362. }
  363. void worker_piper_sighandler(int signum)
  364. {
  365. worker_piper_sigrcv = 1;
  366. //pyfcgi_log(LOG_DEBUG, "SIG");
  367. }
  368. int ctl_get_rep_sz(int ctl_pipe, size_t* rep_sz)
  369. {
  370. struct pollfd fds;
  371. int ret, err;
  372. fds.fd = ctl_pipe;
  373. fds.events = POLLIN;
  374. fds.revents = 0;
  375. if( (ret = poll(&fds, 1, 0)) < 0)
  376. {
  377. err = errno;
  378. pyfcgi_log(LOG_ERR, "Failed to poll ctl pipe : %s",
  379. strerror(err));
  380. return -1;
  381. }
  382. if(!ret)
  383. {
  384. pyfcgi_log(LOG_ERR, "No data in ctl pipe for rep_sz...");
  385. return -1;
  386. }
  387. ret = read(ctl_pipe, rep_sz, sizeof(size_t));
  388. if(ret < 0)
  389. {
  390. pyfcgi_log(LOG_ERR, "Error reading ctl pipe : %s",
  391. strerror(errno));
  392. return -1;
  393. }
  394. if(ret < sizeof(size_t))
  395. {
  396. pyfcgi_log(LOG_ERR, "Incomplete read from ctl pipe, no rep_sz...");
  397. return -1;
  398. }
  399. return 0;
  400. }
  401. static PyObject* _fetch_pyflush(const char *fdname)
  402. {
  403. PyObject *pyfd, *pyflush;
  404. pyfd = PySys_GetObject(fdname);
  405. if(!pyfd)
  406. {
  407. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s", fdname);
  408. log_expt(LOG_ALERT);
  409. Py_Exit(EXIT_PYERR);
  410. }
  411. pyflush = PyObject_GetAttrString(pyfd, "flush");
  412. Py_DECREF(pyfd);
  413. if(!pyflush)
  414. {
  415. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s.flush", fdname);
  416. log_expt(LOG_ALERT);
  417. Py_Exit(EXIT_PYERR);
  418. }
  419. if(!PyCallable_Check(pyflush))
  420. {
  421. pyfcgi_log(LOG_ALERT, "sys.%s.flush is not callable !",
  422. fdname);
  423. Py_Exit(EXIT_PYERR);
  424. }
  425. return pyflush;
  426. }
  427. void fetch_pyflush(PyObject** pystdout_flush, PyObject** pystderr_flush)
  428. {
  429. *pystdout_flush = _fetch_pyflush("stdout");
  430. *pystderr_flush = _fetch_pyflush("stderr");
  431. }
  432. void update_python_fd(int pipe_out[2], int pipe_err[2])
  433. {
  434. int pri, err;
  435. char *err_fmt;
  436. PyObject *os_mod, *pyfdopen, *args, *new_fd;
  437. pri = LOG_ALERT;
  438. if(pipe2(pipe_out, O_DIRECT) == -1)
  439. {
  440. err = errno;
  441. err_fmt = "Unable to create pipe for python stdout : %s";
  442. goto update_python_fd_err;
  443. }
  444. if(pipe2(pipe_err, O_DIRECT) == -1)
  445. {
  446. err = errno;
  447. err_fmt = "Unable to create pipe for python stderr : %s";
  448. goto update_python_fd_err_pipeout;
  449. }
  450. os_mod = PyImport_ImportModule("os");
  451. if(!os_mod)
  452. {
  453. if(PyErr_Occurred())
  454. {
  455. log_expt(LOG_ALERT);
  456. }
  457. else
  458. {
  459. pyfcgi_log( LOG_ALERT,
  460. "Unable to import python os module, got NULL.");
  461. }
  462. err_fmt = NULL;
  463. goto update_python_fd_err_pipes;
  464. }
  465. pyfdopen = PyObject_GetAttrString(os_mod, "fdopen");
  466. Py_DECREF(os_mod);
  467. if(!pyfdopen)
  468. {
  469. if(PyErr_Occurred())
  470. {
  471. log_expt(LOG_ALERT);
  472. }
  473. else
  474. {
  475. pyfcgi_log( LOG_ALERT,
  476. "Unable to fetch os.fdopen() , got NULL.");
  477. }
  478. err_fmt = NULL;
  479. goto update_python_fd_err_pipes;
  480. }
  481. args = Py_BuildValue("is", pipe_out[1], "w");
  482. if(!args)
  483. {
  484. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stdout",
  485. pipe_out[1], "w");
  486. log_expt(LOG_ALERT);
  487. err_fmt = NULL;
  488. goto update_python_fd_err_fdopen;
  489. }
  490. new_fd = PyObject_CallObject(pyfdopen, args);
  491. if(!new_fd || PyErr_Occurred())
  492. {
  493. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  494. pipe_out[1], "w");
  495. log_expt(LOG_ALERT);
  496. err_fmt = NULL;
  497. goto update_python_fd_err_args;
  498. }
  499. Py_DECREF(args);
  500. if(PySys_SetObject("stdout", new_fd))
  501. {
  502. pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
  503. log_expt(LOG_ALERT);
  504. goto update_python_fd_err_newfd;
  505. }
  506. Py_DECREF(new_fd);
  507. args = Py_BuildValue("is", pipe_err[1], "w");
  508. if(!args)
  509. {
  510. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stderr",
  511. pipe_out[1], "w");
  512. log_expt(LOG_ALERT);
  513. err_fmt = NULL;
  514. goto update_python_fd_err_fdopen;
  515. }
  516. new_fd = PyObject_CallObject(pyfdopen, args);
  517. if(!new_fd || PyErr_Occurred())
  518. {
  519. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  520. pipe_out[1], "w");
  521. log_expt(LOG_ALERT);
  522. err_fmt = NULL;
  523. goto update_python_fd_err_args;
  524. }
  525. Py_DECREF(args);
  526. if(PySys_SetObject("stderr", new_fd))
  527. {
  528. pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
  529. log_expt(LOG_ALERT);
  530. goto update_python_fd_err_newfd;
  531. }
  532. Py_DECREF(new_fd);
  533. return;
  534. update_python_fd_err_newfd:
  535. Py_DECREF(new_fd);
  536. update_python_fd_err_args:
  537. Py_DECREF(args);
  538. update_python_fd_err_fdopen:
  539. Py_DECREF(fdopen);
  540. update_python_fd_err_pipes:
  541. close(pipe_err[0]);
  542. close(pipe_err[1]);
  543. update_python_fd_err_pipeout:
  544. close(pipe_out[0]);
  545. close(pipe_out[1]);
  546. update_python_fd_err:
  547. if(err_fmt)
  548. {
  549. pyfcgi_log(pri, err_fmt, strerror(err));
  550. }
  551. exit(1);
  552. }
  553. void update_pyenv(PyObject *py_osmod, char **environ)
  554. {
  555. PyObject *pyenv, *pykey, *pyval;
  556. char *key, *value, **cur;
  557. cur = environ;
  558. pyenv = PyObject_GetAttrString(py_osmod, "environ");
  559. if(!pyenv)
  560. {
  561. pyfcgi_log(LOG_WARNING, "Unable to get os.environ");
  562. log_expt(LOG_ALERT);
  563. }
  564. else
  565. {
  566. Py_DECREF(pyenv);
  567. }
  568. pyenv = PyDict_New();
  569. while(*cur)
  570. {
  571. key = value = *cur;
  572. while(*value && *value != '=')
  573. {
  574. value++;
  575. }
  576. if(!*value)
  577. {
  578. pyfcgi_log(LOG_WARNING, "Droping environment value without value : '%s'",
  579. key);
  580. cur++;
  581. continue;
  582. }
  583. value++;
  584. *(value-1) = '\0'; // dirty modification of **environ
  585. //pyfcgi_log(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
  586. pykey = PyUnicode_DecodeLocale(key, "surrogateescape");
  587. if(!pykey)
  588. {
  589. *(value-1) = '='; // **environ restore
  590. pyfcgi_log(LOG_ALERT, "Unable to parse environ key string '%s'",
  591. key);
  592. log_expt(LOG_ALERT);
  593. Py_Exit(EXIT_PYERR);
  594. }
  595. *(value-1) = '='; // **environ restore
  596. pyval = PyUnicode_DecodeFSDefault(value);
  597. if(!pyval)
  598. {
  599. pyfcgi_log(LOG_ALERT, "Unable to parse environ val string '%s'",
  600. value);
  601. log_expt(LOG_ALERT);
  602. Py_Exit(EXIT_PYERR);
  603. }
  604. if(PyDict_SetItem(pyenv, pykey, pyval) == -1)
  605. {
  606. pyfcgi_log(LOG_ERR, "Unable to set environ '%s'='%s'",
  607. key, value);
  608. log_expt(LOG_ERR);
  609. }
  610. Py_DECREF(pyval);
  611. Py_DECREF(pykey);
  612. cur++;
  613. }
  614. PyObject_SetAttrString(py_osmod, "environ", pyenv);
  615. }