/* * Copyright (C) 2019 Weber Yann * * This file is part of PyFCGI. * * PyFCGI is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * PyFCGI is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PyFCGI. If not, see . */ #ifndef __CONF_H___ #define __CONF_H___ #include #include /* fcgi library; put it first*/ #define PY_SSIZE_T_CLEAN #include #include "structmember.h" #include #include #include #include #include #include #include "config.h" /**@file conf.h * @brief PyFCGI configuration handling headers * @ingroup conf_internal */ /**@defgroup conf_internal PyFCGI configuration handling * @brief Informations about PyFCGI configuration handling */ /**@defgroup conf_glob PYFCGI global (for all process) configurations * @see struct_pyfcgi_conf_s * @ingroup conf_internal */ /**@brief Friendly name for @ref pyfcgi_conf_s * @see struct pyfcgi_conf_s * @ingroup conf_internal */ typedef struct pyfcgi_conf_s pyfcgi_conf_t; typedef struct pyfcgi_conf_logger_s pyfcgi_conf_logger_t; typedef struct pyfcgi_context_s pyfcgi_context_t; typedef struct pyfcgi_semdata_s pyfcgi_semdata_t; typedef struct pyfcgi_shmdata_s pyfcgi_shmdata_t; /**@brief Configuration globals, inherited from parent process to childs * @ingroup conf_internal * @see pyfcgi_conf_s */ pyfcgi_conf_t PyFCGI_conf; /* Include other PyFCGI stuff */ #include "logger.h" #include "pyutils.h" #include "ipc.h" #include "monitor.h" /**@defgroup ret_status Function & process return status * @brief Constants * @ingroup proc_intern */ /**@ingroup ret_status */ #define EXIT_PYERR 4 /**@ingroup ret_status */ #define PYFCGI_TIMEOUT 8 /**@ingroup ret_status */ #define PYFCGI_ERR 16 /**@ingroup ret_status */ #define PYFCGI_WORKER_FAIL 32 /**@ingroup ret_status */ #define PYFCGI_MASTER_FAIL 64 /**@ingroup ret_status */ #define PYFCGI_FATAL 128 /**@brief Backlog argument for socket creation */ #define PYFCGI_SOCK_BACKLOG 100 /**@brief Program name */ #define PYFCGI_NAME "pyfcgi" /**@brief Short options specifications */ #define PYFCGI_SHORT_OPT "C:l:e:E:Aw:W:m:ft:L:SPs:vVh" /**@brief Long options specifications */ #define PYFCGI_LONG_OPT { \ {"config", required_argument, 0, 'C'},\ {"listen", required_argument, 0, 'l'},\ {"pymodule", required_argument, 0, 'e'},\ {"pyapp", required_argument, 0, 'E'},\ {"alt-io", no_argument, 0, 'A'},\ {"min-worker", required_argument, 0, 'w'},\ {"max-worker", required_argument, 0, 'W'},\ {"max-request", required_argument, 0, 'm'},\ {"fast-spawn", no_argument, 0, 'f'},\ {"timeout", required_argument, 0, 't'},\ {"log", required_argument, 0, 'L'},\ {"syslog", no_argument, 0, 'S'},\ {"pid-file", required_argument, 0, 'P'},\ {"socket-server", required_argument, 0, 's'},\ {"verbose", no_argument, 0, 'v'},\ {"version", no_argument, 0, 'V'},\ {"help", no_argument, 0, 'h' },\ {0, 0, 0, 0}\ } /**@brief Data for help generation * * For each options {HELP_TEXT, ARGS_PLACEOLDER} are specified PLACEOLDER * is NULL for flags*/ #define PYFCGI_OPT_HELP {\ {"Load options from configuration file", "CONFIG"},\ {"Listen socket path (a path for UNIX socket or a 'IPV4:PORT' string)", "SOCK_PATH"},\ {"Search application function in given python module", "MODULE_NAME"},\ {"Python application entrypoint function name", "FUNC_NAME"},\ {"Use stdout to communicate with web server instead of entrypoint return as specified in PEP 333", NULL},\ {"Minimum worker in the pool", "INT"},\ {"Maximum worker in the pool", "INT"},\ {"Request count after wich the worker is restarted (if 0 never restart)", "INT"},\ {"If not given there is at least 1s between 2 child creation, else childs may be spawned in small burst", NULL},\ {"Request timeout (before worker restarts)", "SECONDS"},\ {"Add a logfile using syntax : 'LOGFILE[;FILT][;FMT]'", "LOGGER_SPEC"},\ {"Use syslog for logging", NULL},\ {"Create a PID file", "FILENAME"},\ {"Indicate a socket (for example 'udp://localhost:8765' ) to listen on, replying stats & status", "SOCKET"},\ {"Send all loglines on stderr", NULL},\ {"Print PyFCGI and Python version and exit", NULL},\ {"Display this help and exit", NULL},\ } #define PYFCGI_HELP_TEXT "\n\ Logger specification format 'LOGFILE[;FILT][;FMT]' with :\n\ \t- LOGFILE the log file name\n\ \t- FILT a number (in decimal or hexadicimal 0xXX) indicating wich\n\ \t facility/level to log\n\ \t- FMT the logline format in a special markup format using fields between { }\n\ \t supported fields are :\n\ \t\t- {datetime} {datetime:SIZE} {datetime:SIZE:FMT} defines a format and a \n\ \t\t constant length for a datetime field. Default : {datetime:25:%F %T%z}\n\ \t\t- {level} the loglevel \n\ \t\t- {facility} the log facility\n\ \t\t- {pid} the process PID\n\ \t\t- {ident} the defined ident (set by process)\n\ \t\t- {msg} the log message (can only appear once)\n\ You can escape { and } by using {{ and }} and all field names can by\n\ abbreviated to one character.\n\n\ Socket URL specification format : PROT://HOST[:PORT] with PROT on of \n\ \t- tcp for tcp sockets and HOST a valid INET address\n\ \t- udp for udp sockets and HOST a valid INET address\n\ \t- unix for file sockets and HOST a valid path\n\ " #define PYENTRY_DEFAULT_FUN "application" /**@brief Stores a semaphore name and pointer */ struct pyfcgi_semdata_s { /**@brief Semaphore names. Set by master process */ char name[NAME_MAX - 4]; /**@brief Semaphore pointer. */ sem_t *sem; }; /**@brief Stores an SHM name and pointer */ struct pyfcgi_shmdata_s { /**@brief SHM name. Set by master process */ char name[NAME_MAX]; /**@brief SHM file descriptor */ int fd; /**@brief SHM size */ size_t len; /**@brief SHM pointer */ void *ptr; }; /**@brief Stores contextual informations about current process */ struct pyfcgi_context_s { /**@brief Timestamp of master process starts */ time_t uptime; /**@brief Stores current process PID */ pid_t pid; /**@brief Stores parent process PID */ pid_t ppid; /**@brief Returned by FCGX_OpenSocket for responder process */ int fcgi_socket; /**@brief Request information (local to each worker) */ FCGX_Request fcgi_request; /**@brief Stores the watchdog timer */ timer_t wd_timer; /**@brief Security timer sending a sigkill */ timer_t wd_timerkill; /**@brief Watchdog delay */ struct timespec wd_delay; /**@brief SIGKILL watchdog delay */ struct timespec wd_killdelay; /**@brief Watchdog sig restorer */ struct sigaction wd_oldsig; /**@brief watchdog flag */ short wd; /**@brief array of worker pids (pool handler context) */ pid_t *wrk_pids; /**@brief workers count */ unsigned int n_wrk; /**@brief Stores IPC flag used in init */ pyfcgi_ipc_flag_t ipc_flag; /**@brief Stores python_path (not dupped by python) */ wchar_t python_path[PATH_MAX]; /**@brief Stores venv_path for python home (not dupped by python) */ wchar_t venv_path[PATH_MAX]; /**@brief Stores a part of the environ (containing wsgi.* keys) */ PyObject *wsgi_dict; }; /**@brief Structure containing configuration * @ingroup conf_internal * The structure is used for the global @ref PyFCGI_conf variable. * @see pyfcgi_conf_s */ struct pyfcgi_conf_s { /** @brief Stores pidfile path */ char *pidfile; /**@brief Stores the socket path argument * @ingroup conf_glob * Can be a file path for a UNIX socket or IPv4:PORT string */ char *sock_path; /**@brief Entrypoint module name * @ingroup conf_glob */ char *py_entrymod; /**@brief Entrypoint function name * @ingroup conf_glob */ char *py_entryfun; /**@brief If 0 use stdout to communicate with webserver, else * PyFCGI will expect PEP333 compliant entrypoint */ short pep333; /**@brief Minimum count worker in pool * @ingroup conf_glob */ int min_wrk; /**@brief Maximum count workers in pool * @ingroup conf_glob */ int max_wrk; /**@brief Maximum request before a worker restarts (0 for no restart) * @ingroup conf_glob */ int max_reqs; //Pool handling conf /**@brief Idle timeout : starts GC workers after Xs idle */ time_t worker_gc_timeout; /**@brief Fast spawn : if 1 spawn more childs faster * * When not activated there is at least 1s between 2 child creation, * else child are created in small burst ( ~2 childs burst) */ short worker_fast_spawn; //watchdogs config /**@brief Worker timeout in seconds (if 0 no timeout)*/ time_t worker_timeout; /**@brief Pool timeout in seconds (if 0 no timeout)*/ time_t pool_timeout; // logger config /**@brief 0 is silent 1 means logs to stderr */ short verbosity; /**@brief Logger configuration * @ingroup conf_glob */ pyfcgi_conf_logger_t logs; /**@brief Context informations */ pyfcgi_context_t context; /**@brief Semaphores informations * * - sems[0] is the worker state indicator : when a worker is idle the * sem is incremented * - sems[1] is the request counter sem : when a request is handled * this sem is incremented * - sems[2] is the stats/status SHM semaphore */ pyfcgi_semdata_t sems[PYFCGI_NSEM]; /**@brief Shared memory informations */ pyfcgi_shmdata_t shm; /**@brief Stores a copy of the specified socket URL to listen to. */ char *mon_socket; /**@brief If 1 force ipv4 */ short ipv4; /**@brief If 1 force ipv6 */ short ipv6; }; /**@brief Print usage on FD 2 (stdout) */ void usage(); /**@brief Print pyfcgi & python version on given fd */ void print_version(int); /**@brief Init conf with default values */ void default_conf(); /**@brief Parse arguments and store them in conf * @return 0 if no error */ int parse_args(int argc, char *argv[]); /**@brief Check Python entrypoint import * * Forks into a childprocess that will start python and import the entrypoint. * The child process exit status indicate if the import was successfull or not * @return 0 if no errors 1 if import fails */ int check_entrypoint_import(); /**@brief Parse a logger declaration option * Expected format is : LOGFILE[;FILTER][;FMT] * @param logspec The logspec option value * @return 0 if no error else 1 */ int parse_optlog(const char* logspec); /**@defgroup pyfcgi_watchdog PyFCGI watchdogs * @brief Child process watchdogs mecanism * * @ingroup proc_intern */ /**@brief Initialize the watchdog * @ingroup pyfcgi_watchdog * @param wd_sig_cleaner pointer on a signal handler (or NULL to * use @ref pyfcgi_wd_default_sighandler * @param delay a pointer on the watchdog timeout * @return 0 if no error else -1 */ int pyfcgi_wd_init(void (*wd_sig_cleaner)(int), const struct timespec *delay); /**@brief (Re)starts watchdog timer * @ingroup pyfcgi_watchdog *@return 0 if no error else -1 */ int pyfcgi_wd_arm(); /**@brief Temporarly disables watchdog * @ingroup pyfcgi_watchdog *@return 0 if no error else -1 */ int pyfcgi_wd_pause(); /**@brief Stop and delete a watchdog * @ingroup pyfcgi_watchdog * @return 0 if no error else -1*/ int pyfcgi_wd_stop(); /**@brief Watchdog default signal handler * @ingroup pyfcgi_watchdog * @param signum signal number*/ void pyfcgi_wd_default_sighandler(int signum); /**@brief Return a string representing a status * @param status Status * @return a char* that should be freed */ char *status2str(int status); void pyfcgi_sighandler_drop(int signum); #endif