svn commit: trunk/busybox/networking

vda at busybox.net vda at busybox.net
Sun Sep 23 13:56:58 UTC 2007


Author: vda
Date: 2007-09-23 06:56:57 -0700 (Sun, 23 Sep 2007)
New Revision: 19967

Log:
httpd: simplify CGI i/o loop. -200 bytes.



Modified:
   trunk/busybox/networking/httpd.c


Changeset:
Modified: trunk/busybox/networking/httpd.c
===================================================================
--- trunk/busybox/networking/httpd.c	2007-09-23 12:12:22 UTC (rev 19966)
+++ trunk/busybox/networking/httpd.c	2007-09-23 13:56:57 UTC (rev 19967)
@@ -104,13 +104,16 @@
 //#define DEBUG 1
 #define DEBUG 0
 
+#define IOBUF_SIZE 8192    /* IO buffer */
+
 /* amount of buffering in a pipe */
 #ifndef PIPE_BUF
 # define PIPE_BUF 4096
 #endif
+#if PIPE_BUF >= IOBUF_SIZE
+# error "PIPE_BUF >= IOBUF_SIZE"
+#endif
 
-#define IOBUF_SIZE 8192    /* IO buffer */
-
 #define HEADER_READ_TIMEOUT 60
 
 static const char default_path_httpd_conf[] ALIGN1 = "/etc";
@@ -1029,6 +1032,173 @@
 }
 
 #if ENABLE_FEATURE_HTTPD_CGI
+
+/* gcc 4.2.1 fares better with NOINLINE */
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) ATTRIBUTE_NORETURN;
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
+{
+	enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
+	struct pollfd pfd[3];
+	int out_cnt; /* we buffer a bit of initial CGI output */
+	int count;
+
+	/* iobuf is used for CGI -> network data,
+	 * hdr_buf is for network -> CGI data (POSTDATA) */
+
+	/* If CGI dies, we still want to correctly finish reading its output
+	 * and send it to the peer. So please no SIGPIPEs! */
+	signal(SIGPIPE, SIG_IGN);
+
+	/* NB: breaking out of this loop jumps to log_and_exit() */
+	out_cnt = 0;
+	while (1) {
+		memset(pfd, 0, sizeof(pfd));
+
+		pfd[FROM_CGI].fd = fromCgi_rd;
+		pfd[FROM_CGI].events = POLLIN;
+
+		if (toCgi_wr) {
+			pfd[TO_CGI].fd = toCgi_wr;
+			if (hdr_cnt > 0) {
+				pfd[TO_CGI].events = POLLOUT;
+			} else if (post_len > 0) {
+				pfd[0].events = POLLIN;
+			} else {
+				/* post_len <= 0 && hdr_cnt <= 0:
+				 * no more POST data to CGI,
+				 * let CGI see EOF on CGI's stdin */
+				close(toCgi_wr);
+				toCgi_wr = 0;
+			}
+		}
+
+		/* Now wait on the set of sockets */
+		count = poll(pfd, 3, -1);
+		if (count <= 0) {
+#if 0
+			if (errno == EINTR)
+				continue;
+#endif
+#if 0
+			if (waitpid(pid, &status, WNOHANG) <= 0) {
+				/* Weird. CGI didn't exit and no fd's
+				 * are ready, yet poll returned?! */
+				continue;
+			}
+			if (DEBUG && WIFEXITED(status))
+				bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
+			if (DEBUG && WIFSIGNALED(status))
+				bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
+#endif
+			break;
+		}
+
+		if (pfd[TO_CGI].revents) {
+			/* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
+			/* Have data from peer and can write to CGI */
+			count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
+			/* Doesn't happen, we dont use nonblocking IO here
+			 *if (count < 0 && errno == EAGAIN) {
+			 *	...
+			 *} else */
+			if (count > 0) {
+				hdr_ptr += count;
+				hdr_cnt -= count;
+			} else {
+				/* EOF/broken pipe to CGI, stop piping POST data */
+				hdr_cnt = post_len = 0;
+			}
+		}
+
+		if (pfd[0].revents) {
+			/* post_len > 0 && hdr_cnt == 0 here */
+			/* We expect data, prev data portion is eaten by CGI
+			 * and there *is* data to read from the peer
+			 * (POSTDATA) */
+			//count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
+			//count = safe_read(0, hdr_buf, count);
+			count = safe_read(0, hdr_buf, sizeof(hdr_buf));
+			if (count > 0) {
+				hdr_cnt = count;
+				hdr_ptr = hdr_buf;
+				post_len -= count;
+			} else {
+				/* no more POST data can be read */
+				post_len = 0;
+			}
+		}
+
+		if (pfd[FROM_CGI].revents) {
+			/* There is something to read from CGI */
+			char *rbuf = iobuf;
+
+			/* Are we still buffering CGI output? */
+			if (out_cnt >= 0) {
+				/* HTTP_200[] has single "\r\n" at the end.
+				 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
+				 * CGI scripts MUST send their own header terminated by
+				 * empty line, then data. That's why we have only one
+				 * <cr><lf> pair here. We will output "200 OK" line
+				 * if needed, but CGI still has to provide blank line
+				 * between header and body */
+
+				/* Must use safe_read, not full_read, because
+				 * CGI may output a few first bytes and then wait
+				 * for POSTDATA without closing stdout.
+				 * With full_read we may wait here forever. */
+				count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
+				if (count <= 0) {
+					/* eof (or error) and there was no "HTTP",
+					 * so write it, then write received data */
+					if (out_cnt) {
+						full_write(1, HTTP_200, sizeof(HTTP_200)-1);
+						full_write(1, rbuf, out_cnt);
+					}
+					break; /* CGI stdout is closed, exiting */
+				}
+				out_cnt += count;
+				count = 0;
+				/* "Status" header format is: "Status: 302 Redirected\r\n" */
+				if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
+					/* send "HTTP/1.0 " */
+					if (full_write(1, HTTP_200, 9) != 9)
+						break;
+					rbuf += 8; /* skip "Status: " */
+					count = out_cnt - 8;
+					out_cnt = -1; /* buffering off */
+				} else if (out_cnt >= 4) {
+					/* Did CGI add "HTTP"? */
+					if (memcmp(rbuf, HTTP_200, 4) != 0) {
+						/* there is no "HTTP", do it ourself */
+						if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
+							break;
+					}
+					/* Commented out:
+					if (!strstr(rbuf, "ontent-")) {
+						full_write(s, "Content-type: text/plain\r\n\r\n", 28);
+					}
+					 * Counter-example of valid CGI without Content-type:
+					 * echo -en "HTTP/1.0 302 Found\r\n"
+					 * echo -en "Location: http://www.busybox.net\r\n"
+					 * echo -en "\r\n"
+					 */
+					count = out_cnt;
+					out_cnt = -1; /* buffering off */
+				}
+			} else {
+				count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
+				if (count <= 0)
+					break;  /* eof (or error) */
+			}
+			if (full_write(1, rbuf, count) != count)
+				break;
+			if (DEBUG)
+				fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
+		} /* if (pfd[FROM_CGI].revents) */
+	} /* while (1) */
+	log_and_exit();
+}
+
 static void setenv1(const char *name, const char *value)
 {
 	setenv(name, value ? value : "", 1);
@@ -1038,25 +1208,25 @@
  * Spawn CGI script, forward CGI's stdin/out <=> network
  *
  * Environment variables are set up and the script is invoked with pipes
- * for stdin/stdout.  If a post is being done the script is fed the POST
+ * for stdin/stdout.  If a POST is being done the script is fed the POST
  * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
  *
  * Parameters:
  * const char *url              The requested URL (with leading /).
- * int bodyLen                  Length of the post body.
+ * int post_len                 Length of the POST body.
  * const char *cookie           For set HTTP_COOKIE.
  * const char *content_type     For set CONTENT_TYPE.
  */
 static void send_cgi_and_exit(
 		const char *url,
 		const char *request,
-		int bodyLen,
+		int post_len,
 		const char *cookie,
 		const char *content_type) ATTRIBUTE_NORETURN;
 static void send_cgi_and_exit(
 		const char *url,
 		const char *request,
-		int bodyLen,
+		int post_len,
 		const char *cookie,
 		const char *content_type)
 {
@@ -1065,9 +1235,7 @@
 	char *fullpath;
 	char *script;
 	char *purl;
-	int buf_count;
-	int status;
-	int pid = 0;
+	int pid;
 
 	/*
 	 * We are mucking with environment _first_ and then vfork/exec,
@@ -1138,8 +1306,8 @@
 		}
 	}
 	setenv1("HTTP_USER_AGENT", user_agent);
-	if (bodyLen)
-		putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen));
+	if (post_len)
+		putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
 	if (cookie)
 		setenv1("HTTP_COOKIE", cookie);
 	if (content_type)
@@ -1215,168 +1383,15 @@
 
 	/* Parent process */
 
-	/* First, restore variables possibly changed by child */
+	/* Restore variables possibly changed by child */
 	xfunc_error_retval = 0;
 
-	/* Prepare for pumping data.
-	 * iobuf is used for CGI -> network data,
-	 * hdr_buf is for network -> CGI data (POSTDATA) */
-	buf_count = 0;
+	/* Pump data */
 	close(fromCgi.wr);
 	close(toCgi.rd);
+	cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
+}
 
-	/* If CGI dies, we still want to correctly finish reading its output
-	 * and send it to the peer. So please no SIGPIPEs! */
-	signal(SIGPIPE, SIG_IGN);
-
-	/* This loop still looks messy. What is an exit criteria?
-	 * "CGI's output closed"? Or "CGI has exited"?
-	 * What to do if CGI has closed both input and output, but
-	 * didn't exit? etc... */
-
-	/* NB: breaking out of this loop jumps to log_and_exit() */
-	while (1) {
-		fd_set readSet;
-		fd_set writeSet;
-		int nfound;
-		int count;
-
-		FD_ZERO(&readSet);
-		FD_ZERO(&writeSet);
-		FD_SET(fromCgi.rd, &readSet);
-		if (bodyLen > 0 || hdr_cnt > 0) {
-			FD_SET(toCgi.wr, &writeSet);
-			nfound = toCgi.wr > fromCgi.rd ? toCgi.wr : fromCgi.rd;
-			if (hdr_cnt <= 0)
-				FD_SET(0, &readSet);
-			/* Now wait on the set of sockets! */
-			nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL);
-		} else {
-			if (!bodyLen) {
-				close(toCgi.wr); /* no more POST data to CGI */
-				bodyLen = -1;
-			}
-			nfound = select(fromCgi.rd + 1, &readSet, NULL, NULL, NULL);
-		}
-
-		if (nfound <= 0) {
-			if (waitpid(pid, &status, WNOHANG) <= 0) {
-				/* Weird. CGI didn't exit and no fd's
-				 * are ready, yet select returned?! */
-				continue;
-			}
-			close(fromCgi.rd);
-			if (DEBUG && WIFEXITED(status))
-				bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
-			if (DEBUG && WIFSIGNALED(status))
-				bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
-			break;
-		}
-
-		if (hdr_cnt > 0 && FD_ISSET(toCgi.wr, &writeSet)) {
-			/* Have data from peer and can write to CGI */
-			count = safe_write(toCgi.wr, hdr_ptr, hdr_cnt);
-			/* Doesn't happen, we dont use nonblocking IO here
-			 *if (count < 0 && errno == EAGAIN) {
-			 *	...
-			 *} else */
-			if (count > 0) {
-				hdr_ptr += count;
-				hdr_cnt -= count;
-			} else {
-				hdr_cnt = bodyLen = 0; /* EOF/broken pipe to CGI */
-			}
-		} else if (bodyLen > 0 && hdr_cnt == 0
-		 && FD_ISSET(0, &readSet)
-		) {
-			/* We expect data, prev data portion is eaten by CGI
-			 * and there *is* data to read from the peer
-			 * (POSTDATA?) */
-			count = bodyLen > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : bodyLen;
-			count = safe_read(0, hdr_buf, count);
-			if (count > 0) {
-				hdr_cnt = count;
-				hdr_ptr = hdr_buf;
-				bodyLen -= count;
-			} else {
-				bodyLen = 0; /* closed */
-			}
-		}
-
-#define PIPESIZE PIPE_BUF
-#if PIPESIZE >= IOBUF_SIZE
-# error "PIPESIZE >= IOBUF_SIZE"
-#endif
-		if (FD_ISSET(fromCgi.rd, &readSet)) {
-			/* There is something to read from CGI */
-			char *rbuf = iobuf;
-
-			/* Are we still buffering CGI output? */
-			if (buf_count >= 0) {
-				/* HTTP_200[] has single "\r\n" at the end.
-				 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
-				 * CGI scripts MUST send their own header terminated by
-				 * empty line, then data. That's why we have only one
-				 * <cr><lf> pair here. We will output "200 OK" line
-				 * if needed, but CGI still has to provide blank line
-				 * between header and body */
-
-				/* Must use safe_read, not full_read, because
-				 * CGI may output a few first bytes and then wait
-				 * for POSTDATA without closing stdout.
-				 * With full_read we may wait here forever. */
-				count = safe_read(fromCgi.rd, rbuf + buf_count, PIPESIZE - 8);
-				if (count <= 0) {
-					/* eof (or error) and there was no "HTTP",
-					 * so write it, then write received data */
-					if (buf_count) {
-						full_write(1, HTTP_200, sizeof(HTTP_200)-1);
-						full_write(1, rbuf, buf_count);
-					}
-					break; /* CGI stdout is closed, exiting */
-				}
-				buf_count += count;
-				count = 0;
-				/* "Status" header format is: "Status: 302 Redirected\r\n" */
-				if (buf_count >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
-					/* send "HTTP/1.0 " */
-					if (full_write(1, HTTP_200, 9) != 9)
-						break;
-					rbuf += 8; /* skip "Status: " */
-					count = buf_count - 8;
-					buf_count = -1; /* buffering off */
-				} else if (buf_count >= 4) {
-					/* Did CGI add "HTTP"? */
-					if (memcmp(rbuf, HTTP_200, 4) != 0) {
-						/* there is no "HTTP", do it ourself */
-						if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
-							break;
-					}
-					/* Commented out:
-					if (!strstr(rbuf, "ontent-")) {
-						full_write(s, "Content-type: text/plain\r\n\r\n", 28);
-					}
-					 * Counter-example of valid CGI without Content-type:
-					 * echo -en "HTTP/1.0 302 Found\r\n"
-					 * echo -en "Location: http://www.busybox.net\r\n"
-					 * echo -en "\r\n"
-					 */
-					count = buf_count;
-					buf_count = -1; /* buffering off */
-				}
-			} else {
-				count = safe_read(fromCgi.rd, rbuf, PIPESIZE);
-				if (count <= 0)
-					break;  /* eof (or error) */
-			}
-			if (full_write(1, rbuf, count) != count)
-				break;
-			if (DEBUG)
-				fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
-		} /* if (FD_ISSET(fromCgi.rd)) */
-	} /* while (1) */
-	log_and_exit();
-}
 #endif          /* FEATURE_HTTPD_CGI */
 
 /*




More information about the busybox-cvs mailing list