I've finished modifying Fossil to support SCGI. Some notes: - I rewrote the accept loop of the server/ui command at the same time. It no longer uses select with a timeout in order to reap child processes; instead, a signal handler is installed for SIGCHLD in order to reap them and maintain the child process count. This both means Fossil doesn't wake up pointlessly every 60 seconds, and that the code is much simpler. - Instead of calling sleep when too many connections have been established for an arbritary time period with a minimum length of 2 seconds, we call pause() until the number of connections drops to a satisfactory level.
(In fact, both SCGI and HTTP modes share an accept loop). fossil scgi performs admirably behind Apache with mod_scgi. It works fine for GET requests behind nginx' mod_scgi; but POST requests cause it to lose its marbles. This is an issue on nginx' end; I'll have to look into that. scgi is currently only supported on !win32 because I don't have any experience with winsock. Additionally, as with Fossil's standard server handler, it depends upon fork. Support is provided for both TCP and Unix domain sockets. Which to use is determined by whether the port parameter is an integer or not.
Index: src/cgi.c =================================================================== --- src/cgi.c +++ src/cgi.c @@ -29,14 +29,20 @@ # include <ws2tcpip.h> #else # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> +# include <sys/un.h> # include <sys/times.h> # include <sys/time.h> # include <sys/wait.h> # include <sys/select.h> + +#ifndef UNIX_PATH_MAX + static struct sockaddr_un uds_sizecheck; +# define UNIX_PATH_MAX (sizeof(uds_sizecheck.sun_path)) +# endif #endif #ifdef __EMX__ typedef int socklen_t; #endif #include <time.h> @@ -1144,15 +1150,107 @@ } cgi_init(); } +static char* tok_scgi(char **s) +{ + char *rv = *s; + char *buf = *s; + while(*(buf++)); + *s = buf; + return rv; +} + +void cgi_handle_scgi_request() +{ + char *z, *zEnd, *zKey, *zVal; + size_t len; + + if (scanf("%5lu", &len) < 1) malformed_request(); + if (getchar() != ':') malformed_request(); + + z = malloc(len + 1); + if(!z) abort(); + z[len] = 0; + zEnd = z + len; + + if(fread(z, 1, len, stdin) < len) malformed_request(); + if (getchar() != ',') malformed_request(); + + while(z < zEnd) { + zKey = tok_scgi(&z); + zVal = tok_scgi(&z); + + cgi_setenv(zKey, zVal); + } + + cgi_init(); +} + /* ** Maximum number of child processes that we can have running ** at one time before we start slowing things down. */ #define MAX_PARALLEL 2 + +#ifndef __MINGW32__ +/* +** Automatic child reaping +*/ +static sig_atomic_t iNumChildren = 0; +static void reap_child(int sig){ + while( waitpid(0, 0, WNOHANG) > 0 ){ + iNumChildren--; + } + + (void) sig; +} + +/* +** Standard child CGI management +*/ +static int wait_connections(int sock) +{ + struct sigaction sa; + int fd; // Accepted socket + pid_t child; // Child PID + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = reap_child; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, NULL); + + for( ;; ) { + // While too many connections, wait for them to be reaped + while(iNumChildren > MAX_PARALLEL) { + pause(); + } + + fd = accept(sock, NULL, NULL); + if(fd < 0) continue; // Probably EINTR + + child = fork(); + if(child == 0) { // We are the child + dup2(fd, 0); + dup2(fd, 1); + if( !g.fHttpTrace && !g.fSqlTrace ) + dup2(fd, 2); + + close(fd); + return 0; + } else { + iNumChildren++; + close(fd); + } + } + + // Not reached + abort(); +} + +#endif /* ** Implement an HTTP server daemon listening on port iPort. ** ** As new connections arrive, fork a child and let child return @@ -1163,19 +1261,13 @@ ** listening socket, return non-zero. */ int cgi_http_server(int mnPort, int mxPort, char *zBrowser){ #ifdef __MINGW32__ /* Use win32_http_server() instead */ - exit(1); + abort(); #else int listener = -1; /* The server socket */ - int connection; /* A socket for each individual connection */ - fd_set readfds; /* Set of file descriptors for select() */ - size_t lenaddr; /* Length of the inaddr structure */ - int child; /* PID of the child process */ - int nchildren = 0; /* Number of child processes */ - struct timeval delay; /* How long to wait inside select() */ struct sockaddr_in inaddr; /* The socket address */ int opt = 1; /* setsockopt flag */ int iPort = mnPort; while( iPort<=mxPort ){ @@ -1215,51 +1307,80 @@ } if( zBrowser ){ zBrowser = mprintf(zBrowser, iPort); system(zBrowser); } - while( 1 ){ - if( nchildren>MAX_PARALLEL ){ - /* Slow down if connections are arriving too fast */ - sleep( nchildren-MAX_PARALLEL ); + + return wait_connections(listener); +#endif +} + +#ifndef __MINGW32__ +int cgi_scgi_server(const char* zPort){ + int iPort; // TCP port number + int listener = -1; // Listen port + int opt = 1; // setsockopt flag + + iPort = strtol(zPort, NULL, 10); + if(iPort) { // TCP socket + struct sockaddr_in inaddr; + + memset(&inaddr, 0, sizeof(inaddr)); + inaddr.sin_family = AF_INET; + inaddr.sin_addr.s_addr = INADDR_ANY; + inaddr.sin_port = htons(iPort); + listener = socket(AF_INET, SOCK_STREAM, 0); + if( listener<0 ){ + goto tcp_err; + } + + /* if we can't terminate nicely, at least allow the socket to be reused */ + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){ + close(listener); + goto tcp_err; } - delay.tv_sec = 60; - delay.tv_usec = 0; - FD_ZERO(&readfds); - FD_SET( listener, &readfds); - if( select( listener+1, &readfds, 0, 0, &delay) ){ - lenaddr = sizeof(inaddr); - connection = accept(listener, (struct sockaddr*)&inaddr, - (socklen_t*) &lenaddr); - if( connection>=0 ){ - child = fork(); - if( child!=0 ){ - if( child>0 ) nchildren++; - close(connection); - }else{ - close(0); - dup(connection); - close(1); - dup(connection); - if( !g.fHttpTrace && !g.fSqlTrace ){ - close(2); - dup(connection); - } - close(connection); - return 0; - } - } + } else { // Unix domain socket + unlink(zPort); + + struct sockaddr_un udsaddr; + + memset(&udsaddr, 0, sizeof(udsaddr)); + udsaddr.sun_family = AF_UNIX; + strncpy(udsaddr.sun_path, zPort, UNIX_PATH_MAX - 1); + + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if( listener < 0 ) { + goto uds_err; } - /* Bury dead children */ - while( waitpid(0, 0, WNOHANG)>0 ){ - nchildren--; + + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if( bind(listener, (struct sockaddr*)&udsaddr, sizeof(udsaddr))<0 ) { + close(listener); + goto uds_err; } } - /* NOT REACHED */ - exit(1); -#endif + + if( listen(listener, 10) ) { + fossil_fatal("unable to listen to socket \"%s\"", zPort); + } + + printf("Listening for SCGI requests on port \"%s\"\n", zPort); + fflush(stdout); + + return wait_connections(listener); + +tcp_err: + fossil_fatal("unable to open listening socket on TCP port %d", iPort); + abort(); + +uds_err: + fossil_fatal("unable to open listening Unix socket at path \"%s\"", zPort); + abort(); } +#endif /* ** Name of days and months. */ Index: src/main.c =================================================================== --- src/main.c +++ src/main.c @@ -863,18 +863,18 @@ ** the PATH_INFO variable. ** ** If disallowDir is set, then the directory full of repositories method ** is disallowed. */ -static void find_server_repository(int disallowDir){ - if( g.argc<3 ){ +static void find_server_repository(int disallowDir, int argn){ + if( g.argc <= argn ){ db_must_be_within_tree(); - }else if( !disallowDir && file_isdir(g.argv[2])==1 ){ - g.zRepositoryName = mprintf("%s", g.argv[2]); + }else if( !disallowDir && file_isdir(g.argv[argn])==1 ){ + g.zRepositoryName = mprintf("%s", g.argv[argn]); file_simplify_name(g.zRepositoryName, -1); }else{ - db_open_repository(g.argv[2]); + db_open_repository(g.argv[argn]); } } /* ** undocumented format: @@ -914,11 +914,11 @@ }else{ g.httpIn = stdin; g.httpOut = stdout; zIpAddr = 0; } - find_server_repository(0); + find_server_repository(0, 2); g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); cgi_handle_http_request(zIpAddr); process_one_web_page(zNotFound); } @@ -996,11 +996,11 @@ } zPort = find_option("port", "P", 1); zNotFound = find_option("notfound", 0, 1); if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); isUiCmd = g.argv[1][0]=='u'; - find_server_repository(isUiCmd); + find_server_repository(isUiCmd, 2); if( zPort ){ iPort = mxPort = atoi(zPort); }else{ iPort = db_get_int("http-port", 8080); mxPort = iPort+100; @@ -1034,11 +1034,11 @@ g.httpOut = stdout; if( g.fHttpTrace || g.fSqlTrace ){ fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); } g.cgiOutput = 1; - find_server_repository(isUiCmd); + find_server_repository(isUiCmd, 2); g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); cgi_handle_http_request(0); process_one_web_page(zNotFound); #else /* Win32 implementation */ @@ -1048,5 +1048,51 @@ } db_close(); win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zNotFound); #endif } + +#ifndef __MINGW32__ +/* +** COMMAND: scgi +** +** Usage: %fossil scgi PORT ?REPOSITORY? +** +** PORT is interpreted as a TCP port number if a nonzero positive integer, +** or as the path to a Unix Domain Socket otherwise. The optional argument +** is the name of the repository or path containing it (Same as for the +** server command) +**/ +void cmd_scgiserver(void){ + const char* zPort; // Port name + const char* zNotFound; + + zNotFound = find_option("notfound", 0, 1); + g.thTrace = find_option("th-trace", 0, 0) != 0; + g.fullHttpReply = find_option("full-http", 0, 0) != 0; + + if( g.thTrace ){ + blob_zero(&g.thLog); + } + + if( g.argc < 3) usage("PORT ?REPOSITORY?"); + zPort = g.argv[2]; + + if( cgi_scgi_server(zPort) ){ + fossil_fatal("unable to listen on socket %s", zPort); + } + + g.httpIn = stdin; + g.httpOut = stdout; + if( g.fHttpTrace || g.fSqlTrace ){ + fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); + } + g.cgiOutput = 1; + + // --- + find_server_repository(0, 3); + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); + cgi_handle_scgi_request(0); + process_one_web_page(zNotFound); +} + +#endif
_______________________________________________ fossil-users mailing list fossil-users@lists.fossil-scm.org http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users