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.

pyutils.c 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. #include "pyutils.h"
  2. void pyinit()
  3. {
  4. update_python_path();
  5. Py_Initialize();
  6. }
  7. void update_python_path()
  8. {
  9. wchar_t *ppath, *wcwd, *wtmp;
  10. char *cwd, *ncwd, *err_fmt;
  11. size_t wcwd_sz, ppath_sz;
  12. int err;
  13. // Add a ':' in front of CWD and convert to wchar_t*
  14. cwd = get_current_dir_name();
  15. if(!cwd)
  16. {
  17. err = errno;
  18. err_fmt = "Unable to fecth CWD : %s";
  19. goto update_python_path_err;
  20. }
  21. ncwd = malloc(sizeof(char) * (strlen(cwd) + 2));
  22. if(!ncwd)
  23. {
  24. err = errno;
  25. err_fmt = "Unable to allocate memory for new CWD : %s";
  26. goto update_python_path_err_cwd;
  27. }
  28. ncwd[0] = ':';
  29. strcpy(ncwd+1, cwd);
  30. free(cwd);
  31. cwd = ncwd;
  32. wcwd_sz = mbstowcs(NULL, cwd, 0);
  33. if(wcwd_sz == (size_t)-1)
  34. {
  35. err = errno;
  36. err_fmt = "Unable to convert CWD to wchar : %s";
  37. goto update_python_path_err_cwd;
  38. }
  39. wcwd_sz++;
  40. wcwd = malloc(sizeof(wchar_t) * wcwd_sz);
  41. if(!wcwd)
  42. {
  43. err = errno;
  44. err_fmt = "Unable to allocate wchar for CWD : %s";
  45. goto update_python_path_err_cwd;
  46. }
  47. if(mbstowcs(wcwd, cwd, wcwd_sz) == -1)
  48. {
  49. err = errno;
  50. err_fmt = "Unable to convert CWD to wchar : %s";
  51. goto update_python_path_err_wcwd;
  52. }
  53. // Fetch, dup & update the python path
  54. pyfcgi_log(LOG_DEBUG, "DEBUG 0");
  55. ppath = Py_GetPath();
  56. pyfcgi_log(LOG_DEBUG, "DEBUG 1");
  57. if(!ppath)
  58. {
  59. if(PyErr_Occurred())
  60. {
  61. log_expt(LOG_ALERT);
  62. }
  63. else
  64. {
  65. pyfcgi_log(LOG_ALERT, "Unable to fetch python path, got NULL.");
  66. }
  67. err_fmt = NULL;
  68. err = 0;
  69. goto update_python_path_err_wcwd;
  70. }
  71. ppath = wcsdup(ppath);
  72. if(!wcwd)
  73. {
  74. err = errno;
  75. err_fmt = "Unable to dup the python PATH : %s";
  76. goto update_python_path_err_wcwd;
  77. }
  78. ppath_sz = wcslen(ppath);
  79. wtmp = realloc(ppath, sizeof(wchar_t) * (ppath_sz + wcwd_sz + 1));
  80. if(!wtmp)
  81. {
  82. err = errno;
  83. err_fmt = "Unable reallocate new python path : %s";
  84. goto update_python_path_err_ppath;
  85. }
  86. ppath = wtmp;
  87. wcsncpy(ppath+ppath_sz, wcwd, wcwd_sz);
  88. Py_SetPath(ppath);
  89. free(ppath);
  90. free(wcwd);
  91. free(cwd);
  92. return;
  93. update_python_path_err_ppath:
  94. free(ppath);
  95. update_python_path_err_wcwd:
  96. free(wcwd);
  97. update_python_path_err_cwd:
  98. free(cwd);
  99. update_python_path_err:
  100. if(err_fmt)
  101. {
  102. pyfcgi_log( LOG_ALERT, err_fmt, strerror(err));
  103. }
  104. exit(err);
  105. }
  106. static PyObject* _fetch_pyflush(const char *fdname)
  107. {
  108. PyObject *pyfd, *pyflush;
  109. pyfd = PySys_GetObject(fdname);
  110. if(!pyfd)
  111. {
  112. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s", fdname);
  113. log_expt(LOG_ALERT);
  114. Py_Exit(EXIT_PYERR);
  115. }
  116. pyflush = PyObject_GetAttrString(pyfd, "flush");
  117. Py_DECREF(pyfd);
  118. if(!pyflush)
  119. {
  120. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s.flush", fdname);
  121. log_expt(LOG_ALERT);
  122. Py_Exit(EXIT_PYERR);
  123. }
  124. if(!PyCallable_Check(pyflush))
  125. {
  126. pyfcgi_log(LOG_ALERT, "sys.%s.flush is not callable !",
  127. fdname);
  128. Py_Exit(EXIT_PYERR);
  129. }
  130. return pyflush;
  131. }
  132. void fetch_pyflush(PyObject** pystdout_flush, PyObject** pystderr_flush)
  133. {
  134. *pystdout_flush = _fetch_pyflush("stdout");
  135. *pystderr_flush = _fetch_pyflush("stderr");
  136. }
  137. void update_python_fd(int pipe_out[2], int pipe_err[2])
  138. {
  139. int pri, err;
  140. char *err_fmt;
  141. PyObject *os_mod, *pyfdopen, *args, *new_fd;
  142. pri = LOG_ALERT;
  143. if(pipe2(pipe_out, O_DIRECT) == -1)
  144. {
  145. err = errno;
  146. err_fmt = "Unable to create pipe for python stdout : %s";
  147. goto update_python_fd_err;
  148. }
  149. if(pipe2(pipe_err, O_DIRECT) == -1)
  150. {
  151. err = errno;
  152. err_fmt = "Unable to create pipe for python stderr : %s";
  153. goto update_python_fd_err_pipeout;
  154. }
  155. os_mod = PyImport_ImportModule("os");
  156. if(!os_mod)
  157. {
  158. if(PyErr_Occurred())
  159. {
  160. log_expt(LOG_ALERT);
  161. }
  162. else
  163. {
  164. pyfcgi_log( LOG_ALERT,
  165. "Unable to import python os module, got NULL.");
  166. }
  167. err_fmt = NULL;
  168. goto update_python_fd_err_pipes;
  169. }
  170. pyfdopen = PyObject_GetAttrString(os_mod, "fdopen");
  171. Py_DECREF(os_mod);
  172. if(!pyfdopen)
  173. {
  174. if(PyErr_Occurred())
  175. {
  176. log_expt(LOG_ALERT);
  177. }
  178. else
  179. {
  180. pyfcgi_log( LOG_ALERT,
  181. "Unable to fetch os.fdopen() , got NULL.");
  182. }
  183. err_fmt = NULL;
  184. goto update_python_fd_err_pipes;
  185. }
  186. args = Py_BuildValue("is", pipe_out[1], "w");
  187. if(!args)
  188. {
  189. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stdout",
  190. pipe_out[1], "w");
  191. log_expt(LOG_ALERT);
  192. err_fmt = NULL;
  193. goto update_python_fd_err_fdopen;
  194. }
  195. new_fd = PyObject_CallObject(pyfdopen, args);
  196. if(!new_fd || PyErr_Occurred())
  197. {
  198. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  199. pipe_out[1], "w");
  200. log_expt(LOG_ALERT);
  201. err_fmt = NULL;
  202. goto update_python_fd_err_args;
  203. }
  204. Py_DECREF(args);
  205. if(PySys_SetObject("stdout", new_fd))
  206. {
  207. pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
  208. log_expt(LOG_ALERT);
  209. goto update_python_fd_err_newfd;
  210. }
  211. Py_DECREF(new_fd);
  212. args = Py_BuildValue("is", pipe_err[1], "w");
  213. if(!args)
  214. {
  215. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stderr",
  216. pipe_out[1], "w");
  217. log_expt(LOG_ALERT);
  218. err_fmt = NULL;
  219. goto update_python_fd_err_fdopen;
  220. }
  221. new_fd = PyObject_CallObject(pyfdopen, args);
  222. if(!new_fd || PyErr_Occurred())
  223. {
  224. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  225. pipe_out[1], "w");
  226. log_expt(LOG_ALERT);
  227. err_fmt = NULL;
  228. goto update_python_fd_err_args;
  229. }
  230. Py_DECREF(args);
  231. if(PySys_SetObject("stderr", new_fd))
  232. {
  233. pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
  234. log_expt(LOG_ALERT);
  235. goto update_python_fd_err_newfd;
  236. }
  237. Py_DECREF(new_fd);
  238. return;
  239. update_python_fd_err_newfd:
  240. Py_DECREF(new_fd);
  241. update_python_fd_err_args:
  242. Py_DECREF(args);
  243. update_python_fd_err_fdopen:
  244. Py_DECREF(fdopen);
  245. update_python_fd_err_pipes:
  246. close(pipe_err[0]);
  247. close(pipe_err[1]);
  248. update_python_fd_err_pipeout:
  249. close(pipe_out[0]);
  250. close(pipe_out[1]);
  251. update_python_fd_err:
  252. if(err_fmt)
  253. {
  254. pyfcgi_log(pri, err_fmt, strerror(err));
  255. }
  256. exit(1);
  257. }
  258. PyObject* update_pyenv(PyObject *py_osmod, char **environ)
  259. {
  260. PyObject *pyenv, *pykey, *pyval;
  261. char *key, *value, **cur;
  262. cur = environ;
  263. pyenv = PyObject_GetAttrString(py_osmod, "environ");
  264. if(!pyenv)
  265. {
  266. pyfcgi_log(LOG_WARNING, "Unable to get os.environ");
  267. log_expt(LOG_ALERT);
  268. }
  269. else
  270. {
  271. Py_DECREF(pyenv);
  272. }
  273. pyenv = PyDict_New();
  274. while(*cur)
  275. {
  276. key = value = *cur;
  277. while(*value && *value != '=')
  278. {
  279. value++;
  280. }
  281. if(!*value)
  282. {
  283. pyfcgi_log(LOG_WARNING, "Droping environment value without value : '%s'",
  284. key);
  285. cur++;
  286. continue;
  287. }
  288. value++;
  289. *(value-1) = '\0'; // dirty modification of **environ
  290. //pyfcgi_log(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
  291. pykey = PyUnicode_DecodeLocale(key, "surrogateescape");
  292. if(!pykey)
  293. {
  294. *(value-1) = '='; // **environ restore
  295. pyfcgi_log(LOG_ALERT, "Unable to parse environ key string '%s'",
  296. key);
  297. log_expt(LOG_ALERT);
  298. Py_Exit(EXIT_PYERR);
  299. }
  300. *(value-1) = '='; // **environ restore
  301. pyval = PyUnicode_DecodeFSDefault(value);
  302. if(!pyval)
  303. {
  304. pyfcgi_log(LOG_ALERT, "Unable to parse environ val string '%s'",
  305. value);
  306. log_expt(LOG_ALERT);
  307. Py_Exit(EXIT_PYERR);
  308. }
  309. if(PyDict_SetItem(pyenv, pykey, pyval) == -1)
  310. {
  311. pyfcgi_log(LOG_ERR, "Unable to set environ '%s'='%s'",
  312. key, value);
  313. log_expt(LOG_ERR);
  314. }
  315. Py_DECREF(pyval);
  316. Py_DECREF(pykey);
  317. cur++;
  318. }
  319. PyObject_SetAttrString(py_osmod, "environ", pyenv);
  320. return pyenv;
  321. }
  322. PyObject* import_entrypoint()
  323. {
  324. PyObject *entry_fname, *entry_module, *entry_fun;
  325. entry_fname = PyUnicode_DecodeFSDefault(PyFCGI_conf.py_entrymod);
  326. entry_module = PyImport_Import(entry_fname);
  327. Py_DECREF(entry_fname);
  328. if(!entry_module)
  329. {
  330. //TODO syslog python error / traceback
  331. pyfcgi_log(LOG_CRIT, "Unable to import python file '%s'",
  332. PyFCGI_conf.py_entrymod);
  333. //pyfcgi_log( LOG_INFO, "%s", getcwd(NULL, 256));
  334. log_expt(LOG_ERR);
  335. return NULL;
  336. }
  337. // getting entrypoint function
  338. entry_fun = PyObject_GetAttrString(entry_module, PyFCGI_conf.py_entryfun);
  339. Py_DECREF(entry_module);
  340. if(!entry_fun)
  341. {
  342. //TODO syslog python error / traceback
  343. pyfcgi_log(LOG_CRIT,
  344. "Unable to import object '%s' from '%s'",
  345. PyFCGI_conf.py_entryfun, PyFCGI_conf.py_entrymod);
  346. log_expt(LOG_ERR);
  347. return NULL;
  348. }
  349. if(!PyCallable_Check(entry_fun))
  350. {
  351. pyfcgi_log( LOG_CRIT,
  352. "When imported from '%s', '%s' was not a callable",
  353. PyFCGI_conf.py_entrymod, PyFCGI_conf.py_entryfun);
  354. return NULL;
  355. }
  356. return entry_fun;
  357. }
  358. PyObject* get_start_response()
  359. {
  360. PyObject *module;
  361. if(!libpyfcgi.self)
  362. {
  363. module = PyInit_libpyfcgi();
  364. if(module == NULL)
  365. {
  366. pyfcgi_log(LOG_ERR, "Unable to create libpyfcgi python module");
  367. return NULL;
  368. }
  369. libpyfcgi.self = module;
  370. }
  371. return PyObject_GetAttrString(libpyfcgi.self, "start_response");
  372. }
  373. void log_expt(int priority)
  374. {
  375. if(!PyErr_Occurred())
  376. {
  377. pyfcgi_log(priority, "No exception");
  378. return;
  379. }
  380. PyObject *expt, *expt_bytes, *expt_cls,
  381. *expt_val, *expt_type, *traceback;
  382. char *msg, *type, *val;
  383. int msg_sz;
  384. char msg_fmt[] = "%s: %s";
  385. PyErr_Fetch(&expt_cls, &expt, &traceback);
  386. // Fetching exception message using __str__()
  387. expt_val = PyObject_ASCII(expt);
  388. expt_bytes = PyUnicode_AsUTF8String(expt_val);
  389. msg = PyBytes_AsString(expt_bytes);
  390. val = strndup(msg, 1024); // TODO check out of mem if !val
  391. // Fetching exception class name
  392. expt_type = PyObject_GetAttrString(expt_cls, "__name__");
  393. expt_bytes = PyUnicode_AsUTF8String(expt_type);
  394. msg = PyBytes_AsString(expt_bytes);
  395. type = strndup(msg, 1024); // TODO check out of mem if !type
  396. msg_sz = snprintf(NULL, 0, msg_fmt, type, val);
  397. msg_sz++;
  398. msg = malloc(sizeof(char) * msg_sz);
  399. snprintf(msg, msg_sz, msg_fmt, type, val);
  400. pyfcgi_log(priority, msg);
  401. }
  402. void pyfcgi_python_version(char version[16])
  403. {
  404. snprintf(version, 16, "%d.%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION,
  405. PY_MICRO_VERSION);
  406. }
  407. PyObject* python_osmod()
  408. {
  409. PyObject *ret;
  410. ret = PyImport_ImportModule("os");
  411. if(!ret)
  412. {
  413. pyfcgi_log(LOG_ALERT, "Unable to import os module");
  414. log_expt(LOG_ALERT);
  415. Py_Exit(EXIT_PYERR);
  416. }
  417. return ret;
  418. }