diff options
| author | Mark A. Hershberger <mah@debian.(none)> | 2009-03-25 00:35:28 -0400 |
|---|---|---|
| committer | Mark A. Hershberger <mah@debian.(none)> | 2009-03-25 00:35:28 -0400 |
| commit | ba50031707469046407a35b77a3cd81351e951b3 (patch) | |
| tree | 5c03e723bdbfabae09d41a3ab1253dff41eeed4a /sapi/cgi | |
| parent | 0a36161e13484a99ccf69bb38f206462d27cc6d6 (diff) | |
| download | php-ba50031707469046407a35b77a3cd81351e951b3.tar.gz | |
Imported Upstream version 5.1.5upstream/5.1.5
Diffstat (limited to 'sapi/cgi')
| -rw-r--r-- | sapi/cgi/cgi_main.c | 113 | ||||
| -rw-r--r-- | sapi/cgi/config.w32 | 5 | ||||
| -rw-r--r-- | sapi/cgi/config9.m4 | 11 | ||||
| -rw-r--r-- | sapi/cgi/fastcgi.c | 955 | ||||
| -rw-r--r-- | sapi/cgi/fastcgi.h | 185 |
5 files changed, 1225 insertions, 44 deletions
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index 9d26ae6a5..317c823f4 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -20,7 +20,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: cgi_main.c,v 1.267.2.5 2006/01/01 12:50:18 sniper Exp $ */ +/* $Id: cgi_main.c,v 1.267.2.16 2006/05/24 07:55:38 dmitry Exp $ */ #include "php.h" #include "php_globals.h" @@ -69,6 +69,7 @@ #ifdef __riscos__ #include <unixlib/local.h> +int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; #endif #include "zend_compile.h" @@ -79,9 +80,7 @@ #include "php_getopt.h" #if PHP_FASTCGI -#include "fcgi_config.h" -#include "fcgiapp.h" -/* don't want to include fcgios.h, causes conflicts */ +#include "fastcgi.h" #ifdef PHP_WIN32 extern int OS_SetImpersonate(void); #else @@ -152,6 +151,13 @@ static const opt_struct OPTIONS[] = { long fix_pathinfo = 1; #endif +#if PHP_FASTCGI +long fcgi_logging = 1; +#endif + +static long rfc2616_headers = 0; +static long cgi_nph = 0; + #ifdef PHP_WIN32 #define TRANSLATE_SLASHES(path) \ { \ @@ -276,7 +282,7 @@ static void sapi_cgibin_flush(void *server_context) #ifndef PHP_WIN32 !parent && #endif - (!request || FCGX_FFlush(request->out) == -1)) { + request && FCGX_FFlush(request->out) == -1) { php_handle_aborted_connection(); } return; @@ -294,25 +300,12 @@ static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) char buf[SAPI_CGI_MAX_HEADER_LENGTH]; sapi_header_struct *h; zend_llist_position pos; - long rfc2616_headers = 0, nph = 0; if (SG(request_info).no_headers == 1) { return SAPI_HEADER_SENT_SUCCESSFULLY; } - /* Check wheater to send RFC2616 style headers compatible with - * PHP versions 4.2.3 and earlier compatible with web servers - * such as IIS. Default is informal CGI RFC header compatible - * with Apache. - */ - if (cfg_get_long("cgi.rfc2616_headers", &rfc2616_headers) == FAILURE) { - rfc2616_headers = 0; - } - if (cfg_get_long("cgi.nph", &nph) == FAILURE) { - nph = 0; - } - - if (nph || SG(sapi_headers).http_response_code != 200) + if (cgi_nph || SG(sapi_headers).http_response_code != 200) { int len; @@ -438,9 +431,28 @@ static char *sapi_cgi_read_cookies(TSRMLS_D) #if PHP_FASTCGI void cgi_php_import_environment_variables(zval *array_ptr TSRMLS_DC) { + if (PG(http_globals)[TRACK_VARS_ENV] && + array_ptr != PG(http_globals)[TRACK_VARS_ENV] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_ENV]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_ENV])) > 0) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_ENV]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } else if (PG(http_globals)[TRACK_VARS_SERVER] && + array_ptr != PG(http_globals)[TRACK_VARS_SERVER] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER])) > 0) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_SERVER]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } if (!FCGX_IsCGI()) { FCGX_Request *request = (FCGX_Request *) SG(server_context); - char **env, *p, *t; + char **env, *p; int magic_quotes_gpc = PG(magic_quotes_gpc); /* turn off magic_quotes while importing environment variables */ @@ -450,9 +462,9 @@ void cgi_php_import_environment_variables(zval *array_ptr TSRMLS_DC) if (!p) { /* malformed entry? */ continue; } - t = estrndup(*env, p - *env); - php_register_variable(t, p + 1, array_ptr TSRMLS_CC); - efree(t); + *p = 0; + php_register_variable(*env, p + 1, array_ptr TSRMLS_CC); + *p = '='; } PG(magic_quotes_gpc) = magic_quotes_gpc; } @@ -474,15 +486,11 @@ static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC) static void sapi_cgi_log_message(char *message) { #if PHP_FASTCGI - long logging = 1; - TSRMLS_FETCH(); - - if (cfg_get_long("fastcgi.logging", &logging) == FAILURE) { - logging = 1; - } - - if (!FCGX_IsCGI() && logging) { - FCGX_Request *request = (FCGX_Request *) SG(server_context); + if (!FCGX_IsCGI() && fcgi_logging) { + FCGX_Request *request; + TSRMLS_FETCH(); + + request = (FCGX_Request *) SG(server_context); if (request) { FCGX_FPrintF(request->err, "%s\n", message); } @@ -932,6 +940,21 @@ void fastcgi_cleanup(int signal) } #endif +#if PHP_FASTCGI +#ifndef PHP_WIN32 +static int is_port_number(const char *bindpath) +{ + while (*bindpath) { + if (*bindpath < '0' || *bindpath > '9') { + return 0; + } + bindpath++; + } + return 1; +} +#endif +#endif + /* {{{ main */ int main(int argc, char *argv[]) @@ -1127,6 +1150,25 @@ consult the installation file that came with this distribution, or visit \n\ #endif #if PHP_FASTCGI + if (cfg_get_long("fastcgi.logging", &fcgi_logging) == FAILURE) { + fcgi_logging = 1; + } +#endif + + /* Check wheater to send RFC2616 style headers compatible with + * PHP versions 4.2.3 and earlier compatible with web servers + * such as IIS. Default is informal CGI RFC header compatible + * with Apache. + */ + if (cfg_get_long("cgi.rfc2616_headers", &rfc2616_headers) == FAILURE) { + rfc2616_headers = 0; + } + + if (cfg_get_long("cgi.nph", &cgi_nph) == FAILURE) { + cgi_nph = 0; + } + +#if PHP_FASTCGI #ifndef PHP_WIN32 /* for windows, socket listening is broken in the fastcgi library itself so dissabling this feature on windows till time is available to fix it */ @@ -1139,7 +1181,7 @@ consult the installation file that came with this distribution, or visit \n\ * path (it's what the fastcgi library expects) */ - if (strchr(bindpath, ':') == NULL) { + if (strchr(bindpath, ':') == NULL && is_port_number(bindpath)) { char *tmp; tmp = malloc(strlen(bindpath) + 2); @@ -1244,7 +1286,8 @@ consult the installation file that came with this distribution, or visit \n\ #ifdef DEBUG_FASTCGI fprintf(stderr, "Wait for kids, pid %d\n", getpid()); #endif - wait(&status); + while (wait(&status) < 0) { + } running--; } } @@ -1429,6 +1472,7 @@ consult the installation file that came with this distribution, or visit \n\ if (script_file) { /* override path_translated if -f on command line */ + STR_FREE(SG(request_info).path_translated); SG(request_info).path_translated = script_file; } @@ -1658,6 +1702,7 @@ fastcgi_request_done: exit_status = 255; } zend_end_try(); + SG(server_context) = NULL; php_module_shutdown(TSRMLS_C); sapi_shutdown(); diff --git a/sapi/cgi/config.w32 b/sapi/cgi/config.w32 index f4a11a792..f9805a636 100644 --- a/sapi/cgi/config.w32 +++ b/sapi/cgi/config.w32 @@ -1,5 +1,5 @@ // vim:ft=javascript -// $Id: config.w32,v 1.2 2004/01/08 10:56:39 wez Exp $ +// $Id: config.w32,v 1.2.4.1 2006/02/02 08:17:23 dmitry Exp $ ARG_ENABLE('cgi', 'Build CGI version of PHP', 'yes'); ARG_ENABLE('fastcgi', 'Build FastCGI support into CGI binary', 'yes'); @@ -16,8 +16,7 @@ AC_DEFINE("ENABLE_PATHINFO_CHECK", PHP_PATH_INFO_CHECK == "yes" ? 1 : 0, "Pathin if (PHP_CGI == "yes") { AC_DEFINE('PHP_FASTCGI', PHP_FASTCGI == "yes" ? 1 : 0); if (PHP_FASTCGI == "yes") { - SAPI('cgi', 'cgi_main.c getopt.c', 'php-cgi.exe', '/I sapi/cgi/libfcgi/include /D FCGI_STATIC'); - ADD_SOURCES('sapi/cgi/libfcgi', 'fcgi_stdio.c fcgiapp.c os_win32.c', 'cgi'); + SAPI('cgi', 'cgi_main.c getopt.c fastcgi.c', 'php-cgi.exe', '/I sapi/cgi/libfcgi/include /D FCGI_STATIC'); ADD_FLAG('LIBS_CGI', 'ws2_32.lib kernel32.lib advapi32.lib'); } else { SAPI('cgi', 'cgi_main.c getopt.c', 'php-cgi.exe'); diff --git a/sapi/cgi/config9.m4 b/sapi/cgi/config9.m4 index a3c4522c9..c521beb2d 100644 --- a/sapi/cgi/config9.m4 +++ b/sapi/cgi/config9.m4 @@ -1,5 +1,5 @@ dnl -dnl $Id: config9.m4,v 1.17 2005/07/07 05:54:43 dmitry Exp $ +dnl $Id: config9.m4,v 1.17.2.2 2006/02/02 09:59:23 dmitry Exp $ dnl AC_ARG_ENABLE(cgi, @@ -133,15 +133,12 @@ if test "$PHP_SAPI" = "default"; then exit 1 fi if test "$PHP_ENABLE_FASTCGI" = "yes"; then - PHP_ADD_BUILD_DIR($abs_builddir/sapi/cgi/libfcgi) PHP_FASTCGI=1 - PHP_FCGI_FILES="libfcgi/fcgi_stdio.c libfcgi/fcgiapp.c libfcgi/os_unix.c" - PHP_FCGI_INCLUDE="-I$PHP_LIBFCGI_DIR/include" + PHP_FCGI_FILES="fastcgi.c" PHP_FCGI_STATIC=1 else PHP_FASTCGI=0 PHP_FCGI_FILES="" - PHP_FCGI_INCLUDE="" PHP_FCGI_STATIC=0 fi AC_DEFINE_UNQUOTED(PHP_FASTCGI, $PHP_FASTCGI, [ ]) @@ -149,11 +146,11 @@ if test "$PHP_SAPI" = "default"; then AC_MSG_RESULT($PHP_ENABLE_FASTCGI) INSTALL_IT="@echo \"Installing PHP CGI into: \$(INSTALL_ROOT)\$(bindir)/\"; \$(INSTALL) -m 0755 \$(SAPI_CGI_PATH) \$(INSTALL_ROOT)\$(bindir)/\$(program_prefix)php\$(program_suffix)\$(EXEEXT)" - PHP_SELECT_SAPI(cgi, program, $PHP_FCGI_FILES cgi_main.c getopt.c, $PHP_FCGI_INCLUDE, '$(SAPI_CGI_PATH)') + PHP_SELECT_SAPI(cgi, program, $PHP_FCGI_FILES cgi_main.c getopt.c, , '$(SAPI_CGI_PATH)') case $host_alias in *aix*) - BUILD_CGI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_SAPI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/.libs\/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_SAPI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" + BUILD_CGI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_SAPI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_SAPI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" ;; *darwin*) BUILD_CGI="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_SAPI_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" diff --git a/sapi/cgi/fastcgi.c b/sapi/cgi/fastcgi.c new file mode 100644 index 000000000..1e0a0d344 --- /dev/null +++ b/sapi/cgi/fastcgi.c @@ -0,0 +1,955 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2006 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: fastcgi.c,v 1.4.2.17 2006/06/07 14:28:26 stas Exp $ */ + +#include "fastcgi.h" +#include "php.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> + +#ifdef _WIN32 + +#include <windows.h> + + typedef unsigned int size_t; + + struct sockaddr_un { + short sun_family; + char sun_path[MAXPATHLEN]; + }; + + static HANDLE fcgi_accept_mutex = INVALID_HANDLE_VALUE; + static int is_impersonate = 0; + +#define FCGI_LOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + DWORD ret; \ + while ((ret = WaitForSingleObject(fcgi_accept_mutex, 1000)) == WAIT_TIMEOUT) { \ + if (in_shutdown) return -1; \ + } \ + if (ret == WAIT_FAILED) { \ + fprintf(stderr, "WaitForSingleObject() failed\n"); \ + return -1; \ + } \ + } + +#define FCGI_UNLOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + ReleaseMutex(fcgi_accept_mutex); \ + } + +#else + +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +# include <fcntl.h> +# include <sys/socket.h> +# include <sys/un.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <signal.h> + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +# ifndef HAVE_SOCKLEN_T + typedef unsigned int socklen_t; +# endif + +# ifdef USE_LOCKING +# define FCGI_LOCK(fd) \ + do { \ + struct flock lock; \ + lock.l_type = F_WRLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLKW, &lock) != -1) { \ + break; \ + } else if (errno != EINTR || in_shutdown) { \ + return -1; \ + } \ + } while (1) + +# define FCGI_UNLOCK(fd) \ + do { \ + int orig_errno = errno; \ + while (1) { \ + struct flock lock; \ + lock.l_type = F_UNLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLK, &lock) != -1) { \ + break; \ + } else if (errno != EINTR) { \ + return -1; \ + } \ + } \ + errno = orig_errno; \ + } while (0) +# else +# define FCGI_LOCK(fd) +# define FCGI_UNLOCK(fd) +# endif + +#endif + +typedef union _sa_t { + struct sockaddr sa; + struct sockaddr_un sa_unix; + struct sockaddr_in sa_inet; +} sa_t; + +typedef struct _fcgi_mgmt_rec { + char* name; + char val; +} fcgi_mgmt_rec; + +static const fcgi_mgmt_rec fcgi_mgmt_vars[] = { + {"FCGI_MAX_CONNS", 1}, + {"FCGI_MAX_REQS", 1}, + {"FCGI_MPXS_CONNS", 0} +}; + + +static int is_initialized = 0; +static int is_fastcgi = 0; +static int in_shutdown = 0; + +static inline char* fcgi_strndup(const char *str, int len) +{ + char *s = malloc(len+1); + memcpy(s, str, len+1); + return s; +} + +#ifdef _WIN32 + +static DWORD WINAPI fcgi_shutdown_thread(LPVOID arg) +{ + HANDLE shutdown_event = (HANDLE) arg; + WaitForSingleObject(shutdown_event, INFINITE); + in_shutdown = 1; + return 0; +} + +#else + +static void fcgi_signal_handler(int signo) +{ + if (signo == SIGUSR1 || signo == SIGTERM) { + in_shutdown = 1; + } +} + +#endif + +int fcgi_init(void) +{ + if (!is_initialized) { +#ifdef _WIN32 +# if 0 + /* TODO: Support for TCP sockets */ + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2,0), &wsaData)) { + fprintf(stderr, "Error starting Windows Sockets. Error: %d", WSAGetLastError()); + return 0; + } +# endif + is_initialized = 1; + + if ((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_ERROR_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE)) { + char *str; + DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_WAIT; + HANDLE pipe = GetStdHandle(STD_INPUT_HANDLE); + + SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL); + + str = getenv("_FCGI_SHUTDOWN_EVENT_"); + if (str != NULL) { + HANDLE shutdown_event = (HANDLE) atoi(str); + if (!CreateThread(NULL, 0, fcgi_shutdown_thread, + shutdown_event, 0, NULL)) { + return -1; + } + } + str = getenv("_FCGI_MUTEX_"); + if (str != NULL) { + fcgi_accept_mutex = (HANDLE) atoi(str); + } + return is_fastcgi = 1; + } else { + return is_fastcgi = 0; + } +#else + sa_t sa; + socklen_t len = sizeof(sa); + + is_initialized = 1; + errno = 0; + if (getpeername(0, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) { + struct sigaction new_sa, old_sa; + + sigemptyset(&new_sa.sa_mask); + new_sa.sa_flags = 0; + new_sa.sa_handler = fcgi_signal_handler; + sigaction(SIGUSR1, &new_sa, NULL); + sigaction(SIGTERM, &new_sa, NULL); + sigaction(SIGPIPE, NULL, &old_sa); + if (old_sa.sa_handler == SIG_DFL) { + sigaction(SIGPIPE, &new_sa, NULL); + } + + return is_fastcgi = 1; + } else { + return is_fastcgi = 0; + } +#endif + } + return is_fastcgi; +} + + +int fcgi_is_fastcgi(void) +{ + if (!is_initialized) { + return fcgi_init(); + } else { + return is_fastcgi; + } +} + +int fcgi_listen(const char *path, int backlog) +{ +#ifdef _WIN32 + /* TODO: Support for manual binding on TCP sockets (php -b <port>) */ + return -1; +#else + char *s; + int tcp = 0; + char host[MAXPATHLEN]; + short port = 0; + int listen_socket; + sa_t sa; + socklen_t sa_len; + + if ((s = strchr(path, ':'))) { + port = atoi(s+1); + if (port != 0 && (s-path) < MAXPATHLEN) { + strncpy(host, path, s-path); + host[s-path] = '\0'; + tcp = 1; + } + } + + /* Prepare socket address */ + if (tcp) { + memset(&sa.sa_inet, 0, sizeof(sa.sa_inet)); + sa.sa_inet.sin_family = AF_INET; + sa.sa_inet.sin_port = htons(port); + sa_len = sizeof(sa.sa_inet); + + if (!*host || !strncmp(host, "*", sizeof("*")-1)) { + sa.sa_inet.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + sa.sa_inet.sin_addr.s_addr = inet_addr(host); + if (sa.sa_inet.sin_addr.s_addr == INADDR_NONE) { + struct hostent *hep; + + hep = gethostbyname(host); + if (!hep || hep->h_addrtype != AF_INET || !hep->h_addr_list[0]) { + fprintf(stderr, "Cannot resolve host name '%s'!\n", host); + return -1; + } else if (hep->h_addr_list[1]) { + fprintf(stderr, "Host '%s' has multiple addresses. You must choose one explicitly!\n", host); + return -1; + } + sa.sa_inet.sin_addr.s_addr = ((struct in_addr*)hep->h_addr_list[0])->s_addr; + } + } + } else { + int path_len = strlen(path); + + if (path_len >= sizeof(sa.sa_unix.sun_path)) { + fprintf(stderr, "Listening socket's path name is too long.\n"); + return -1; + } + + memset(&sa.sa_unix, 0, sizeof(sa.sa_unix)); + sa.sa_unix.sun_family = AF_UNIX; + memcpy(sa.sa_unix.sun_path, path, path_len + 1); + sa_len = (size_t)(((struct sockaddr_un *)0)->sun_path) + path_len; +#ifdef HAVE_SOCKADDR_UN_SUN_LEN + sa.sa_unix.sun_len = sa_len; +#endif + unlink(path); + } + + /* Create, bind socket and start listen on it */ + if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || + bind(listen_socket, (struct sockaddr *) &sa, sa_len) < 0 || + listen(listen_socket, backlog) < 0) { + + fprintf(stderr, "Cannot bind/listen socket - [%d] %s.\n",errno, strerror(errno)); + return -1; + } + + if (!tcp) { + chmod(path, 0777); + } + + if (!is_initialized) { + fcgi_init(); + } + is_fastcgi = 1; + return listen_socket; +#endif +} + +void fcgi_init_request(fcgi_request *req, int listen_socket) +{ + memset(req, 0, sizeof(fcgi_request)); + req->listen_socket = listen_socket; + req->fd = -1; + req->id = -1; + + req->in_len = 0; + req->in_pad = 0; + + req->out_hdr = NULL; + req->out_pos = req->out_buf; +} + +static inline ssize_t safe_write(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + ret = write(req->fd, ((char*)buf)+n, count-n); + if (ret > 0) { + n += ret; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + ret = read(req->fd, ((char*)buf)+n, count-n); + if (ret > 0) { + n += ret; + } else if (ret == 0 && errno == 0) { + return n; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline int fcgi_make_header(fcgi_header *hdr, fcgi_request_type type, int req_id, int len) +{ + int pad = ((len + 7) & ~7) - len; + + hdr->contentLengthB0 = (unsigned char)(len & 0xff); + hdr->contentLengthB1 = (unsigned char)((len >> 8) & 0xff); + hdr->paddingLength = (unsigned char)pad; + hdr->requestIdB0 = (unsigned char)(req_id & 0xff); + hdr->requestIdB1 = (unsigned char)((req_id >> 8) & 0xff); + hdr->reserved = 0; + hdr->type = type; + hdr->version = FCGI_VERSION_1; + return pad; +} + +static int fcgi_get_params(fcgi_request *req, unsigned char *p, unsigned char *end, int n) +{ + int name_len, val_len; + char *s; + + while (p < end && n < FCGI_MAX_ENV_VARS - 1) { + name_len = *p++; + if (name_len >= 128) { + name_len = ((name_len & 0x7f) << 24); + name_len |= (*p++ << 16); + name_len |= (*p++ << 8); + name_len |= *p++; + } + val_len = *p++; + if (val_len >= 128) { + val_len = ((val_len & 0x7f) << 24); + val_len |= (*p++ << 16); + val_len |= (*p++ << 8); + val_len |= *p++; + } + req->env[n] = s = malloc(name_len + val_len + 2); + memcpy(s, p, name_len); + p += name_len; + s[name_len] = '='; + memcpy(s+name_len+1, p, val_len); + p += val_len; + s[name_len+1+val_len] = '\0'; + n++; + } + return n; +} + +static int fcgi_read_request(fcgi_request *req) +{ + fcgi_header hdr; + int len, padding; + int n = 1; + char *s; + unsigned char buf[FCGI_MAX_LENGTH+8]; + + req->keep = 0; + req->in_len = 0; + req->out_hdr = NULL; + req->out_pos = req->out_buf; + memset(req->env, 0, sizeof(req->env)); + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_STDIN && len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + + req->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0; + + if (hdr.type == FCGI_BEGIN_REQUEST && len == sizeof(fcgi_begin_request)) { + if (safe_read(req, buf, len+padding) != len+padding) { + return 0; + } + + req->keep = (((fcgi_begin_request*)buf)->flags & FCGI_KEEP_CONN); + switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) { + case FCGI_RESPONDER: + req->env[0] = fcgi_strndup("FCGI_ROLE=RESPONDER", sizeof("FCGI_ROLE=RESPONDER")-1); + break; + case FCGI_AUTHORIZER: + req->env[0] = fcgi_strndup("FCGI_ROLE=AUTHORIZER", sizeof("FCGI_ROLE=AUTHORIZER")-1); + break; + case FCGI_FILTER: + req->env[0] = fcgi_strndup("FCGI_ROLE=FILTER", sizeof("FCGI_ROLE=FILTER")-1); + break; + default: + return 0; + } + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_PARAMS && len > 0) { + if (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + n = fcgi_get_params(req, buf, buf+len, n); + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + req->keep = 0; + return 0; + } + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + } else if (hdr.type == FCGI_GET_VALUES) { + int i, j; + int name_len; + unsigned char *p = buf + sizeof(fcgi_header); + + if (safe_read(req, buf, len+padding) != len+padding) { + return 0; + } + n = fcgi_get_params(req, buf, buf+len, 0); + for (i = 0; i < n; i++) { + if ((s = strchr(req->env[i], '=')) != NULL) { + *s = '\0'; + name_len = s - req->env[i]; + } else { + name_len = strlen(req->env[i]); + } + for (j = 0; j < sizeof(fcgi_mgmt_vars)/sizeof(fcgi_mgmt_vars[0]); j++) { + if (strncmp(req->env[i], fcgi_mgmt_vars[j].name, name_len) == 0) { + sprintf((char*)p, "%c%c%s%c", name_len, 1, fcgi_mgmt_vars[j].name, fcgi_mgmt_vars[j].val); + p += name_len+3; + } + } + } + len = p - buf - sizeof(fcgi_header); + len += fcgi_make_header((fcgi_header*)buf, FCGI_GET_VALUES_RESULT, 0, len); + if (safe_write(req, buf, sizeof(fcgi_header)+len) != (int)sizeof(fcgi_header)+len) { + return 0; + } + return 0; + } else { + return 0; + } + + return 1; +} + +int fcgi_read(fcgi_request *req, char *str, int len) +{ + int ret, n, rest; + fcgi_header hdr; + unsigned char buf[8]; + + n = 0; + rest = len; + while (rest > 0) { + if (req->in_len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1 || + hdr.type != FCGI_STDIN) { + req->keep = 0; + return 0; + } + req->in_len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + req->in_pad = hdr.paddingLength; + if (req->in_len == 0) { + return n; + } + } + + if (req->in_len >= rest) { + ret = safe_read(req, str, rest); + } else { + ret = safe_read(req, str, req->in_len); + } + if (ret < 0) { + req->keep = 0; + return ret; + } else if (ret > 0) { + req->in_len -= ret; + rest -= ret; + n += ret; + str += ret; + if (req->in_len == 0) { + if (req->in_pad) { + if (safe_read(req, buf, req->in_pad) != req->in_pad) { + req->keep = 0; + return ret; + } + } + } else { + return n; + } + } else { + return n; + } + } + return n; +} + +static inline void fcgi_close(fcgi_request *req, int force, int destroy) +{ + if (destroy) { + char **env = req->env; + while (*env) { + free(*env++); + } + } + if ((force || !req->keep) && req->fd >= 0) { +#ifdef _WIN32 + HANDLE pipe = (HANDLE)_get_osfhandle(req->fd); + + if (!force) { + FlushFileBuffers(pipe); + } + DisconnectNamedPipe(pipe); + if (is_impersonate) { + RevertToSelf(); + } +#else + char buf[8]; + + shutdown(req->fd, 1); + while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} + close(req->fd); +#endif + req->fd = -1; + } +} + +int fcgi_accept_request(fcgi_request *req) +{ +#ifdef _WIN32 + HANDLE pipe; + OVERLAPPED ov; +#endif + fcgi_finish_request(req); + + while (1) { + if (req->fd < 0) { + while (1) { + if (in_shutdown) { + return -1; + } +#ifdef _WIN32 + pipe = (HANDLE)_get_osfhandle(req->listen_socket); + + FCGI_LOCK(req->listen_socket); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ConnectNamedPipe(pipe, &ov)) { + errno = GetLastError(); + if (errno == ERROR_IO_PENDING) { + while (WaitForSingleObject(ov.hEvent, 1000) == WAIT_TIMEOUT) { + if (in_shutdown) { + CloseHandle(ov.hEvent); + FCGI_UNLOCK(req->listen_socket); + return -1; + } + } + } else if (errno != ERROR_PIPE_CONNECTED) { + } + } + CloseHandle(ov.hEvent); + if (is_impersonate && !ImpersonateNamedPipeClient(pipe)) { + DisconnectNamedPipe(pipe); + req->fd = -1; + } else { + req->fd = req->listen_socket; + } + FCGI_UNLOCK(req->listen_socket); +#else + { + sa_t sa; + socklen_t len = sizeof(sa); + + FCGI_LOCK(req->listen_socket); + req->fd = accept(req->listen_socket, (struct sockaddr *)&sa, &len); + FCGI_UNLOCK(req->listen_socket); + } +#endif + + if (req->fd < 0 && (in_shutdown || errno != EINTR)) { + return -1; + } + +#ifdef _WIN32 + break; +#else + if (req->fd >= 0) { + struct timeval tv = {1,0}; + fd_set set; + + FD_ZERO(&set); + FD_SET(req->fd, &set); +try_again: + errno = 0; + if (select(req->fd + 1, &set, NULL, NULL, &tv) >= 0 && FD_ISSET(req->fd, &set)) { + break; + } + if (errno == EINTR) goto try_again; + fcgi_close(req, 1, 0); + } +#endif + } + } else if (in_shutdown) { + return -1; + } + if (fcgi_read_request(req)) { + return req->fd; + } else { + fcgi_close(req, 1, 1); + } + } +} + +static inline fcgi_header* open_packet(fcgi_request *req, fcgi_request_type type) +{ + req->out_hdr = (fcgi_header*) req->out_pos; + req->out_hdr->type = type; + req->out_pos += sizeof(fcgi_header); + return req->out_hdr; +} + +static inline void close_packet(fcgi_request *req) +{ + if (req->out_hdr) { + int len = req->out_pos - ((unsigned char*)req->out_hdr + sizeof(fcgi_header)); + + req->out_pos += fcgi_make_header(req->out_hdr, req->out_hdr->type, req->id, len); + req->out_hdr = NULL; + } +} + +int fcgi_flush(fcgi_request *req, int close) +{ + int len; + + close_packet(req); + + len = req->out_pos - req->out_buf; + + if (close) { + fcgi_end_request_rec *rec = (fcgi_end_request_rec*)(req->out_pos); + + fcgi_make_header(&rec->hdr, FCGI_END_REQUEST, req->id, sizeof(fcgi_end_request)); + rec->body.appStatusB3 = 0; + rec->body.appStatusB2 = 0; + rec->body.appStatusB1 = 0; + rec->body.appStatusB0 = 0; + rec->body.protocolStatus = FCGI_REQUEST_COMPLETE; + len += sizeof(fcgi_end_request_rec); + } + + if (safe_write(req, req->out_buf, len) != len) { + req->keep = 0; + return 0; + } + + req->out_pos = req->out_buf; + return 1; +} + +int fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len) +{ + int limit, rest; + + if (len <= 0) { + return 0; + } + + if (req->out_hdr && req->out_hdr->type != type) { + close_packet(req); + } +#if 0 + /* Unoptimized, but clear version */ + rest = len; + while (rest > 0) { + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + + if (!req->out_hdr) { + if (limit < sizeof(fcgi_header)) { + if (!fcgi_flush(req, 0)) { + return -1; + } + } + open_packet(req, type); + } + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + if (rest < limit) { + memcpy(req->out_pos, str, rest); + req->out_pos += rest; + return len; + } else { + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + rest -= limit; + str += limit; + if (!fcgi_flush(req, 0)) { + return -1; + } + } + } +#else + /* Optimized version */ + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + if (!req->out_hdr) { + limit -= sizeof(fcgi_header); + if (limit < 0) limit = 0; + } + + if (len < limit) { + if (!req->out_hdr) { + open_packet(req, type); + } + memcpy(req->out_pos, str, len); + req->out_pos += len; + } else if (len - limit < sizeof(req->out_buf) - sizeof(fcgi_header)) { + if (!req->out_hdr) { + open_packet(req, type); + } + if (limit > 0) { + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + } + if (!fcgi_flush(req, 0)) { + return -1; + } + if (len > limit) { + open_packet(req, type); + memcpy(req->out_pos, str + limit, len - limit); + req->out_pos += len - limit; + } + } else { + int pos = 0; + int pad; + + close_packet(req); + while ((len - pos) > 0xffff) { + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, 0xfff8); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, 0xfff8) != 0xfff8) { + req->keep = 0; + return -1; + } + pos += 0xfff8; + } + + pad = (((len - pos) + 7) & ~7) - (len - pos); + rest = pad ? 8 - pad : 0; + + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, (len - pos) - rest); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, (len - pos) - rest) != (len - pos) - rest) { + req->keep = 0; + return -1; + } + if (pad) { + open_packet(req, type); + memcpy(req->out_pos, str + len - rest, rest); + req->out_pos += rest; + } + } +#endif + return len; +} + +int fcgi_finish_request(fcgi_request *req) +{ + if (req->fd >= 0) { + fcgi_flush(req, 1); + fcgi_close(req, 0, 1); + } + return 1; +} + +char* fcgi_getenv_helper(char** env, const char *name, int len) +{ + if (name && env) { + while (*env) { + if ((strncmp(name, *env, len) == 0) && ((*env)[len] == '=')) { + return *env+len+1; + } + env++; + } + } + return NULL; +} + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len) +{ + if (!req) return NULL; + return fcgi_getenv_helper(req->env, var, var_len); +} + +void fcgi_putenv(fcgi_request *req, char* var, int var_len) +{ + if (var && req) { + char **env = req->env; + char *s = strchr(var, '='); + int len; + + if (!s) return ; + len = s - var + 1; + while (*env) { + if ((strncmp(var, *env, len) == 0)) { + free(*env); + *env = fcgi_strndup(var, var_len); + return; + } + env++; + } + if (env != &req->env[FCGI_MAX_ENV_VARS - 1]) { + *env = fcgi_strndup(var, var_len); + } + } +} + +int FCGX_FPrintF(FCGX_Stream stream, const char *format, ...) +{ + int result; + va_list args; + char buf[4096]; + + va_start(args, format); + result = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + fcgi_write(stream.req, stream.type, buf, result); + return result; +} + +#ifdef _WIN32 +void OS_SetImpersonate(void) +{ + char *os_name; + + os_name = getenv("OS"); + if (os_name && stricmp(os_name, "Windows_NT") == 0) { + is_impersonate = 1; + } +} +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cgi/fastcgi.h b/sapi/cgi/fastcgi.h new file mode 100644 index 000000000..2b9265b3a --- /dev/null +++ b/sapi/cgi/fastcgi.h @@ -0,0 +1,185 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2006 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: fastcgi.h,v 1.2.2.6 2006/05/25 07:06:04 dmitry Exp $ */ + +/* FastCGI protocol */ + +#define FCGI_VERSION_1 1 + +#define FCGI_MAX_LENGTH 0xffff + +#define FCGI_KEEP_CONN 1 + +#define FCGI_MAX_ENV_VARS 256 + +typedef enum _fcgi_role { + FCGI_RESPONDER = 1, + FCGI_AUTHORIZER = 2, + FCGI_FILTER = 3 +} fcgi_role; + +typedef enum _fcgi_request_type { + FCGI_BEGIN_REQUEST = 1, /* [in] */ + FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */ + FCGI_END_REQUEST = 3, /* [out] */ + FCGI_PARAMS = 4, /* [in] environment variables */ + FCGI_STDIN = 5, /* [in] post data */ + FCGI_STDOUT = 6, /* [out] response */ + FCGI_STDERR = 7, /* [out] errors */ + FCGI_DATA = 8, /* [in] filter data (not supported) */ + FCGI_GET_VALUES = 9, /* [in] */ + FCGI_GET_VALUES_RESULT = 10 /* [out] */ +} fcgi_request_type; + +typedef enum _fcgi_protocol_status { + FCGI_REQUEST_COMPLETE = 0, + FCGI_CANT_MPX_CONN = 1, + FCGI_OVERLOADED = 2, + FCGI_UNKNOWN_ROLE = 3 +} dcgi_protocol_status; + +typedef struct _fcgi_header { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} fcgi_header; + +typedef struct _fcgi_begin_request { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} fcgi_begin_request; + +typedef struct _fcgi_begin_request_rec { + fcgi_header hdr; + fcgi_begin_request body; +} fcgi_begin_request_rec; + +typedef struct _fcgi_end_request { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} fcgi_end_request; + +typedef struct _fcgi_end_request_rec { + fcgi_header hdr; + fcgi_end_request body; +} fcgi_end_request_rec; + +/* FastCGI client API */ + +typedef struct _fcgi_request { + int listen_socket; + int fd; + int id; + int keep; + + int in_len; + int in_pad; + + fcgi_header *out_hdr; + unsigned char *out_pos; + unsigned char out_buf[1024*8]; + unsigned char reserved[sizeof(fcgi_end_request_rec)]; + + char *env[FCGI_MAX_ENV_VARS]; +} fcgi_request; + +int fcgi_init(void); +int fcgi_is_fastcgi(void); +int fcgi_listen(const char *path, int backlog); +void fcgi_init_request(fcgi_request *req, int listen_socket); +int fcgi_accept_request(fcgi_request *req); +int fcgi_finish_request(fcgi_request *req); + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len); +void fcgi_putenv(fcgi_request *req, char* var, int var_len); + +int fcgi_read(fcgi_request *req, char *str, int len); + +int fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len); +int fcgi_flush(fcgi_request *req, int close); + +/* Some defines for limited libfcgi comatibility */ + +typedef struct _FCGX_Stream { + fcgi_request *req; + fcgi_request_type type; +} FCGX_Stream; + +typedef struct _FCGX_Request { + fcgi_request req; + FCGX_Stream in; + FCGX_Stream out; + FCGX_Stream err; + char **envp; +} FCGX_Request; + +#define FCGX_Init() +#define FCGX_IsCGI() (!fcgi_is_fastcgi()) +#define FCGX_OpenSocket(path, backlog) fcgi_listen(path, backlog) + +#define FCGX_InitRequest(r, sock, flags) \ + do { \ + fcgi_init_request(&(r)->req, sock); \ + (r)->in.req = &(r)->req; \ + (r)->out.req = &(r)->req; \ + (r)->err.req = &(r)->req; \ + (r)->in.type = FCGI_STDIN; \ + (r)->out.type = FCGI_STDOUT; \ + (r)->err.type = FCGI_STDERR; \ + (r)->envp = (r)->req.env; \ + } while (0); + + +#define FCGX_Accept_r(r) fcgi_accept_request(&(r)->req) +#define FCGX_Finish_r(r) fcgi_finish_request(&(r)->req) + +#define FCGX_PutStr(str, len, stream) fcgi_write((stream).req, (stream).type, str, len) +#define FCGX_PutS(str, len, stream) fcgi_write((stream).req, (stream).type, str, len) +#define FCGX_FFlush(stream) (fcgi_flush((stream).req, 0)?0:-1) +#define FCGX_GetStr(str, len, stream) fcgi_read((stream).req, str, len) + +#define FCGX_GetParam(var, envp) fcgi_getenv_helper(envp, var, strlen(var)); + +#define FCGX_PutEnv(r, var) fcgi_putenv(&(r)->req, var, strlen(var)); + +int FCGX_FPrintF(FCGX_Stream stream, const char *format, ...); + +/* Internal helper functions. They shouldn't be used directly. */ + +char* fcgi_getenv_helper(char** env, const char *name, int len); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ |
