You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Mike Abbott <mj...@trudge.engr.sgi.com> on 1999/09/02 22:40:31 UTC

[PATCH] 10x performance increase patch #8

Apache Developers,

Here is my eighth patch for increasing Apache's performance.  This one
adds a static-content cache called the QSC.  Complete documentation is
included.

The base for this patch is Apache/1.3.6 + 10x performance increase
patches #1 through #7.  If there is sufficient interest and time I'll
port all my patches to the latest Apache release.

Note that this patch includes two new files, htdocs/manual/qsc.html and
src/main/qsc.c.  When I apply the patch to a pure 1.3.6 tree my version
of the patch command puts these files in the current directory rather
than where they belong.  I hope yours works!

As always, I welcome your review, questions, and feedback.
--
Michael J. Abbott    mja@sgi.com

========================================================================

diff -Naur apache_1.3.6+01-07/conf/httpd.conf-dist apache_1.3.6+01-08/conf/httpd.conf-dist
--- apache_1.3.6+01-07/conf/httpd.conf-dist	Tue Jul 20 22:19:58 1999
+++ apache_1.3.6+01-08/conf/httpd.conf-dist	Thu Sep  2 10:12:04 1999
@@ -210,6 +210,16 @@
 #
 #ExtendedStatus On
 
+#
+# QSC:  Enable the Quick Shortcut Cache.  The QSC caches response
+# headers and data together for very fast response to requests for
+# static content.  It is tightly integrated with and requires the
+# mmap_static module.
+#
+<IfModule mod_mmap_static.c>
+    QSC on
+</IfModule>
+
 ### Section 2: 'Main' server configuration
 #
 # The directives in this section set up the values used by the 'main'
diff -Naur apache_1.3.6+01-07/htdocs/manual/index.html apache_1.3.6+01-08/htdocs/manual/index.html
--- apache_1.3.6+01-07/htdocs/manual/index.html	Thu Jul  8 16:08:43 1999
+++ apache_1.3.6+01-08/htdocs/manual/index.html	Thu Sep  2 10:12:18 1999
@@ -47,6 +47,7 @@
 <LI><A HREF="misc/API.html">The Apache API</A>
 <LI><A HREF="suexec.html">Using SetUserID Execution for CGI</A>
 <LI><A HREF="64-bit.html">Guide to 64-bit Apache</A>
+<LI><A HREF="qsc.html">The Quick Shortcut Cache</A>
 </UL>
 
 <H3><A NAME="oth">Other Notes</A></H3>
diff -Naur apache_1.3.6+01-07/htdocs/manual/mod/core.html apache_1.3.6+01-08/htdocs/manual/mod/core.html
--- apache_1.3.6+01-07/htdocs/manual/mod/core.html	Tue Jul 20 22:26:34 1999
+++ apache_1.3.6+01-08/htdocs/manual/mod/core.html	Thu Sep  2 10:12:58 1999
@@ -75,6 +75,7 @@
 <LI><A HREF="#options">Options</A>
 <LI><A HREF="#pidfile">PidFile</A>
 <LI><A HREF="#port">Port</A>
+<LI><A HREF="#qsc">QSC</A>
 <LI><A HREF="#require">require</A>
 <LI><A HREF="#resourceconfig">ResourceConfig</A>
 <LI><A HREF="#rlimitcpu">RLimitCPU</A>
@@ -2349,6 +2350,34 @@
 not to set <A HREF="#user">User</A> to root. If you run the server as
 root whilst handling connections, your site may be open to a major security
 attack.<P><HR>
+
+<H2><A NAME="qsc">QSC directive</A></H2>
+<!--%plaintext &lt;?INDEX {\tt QSC} directive&gt; -->
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> QSC <EM>on|off</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>&lt;IfModule mod_mmap_static.c&gt; QSC on &lt;/IfModule&gt;</CODE> <EM>else QSC off</EM><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> server config<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> core<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> The QSC is only available in Apache
+1.3.X and later.
+<P>
+This directive enables or disables the <A HREF="../qsc.html">Quick Shortcut Cache</A>.
+<P>
+<HR>
 
 <H2><A NAME="require">require directive</A></H2>
 <!--%plaintext &lt;?INDEX {\tt require} directive&gt; -->
diff -Naur apache_1.3.6+01-07/htdocs/manual/mod/directives.html apache_1.3.6+01-08/htdocs/manual/mod/directives.html
--- apache_1.3.6+01-07/htdocs/manual/mod/directives.html	Tue Jul 20 22:27:13 1999
+++ apache_1.3.6+01-08/htdocs/manual/mod/directives.html	Thu Sep  2 10:13:06 1999
@@ -167,6 +167,7 @@
 <LI><A HREF="mod_proxy.html#proxyremote">ProxyRemote</A>
 <LI><A HREF="mod_proxy.html#proxyrequests">ProxyRequests</A>
 <LI><A HREF="mod_proxy.html#proxyvia">ProxyVia</A>
+<LI><A HREF="core.html#qsc">QSC</A>
 <LI><A HREF="mod_autoindex.html#readmename">ReadmeName</A>
 <LI><A HREF="mod_alias.html#redirect">Redirect</A>
 <LI><A HREF="mod_alias.html#redirectmatch">RedirectMatch</A>
diff -Naur apache_1.3.6+01-07/htdocs/manual/mod/mod_mmap_static.html apache_1.3.6+01-08/htdocs/manual/mod/mod_mmap_static.html
--- apache_1.3.6+01-07/htdocs/manual/mod/mod_mmap_static.html	Thu Jul  8 12:05:38 1999
+++ apache_1.3.6+01-08/htdocs/manual/mod/mod_mmap_static.html	Thu Sep  2 11:40:28 1999
@@ -31,6 +31,12 @@
   </PRE>
   </P>
 
+  <P>
+  Use of this module allows use of the Quick Shortcut (or Static-content)
+  Cache (QSC) for very fast static-content serving.
+  See the <A HREF="../qsc.html">QSC documentation</A> for details.
+  </P>
+
   <H2>Summary</H2>
   <P>
   This is an <STRONG>experimental</STRONG> module and should be used with
diff -Naur apache_1.3.6+01-07/htdocs/manual/mod/mod_status.html apache_1.3.6+01-08/htdocs/manual/mod/mod_status.html
--- apache_1.3.6+01-07/htdocs/manual/mod/mod_status.html	Mon Mar 22 16:17:41 1999
+++ apache_1.3.6+01-08/htdocs/manual/mod/mod_status.html	Thu Sep  2 10:13:26 1999
@@ -47,6 +47,8 @@
 <LI>The current percentage CPU used by each child and in total by
 Apache (*)
 <LI>The current hosts and requests being processed (*)
+<LI><A HREF="../qsc.html#status">QSC statistics</A>, if the QSC is
+present and enabled.
 </UL>
 
 A compile-time option must be used to display the details marked "(*)" as
diff -Naur apache_1.3.6+01-07/htdocs/manual/qsc.html apache_1.3.6+01-08/htdocs/manual/qsc.html
--- apache_1.3.6+01-07/htdocs/manual/qsc.html
+++ apache_1.3.6+01-08/htdocs/manual/qsc.html	Thu Sep  2 10:10:40 1999
@@ -0,0 +1,705 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+    <META NAME="Generator" CONTENT="Cosmo Create 1.0.3">
+    <TITLE>The Quick Shortcut Cache (QSC)</TITLE>
+</HEAD>
+<!-- Background white, links blue (unvisited), navy (visited), red (active) -->
+<BODY ALINK="#ff0000" VLINK="#000080" LINK="#0000ff" BGCOLOR="#ffffff"
+ TEXT="#000000">
+<P>
+<DIV ALIGN="CENTER"><IMG SRC="images/sub.gif"
+ ALT="[APACHE DOCUMENTATION]"> </P>
+<H3>
+Apache HTTP Server Version 1.3 </H3>
+<P>
+</DIV></P>
+<CENTER><H1 ALIGN="CENTER">
+The Quick Shortcut Cache (QSC)</H1>
+</CENTER><CENTER><H2 ALIGN="CENTER">
+Also known as the Quick Static-content Cache</H2>
+</CENTER><CENTER><P ALIGN="CENTER">
+Mike Abbott - <A HREF="mailto:mja@sgi.com">mja@sgi.com</A></P>
+</CENTER><HR>
+<P>
+Apache/1.3.X and beyond support a very fast cache of static content and 
+HTTP response headers known as the Quick Shortcut (or Static-content) 
+Cache, or QSC. The QSC is meant for sites that serve lots of data as-is 
+from disk, such as images, unparsed HTML, and plain text. Sites that 
+serve mostly dynamically-generated content, such as CGI output or 
+on-disk content with headers or footers generated on the fly, probably 
+should not use the QSC.</P>
+<H2>
+Contents</H2>
+<UL>
+    <LI>
+    <A HREF="#primer">QSC Primer</A> 
+    <LI>
+    <A HREF="#using">Using the QSC</A> 
+    <LI>
+    <A HREF="#status">Monitoring the QSC</A> 
+    <LI>
+    <A HREF="#advanced">Advanced Options</A> 
+</UL>
+<H2>
+<A NAME="primer">QSC Primer</A></H2>
+<P>
+Normally Apache processes an HTTP request by following a long list of <A
+ HREF="misc/API.html#HMR">rules</A>, such as converting the URI to a 
+file name, authenticating the request, generating HTTP headers for the 
+response, sending the response, and logging information about the 
+transaction. Apache performs all these steps (more or less) for every 
+request, even if it has handled the same request previously. This 
+memory-less behavior is required for steps such as authentication but 
+is unnecessary for URI-to-file translation and HTTP response header 
+generation when the response consists of static content. The QSC adds 
+memory to Apache, allowing it to shortcut the processing of 
+previously-seen requests for static content.</P>
+<H3>
+Operation</H3>
+<P>
+After Apache reads an HTTP request and locates the appropriate <A
+ HREF="vhosts/index.html">virtual host</A> context in which to handle 
+the request, it checks whether the QSC can respond to the request -- 
+whether the request is <A HREF="#cachable">cachable</A> and whether the 
+URI and virtual host match a cached entry. If so, the QSC bypasses all 
+unnecessary processing and sends the previously-generated HTTP response 
+quickly, then Apache logs the transaction and moves on to the next 
+request.</P>
+<P>
+When the QSC cannot respond quickly, Apache continues processing the 
+request normally. When such normal processing results in the <A
+ HREF="mod/mod_mmap_static.html">mmap_static module</A> sending the 
+HTTP response, that module tries to insert the request and response 
+into the QSC -- which succeeds only if the request and response are <A
+ HREF="#cachable">cachable</A> and the cache isn't <A HREF="#limit">full</A>. 
+Finally, as with the cached response, Apache logs the transaction and 
+moves on to the next request. Note that the QSC caches both the 
+response headers and the response body.</P>
+<H3>
+Interaction with the mmap_static Module</H3>
+<P>
+Only the mmap_static module inserts entries into the QSC, for a number 
+of reasons:</P>
+<UL>
+    <LI>
+    The mmap_static module keeps file contents mapped into memory so 
+    sending the response body is simple and quick. (QSC entries point to 
+    the memory-mapped file contents held by the mmap_static module; the QSC 
+    sends this data as the HTTP response body.) 
+    <LI>
+    The mmap_static module responds only to requests for static content. 
+    <LI>
+    The mmap_static module responds only to requests for 
+    explicitly-designated files. The administrator has full control. 
+    <LI>
+    Both the QSC and the mmap_static module reset themselves upon server 
+    restarts. 
+    <LI>
+    Both the QSC and the mmap_static module share a sole purpose: to 
+    increase performance. 
+</UL>
+<H3>
+<A NAME="vhost">Virtual Hosts</A></H3>
+<P>
+When looking for a cache entry to satisfy a request, the QSC matches 
+the virtual host as well as the URI because different virtual hosts can 
+map the same URI to different files.</P>
+<H3>
+<A NAME="resheaders">Response Headers</A></H3>
+<P>
+Each QSC entry contains two nearly identical sets of HTTP response 
+headers, one for keep-alive connections (the headers contain a <CODE>Connection: 
+keep-alive</CODE> header) and one for non-keep-alive connections (the 
+headers contain a <CODE>Connection: close</CODE> header). Caching both 
+versions allows the QSC to respond quickly regardless of the nature of 
+the connection and without having to generate the HTTP headers for each 
+request -- a key ingredient for quick response.</P>
+<P>
+Furthermore, the QSC aligns both sets of response headers on a certain 
+memory boundary and pads them out to a certain length. Generally this 
+alignment is the secondary cache line size of the system on which 
+Apache runs. When asked to send data out on a network, operating 
+systems typically align misaligned data by copying it. The QSC 
+pre-aligns the headers (and the mmap_static module automatically aligns 
+the body due to the nature of memory-mapping files) and pads the 
+headers (by adding spaces to the <CODE>Server</CODE> header value) to 
+eliminate this overhead. You can <A HREF="#align">adjust or disable the 
+header alignment</A> manually.</P>
+<H2>
+<A NAME="using">Using the QSC</A></H2>
+<P>
+This section describes how to compile and enable QSC support in your 
+Apache server.</P>
+<P>
+The QSC requires the <A HREF="mod/mod_mmap_static.html">mmap_static 
+module</A>. In the standard Apache distribution neither the QSC nor the 
+mmap_static module are compiled into the server. You must add them both 
+to the compilation configuration to use the QSC:</P>
+<PRE>
+  $ CFLAGS=-DUSE_QSC configure --enable-module=mmap_static
+  $ make
+</PRE>
+<P>
+(The QSC is an enhancement to core Apache, while mmap_static is an 
+Apache <A HREF="mod/index.html">module</A>.) There are also advanced 
+options to controlling QSC behavior, described <A HREF="#advanced">below</A>.</P>
+<P>
+In addition, a run-time configuration directive, <A
+ HREF="mod/core.html#qsc"><CODE>QSC</CODE></A>, enables and disables 
+the QSC. By default the <CODE>QSC</CODE> is <CODE>off</CODE> (disabled) 
+but this snippet from the standard httpd.conf file enables it when the 
+mmap_static module is also compiled into the server:</P>
+<PRE>
+  &lt;<A HREF="mod/core.html#ifmodule">IfModule</A> mod_mmap_static.c&gt;
+      QSC on
+  &lt;/IfModule&gt;
+</PRE>
+<P>
+In other words, compiling both the QSC and the mmap_static module 
+automatically enables the QSC. The <CODE>QSC</CODE> directive exists to 
+allow you to disable it.</P>
+<P>
+You must also configure the mmap_static module by adding an <A
+ HREF="mod/mod_mmap_static.html#mmapfile">mmapfile</A> directive for 
+each file you want cached.</P>
+<P>
+Once configured the QSC will operate automatically.</P>
+<H3>
+Shared Memory</H3>
+<P>
+All of the data the QSC stores is in shared memory (memory accessible 
+to all of Apache's subprocesses) so that the cache is not duplicated 
+for each Apache child process. Systems that do not support anonymous 
+shared memory (that define neither <CODE>HAVE_MMAP</CODE> nor <CODE>HAVE_SHMGET</CODE>) 
+cannot use the QSC.</P>
+<H3>
+Atomic Compare-and-Swap</H3>
+<P>
+The QSC requires one piece of functionality that is completely new to 
+Apache and so has not had the benefit of years of multi-platform 
+porting: a way to compare and swap (<I>cas</I>) two values atomically 
+(i.e., in a thread-safe manner). All of the QSC's internal data 
+structures are stored in shared memory so every update to that data 
+must be done in a way that is guaranteed to be safe and correct for all 
+the child processes. If your attempt to compile the QSC fails with the 
+error &quot;need atomic compare-and-swap function,&quot; you must port 
+the function <CODE>qsc_cas()</CODE> to your system.</P>
+<H2>
+<A NAME="status">Monitoring the QSC</A></H2>
+<P>
+The QSC gathers statistics about its operation. You can view these 
+statistics on the page that the <A HREF="mod/mod_status.html">status 
+module</A> generates in response to requests of the form:</P>
+<PRE>
+  http://your.server.name/server-status
+</PRE>
+<P>
+This section has examples and explanations of this information.</P>
+<P>
+This is what the status page looks like when the QSC is disabled:</P>
+<PRE>
+  Quick Shortcut Cache (QSC) Status:
+  QSC disabled
+</PRE>
+<P>
+There may be an explanation why the QSC is disabled in the server's 
+error log. The next example shows the statistics from a freshly-started 
+server with the QSC enabled:</P>
+<PRE>
+  Quick Shortcut Cache (QSC) Status:
+    hit ratio            0/1 (0.00%)
+    uncachable           1/1 (100.00%)
+    uncachable misses    1/1 (100.00%)
+    uncachable requests  0/1 (0.00%)
+    uncachable responses 0/1 (0.00%)
+    resets               1
+  Hash table
+    failed insertions    0
+    entries              0
+    duplicate entries    0
+    bucket use           0/32768 (0.00%)
+    hash effectiveness   0/0 (0.00%)
+    longest chain        0
+    avg. chain           0.0
+    avg. nonempty chain  0.0
+    Chain length histogram:
+          1     2     3     4     5+
+          0     0     0     0     0 
+  Memory use (in bytes)
+    table + misc         131104
+    entries              0
+    URIs                 0
+    headers              0
+    total                135264/5000000 (2.71%)
+    mapped file data     0
+    mapped file vaddrs   0 (0 16384-byte pages)
+</PRE>
+<P>
+It's pretty clear that the cache is empty at this point. The next 
+example shows the statistics from the same server after running for a 
+while:</P>
+<PRE>
+  Quick Shortcut Cache (QSC) Status:
+    hit ratio            2104749/2112853 (99.62%)
+    uncachable           40/2112853 (0.00%)
+    uncachable misses    40/8104 (0.49%)
+    uncachable requests  0/2112853 (0.00%)
+    uncachable responses 0/2112853 (0.00%)
+    resets               1
+  Hash table
+    failed insertions    0
+    entries              8064
+    duplicate entries    0
+    bucket use           7923/32768 (24.18%)
+    hash effectiveness   7923/8064 (98.25%)
+    longest chain        2
+    avg. chain           0.2
+    avg. nonempty chain  1.0
+    Chain length histogram:
+          1     2     3     4     5+
+       7782   141     0     0     0 
+  Memory use (in bytes)
+    table + misc         131104
+    entries              322560
+    URIs                 246024
+    headers              4128768
+    total                4844640/5000000 (96.89%)
+    mapped file data     1146761280
+    mapped file vaddrs   1229455360 (75040 16384-byte pages)
+</PRE>
+<P>
+The QSC computes some of the statistics (such as the hash chain 
+lengths, histogram, and memory use) only when requested, and computing 
+them frequently may interfere with normal server operation. You can 
+view a condensed statistics page that skips the computation by 
+appending <CODE>?qsc=quick</CODE> to your request, like this:</P>
+<PRE>
+  http://your.server.name/server-status?qsc=quick
+</PRE>
+<P>
+which produces this output:</P>
+<PRE>
+  Quick Shortcut Cache (QSC) Status:
+    hit ratio            2104749/2112854 (99.62%)
+    uncachable requests  0/2112854 (0.00%)
+    uncachable responses 0/2112854 (0.00%)
+    resets               1
+  Hash table
+    failed insertions    0
+</PRE>
+<P>
+Alternatively, you can view detailed QSC information by appending <CODE>?qsc=full</CODE>, 
+like this:</P>
+<PRE>
+  http://your.server.name/server-status?qsc=full
+</PRE>
+<P>
+which produces this output (with a large portion omitted for brevity):</P>
+<PRE>
+  Quick Shortcut Cache (QSC) Status:
+    hit ratio            2104749/2112855 (99.62%)
+    uncachable           42/2112855 (0.00%)
+    uncachable misses    42/8106 (0.52%)
+    uncachable requests  0/2112855 (0.00%)
+    uncachable responses 0/2112855 (0.00%)
+    resets               1
+  Hash table
+    failed insertions    0
+    entries              8064
+    duplicate entries    0
+    bucket use           7923/32768 (24.18%)
+    hash effectiveness   7923/8064 (98.25%)
+    longest chain        2
+    avg. chain           0.2
+    avg. nonempty chain  1.0
+    Chain length histogram:
+          1     2     3     4     5+
+       7782   141     0     0     0 
+  Memory use (in bytes)
+    table + misc         131104
+    entries              322560
+    URIs                 246024
+    headers              4128768
+    total                4844640/5000000 (96.89%)
+    mapped file data     1146761280
+    mapped file vaddrs   1229455360 (75040 16384-byte pages)
+  Full entry info
+    server * URI @ hash-bucket -&gt; keep-alive-header-bytes;non-keep-alive-header-bytes + body-bytes file-name
+    main * /spec/file_set/dir115/class0_0 @ 37 -&gt; 256;256 + 102 /a/htdocs/spec/file_set/dir115/class0_0
+    main * /spec/file_set/dir115/class0_1 @ 38 -&gt; 256;256 + 204 /a/htdocs/spec/file_set/dir115/class0_1
+    main * /spec/file_set/dir115/class0_2 @ 39 -&gt; 256;256 + 306 /a/htdocs/spec/file_set/dir115/class0_2
+    main * /spec/file_set/dir115/class0_3 @ 40 -&gt; 256;256 + 408 /a/htdocs/spec/file_set/dir115/class0_3
+    <EM>... thousands of lines elided for brevity ...</EM>
+    main * /spec/file_set/dir214/class3_8 @ 32752 -&gt; 256;256 + 921600 /a/htdocs/spec/file_set/dir214/class3_8
+</PRE>
+<H3>
+What Do the Statistics Mean?</H3>
+<P>
+This section explains the final example above in great detail.</P>
+<PRE>
+    hit ratio            2104749/2112855 (99.62%)
+</PRE>
+<P>
+The <EM>hit ratio</EM> is the ratio of the number of requests 
+successfully served by the QSC to the total number of requests made to 
+the server. The number in parentheses is the ratio expressed as a 
+percentage. In this case there were 2,112,855 total requests, 2,104,749 
+or 99.62% of which were cache hits -- the QSC responded to the requests 
+quickly -- and 8,106 or 0.38% were cache misses -- Apache processed the 
+requests without assistance from the QSC.</P>
+<PRE>
+    uncachable           42/2112855 (0.00%)
+    uncachable misses    42/8106 (0.52%)
+    uncachable requests  0/2112855 (0.00%)
+    uncachable responses 0/2112855 (0.00%)
+</PRE>
+<P>
+These explain the cache misses. Of the 2,112,855 total requests, 42 
+were <EM>uncachable</EM> meaning that not only did they miss (were not 
+in) the cache but also the QSC could not enter them into its cache for 
+some reason. In this case, all 42 uncachable requests were <EM>uncachable 
+misses</EM> meaning some handler other than the mmap_static module's 
+handled the request. For instance, all server-status requests are 
+handled by the status module and so are uncachable misses. (You can see 
+the number of uncachable misses increasing by one for each example 
+above.) Other reasons requests may be uncachable are <EM>uncachable 
+requests</EM> and <EM>uncachable responses</EM>.</P>
+<P>
+<A NAME="cachable">The QSC caches</A> responses to HTTP requests only 
+when both the request and the response meet certain criteria. To be 
+cachable a request must:</P>
+<UL>
+    <LI>
+    be an HTTP GET request, <U>and</U> 
+    <LI>
+    not have been marked uncachable by any module (e.g., <CODE>r-&gt;no_cache 
+    == 0</CODE>), <U>and</U> 
+    <LI>
+    not contain any of the following headers: 
+    <UL>
+        <LI>
+        Authorization: ... 
+        <LI>
+        Cache-Control: ... 
+        <LI>
+        If-Modified-Since: ... 
+        <LI>
+        If-None-Match: ... 
+        <LI>
+        If-Range: ... 
+        <LI>
+        Pragma: no-cache 
+        <LI>
+        Range: ... 
+    </UL>
+</UL>
+<P>
+and its response must:</P>
+<UL>
+    <LI>
+    have status 200 OK, <U>and</U> 
+    <LI>
+    not be chunked, <U>and</U> 
+    <LI>
+    not have been marked uncachable by any module (e.g., <CODE>r-&gt;no_cache 
+    == 0</CODE>), <U>and</U> 
+    <LI>
+    not contain any of the following headers: 
+    <UL>
+        <LI>
+        Cache-Control: ... 
+        <LI>
+        Content-Range: ... 
+        <LI>
+        ETag: W/... (that is, strong etags are cachable, weaks ones are not) 
+        <LI>
+        Expires: ... 
+        <LI>
+        Pragma: no-cache 
+        <LI>
+        Vary: ... 
+    </UL>
+</UL>
+<P>
+For example, pressing the &quot;Reload&quot; button on some popular 
+browsers causes them to issue requests with a <CODE>Pragma: no-cache</CODE>
+ and/or <CODE>Cache-control</CODE> header which are meant to bypass 
+caching mechanisms such as the QSC.</P>
+<PRE>
+    resets               1
+</PRE>
+<P>
+The QSC counts the number of times it has been reset. The counter 
+starts at zero but Apache's normal startup procedures cause one reset. 
+Each time the server is restarted the QSC clears its cache completely, 
+zeros-out all the statistics except this counter, and increments this 
+counter. Thus, the statistics displayed are since the last reset, not 
+since the server was started.</P>
+<PRE>
+  Hash table
+    failed insertions    0
+</PRE>
+<P>
+This is the number of times the QSC tried to insert a new entry into 
+its cache and failed. Failure can occur when, for example, the QSC has 
+consumed all the memory it is allowed to use (i.e., the cache is full). 
+If you see a large number of failed insertions, consider increasing 
+your QSC's <A HREF="#limit">cache size</A>.</P>
+<PRE>
+    entries              8064
+    duplicate entries    0
+</PRE>
+<P>
+This shows you how many entries are in the cache, and how many of those 
+entries are duplicates of one another. Duplicate entries are harmless 
+aside from wasting a little memory.</P>
+<PRE>
+    bucket use           7923/32768 (24.18%)
+    hash effectiveness   7923/8064 (98.25%)
+    longest chain        2
+    avg. chain           0.2
+    avg. nonempty chain  1.0
+    Chain length histogram:
+          1     2     3     4     5+
+       7782   141     0     0     0 
+</PRE>
+<P>
+The above information describes the effectiveness of the QSC's hash 
+algorithm. This particular instance has 32,768 cache buckets of which 
+7,923 or 24.18% have at least one entry (the rest are empty). The 
+effectiveness of the hash function is the ratio of the number of 
+buckets over which entries are spread to the number of entries, in this 
+case 7,923 to 8,064 or 98.25% effective. Higher effectiveness means 
+shorter hash chains which are faster when looking up entries. The 
+longest hash chain has only two entries which is very good. If your 
+server shows a low hash efficiency and long hash chains, consider 
+increasing your QSC's <A HREF="#limit">number of hash buckets</A>. The 
+average (arithmetic mean) chain length is just 0.2 entries per bucket, 
+including empty buckets, and the average chain length of non-empty 
+buckets is 1.0 which is excellent. The histogram displays the number of 
+hash buckets having chains with one, two, three, four, and five-or-more 
+entries. You can control the <A HREF="#debug">number of histogram bins</A>.</P>
+<PRE>
+  Memory use (in bytes)
+    table + misc         131104
+</PRE>
+<P>
+The QSC carefully manages the amount of memory it uses and this part of 
+the report explains where all the bytes are going. This line accounts 
+for the empty hash table -- the size of which is directly related to 
+the number of hash buckets -- and other data structures necessary for 
+the QSC's operation such as the statistics counters. In this example 
+there are 32,768 hash buckets each of which is four bytes in size so 
+the whole table consumes 131,072 bytes. The remaining 32 bytes (for a 
+total of 131,104) are for the statistics counters and other overhead.</P>
+<PRE>
+    entries              322560
+</PRE>
+<P>
+This line accounts for the memory used for the hash entry data 
+structures. In this example each entry consumes 40 bytes and there are 
+8,064 of them for a total of 322,560 bytes.</P>
+<PRE>
+    URIs                 246024
+</PRE>
+<P>
+Each hash entry maps a URI and virtual host to HTTP response headers 
+and data. This counts the amount of memory consumed by remembering 
+those URIs. The average cached URI length in this example is 246,024 
+bytes divided by 8,064 entries or about 31 bytes.</P>
+<PRE>
+    headers              4128768
+</PRE>
+<P>
+This is the number of bytes consumed by remembering the HTTP response 
+headers for each cached entry. This is approximately double the number 
+of bytes of header information sent in response to a cached entry 
+because the QSC keeps <A HREF="#resheaders">two sets of headers</A>, 
+one for keep-alive connections and one for non-keep-alive connections. 
+The average number of bytes of cached headers is 4,128,768 bytes 
+divided by 8,064 entries or exactly 512 bytes (two sets of 256-byte 
+headers per entry). This number is so tidy because of <A HREF="#align">header 
+padding and alignment</A>.</P>
+<PRE>
+    total                4844640/5000000 (96.89%)
+</PRE>
+<P>
+This line displays the total amount of memory that the QSC is using and 
+the maximum amount to which it limits itself. In this case the cache is 
+pretty close to full. You can <A HREF="#limit">control the maximum 
+cache size</A>.</P>
+<PRE>
+    mapped file data     1146761280
+    mapped file vaddrs   1229455360 (75040 16384-byte pages)
+</PRE>
+<P>
+The QSC itself manages only the hash table, the URI strings, and the 
+headers. The mmap_static module manages the cache of memory-mapped file 
+contents. These two lines count the number of bytes of response body 
+data (in this case 1,146,761,280 bytes for an average file size of 
+142,208 bytes) and the number of bytes of virtual memory consumed 
+(1,229,455,360 bytes). The latter is larger because memory-mapping a 
+file whose size is not an exact multiple of the machine's page size 
+wastes the space between the end of the file and the end of the page. 
+In this case the machine's page size is 16 KB and 75,040 pages are used 
+to map the file contents.</P>
+<PRE>
+  Full entry info
+    server * URI @ hash-bucket -&gt; keep-alive-header-bytes;non-keep-alive-header-bytes + body-bytes file-name
+    main * /spec/file_set/dir115/class0_0 @ 37 -&gt; 256;256 + 102 /a/htdocs/spec/file_set/dir115/class0_0
+    main * /spec/file_set/dir115/class0_1 @ 38 -&gt; 256;256 + 204 /a/htdocs/spec/file_set/dir115/class0_1
+    main * /spec/file_set/dir115/class0_2 @ 39 -&gt; 256;256 + 306 /a/htdocs/spec/file_set/dir115/class0_2
+    main * /spec/file_set/dir115/class0_3 @ 40 -&gt; 256;256 + 408 /a/htdocs/spec/file_set/dir115/class0_3
+    <EM>... thousands of lines elided for brevity ...</EM>
+    main * /spec/file_set/dir214/class3_8 @ 32752 -&gt; 256;256 + 921600 /a/htdocs/spec/file_set/dir214/class3_8
+</PRE>
+<P>
+This final section, available using the <CODE>?qsc=full</CODE>
+ server-status extension, lists all of the information known about each 
+cache entry. There were as many lines as there are cache entries so 
+most of them were omitted for brevity. The following information is 
+printed for each entry, as the list's header notes:</P>
+<TABLE BORDER="1">
+    <TR>
+        <TD>server</TD>
+        <TD>The file name and line number where the virtual server was 
+            defined (for lack of better virtual host identification), or <CODE>main</CODE>
+             for the main server.</TD>
+    </TR>
+    <TR>
+        <TD>URI</TD>
+        <TD>The URI for which the response is cached.</TD>
+    </TR>
+    <TR>
+        <TD>hash-bucket</TD>
+        <TD>The ordinal of the bucket into which the URI hashes.</TD>
+    </TR>
+    <TR>
+        <TD>keep-alive-header-bytes</TD>
+        <TD>The number of bytes of HTTP response header cached for a 
+            keep-alive response.</TD>
+    </TR>
+    <TR>
+        <TD>non-keep-alive-header-bytes</TD>
+        <TD>The number of bytes of HTTP response header cached for a 
+            non-keep-alive (i.e., Connection: close) response.</TD>
+    </TR>
+    <TR>
+        <TD>body-bytes</TD>
+        <TD>The number of bytes of HTTP response body.</TD>
+    </TR>
+    <TR>
+        <TD>file-name</TD>
+        <TD>The name of the cached file, available only when the QSC is 
+            compiled with <CODE>QSC_DEBUG</CODE> enabled. Without <CODE>QSC_DEBUG</CODE>
+             <CODE>n/a</CODE> is displayed (&quot;not available&quot;).</TD>
+    </TR>
+</TABLE>
+<H2>
+<A NAME="advanced">Advanced Options</A></H2>
+<P>
+All of the following are compile-time options. The first two must be 
+defined manually. The rest are defined in the QSC source code.</P>
+<TABLE BORDER="1" WIDTH="100%">
+    <TR>
+        <TD WIDTH="25%"><CODE>USE_QSC</CODE></TD>
+        <TD><P>
+            <A HREF="#using">Compiles the QSC into Apache</A>.</P>
+            Default: <CODE>USE_QSC</CODE> is not defined so the QSC is 
+            disabled.</TD>
+    </TR>
+</TABLE>
+<H3>
+<A NAME="debug">Debugging</A> / Additional Information</H3>
+<TABLE BORDER="1" WIDTH="100%">
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_DEBUG</CODE></TD>
+        <TD><P>
+            Enables internal consistency checks.</P>
+            <P>
+            Makes the QSC keep track of the name of the file mapped by the 
+            mmap_static module for each entry. The file name is displayed on the <A
+             HREF="#status">full status page</A>.</P>
+            Default: <CODE>QSC_DEBUG</CODE> is not defined so debugging 
+            is disabled.</TD>
+    </TR>
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_HIST_SIZE</CODE></TD>
+        <TD><P>
+            Sets the number of hash bucket histogram bins the <A
+             HREF="#status">status page</A> displays.</P>
+            Default: 5.</TD>
+    </TR>
+</TABLE>
+<H3>
+<A NAME="limit">Size Restriction</A></H3>
+<TABLE BORDER="1" WIDTH="100%">
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_MAX_SIZE</CODE></TD>
+        <TD><P>
+            Sets the maximum number of bytes of memory the QSC will consume. Note 
+            that this does not include mapped file data.</P>
+            Default: 4194304 (4 MB).</TD>
+    </TR>
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_HASH_SIZE</CODE></TD>
+        <TD><P>
+            Sets the number of hash buckets.</P>
+            Default: 128.</TD>
+    </TR>
+</TABLE>
+<H3>
+<A NAME="align">Header Alignment</A></H3>
+<TABLE BORDER="1" WIDTH="100%">
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_HEADER_GRAIN</CODE></TD>
+        <TD><P>
+            Sets both the virtual address alignment boundary of cached HTTP 
+            response headers and the number of bytes to which the headers are 
+            padded. For best performance make this equal to the size of the largest 
+            cache line size on the system on which Apache runs. Must be a power of 
+            two. The special value 0 disables alignment and padding.</P>
+            Default: system dependent (typically 32 or 128).</TD>
+    </TR>
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_GRAIN</CODE></TD>
+        <TD><P>
+            Sets both the virtual address alignment boundary of internal memory 
+            allocations and the number of bytes to which the allocations are 
+            padded. Must be a power of two at least as large as the larger of a 
+            pointer and a long. Same idea as <CODE>CLICK_SZ</CODE> in 
+            Apache's own memory management subsystem.</P>
+            Default: system dependent (typically 4 or 8).</TD>
+    </TR>
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_MAX_ALLOC</CODE></TD>
+        <TD><P>
+            Sets the maximum single-allocation size in bytes. Internal memory 
+            allocations larger than this will fail and the request/response will 
+            not be cached.</P>
+            Default: 512.</TD>
+    </TR>
+    <TR>
+        <TD WIDTH="25%"><CODE>QSC_RED_ZONE</CODE></TD>
+        <TD><P>
+            Sets the number of bytes of safety margin required between the two 
+            internal memory allocation zones. Should be a small multiple of <CODE>QSC_MAX_ALLOC</CODE>
+             for safety.</P>
+            Default: 4096.</TD>
+    </TR>
+</TABLE>
+<H2>
+See also</H2>
+<P>
+<A HREF="misc/API.html">Apache API</A>, <A
+ HREF="mod/mod_mmap_static.html">mmap_static module</A>, <A
+ HREF="mod/mod_status.html">status module</A>, <A
+ HREF="vhosts/index.html">virtual hosts</A></P>
+<HR>
+<CENTER><H3 ALIGN="CENTER">
+Apache HTTP Server Version 1.3 </H3>
+</CENTER><P>
+<A HREF="./"><IMG SRC="images/index.gif" ALT="Index"></A> </P>
+</BODY>
+</HTML>
diff -Naur apache_1.3.6+01-07/htdocs/manual/vhosts/index.html apache_1.3.6+01-08/htdocs/manual/vhosts/index.html
--- apache_1.3.6+01-07/htdocs/manual/vhosts/index.html	Mon Mar 22 16:17:46 1999
+++ apache_1.3.6+01-08/htdocs/manual/vhosts/index.html	Thu Sep  2 10:13:33 1999
@@ -48,6 +48,7 @@
 <LI><A HREF="details.html">In-Depth Discussion of Virtual Host Matching</A>
 <LI><A HREF="fd-limits.html">File Descriptor Limits</A>
 <LI><A HREF="mass.html">Dynamically Configured Mass Virtual Hosting with mod_rewrite</A>
+<LI><A HREF="../qsc.html#vhost">QSC Interaction with Virtual Hosts</A>
 </UL>
 
 <H2>Configuration directives</H2>
diff -Naur apache_1.3.6+01-07/src/include/ap_config.h apache_1.3.6+01-08/src/include/ap_config.h
--- apache_1.3.6+01-07/src/include/ap_config.h	Tue Jul 20 22:58:29 1999
+++ apache_1.3.6+01-08/src/include/ap_config.h	Thu Sep  2 10:14:17 1999
@@ -1028,7 +1028,7 @@
 int setrlimit(int, struct rlimit *);
 #endif
 #endif
-#ifdef USE_MMAP_SCOREBOARD
+#ifdef HAVE_MMAP
 #if !defined(OS2) && !defined(WIN32)
 /* This file is not needed for OS/2 */
 #include <sys/mman.h>
@@ -1038,6 +1038,12 @@
 #define MAP_ANON MAP_ANONYMOUS
 #endif
 
+#ifdef HAVE_SHMGET
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#endif
+
 #if defined(USE_MMAP_FILES) && (defined(NO_MMAP) || !defined(HAVE_MMAP))
 #undef USE_MMAP_FILES
 #endif
@@ -1202,6 +1208,14 @@
  */
 #if defined(__WCOREDUMP) && !defined(WCOREDUMP)
 #define WCOREDUMP __WCOREDUMP
+#endif
+
+/*
+ * Maximum number of bytes to write from an mmap'ed region per write().
+ * See ap_send_mmap() and qsc_process_request().
+ */
+#ifndef MMAP_SEGMENT_SIZE
+#define MMAP_SEGMENT_SIZE       32768
 #endif
 
 /*
diff -Naur apache_1.3.6+01-07/src/include/buff.h apache_1.3.6+01-08/src/include/buff.h
--- apache_1.3.6+01-07/src/include/buff.h	Thu Jul 15 14:05:15 1999
+++ apache_1.3.6+01-08/src/include/buff.h	Thu Sep  2 10:14:23 1999
@@ -175,6 +175,7 @@
 API_EXPORT(int) ap_blookc(char *buff, BUFF *fb);
 API_EXPORT(int) ap_bskiplf(BUFF *fb);
 API_EXPORT(int) ap_bwrite(BUFF *fb, const void *buf, int nbyte);
+API_EXPORT(int) ap_bwritev(BUFF *fb, struct iovec *, int);
 API_EXPORT(int) ap_bflush(BUFF *fb);
 API_EXPORT(int) ap_bputs(const char *x, BUFF *fb);
 API_EXPORT(int) ap_bvputs(BUFF *fb,...);
diff -Naur apache_1.3.6+01-07/src/include/http_conf_globals.h apache_1.3.6+01-08/src/include/http_conf_globals.h
--- apache_1.3.6+01-07/src/include/http_conf_globals.h	Tue Jul 20 22:30:01 1999
+++ apache_1.3.6+01-08/src/include/http_conf_globals.h	Thu Sep  2 10:14:28 1999
@@ -86,6 +86,9 @@
 extern int ap_listenbacklog;
 extern int ap_dump_settings;
 extern API_VAR_EXPORT int ap_extended_status;
+#ifdef USE_QSC
+extern int ap_qsc_enabled;
+#endif
 
 #ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 extern int ap_single_listen;
diff -Naur apache_1.3.6+01-07/src/include/http_main.h apache_1.3.6+01-08/src/include/http_main.h
--- apache_1.3.6+01-07/src/include/http_main.h	Fri Jan  1 11:04:40 1999
+++ apache_1.3.6+01-08/src/include/http_main.h	Thu Sep  2 10:14:33 1999
@@ -127,6 +127,13 @@
 unsigned int ap_set_callback_and_alarm(void (*fn) (int), int x);
 API_EXPORT(int) ap_check_alarm(void);
 
+#ifdef HAVE_MMAP
+API_EXPORT(caddr_t) ap_map_anonymous(pool *p, caddr_t addr, size_t size);
+#endif
+#ifdef HAVE_SHMGET
+API_EXPORT(caddr_t) ap_share_anonymous(pool *p, caddr_t addr, size_t size);
+#endif
+
 #ifndef NO_OTHER_CHILD
 /*
  * register an other_child -- a child which the main loop keeps track of
diff -Naur apache_1.3.6+01-07/src/include/http_protocol.h apache_1.3.6+01-08/src/include/http_protocol.h
--- apache_1.3.6+01-07/src/include/http_protocol.h	Thu Jul  8 12:12:57 1999
+++ apache_1.3.6+01-08/src/include/http_protocol.h	Thu Sep  2 10:14:39 1999
@@ -82,10 +82,12 @@
  * ap_send_http_header().
  */
 API_EXPORT(void) ap_basic_http_header(request_rec *r);
+API_EXPORT(void) ap_tee_basic_http_header(request_rec *r, char *, size_t *);
 
 /* Send the Status-Line and header fields for HTTP response */
 
 API_EXPORT(void) ap_send_http_header(request_rec *l);
+API_EXPORT(void) ap_tee_http_header(request_rec *l, char *, size_t *);
 
 /* Send the response to special method requests */
 
@@ -139,6 +141,8 @@
 
 API_EXPORT(size_t) ap_send_mmap(void *mm, request_rec *r, size_t offset,
                              size_t length);
+
+API_EXPORT(size_t) ap_send_iovec(request_rec *, struct iovec *, int);
 
 /* Hmmm... could macrofy these for now, and maybe forever, though the
  * definitions of the macros would get a whole lot hairier.
diff -Naur apache_1.3.6+01-07/src/include/httpd.h apache_1.3.6+01-08/src/include/httpd.h
--- apache_1.3.6+01-07/src/include/httpd.h	Tue Jul 20 22:31:15 1999
+++ apache_1.3.6+01-08/src/include/httpd.h	Thu Sep  2 10:14:46 1999
@@ -68,6 +68,7 @@
 
 /* Mike Abbott - mja@sgi.com */
 #ifdef SPEED_DAEMON	/* that's me, a real speed demon */
+# define USE_QSC	/* enable the Quick Shortcut Cache */
 # define FAST_TIME	/* eliminate redundant calls to time() */
 # define BUFFERED_LOGS	/* buffer log messages */
 # define NO_GRACEFUL	/* eliminate per-request signal manipulation */
@@ -1147,6 +1148,15 @@
 #undef strtoul
 #endif
 #define strtoul strtoul_is_not_a_portable_function_use_strtol_instead
+
+#ifdef USE_QSC
+/* QSC - Quick Shortcut Cache */
+extern void qsc_init(pool *);
+extern int qsc_process_request(request_rec *);
+extern void qsc_insert_request(request_rec *, const char *, size_t, void *,
+    size_t, const char *);
+extern void qsc_status(request_rec *, const char *);
+#endif
 
 /*
  * 64-bit porting aides.  On 64-bit Irix, strlen() returns size_t which
diff -Naur apache_1.3.6+01-07/src/include/scoreboard.h apache_1.3.6+01-08/src/include/scoreboard.h
--- apache_1.3.6+01-07/src/include/scoreboard.h	Tue Jul 20 22:32:03 1999
+++ apache_1.3.6+01-08/src/include/scoreboard.h	Thu Sep  2 10:14:49 1999
@@ -163,6 +163,12 @@
     int  cpu;			/* cpu to which it is bound */
     struct sockaddr_in single_addr;	/* if ap_single_listen, listen addr */
 #endif
+#ifdef USE_QSC
+    ap_atomic qsc_nucreq;	/* uncachable requests */
+    ap_atomic qsc_nucres;	/* uncachable responses */
+    ap_atomic qsc_nhits;	/* successful qsc lookups */
+    ap_atomic qsc_nmisses;	/* unsuccessful qsc lookups */
+#endif
 } short_score;
 
 typedef struct {
diff -Naur apache_1.3.6+01-07/src/main/Makefile.tmpl apache_1.3.6+01-08/src/main/Makefile.tmpl
--- apache_1.3.6+01-07/src/main/Makefile.tmpl	Mon Jul 19 16:24:16 1999
+++ apache_1.3.6+01-08/src/main/Makefile.tmpl	Thu Sep  2 10:14:56 1999
@@ -11,7 +11,7 @@
       http_config.o http_core.o http_log.o \
       http_main.o http_protocol.o http_request.o http_vhost.o \
       util.o util_date.o util_script.o util_uri.o util_md5.o \
-      rfc1413.o
+      rfc1413.o qsc.o
 
 .c.o:
 	$(CC) -c $(INCLUDES) $(CFLAGS) $<
@@ -139,6 +139,14 @@
  $(INCDIR)/http_config.h $(INCDIR)/http_conf_globals.h \
  $(INCDIR)/http_log.h $(INCDIR)/http_vhost.h \
  $(INCDIR)/http_protocol.h
+qsc.o: qsc.c $(INCDIR)/httpd.h $(INCDIR)/ap_types.h \
+ $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
+ $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h $(OSDIR)/os-inline.c \
+ $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h $(INCDIR)/alloc.h \
+ $(INCDIR)/buff.h $(INCDIR)/ap.h $(INCDIR)/util_uri.h \
+ $(INCDIR)/http_log.h $(INCDIR)/http_config.h \
+ $(INCDIR)/http_conf_globals.h $(INCDIR)/http_protocol.h \
+ $(INCDIR)/http_main.h $(INCDIR)/scoreboard.h
 rfc1413.o: rfc1413.c $(INCDIR)/httpd.h $(INCDIR)/ap_types.h \
  $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
  $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h $(OSDIR)/os-inline.c \
diff -Naur apache_1.3.6+01-07/src/main/buff.c apache_1.3.6+01-08/src/main/buff.c
--- apache_1.3.6+01-07/src/main/buff.c	Thu Jul 15 14:05:15 1999
+++ apache_1.3.6+01-08/src/main/buff.c	Thu Sep  2 10:17:06 1999
@@ -1029,43 +1029,93 @@
  */
 static int writev_it_all(BUFF *fb, struct iovec *vec, int nvec)
 {
-    int i, rv;
+    return (ap_bwritev(fb, vec, nvec) >= 0) ? 0 : -1;
+}
+#endif
+
+/*
+ * Write data using writev() if available, write() otherwise.
+ * Returns number of bytes written or -1 on error.
+ * Note that it may modify iov;
+ */
+API_EXPORT(int)
+ap_bwritev(BUFF *fb, struct iovec *iov, int iovcnt)
+{
+    int rval;
+
+    if ((fb->flags & (B_EOUT | B_WRERR | B_WR)) == B_WR) {
+#ifndef NO_WRITEV
+	int i, len;
+
+	rval = 0;
+
+	len = 0;
+	for (i = 0; i < iovcnt; i++)
+	    len += iov[i].iov_len;
+
+	while (len > 0) {
+	    int w;
+
+	    w = (iovcnt > 1) ? (int) writev(fb->fd, iov, iovcnt) :
+		(int) write(fb->fd, iov->iov_base, iov->iov_len);
+	    if (w >= 0) {
+		rval += w;
+		fb->bytes_sent += w;
+
+		len -= w;
+		if (len == 0)
+		    break;	/* shortcut the common case */
 
-    /* while it's nice an easy to build the vector and crud, it's painful
-     * to deal with a partial writev()
-     */
-    i = 0;
-    while (i < nvec) {
-	do
-	    rv = (int) writev(fb->fd, &vec[i], nvec - i);
-	while (rv == -1 && (errno == EINTR || errno == EAGAIN)
-	       && !(fb->flags & B_EOUT));
-	if (rv == -1) {
-	    if (errno != EINTR && errno != EAGAIN) {
+		while (w > iov[0].iov_len) {
+		    w -= iov[0].iov_len;
+		    iov++;
+		    iovcnt--;
+		}
+		iov[0].iov_base = (char *) iov[0].iov_base + w;
+		iov[0].iov_len -= w;
+	    } else if (errno != EINTR && errno != EAGAIN) {
 		doerror(fb, B_WR);
+		rval = -1;
+		break;
 	    }
-	    return -1;
-	}
-	fb->bytes_sent += rv;
-	/* recalculate vec to deal with partial writes */
-	while (rv > 0) {
-	    if (rv < vec[i].iov_len) {
-		vec[i].iov_base = (char *) vec[i].iov_base + rv;
-		vec[i].iov_len -= rv;
-		rv = 0;
+
+	    if (fb->flags & B_EOUT) {	/* set asynchronously */
+		rval = -1;
+		break;
 	    }
-	    else {
-		rv -= vec[i].iov_len;
-		++i;
+	}
+#else
+	int i;
+
+	rval = 0;
+	for (i = 0; i < iovcnt; i++) {
+	    while (iov[i].iov_len > 0) {
+		int w;
+
+		w = write(fb->fd, iov[i].iov_base, iov[i].iov_len);
+		if (w >= 0) {
+		    rval += w;
+		    fb->bytes_sent += w;
+		    iov[i].iov_base = (char *) iov[i].iov_base + w;
+		    iov[i].iov_len -= w;
+		} else if (errno != EINTR && errno != EAGAIN) {
+		    doerror(fb, B_WR);
+		    rval = -1;
+		    break;
+		}
+
+		if (fb->flags & B_EOUT)	{	/* set asynchronously */
+		    rval = -1;
+		    break;
+		}
 	    }
 	}
-	if (fb->flags & B_EOUT)
-	    return -1;
-    }
-    /* if we got here, we wrote it all */
-    return 0;
-}
 #endif
+    } else
+	rval = -1;
+
+    return rval;
+}
 
 /* A wrapper for buff_write which deals with error conditions and
  * bytes_sent.  Also handles non-blocking writes.
diff -Naur apache_1.3.6+01-07/src/main/http_config.c apache_1.3.6+01-08/src/main/http_config.c
--- apache_1.3.6+01-07/src/main/http_config.c	Tue Jul 20 22:33:40 1999
+++ apache_1.3.6+01-08/src/main/http_config.c	Thu Sep  2 10:17:11 1999
@@ -1399,6 +1399,9 @@
     ap_listeners = NULL;
     ap_listenbacklog = DEFAULT_LISTENBACKLOG;
     ap_extended_status = 0;
+#ifdef USE_QSC
+    ap_qsc_enabled = 0;
+#endif
 #ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
     ap_single_listen = 0;
 #endif
diff -Naur apache_1.3.6+01-07/src/main/http_core.c apache_1.3.6+01-08/src/main/http_core.c
--- apache_1.3.6+01-07/src/main/http_core.c	Tue Jul 20 23:29:49 1999
+++ apache_1.3.6+01-08/src/main/http_core.c	Thu Sep  2 10:17:24 1999
@@ -2698,7 +2698,7 @@
     return NULL;
 }
 
-#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+#if defined(USE_QSC) || defined(SINGLE_LISTEN_UNSERIALIZED_ACCEPT)
 static int
 set_binary(const char *how, int *var)
 {
@@ -2720,6 +2720,20 @@
 #endif
 
 /*ARGSUSED1*/
+static const char *set_qsc_enabled(cmd_parms *cmd, void *dummy, char *arg) 
+{
+    const char *err;
+
+    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+#ifdef USE_QSC
+    if (err == NULL && !set_binary(arg, &ap_qsc_enabled))
+	err = "Unknown QSC control; try \"on\" or \"off\"";
+#endif
+
+    return err;
+}
+
+/*ARGSUSED1*/
 static const char *set_single_listen(cmd_parms *cmd, void *dummy, char *arg) 
 {
     const char *err;
@@ -2973,6 +2987,13 @@
   (void*)XtOffsetOf(core_dir_config, limit_req_body),
   OR_ALL, TAKE1,
   "Limit (in bytes) on maximum size of request message body" },
+{ "QSC", set_qsc_enabled, NULL, RSRC_CONF, TAKE1,
+#ifdef USE_QSC
+  "Enable/disable Quick Shortcut Cache"
+#else
+  "Ignored because !USE_QSC"
+#endif
+},
 { "SingleListen", set_single_listen, NULL, RSRC_CONF, TAKE1,
 #ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
   "Force each child to serve a single Listen address"
diff -Naur apache_1.3.6+01-07/src/main/http_main.c apache_1.3.6+01-08/src/main/http_main.c
--- apache_1.3.6+01-07/src/main/http_main.c	Tue Jul 20 22:40:59 1999
+++ apache_1.3.6+01-08/src/main/http_main.c	Thu Sep  2 10:18:50 1999
@@ -104,11 +104,6 @@
 #include "scoreboard.h"
 #include "multithread.h"
 #include <sys/stat.h>
-#ifdef USE_SHMGET_SCOREBOARD
-#include <sys/types.h>
-#include <sys/ipc.h>
-#include <sys/shm.h>
-#endif
 #ifdef SecureWare
 #include <sys/security.h>
 #include <sys/audit.h>
@@ -249,6 +244,9 @@
 int ap_listenbacklog;
 int ap_dump_settings = 0;
 API_VAR_EXPORT int ap_extended_status = 0;
+#ifdef USE_QSC
+int ap_qsc_enabled;
+#endif
 #ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 int ap_single_listen;
 #endif
@@ -1608,6 +1606,146 @@
 }
 #endif
 
+#ifdef HAVE_MMAP
+/*ARGSUSED*/
+caddr_t
+ap_map_anonymous(pool *p, caddr_t addr, size_t size)
+{
+    caddr_t m = (caddr_t) (ap_ptr) -1;
+
+#ifdef MAP_ANON
+/* BSD style */
+#ifdef CONVEXOS11
+    {
+	unsigned int len = size;
+
+	m = mmap(addr, &len, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
+	    NOFD, 0);
+	if (m == (caddr_t) (ap_ptr) -1) {
+	    perror("mmap");
+	    fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0);
+	}
+    }
+#elif defined(MAP_TMPFILE)
+    {
+	char *mfile = ap_pstrdup(p, "/tmp/apache_shmem_XXXX");
+	int fd = mkstemp(mfile);
+	if (fd >= 0) {
+	    m = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	    if (m == (caddr_t) (ap_ptr) -1) {
+		perror(mfile);
+		fprintf(stderr, "%s: Could not mmap %s\n", ap_server_argv0, mfile);
+	    }
+	    close(fd);
+	} else {
+	    perror(mfile);
+	    fprintf(stderr, "%s: Could not open %s\n", ap_server_argv0, mfile);
+	}
+	unlink(mfile);
+    }
+#else
+    m = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
+    if (m == (caddr_t) (ap_ptr) -1) {
+	perror("mmap");
+	fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0);
+    }
+#endif
+#else
+/* Sun style */
+    int fd = open("/dev/zero", O_RDWR);
+    if (fd >= 0) {
+	m = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (m == (caddr_t) (ap_ptr) -1) {
+	    perror("/dev/zero");
+	    fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0);
+	}
+	close(fd);
+    } else {
+	perror("/dev/zero");
+	fprintf(stderr, "%s: Could not open /dev/zero\n", ap_server_argv0);
+    }
+#endif
+
+    return m;
+}
+#endif
+
+#ifdef HAVE_SHMGET
+/*ARGSUSED*/
+caddr_t
+ap_share_anonymous(pool *p, caddr_t addr, size_t size)
+{
+    caddr_t m = (caddr_t) (ap_ptr) -1;
+    int shmid;
+
+    shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | SHM_R | SHM_W);
+    if (shmid >= 0) {
+#ifdef MOVEBREAK
+	void *obrk;
+#endif
+
+	ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, server_conf,
+	    "created shared memory segment #%d", shmid);
+
+#ifdef MOVEBREAK
+	/*
+	 * Some SysV systems place the shared segment WAY too close
+	 * to the dynamic memory break point (sbrk(0)). This severely
+	 * limits the use of malloc/sbrk in the program since sbrk will
+	 * refuse to move past that point.
+	 *
+	 * To get around this, we move the break point "way up there",
+	 * attach the segment and then move break back down. Ugly
+	 */
+	if ((obrk = sbrk(MOVEBREAK)) == (void *) (ap_ptr) -1) {
+	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+		"sbrk() could not move break");
+	}
+#endif
+
+	m = shmat(shmid, addr, 0);
+	if (m != (caddr_t) (ap_ptr) -1) {
+	    struct shmid_ds shmbuf;
+
+	    if (shmctl(shmid, IPC_STAT, &shmbuf) == 0) {
+		shmbuf.shm_perm.uid = ap_user_id;
+		shmbuf.shm_perm.gid = ap_group_id;
+		if (shmctl(shmid, IPC_SET, &shmbuf) < 0)
+		    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+			"shmctl(%d, IPC_SET, uid %d gid %d)", shmid,
+			ap_user_id, ap_group_id);
+	    } else
+		ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+		    "shmctl(%d, IPC_STAT)", shmid);
+	} else
+	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, "shmat(%d, %p)",
+		shmid, addr);
+
+	if (shmctl(shmid, IPC_RMID, NULL) < 0)
+	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
+		"shmctl(%d, IPC_RMID)", shmid);
+
+#ifdef MOVEBREAK
+	if (obrk != (void *) (ap_ptr) -1 && sbrk(-(MOVEBREAK)) == (void *) (ap_ptr) -1)
+	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+		"sbrk() could not restore break");
+#endif
+    } else {
+	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+	    "shmget(IPC_PRIVATE, %ld)", (long) size);
+#ifdef LINUX
+	if (errno == ENOSYS)
+	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
+			 "Your kernel was built without CONFIG_SYSVIPC\n"
+			 "%s: Please consult the Apache FAQ for details",
+			 ap_server_argv0);
+#endif
+    }
+
+    return m;
+}
+#endif
+
 /*****************************************************************
  *
  * Dealing with the scoreboard... a lot of these variables are global
@@ -1815,8 +1953,6 @@
 {
     caddr_t m;
 
-#if defined(MAP_ANON)
-/* BSD style */
 #ifdef CONVEXOS11
     /*
      * 9-Aug-97 - Jeff Venters (venters@convex.hp.com)
@@ -1828,59 +1964,14 @@
      * Also, the length requires a pointer as the actual length is
      * returned (rounded up to a page boundary).
      */
-    {
-	unsigned len = SCOREBOARD_SIZE;
-
-	m = mmap((caddr_t) 0xC0000000, &len,
-		 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, NOFD, 0);
-    }
-#elif defined(MAP_TMPFILE)
-    {
-	char mfile[] = "/tmp/apache_shmem_XXXX";
-	int fd = mkstemp(mfile);
-	if (fd == -1) {
-	    perror("open");
-	    fprintf(stderr, "%s: Could not open %s\n", ap_server_argv0, mfile);
-	    exit(APEXIT_INIT);
-	}
-	m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
-		PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-	if (m == (caddr_t) - 1) {
-	    perror("mmap");
-	    fprintf(stderr, "%s: Could not mmap %s\n", ap_server_argv0, mfile);
-	    exit(APEXIT_INIT);
-	}
-	close(fd);
-	unlink(mfile);
-    }
+    m = ap_map_anonymous(p, 0xC0000000, SCOREBOARD_SIZE);
 #else
-    m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
-	     PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
+    m = ap_map_anonymous(p, 0, SCOREBOARD_SIZE);
 #endif
-    if (m == (caddr_t) - 1) {
-	perror("mmap");
-	fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0);
-	exit(APEXIT_INIT);
-    }
-#else
-/* Sun style */
-    int fd;
 
-    fd = open("/dev/zero", O_RDWR);
-    if (fd == -1) {
-	perror("open");
-	fprintf(stderr, "%s: Could not open /dev/zero\n", ap_server_argv0);
+    if (m == (caddr_t) (ap_ptr) -1)
 	exit(APEXIT_INIT);
-    }
-    m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
-	     PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-    if (m == (caddr_t) - 1) {
-	perror("mmap");
-	fprintf(stderr, "%s: Could not mmap /dev/zero\n", ap_server_argv0);
-	exit(APEXIT_INIT);
-    }
-    close(fd);
-#endif
+
     ap_scoreboard_image = (scoreboard *) m;
     ap_scoreboard_image->global.running_generation = 0;
 }
@@ -1891,91 +1982,15 @@
 }
 
 #elif defined(USE_SHMGET_SCOREBOARD)
-static key_t shmkey = IPC_PRIVATE;
-static int shmid = -1;
-
-/*ARGSUSED*/
 static void setup_shared_mem(pool *p)
 {
-    struct shmid_ds shmbuf;
-#ifdef MOVEBREAK
-    char *obrk;
-#endif
-
-    if ((shmid = shmget(shmkey, SCOREBOARD_SIZE, IPC_CREAT | SHM_R | SHM_W)) == -1) {
-#ifdef LINUX
-	if (errno == ENOSYS) {
-	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
-			 "Your kernel was built without CONFIG_SYSVIPC\n"
-			 "%s: Please consult the Apache FAQ for details",
-			 ap_server_argv0);
-	}
-#endif
-	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
-		    "could not call shmget");
-	exit(APEXIT_INIT);
-    }
-
-    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
-		"created shared memory segment #%d", shmid);
-
-#ifdef MOVEBREAK
-    /*
-     * Some SysV systems place the shared segment WAY too close
-     * to the dynamic memory break point (sbrk(0)). This severely
-     * limits the use of malloc/sbrk in the program since sbrk will
-     * refuse to move past that point.
-     *
-     * To get around this, we move the break point "way up there",
-     * attach the segment and then move break back down. Ugly
-     */
-    if ((obrk = sbrk(MOVEBREAK)) == (char *) -1) {
-	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
-	    "sbrk() could not move break");
-    }
-#endif
+    caddr_t m;
 
-#define BADSHMAT	((scoreboard *) (ap_ptr) (-1))
-    if ((ap_scoreboard_image = (scoreboard *) shmat(shmid, 0, 0)) == BADSHMAT) {
-	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, "shmat error");
-	/*
-	 * We exit below, after we try to remove the segment
-	 */
-    }
-    else {			/* only worry about permissions if we attached the segment */
-	if (shmctl(shmid, IPC_STAT, &shmbuf) != 0) {
-	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
-		"shmctl() could not stat segment #%d", shmid);
-	}
-	else {
-	    shmbuf.shm_perm.uid = ap_user_id;
-	    shmbuf.shm_perm.gid = ap_group_id;
-	    if (shmctl(shmid, IPC_SET, &shmbuf) != 0) {
-		ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
-		    "shmctl() could not set segment #%d", shmid);
-	    }
-	}
-    }
-    /*
-     * We must avoid leaving segments in the kernel's
-     * (small) tables.
-     */
-    if (shmctl(shmid, IPC_RMID, NULL) != 0) {
-	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
-		"shmctl: IPC_RMID: could not remove shared memory segment #%d",
-		shmid);
-    }
-    if (ap_scoreboard_image == BADSHMAT)	/* now bailout */
+    m = ap_share_anonymous(p, 0, SCOREBOARD_SIZE);
+    if (m == (caddr_t) (ap_ptr) -1)
 	exit(APEXIT_INIT);
 
-#ifdef MOVEBREAK
-    if (obrk == (char *) -1)
-	return;			/* nothing else to do */
-    if (sbrk(-(MOVEBREAK)) == (char *) -1) {
-	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
-	    "sbrk() could not move break back");
-    }
-#endif
+    ap_scoreboard_image = (scoreboard *) m;
     ap_scoreboard_image->global.running_generation = 0;
 }
 
@@ -3524,6 +3539,26 @@
 #ifdef NO_RELIABLE_PIPED_LOGS
     printf(" -D NO_RELIABLE_PIPED_LOGS\n");
 #endif
+#ifdef USE_QSC
+    printf(" -D USE_QSC\n");
+# ifdef QSC_HASH_SIZE
+    printf(" -D QSC_HASH_SIZE=%d\n", QSC_HASH_SIZE);
+# endif
+# ifdef QSC_MAX_SIZE
+    printf(" -D QSC_MAX_SIZE=%lu\n", (unsigned long) QSC_MAX_SIZE);
+# endif
+# ifdef QSC_HEADER_GRAIN
+    printf(" -D QSC_HEADER_GRAIN=%d\n", QSC_HEADER_GRAIN);
+# endif
+# if !defined(QSC_HEADER_GRAIN) || QSC_HEADER_GRAIN > 0
+#  ifdef QSC_MAX_ALLOC
+    printf(" -D QSC_MAX_ALLOC=%d\n", QSC_MAX_ALLOC);
+#  endif
+#  ifdef QSC_RED_ZONE
+    printf(" -D QSC_RED_ZONE=%d\n", QSC_RED_ZONE);
+#  endif
+# endif
+#endif
 #ifdef FAST_TIME
     printf(" -D FAST_TIME\n");
 #endif
@@ -4760,6 +4795,10 @@
         exit(0);
     }
 
+#ifdef USE_QSC
+    qsc_init(pconf);
+#endif
+
     child_timeouts = !ap_standalone || one_process;
 
     if (ap_standalone) {
@@ -6138,6 +6177,11 @@
     if (!child && !ap_dump_settings && !install) {
 	ap_log_pid(pconf, ap_pid_fname);
     }
+
+#ifdef USE_QSC
+    qsc_init(pconf);
+#endif
+
     ap_set_version();
     ap_init_modules(pconf, server_conf);
     ap_suexec_enabled = init_suexec();
diff -Naur apache_1.3.6+01-07/src/main/http_protocol.c apache_1.3.6+01-08/src/main/http_protocol.c
--- apache_1.3.6+01-07/src/main/http_protocol.c	Tue Jul 20 16:29:55 1999
+++ apache_1.3.6+01-08/src/main/http_protocol.c	Thu Sep  2 10:26:41 1999
@@ -1530,10 +1530,18 @@
 
 API_EXPORT(void) ap_basic_http_header(request_rec *r)
 {
+    ap_tee_basic_http_header(r, NULL, NULL);
+}
+
+API_EXPORT(void) ap_tee_basic_http_header(request_rec *r,
+    char *basic_header_copy, size_t *basic_header_copy_remain)
+{
     char *protocol;
 #ifdef CHARSET_EBCDIC
     int convert = ap_bgetflag(r->connection->client, B_EBCDIC2ASCII);
 #endif /*CHARSET_EBCDIC*/
+    const char *cp;
+    int len;
 
     if (r->assbackwards)
         return;
@@ -1560,10 +1568,44 @@
 
     /* Output the HTTP/1.x Status-Line and the Date and Server fields */
 
+    /*
+     * The whole basic_header_copy* thing is a hack to include the basic
+     * http headers in the QSC entry for mod_mmap_static.  Everything
+     * else uses the headers_out table, but noooo, this function has to
+     * emit headers directly, and since they may be flushed at any point
+     * we must copy them aside for later insertion into the QSC.
+     */
     ap_rvputs(r, protocol, " ", r->status_line, "\015\012", NULL);
-
-    ap_send_header_field(r, "Date", ap_gm_timestr_822(r->pool, r->request_time));
-    ap_send_header_field(r, "Server", ap_get_server_version());
+    if (basic_header_copy) {
+	len = ap_snprintf(basic_header_copy, *basic_header_copy_remain,
+	    "%s %s\015\012", protocol, r->status_line);
+	basic_header_copy += len;
+	*basic_header_copy_remain -= len;
+    }
+
+    cp = ap_gm_timestr_822(r->pool, r->request_time);
+    ap_send_header_field(r, "Date", cp);
+    if (basic_header_copy) {
+	len = ap_snprintf(basic_header_copy, *basic_header_copy_remain,
+	    "Date: %s\015\012", cp);
+	basic_header_copy += len;
+	*basic_header_copy_remain -= len;
+    }
+
+    cp = ap_get_server_version();
+    ap_send_header_field(r, "Server", cp);
+    if (basic_header_copy) {
+	len = ap_snprintf(basic_header_copy, *basic_header_copy_remain,
+	    "Server: %s\015\012", cp);
+	basic_header_copy += len;
+	*basic_header_copy_remain -= len;
+    }
+    /*
+     * NOTE: if you change the basic header such that the Server
+     * response-header is not at the end or its value is other than
+     * ap_get_server_version(), update the most-likely-place probe in
+     * qsc_insert_request().
+     */
 
     ap_table_unset(r->headers_out, "Date");        /* Avoid bogosity */
     ap_table_unset(r->headers_out, "Server");
@@ -1696,6 +1738,12 @@
 
 API_EXPORT(void) ap_send_http_header(request_rec *r)
 {
+    ap_tee_http_header(r, NULL, NULL);
+}
+
+API_EXPORT(void) ap_tee_http_header(request_rec *r, char *basic_header_copy,
+    size_t *basic_header_copy_remain)
+{
     int i;
     const ap_int32 zero = 0;
 #ifdef CHARSET_EBCDIC
@@ -1720,7 +1768,7 @@
 
     ap_hard_timeout("send headers", r);
 
-    ap_basic_http_header(r);
+    ap_tee_basic_http_header(r, basic_header_copy, basic_header_copy_remain);
 
 #ifdef CHARSET_EBCDIC
     ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
@@ -2320,9 +2368,6 @@
  * To take advantage of zero-copy TCP under Solaris 2.6 this should be a
  * multiple of 16k.  (And you need a SunATM2.0 network card.)
  */
-#ifndef MMAP_SEGMENT_SIZE
-#define MMAP_SEGMENT_SIZE       32768
-#endif
 
 /* send data from an in-memory buffer */
 API_EXPORT(size_t) ap_send_mmap(void *mm, request_rec *r, size_t offset,
@@ -2368,6 +2413,36 @@
     ap_kill_timeout(r);
     SET_BYTES_SENT(r);
     return total_bytes_sent;
+}
+
+API_EXPORT(size_t)
+ap_send_iovec(request_rec *r, struct iovec *iov, int iovcnt)
+{
+    size_t sent;
+
+    sent = 0;
+    ap_soft_timeout("send iovec", r);
+    if (!r->connection->aborted) {    
+	int w;
+
+	/*
+	 * No need to loop.  ap_bwritev writes it all unless there was
+	 * an error.
+	 */
+	w = ap_bwritev(r->connection->client, iov, iovcnt);
+	if (w > 0)
+	    sent += w;
+	else {
+	    ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+		"client stopped connection before send iovec completed");
+	    ap_bsetflag(r->connection->client, B_EOUT, 1);
+	    r->connection->aborted = 1;
+	}
+    }
+    ap_kill_timeout(r);
+    SET_BYTES_SENT(r);
+
+    return sent;
 }
 
 API_EXPORT(int) ap_rputc(int c, request_rec *r)
diff -Naur apache_1.3.6+01-07/src/main/http_request.c apache_1.3.6+01-08/src/main/http_request.c
--- apache_1.3.6+01-07/src/main/http_request.c	Thu Jul 15 10:30:03 1999
+++ apache_1.3.6+01-08/src/main/http_request.c	Thu Sep  2 11:40:06 1999
@@ -1225,7 +1225,10 @@
     if (ap_extended_status)
 	ap_time_process_request(r->connection->child_num, START_PREQUEST);
 
-    process_request_internal(r);
+#ifdef USE_QSC
+    if (!qsc_process_request(r))
+#endif
+	process_request_internal(r);
 
     old_stat = ap_update_child_status(r->connection->child_num,
                                    SERVER_BUSY_LOG, r);
diff -Naur apache_1.3.6+01-07/src/main/qsc.c apache_1.3.6+01-08/src/main/qsc.c
--- apache_1.3.6+01-07/src/main/qsc.c
+++ apache_1.3.6+01-08/src/main/qsc.c	Thu Sep  2 12:48:21 1999
@@ -0,0 +1,1336 @@
+/*
+ * Copyright (c) 1999 Silicon Graphics, Inc.  All rights reserved.
+ */
+
+/* ====================================================================
+ * Copyright (c) 1995-1999 The Apache Group.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the Apache Group
+ *    for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Group.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the Apache Group
+ *    for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see <http://www.apache.org/>.
+ *
+ */
+
+/*
+ * QSC - Quick Shortcut Cache (or Quick Static-content Cache).
+ * Tightly integrated with mod_mmap_static.
+ * Mike Abbott - mja@sgi.com
+ *
+ * Cache the response to a request for a static page, so that subsequent
+ * requests for the same page will bypass all the computation that is
+ * identical every time that page is requested.  Caches the HTTP headers
+ * and body together for quick response with a single writev().  Hashes
+ * URIs for fast lookup.  See htdocs/manual/qsc.html.
+ */
+
+#include "httpd.h"
+
+#ifdef USE_QSC
+
+#include "http_log.h"
+#include "http_config.h"
+#include "http_conf_globals.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "scoreboard.h"
+
+/*
+ * Number of hash buckets.  Larger values shorten chain lengths and
+ * increase performance.  Default value is arbitrary.
+ */
+#ifndef QSC_HASH_SIZE
+#define QSC_HASH_SIZE		128
+#endif
+
+/*
+ * Number of qsc_status() hash chain length histogram buckets.  Default
+ * value is arbitrary.
+ */
+#ifndef QSC_HIST_SIZE
+#define QSC_HIST_SIZE		5
+#endif
+
+/*
+ * Total size of shared data, in bytes.  The QSC will not use more than
+ * this much memory.  Default value is arbitrary.
+ */
+#ifndef QSC_MAX_SIZE
+#define QSC_MAX_SIZE		(4 * 1024 * 1024)
+#endif
+
+/*
+ * Granularity of memory allocation in bytes.  Must be power of two at
+ * least as large as max(sizeof (long), sizeof (void *)).
+ */
+#ifndef QSC_GRAIN
+union qsc_granule {
+    size_t s;
+    void *c;
+    ap_atomic a;
+};
+#define QSC_GRAIN		sizeof (union qsc_granule)
+#endif
+
+/*
+ * Granularity of headers, for processor cache alignment, or 0 for none.
+ * The Server response header is padded with spaces to force alignment.
+ */
+#ifndef QSC_HEADER_GRAIN
+#define QSC_HEADER_GRAIN	CACHE_ALIGNMENT
+#endif
+
+#if QSC_HEADER_GRAIN > 0
+/*
+ * Maximum single allocation by qsc_malloc() or qsc_hmalloc(), in bytes,
+ * to avoid overlap race.
+ */
+# ifndef QSC_MAX_ALLOC
+# define QSC_MAX_ALLOC		512
+# endif
+
+/*
+ * Minimum separation of upward-growing nonaligned allocations and
+ * downward-growing aligned allocations, in bytes, to avoid overlap
+ * race.  Should be a small multiple of QSC_MAX_ALLOC for safety.
+ */
+# ifndef QSC_RED_ZONE
+# define QSC_RED_ZONE		4096
+# endif
+#endif
+
+#ifdef QSC_DEBUG
+#define QSC_ASSERT(x)	ap_assert(x)
+#else
+#define QSC_ASSERT(x)	/* nothing */
+#endif
+
+/* Round x up to the next a */
+#define QSC_ALIGN(x, a)	(((x) + (a) - 1) & ~((a) - 1))
+
+/*
+ * Atomic compare-and-swap function.  Returns nonzero on success.
+ */
+#ifdef IRIX
+# if _MIPS_SIM == _ABIO32
+#  if IRIX < 65
+/*
+ * SGI's ucode (o32 ABI) compilers lack an intrinsic so use _r4k_cas
+ * from libc.  This won't work if you're using an R3000 or earlier,
+ * which lack load-linked/store-conditional instructions; you'll have to
+ * use uscas() after calling usinit().
+ */
+extern int _r4k_cas(unsigned long *, unsigned long, unsigned long);
+#   define qsc_cas(p, oldval, newval)	_r4k_cas(p, oldval, newval)
+#  else
+/*
+ * Starting with Irix 6.5 _r4k_cas is hidden in libc so we can't use it.
+ * Either build -n32 or -64, use uscas() after calling usinit(), or
+ * don't use the QSC.
+ */
+#   error "build -n32 or -64 on Irix 6.5 and above"
+#  endif
+# else
+/*
+ * Use SGI's MIPSpro (n32 and 64-bit ABIs) compiler intrinsic
+ * __compare_and_swap().
+ */
+#  define qsc_cas(p, oldval, newval)	__compare_and_swap(p, oldval, newval)
+# endif
+#elif defined(LINUX)
+/*
+ * Use asm statement copied from Linux libpthread's __compare_and_swap().
+ */
+static ap_inline int
+qsc_cas(ap_atomic *p, ap_atomic oldval, ap_atomic newval)
+{
+    char ret;
+    ap_atomic readval;
+
+    __asm__ __volatile__ ("lock; cmpxchgl %3, %1; sete %0"
+                        : "=q" (ret), "=m" (*p), "=a" (readval)
+                        : "r" (newval), "m" (*p), "a" (oldval));
+    return ret;
+}
+#else
+# error "need atomic compare-and-swap function"
+#endif
+
+/* Hash table entry */
+struct qsc_entry {
+    struct qsc_entry	*next;		/* in hash bucket */
+    char		*uri;		/* URI of cache entry */
+    const server_rec	*server;	/* vhost serving entry */
+    void		*kaheaders;	/* keep-alive HTTP response headers */
+    size_t		nkaheaderbytes;	/* length of kaheaders */
+    void		*ccheaders;	/* non-ka HTTP response headers */
+    size_t		nccheaderbytes;	/* length of ccheaders */
+    void		*body;		/* mmap_static response body */
+    size_t		nbodybytes;	/* length of body */
+#ifdef QSC_DEBUG
+    const char		*filename;	/* name of mapped file */
+#endif
+};
+
+/* Counters */
+struct qsc_stats {
+    ap_atomic   nfailures;	/* number of failed insertions */
+    ap_atomic   nucreq;		/* number of uncachable requests */
+    ap_atomic   nucres;		/* number of uncachable responses */
+    ap_atomic   nhits;		/* number of successful hash table lookups */
+    ap_atomic   nmisses;	/* number of unsuccessful hash table lookups */
+    ap_atomic   nresets;	/* number of resets */
+};
+
+/* All QSC data */
+struct qsc {
+    struct qsc_stats	stats;
+    struct qsc_entry	*hash_table[QSC_HASH_SIZE];
+    ap_atomic		alloc_head;
+#if QSC_HEADER_GRAIN > 0
+    ap_atomic		alloc_aligned;
+#endif
+};
+
+static struct qsc *qsc;			/* root of qsc data shared by all */
+
+/*
+ * Add v to *p atomically and return the original value of *p.
+ */
+static ap_inline ap_atomic
+qsc_atomic_add(ap_atomic *p, ap_atomic v)
+{
+    ap_atomic r;
+
+    do
+	r = *p;
+    while (!qsc_cas(p, r, r + v));
+
+    return r;
+}
+
+/*
+ * Atomically insert the new entry into the hash bucket at head and return
+ * the old value of *head.
+ */
+static ap_inline struct qsc_entry *
+qsc_atomic_insert(struct qsc_entry **head, struct qsc_entry *new)
+{
+    do
+	new->next = *head;
+    while (!qsc_cas((ap_atomic *) head, (ap_atomic) new->next,
+	(ap_atomic) new));
+
+    return new->next;
+}
+
+/*
+ * Initialize or reset the shared-memory allocator.
+ */
+static void
+qsc_init_malloc(void)
+{
+    qsc->alloc_head = QSC_ALIGN((ap_atomic) (qsc + 1), QSC_GRAIN);
+#if QSC_HEADER_GRAIN > 0
+    qsc->alloc_aligned = ((ap_atomic) qsc + QSC_MAX_SIZE) &
+        ~(QSC_HEADER_GRAIN - 1);
+    if (qsc->alloc_aligned < qsc->alloc_head)
+	qsc->alloc_aligned = qsc->alloc_head;
+#endif
+}
+
+/*
+ * Allocate nbytes from shared memory.
+ */
+static void *
+qsc_malloc(size_t nbytes)
+{
+    ap_atomic r, n;
+
+    nbytes = QSC_ALIGN(nbytes, QSC_GRAIN);
+#if QSC_HEADER_GRAIN > 0
+    QSC_ASSERT(qsc->alloc_head <= qsc->alloc_aligned);
+    if (nbytes <= QSC_MAX_ALLOC) {
+#endif
+	do {
+	    r = qsc->alloc_head;
+	    n = r + nbytes;
+	    if (n > 
+#if QSC_HEADER_GRAIN > 0
+		qsc->alloc_aligned - QSC_RED_ZONE
+#else
+		(ap_atomic) qsc + QSC_MAX_SIZE
+#endif
+	    ) {
+		r = 0;
+		break;
+	    }
+	} while (!qsc_cas(&qsc->alloc_head, r, n));
+#if QSC_HEADER_GRAIN > 0
+    } else
+	r = 0;
+#endif
+
+    return (void *) r;
+}
+
+#if QSC_HEADER_GRAIN > 0
+/*
+ * Allocate nbytes from shared memory, aligned to a QSC_HEADER_GRAIN
+ * virtual address.  nbytes must be similarly aligned.
+ */
+static void *
+qsc_hmalloc(size_t nbytes)
+{
+    ap_atomic n;
+
+    QSC_ASSERT((qsc->alloc_aligned & (QSC_HEADER_GRAIN - 1)) == 0);
+    QSC_ASSERT((nbytes & (QSC_HEADER_GRAIN - 1)) == 0);
+    QSC_ASSERT(qsc->alloc_head <= qsc->alloc_aligned);
+
+    if (nbytes <= QSC_MAX_ALLOC) {
+	ap_atomic r;
+
+	do {
+	    r = qsc->alloc_aligned;
+	    n = r - nbytes;
+	    if (n < qsc->alloc_head + QSC_RED_ZONE) {
+		n = 0;
+		break;
+	    }
+	} while (!qsc_cas(&qsc->alloc_aligned, r, n));
+    } else
+	n = 0;
+
+    return (void *) n;
+}
+#endif
+
+/*
+ * Free vp to shared memory.
+ */
+/*ARGSUSED0*/
+static void
+qsc_free(void *vp)
+{
+    /* no-op */
+}
+
+/*
+ * Return the number of bytes of shared memory in use.
+ */
+static ap_atomic
+qsc_inuse(void)
+{
+    return qsc->alloc_head - (ap_atomic) qsc
+#if QSC_HEADER_GRAIN > 0
+	+ ((ap_atomic) qsc + QSC_MAX_SIZE) - qsc->alloc_aligned + QSC_RED_ZONE
+#endif
+    ;
+}
+
+/*
+ * Hash the URI into an integer.
+ */
+static unsigned long
+qsc_hash_uri(const char *uri)
+{
+    unsigned long hash = 0;
+
+    while (*uri)
+	hash = (hash << 4) - hash + *uri++;	/* hash = hash * 15 + *uri++ */
+
+    return hash;
+}
+
+/*
+ * Return the hash table entry for the URI and vhost, or NULL if none.
+ */
+static struct qsc_entry *
+qsc_lookup_uri(const char *uri, const server_rec *server)
+{
+    struct qsc_entry *ep;
+
+    ep = qsc->hash_table[qsc_hash_uri(uri) % QSC_HASH_SIZE];
+    while (ep && (ep->server != server || strcmp(ep->uri, uri)))
+	ep = ep->next;
+
+    /* printf("%d: qsc_lookup_uri(%s, 0x%08x) -> 0x%08x\n", getpid(), uri, server, ep); */
+
+    return ep;
+}
+
+/*
+ * Insert a new hash table entry for the URI and vhost and return it, or
+ * NULL if not possible.
+ */
+static struct qsc_entry *
+qsc_insert_uri(const char *uri, const server_rec *server)
+{
+    struct qsc_entry *nep;
+
+    nep = (struct qsc_entry *) qsc_malloc(sizeof *nep);
+    if (nep) {
+	size_t nb;
+
+	nep->next = NULL;
+
+	nb = strlen(uri) + 1;
+	nep->uri = (char *) qsc_malloc(nb);
+	if (nep->uri) {
+	    memcpy(nep->uri, uri, nb);
+	    nep->server = server;
+
+	    /*
+	     * insert it even if some other thread already did;
+	     * suffer an occasional duplicate entry rather than
+	     * have to spin-lock the table
+	     */
+	    qsc_atomic_insert(&qsc->hash_table[qsc_hash_uri(uri) %
+		QSC_HASH_SIZE], nep);
+	} else {
+	    qsc_free(nep);
+	    nep = NULL;
+	}
+    }
+
+    /* printf("%d: qsc_insert_uri(%s, 0x%08x) -> 0x%08x\n", getpid(), uri, server, nep); */
+
+    return nep;
+}
+
+/*
+ * Clear out the shared QSC global data.  Does not destroy it, just
+ * clears it.
+ */
+/*ARGSUSED0*/
+static void
+qsc_cleanup(void *junk)
+{
+    ap_atomic n;
+
+    /*
+     * No locking here because this is only ever called while the server
+     * is single-threaded (single-processed, whatever).
+     */
+
+    n = qsc->stats.nresets;
+    memset(qsc, 0, sizeof *qsc);
+    qsc->stats.nresets = n + 1;
+
+    qsc_init_malloc();
+}
+
+/*
+ * Increment the insertion-failure counter and log an error if this is
+ * the first failure.
+ */
+static void
+qsc_failure(void)
+{
+    if (qsc_atomic_add(&qsc->stats.nfailures, 1) == 0)
+	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, NULL,
+	    "QSC is full; compile with larger QSC_MAX_SIZE and/or QSC_MAX_ALLOC to cache more");
+}
+
+/*
+ * Update a stats counter from a scoreboard counter.
+ */
+static void
+qsc_counter_sync(ap_atomic *score_counter, ap_atomic *stats_counter)
+{
+    ap_atomic n;
+
+    do
+	n = *score_counter;
+    while (n && !qsc_cas(score_counter, n, 0));
+
+    if (n)
+	qsc_atomic_add(stats_counter, n);
+}
+
+/*
+ * Update the QSC stats counters from the individual scoreboard server
+ * counters.
+ */
+static void
+qsc_sync_from_scoreboard(void)
+{
+    int i;
+
+    for (i = 0; i < HARD_SERVER_LIMIT; i++) {
+	qsc_counter_sync(&ap_scoreboard_image->servers[i].qsc_nucreq,
+	    &qsc->stats.nucreq);
+	qsc_counter_sync(&ap_scoreboard_image->servers[i].qsc_nucres,
+	    &qsc->stats.nucres);
+	qsc_counter_sync(&ap_scoreboard_image->servers[i].qsc_nhits,
+	    &qsc->stats.nhits);
+	qsc_counter_sync(&ap_scoreboard_image->servers[i].qsc_nmisses,
+	    &qsc->stats.nmisses);
+
+	/*
+	 * This really only works with shared memory scoreboards.
+	 * To make this work with a file-based scoreboard:
+	 * - make qsc_counter_sync() return nonzero when it updates
+	 * - if any call above returns nonzero, call put_scoreboard_info
+	 * - but first make put_scoreboard_info global
+	 * - call ap_sync_scoreboard_image() once before loop
+	 * - pray two processes don't call this function at the same time
+	 */
+    }
+}
+
+/*
+ * HTTP/1.1 header fields, from RFC 2068, and their effects on QSC operation.
+ *
+ * Header Field Name	Type		Cachability
+ * -----------------	----		------------
+ * Accept		request		ignore, but should obey
+ * Accept-Charset	request		ignore, but should obey
+ * Accept-Encoding	request		ignore, but should obey
+ * Accept-Language	request		ignore, but should obey
+ * Accept-Ranges	response	ignore
+ * Age			response	ignore
+ * Allow		entity		ignore
+ * Alternates		response	ignore
+ * Authorization	request		uncachable by choice (could handle)
+ * Cache-Control	general		uncachable
+ * Connection		general		keep separate ka & !ka headers cached
+ * Content-Base		entity		ignore
+ * Content-Encoding	entity		ignore
+ * Content-Language	entity		ignore
+ * Content-Length	entity		ignore
+ * Content-Location	entity		ignore
+ * Content-MD5		entity		ignore
+ * Content-Range	entity		uncachable by choice (could handle)
+ * Content-Type		entity		ignore
+ * Content-Version	entity		ignore
+ * Date			general		ignore
+ * Derived-From		entity		ignore
+ * ETag			entity		uncachable if weak, otherwise ignore
+ * Expires		entity		uncachable? (see 14.21)
+ * From			request		ignore
+ * Host			request		secondary cache key? (see 14.23)
+ * If-Modified-Since	request		uncachable by choice (could handle)
+ * If-Match		request		ignore: cached etags don't change
+ * If-None-Match	request		uncachable by choice (could handle)
+ * If-Range		request		uncachable by choice (could handle)
+ * If-Unmodified-Since	request		ignore: cached dates don't change
+ * Keep-Alive		connection	ignore
+ * Last-Modified	entity		ignore
+ * Link			entity		ignore
+ * Location		response	ignore
+ * Max-Forwards		request		ignore
+ * Pragma		general		uncachable if "no-cache", otherwise ignore
+ * Proxy-Authenticate	response	ignore
+ * Proxy-Authorization	request		ignore
+ * Public		response	ignore
+ * Range		request		uncachable by choice
+ * Referer		request		ignore
+ * Retry-After		response	ignore
+ * Server		response	ignore
+ * Transfer-Encoding	general		ignore
+ * Upgrade		general		ignore
+ * User-Agent		request		ignore
+ * Vary			response	uncachable
+ * Via			general		ignore
+ * Warning		response	ignore
+ * WWW-Authenticate	response	ignore
+ */
+
+/*
+ * Return nonzero if the request is cachable, zero otherwise.
+ */
+static int
+qsc_request_is_cachable(const request_rec *r)
+{
+    int cachable;
+    const array_header *ap;
+    int i;
+
+    /*
+     * Requests are cachable if they:
+     *	- are GET, and
+     *	- are not explicitly marked no_cache,
+     * unless they contain one of the following headers:
+     *	Authorization: ...
+     *		QSC does not support authorization.
+     *	Cache-Control: ...
+     *		Most values limit cachability so assume all do, for speed.
+     *	If-Modified-Since: ...
+     *	If-None-Match: ...
+     *		Both require extra processing.
+     *	If-Range: ...
+     *		QSC does not support ranges yet.
+     *	Pragma: no-cache
+     *		Explicit.
+     *	Range: ...
+     *		QSC does not support ranges yet.
+     */
+
+    cachable = r->method_number == M_GET && !r->header_only && !r->no_cache;
+    ap = ap_table_elts(r->headers_in);
+    for (i = 0; cachable && i < ap->nelts; i++) {
+	const table_entry *tep = &((table_entry *) ap->elts)[i];
+
+	switch (ap_tolower(tep->key[0])) {
+	case 'a':
+	    if (!strcasecmp(tep->key, "authorization"))
+		cachable = 0;
+	    break;
+	case 'c':
+	    if (!strcasecmp(tep->key, "cache-control"))
+		cachable = 0;
+	    break;
+	case 'i':
+	    if (!strcasecmp(tep->key, "if-modified-since") ||
+		!strcasecmp(tep->key, "if-none-match") ||
+		!strcasecmp(tep->key, "if-range"))
+		cachable = 0;
+	    break;
+	case 'p':
+	    if (!strcasecmp(tep->key, "pragma") &&
+		!strcasecmp(tep->val, "no-cache"))
+		cachable = 0;
+	    break;
+	case 'r':
+	    if (!strcasecmp(tep->key, "range"))
+		cachable = 0;
+	    break;
+	}
+    }
+
+    return cachable;
+}
+
+/*
+ * Return nonzero if the response is cachable, zero otherwise.
+ */
+static int
+qsc_response_is_cachable(const request_rec *r)
+{
+    int cachable;
+    const array_header *ap;
+    int i;
+
+    /*
+     * Responses are cachable if they:
+     *	- have status 200 OK, and
+     *	- are not chunked, and
+     *	- are not explicitly marked no_cache,
+     * unless they contain one of the following headers:
+     *	Cache-Control: ...
+     *		Most values limit cachability so assume all do, for speed.
+     *	Content-Range: ...
+     *		QSC does not support ranges yet.
+     *	ETag: W/... (that is, strong etags are cachable, weaks ones are not)
+     *		Cache only strong entity-tags.  Weak etags will change to
+     *		strong soon enough (see ap_set_etag()).
+     *	Expires: ...
+     *		QSC does not support removal or update of cache entries.
+     *	Pragma: no-cache
+     *		Explicit.
+     *	Vary: ...
+     *		Response depends on values other than URI, and QSC uses only
+     *		the URI to look up cache entries.
+     */
+
+    cachable = r->status == HTTP_OK && !r->chunked && !r->no_cache;
+    ap = ap_table_elts(r->headers_out);
+    for (i = 0; cachable && i < ap->nelts; i++) {
+	const table_entry *tep = &((table_entry *) ap->elts)[i];
+
+	switch (ap_tolower(tep->key[0])) {
+	case 'c':
+	    if (!strcasecmp(tep->key, "cache-control") ||
+		!strcasecmp(tep->key, "content-range"))
+		cachable = 0;
+	    break;
+	case 'e':
+	    if ((!strcasecmp(tep->key, "etag") &&
+		!strncasecmp(tep->val, "w/", 2)) ||
+		!strcasecmp(tep->key, "expires"))
+		cachable = 0;
+	    break;
+	case 'p':
+	    if (!strcasecmp(tep->key, "pragma") &&
+		!strcasecmp(tep->val, "no-cache"))
+		cachable = 0;
+	    break;
+	case 'v':
+	    if (!strcasecmp(tep->key, "vary"))
+		cachable = 0;
+	    break;
+	}
+    }
+
+    return cachable;
+}
+
+/*
+ * Return nonzero if the request uses keep-alive.  Also sets
+ * r->connection->keepalive.
+ */
+static int
+qsc_set_keepalive(request_rec *r)
+{
+    conn_rec *c;
+    const server_rec *s;
+    const char *ch;
+    int ka;
+
+    /*
+     * This performs the same function as ap_set_keepalive() but we can
+     * make some simplifying assumptions which speed up the decision.
+     * Specifically, we know:
+     *	r->status == HTTP_OK
+     *	!r->header_only
+     *	r->headers_out contains a Content-Length header
+     *	r->headers_out does not contain a Connection header
+     *	r->subprocess_env does not contain a nokeepalive header
+     * Also we do not send a Keep-Alive header back, since it's optional
+     * anyway and the value changes every time if s->keep_alive_max > 0
+     * (making it hard to cache and optimize).
+     */
+    c = r->connection;
+    s = r->server;
+    ch = ap_table_get(r->headers_in, "Connection");
+    if (c->keepalive >= 0 &&
+	s->keep_alive &&
+	s->keep_alive_timeout > 0 &&
+	(s->keep_alive_max == 0 || s->keep_alive_max > c->keepalives) &&
+	!ap_find_token(r->pool, ch, "close") &&
+	(r->proto_num >= HTTP_VERSION(1,1) ||
+	    ap_find_token(r->pool, ch, "keep-alive"))) {
+	ka = 1;
+	c->keepalives++;
+    } else
+	ka = 0;
+
+    c->keepalive = ka;
+    return ka;
+}
+
+/*
+ * Initialize the QSC.  The given pool must match the one
+ * mod_mmap_static registers its cleanup upon, since we share pointers
+ * with it which it unmaps during its cleanup.
+ */
+void
+qsc_init(pool *p)
+{
+    if (ap_qsc_enabled) {
+	if (ap_find_linked_module("mod_mmap_static.c")) {
+#if defined(IRIX) && _MIPS_SIM != _ABIO32
+/* disable warning about constant controlling expression */
+#pragma set woff 1209
+#endif
+	    if (QSC_MAX_SIZE >= sizeof *qsc) {
+#if defined(IRIX) && _MIPS_SIM != _ABIO32
+#pragma reset woff 1209
+#endif
+		caddr_t m;
+
+		/*
+		 * Use the same mechanism as the scoreboard, except it
+		 * must be shared memory of some sort (not a file).
+		 */
+#ifdef USE_SHMGET_SCOREBOARD
+		m = ap_share_anonymous(p, 0, QSC_MAX_SIZE);
+#else
+		m = ap_map_anonymous(p, 0, QSC_MAX_SIZE);
+#endif
+		if (m != (caddr_t) (ap_ptr) -1) {
+		    qsc = (struct qsc *) m;
+		    qsc_init_malloc();
+		    ap_register_cleanup(p, NULL, qsc_cleanup, ap_null_cleanup);
+		} else
+		    ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, NULL,
+			"cannot obtain %d bytes of anonymous shared memory; QSC disabled",
+			QSC_MAX_SIZE);
+	    } else
+		ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, NULL,
+		    "QSC_MAX_SIZE too small; QSC disabled");
+	} else
+	    ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, NULL,
+		"mmap_static module not configured; QSC disabled");
+    }
+}
+
+/*
+ * Try to shortcut the request through the QSC.  Returns nonzero if
+ * successful, zero if the server should continue normal processing.
+ */
+int
+qsc_process_request(request_rec *r)
+{
+    int handled = 0;
+
+    if (qsc) {
+	short_score *sp;
+
+	sp = &ap_scoreboard_image->servers[r->connection->child_num];
+	if (qsc_request_is_cachable(r)) {
+	    const struct qsc_entry *ep;
+
+	    /*
+	     * Used to increment shared stats counters directly but on
+	     * large systems that can cause cache line thrashing.
+	     *    qsc_atomic_add(ep ? &qsc->stats.nhits : &qsc->stats.nmisses, 1);
+	     * Instead increment nonshared scoreboard server counters
+	     * and rely on qsc_sync_from_scoreboard() to update the
+	     * shared stats counters.
+	     */
+
+	    QSC_ASSERT(r->prev == NULL);	/* r is original request */
+	    ep = qsc_lookup_uri(r->unparsed_uri, r->server);
+	    if (ep) {
+		struct iovec iovec[2];
+
+		qsc_atomic_add(&sp->qsc_nhits, 1);
+
+		/*
+		 * The set of headers sent depends on the connection
+		 * status, keep-alive (ka) vs. single-shot (cc, for
+		 * connection:  close).
+		 */
+		if (qsc_set_keepalive(r)) {
+		    iovec[0].iov_base = ep->kaheaders;
+		    iovec[0].iov_len = ep->nkaheaderbytes;
+		} else {
+		    iovec[0].iov_base = ep->ccheaders;
+		    iovec[0].iov_len = ep->nccheaderbytes;
+		}
+#if QSC_HEADER_GRAIN > 0
+		QSC_ASSERT((iovec[0].iov_len & (QSC_HEADER_GRAIN - 1)) == 0);
+#endif
+		iovec[1].iov_base = ep->body;
+		iovec[1].iov_len = ep->nbodybytes;
+
+		if (iovec[1].iov_len <= MMAP_SEGMENT_SIZE) {
+		    /* nice and small, write all at once */
+		    ap_send_iovec(r, iovec, 2);
+		} else {
+		    /*
+		     * bigger than we'd like; segment it WITHOUT using
+		     * ap_bwrite() which copies data around and
+		     * unnecessarily handles chunked encoding.
+		     * ap_send_iovec() is streamlined for us.
+		     */
+		    iovec[1].iov_len = MMAP_SEGMENT_SIZE;
+		    if (ap_send_iovec(r, iovec, 2) > 0) {
+			size_t nw = MMAP_SEGMENT_SIZE;
+			do {
+			    iovec[1].iov_base = (char *) ep->body + nw;
+			    iovec[1].iov_len = ep->nbodybytes - nw;
+			    if (iovec[1].iov_len > MMAP_SEGMENT_SIZE)
+				iovec[1].iov_len = MMAP_SEGMENT_SIZE;
+			} while (ap_send_iovec(r, &iovec[1], 1) > 0 &&
+			    (nw += iovec[1].iov_len) < ep->nbodybytes);
+		    }
+		}
+
+		handled = 1;
+	    } else
+		qsc_atomic_add(&sp->qsc_nmisses, 1);
+	} else
+	    qsc_atomic_add(&sp->qsc_nucreq, 1);
+    }
+
+    return handled;
+}
+
+/*
+ * Insert an entry into the QSC.  The given basic header, headers from
+ * the request, and given body are all cached together indexed by the
+ * original request's URI.
+ */
+void
+qsc_insert_request(request_rec *r, const char *basic_header,
+    size_t basic_header_len, void *body, size_t nbodybytes,
+    const char *filename)
+{
+    if (qsc && qsc_request_is_cachable(r) && qsc_response_is_cachable(r)) {
+	array_header *ap;
+	struct header_size {
+	    size_t	keylen;
+	    size_t	vallen;
+	} *header_sizes;
+	int i;
+	size_t nheaderbytes;
+#if QSC_HEADER_GRAIN > 0
+	size_t akalen, acclen;
+	const char *pp, *padpoint;
+#endif
+	char *kaheaders, *ccheaders;
+	static const char katail[] = "Connection: keep-alive\r\n\r\n";
+	static const char cctail[] = "Connection: close\r\n\r\n";
+	enum {
+	    kalength = 26,	/* strlen(katail) */
+	    cclength = 21	/* strlen(cctail) */
+	};
+
+	ap = ap_table_elts(r->headers_out);
+	header_sizes = (struct header_size *) ap_palloc(r->pool,
+	    ap->nelts * sizeof *header_sizes);
+
+#if QSC_HEADER_GRAIN > 0
+	/*
+	 * Find where we will insert padding if necessary:  append to
+	 * the Server header value.  Probe the most likely place first
+	 * (where we think ap_tee_basic_http_header() put it) and
+	 * failing that, scan for it.
+	 */
+	if (basic_header_len >= 12 &&
+	    !strncmp(basic_header + basic_header_len -
+	    strlen(ap_get_server_version()) - 12, "\r\nServer: ", 10))
+	    padpoint = basic_header + basic_header_len - 2;
+	else {
+	    padpoint = NULL;
+	    for (pp = basic_header + basic_header_len; --pp >= basic_header; ) {
+		if (*pp == '\015')
+		    padpoint = pp;
+		if ((*pp == 'S' || *pp == 's') &&
+		    pp + 8 < basic_header + basic_header_len &&
+		    !strncasecmp(pp, "server: ", 8))
+		    break;
+	    }
+	    if (pp < basic_header)
+		padpoint = NULL;
+	}
+#endif
+
+	/* measure lengths of strings and total length of linearized headers */
+	nheaderbytes = basic_header_len;
+	for (i = 0; i < ap->nelts; i++) {
+	    table_entry *tep;
+	    int h;
+
+	    tep = &((table_entry *) ap->elts)[i];
+	    h = 1;
+	    switch (ap_tolower(tep->key[0])) {
+	    case 'c':
+		/* the Connection header is handled separately */
+		if (!strcasecmp(tep->key, "connection"))
+		    h = 0;
+		break;
+	    case 'k':
+		/* don't cache the Keep-Alive header */
+		if (!strcasecmp(tep->key, "keep-alive"))
+		    h = 0;
+		break;
+	    }
+
+	    if (h) {
+		header_sizes[i].keylen = strlen(tep->key);
+		header_sizes[i].vallen = strlen(tep->val);
+
+		/* +4 for ": " and "\r\n" */
+		nheaderbytes += header_sizes[i].keylen +
+		    header_sizes[i].vallen + 4;
+	    } else
+		header_sizes[i].keylen = 0;
+	}
+
+	/* create ka & cc linearized headers using above info */
+#if QSC_HEADER_GRAIN > 0
+	/* round up to QSC_HEADER_GRAIN */
+	akalen = QSC_ALIGN(nheaderbytes + kalength, QSC_HEADER_GRAIN);
+	acclen = QSC_ALIGN(nheaderbytes + cclength, QSC_HEADER_GRAIN);
+	/* align that to QSC_HEADER_GRAIN vaddr */
+	kaheaders = (char *) qsc_hmalloc(akalen);
+	ccheaders = (char *) qsc_hmalloc(acclen);
+#else
+	kaheaders = (char *) qsc_malloc(nheaderbytes + kalength);
+	ccheaders = (char *) qsc_malloc(nheaderbytes + cclength);
+#endif
+	if (kaheaders && ccheaders) {
+#if QSC_HEADER_GRAIN > 0
+	    size_t npad;
+#endif
+	    char *hp;
+	    request_rec *or;
+	    struct qsc_entry *ep;
+
+	    /* linearize ka headers first */
+#if QSC_HEADER_GRAIN > 0
+	    npad = akalen - (nheaderbytes + kalength);
+	    if (padpoint && npad > 0) {
+		size_t padoff = padpoint - basic_header;
+
+		/* copy up to padpoint */
+		memcpy(kaheaders, basic_header, padoff);
+		hp = kaheaders + padoff;
+
+		/* pad with spaces */
+		memset(hp, ' ', npad);
+		hp += npad;
+
+		/* copy the rest */
+		npad = basic_header_len - padoff;
+		memcpy(hp, basic_header + padoff, npad);
+		hp += npad;
+	    } else {
+#endif
+		memcpy(kaheaders, basic_header, basic_header_len);
+		hp = kaheaders + basic_header_len;
+#if QSC_HEADER_GRAIN > 0
+	    }
+#endif
+	    for (i = 0; i < ap->nelts; i++) {
+		if (header_sizes[i].keylen > 0) {
+		    table_entry *tep = &((table_entry *) ap->elts)[i];
+		    memcpy(hp, tep->key, header_sizes[i].keylen);
+		    hp += header_sizes[i].keylen + 2;
+		    hp[-2] = ':';
+		    hp[-1] = ' ';
+		    memcpy(hp, tep->val, header_sizes[i].vallen);
+		    hp += header_sizes[i].vallen + 2;
+		    hp[-2] = '\r';
+		    hp[-1] = '\n';
+		}
+	    }
+	    memcpy(hp, katail, kalength);
+#if QSC_HEADER_GRAIN > 0
+	    QSC_ASSERT(((ap_ptr) kaheaders & (QSC_HEADER_GRAIN - 1)) == 0);
+	    QSC_ASSERT(((ap_ptr) (hp + kalength) & (QSC_HEADER_GRAIN - 1)) == 0 ||
+		padpoint == NULL);
+#endif
+
+	    /* now linearize the cc headers */
+#if QSC_HEADER_GRAIN > 0
+	    npad = acclen - (nheaderbytes + cclength);
+	    if (padpoint && npad > 0) {
+		size_t padoff = padpoint - basic_header;
+
+		/* copy up to padpoint */
+		memcpy(ccheaders, basic_header, padoff);
+		hp = ccheaders + padoff;
+
+		/* pad with spaces */
+		memset(hp, ' ', npad);
+		hp += npad;
+
+		/* copy the rest */
+		npad = basic_header_len - padoff;
+		memcpy(hp, basic_header + padoff, npad);
+		hp += npad;
+	    } else {
+		memcpy(ccheaders, basic_header, basic_header_len);
+		hp = ccheaders + basic_header_len;
+	    }
+	    for (i = 0; i < ap->nelts; i++) {
+		if (header_sizes[i].keylen > 0) {
+		    table_entry *tep = &((table_entry *) ap->elts)[i];
+		    memcpy(hp, tep->key, header_sizes[i].keylen);
+		    hp += header_sizes[i].keylen + 2;
+		    hp[-2] = ':';
+		    hp[-1] = ' ';
+		    memcpy(hp, tep->val, header_sizes[i].vallen);
+		    hp += header_sizes[i].vallen + 2;
+		    hp[-2] = '\r';
+		    hp[-1] = '\n';
+		}
+	    }
+	    memcpy(hp, cctail, cclength);
+	    QSC_ASSERT(((ap_ptr) ccheaders & (QSC_HEADER_GRAIN - 1)) == 0);
+	    QSC_ASSERT(((ap_ptr) (hp + cclength) & (QSC_HEADER_GRAIN - 1)) == 0 ||
+		padpoint == NULL);
+#else
+	    /* ka & cc headers differ only in the tail portion */
+	    QSC_ASSERT(hp - kaheaders == nheaderbytes);
+	    memcpy(ccheaders, kaheaders, nheaderbytes);
+	    memcpy(ccheaders + nheaderbytes, cctail, cclength);
+#endif
+
+	    /*
+	     * insert headers and body into cache, indexed by original
+	     * uri and current vhost
+	     */
+	    for (or = r; or->prev; or = or->prev)
+		;
+	    ep = qsc_insert_uri(or->uri, r->server);
+	    if (ep) {
+		ep->kaheaders = kaheaders;
+		ep->nkaheaderbytes =
+#if QSC_HEADER_GRAIN > 0
+		    padpoint ? akalen :
+#endif
+		    nheaderbytes + kalength;
+		ep->ccheaders = ccheaders;
+		ep->nccheaderbytes =
+#if QSC_HEADER_GRAIN > 0
+		    padpoint ? acclen :
+#endif
+		    nheaderbytes + cclength;
+		ep->body = body;
+		ep->nbodybytes = nbodybytes;
+#ifdef QSC_DEBUG
+		ep->filename = filename;
+#endif
+	    } else {
+		qsc_free(ccheaders);
+		qsc_free(kaheaders);
+		qsc_failure();
+	    }
+	} else {
+	    qsc_failure();
+	    if (ccheaders)
+		qsc_free(ccheaders);
+	    if (kaheaders)
+		qsc_free(kaheaders);
+	}
+    } else if (qsc)
+	qsc_atomic_add(&ap_scoreboard_image->servers[r->connection->child_num].qsc_nucres, 1);
+}
+
+/*
+ * Send a QSC report card.
+ * Options are:
+ *  full	show detailed info about each entry
+ *  quick	don't compute anything, just dump counters
+ */
+void
+qsc_status(request_rec *r, const char *option)
+{
+    ap_rputs("<pre>Quick Shortcut Cache (QSC) Status:\n", r);
+
+    if (qsc) {
+	static const char fmt[] = "  %-20s %s\n";
+	char buf[128];
+	struct qsc_stats stats;
+	int i, full, quick, nbuckets, nentries, longest_chain, ndupent;
+	ap_atomic nlookups, nuribytes, nheaderbytes, nbodybytes, nbodyvas;
+	int histogram[QSC_HIST_SIZE], pagesize;
+	struct qsc_entry *ep;
+
+	full = 0;
+	quick = 0;
+	if (option) {
+	    if (!strcasecmp(option, "full"))
+		full = 1;
+	    else if (!strcasecmp(option, "quick"))
+		quick = 1;
+	    else
+		ap_rprintf(r, "<strong>Unknown QSC status option \"%s\"</strong>\n",
+		    option);
+	}
+
+	qsc_sync_from_scoreboard();
+
+	stats = qsc->stats;	/* take a more or less consistent snapshot */
+	nbuckets = 0;
+	nentries = 0;
+	longest_chain = 0;
+	nuribytes = 0;
+	nheaderbytes = 0;
+	nbodybytes = 0;
+	nbodyvas = 0;
+	ndupent = 0;
+	memset(histogram, 0, sizeof histogram);
+	pagesize = getpagesize();
+	if (pagesize <= 0 || (pagesize & (pagesize - 1)) != 0)
+	    pagesize = 4096;
+
+	if (!quick) {
+	    for (i = 0; i < QSC_HASH_SIZE; i++) {
+		ep = qsc->hash_table[i];
+		if (ep) {
+		    int cl;
+
+		    nbuckets++;
+
+		    cl = 0;
+		    do {
+			struct qsc_entry *dp;
+
+			cl++;
+			nentries++;
+			nuribytes += strlen(ep->uri) + 1;
+			nheaderbytes += ep->nkaheaderbytes + ep->nccheaderbytes;
+			nbodybytes += ep->nbodybytes;
+			nbodyvas += QSC_ALIGN(ep->nbodybytes, pagesize);
+
+			for (dp = ep->next; dp; dp = dp->next)
+			    if (ep->server == dp->server &&
+				!strcmp(ep->uri, dp->uri))
+				ndupent++;
+
+			ep = ep->next;
+		    } while (ep);
+
+		    if (cl > longest_chain)
+			longest_chain = cl;
+		    histogram[((cl <= QSC_HIST_SIZE) ? cl : QSC_HIST_SIZE) - 1]++;
+		}
+	    }
+	}
+
+	/*
+	 * ap_vformatter() botches some conversions (like %.2f) so use
+	 * sprintf() then ap_rprintf()
+	 */
+
+	nlookups = stats.nucreq + stats.nucres + stats.nhits + stats.nmisses;
+	sprintf(buf, "%ld/%ld (%.2f%%)",
+	    (long) stats.nhits, (long) nlookups,
+	    nlookups ? (double) stats.nhits * 100.0 / (double) nlookups : 0);
+	ap_rprintf(r, fmt, "hit ratio", buf);
+	if (!quick) {
+	    ap_atomic uncachable;
+
+	    uncachable = stats.nucreq + stats.nucres + stats.nmisses -
+	        nentries;
+	    sprintf(buf, "%ld/%ld (%.2f%%)",
+		(long) uncachable, (long) nlookups,
+		nlookups ? (double) uncachable * 100.0 / (double) nlookups : 0);
+	    ap_rprintf(r, fmt, "uncachable", buf);
+
+	    uncachable = stats.nmisses - nentries;
+	    sprintf(buf, "%ld/%ld (%.2f%%)",
+		(long) uncachable, (long) stats.nmisses,
+		stats.nmisses ? (double) uncachable * 100.0 / (double) stats.nmisses : 0);
+	    ap_rprintf(r, fmt, "uncachable misses", buf);
+	}
+	sprintf(buf, "%ld/%ld (%.2f%%)",
+	    (long) stats.nucreq, (long) nlookups,
+	    nlookups ? (double) stats.nucreq * 100.0 / (double) nlookups : 0);
+	ap_rprintf(r, fmt, "uncachable requests", buf);
+	sprintf(buf, "%ld/%ld (%.2f%%)",
+	    (long) stats.nucres, (long) nlookups,
+	    nlookups ? (double) stats.nucres * 100.0 / (double) nlookups : 0);
+	ap_rprintf(r, fmt, "uncachable responses", buf);
+	sprintf(buf, "%ld", (long) stats.nresets);
+	ap_rprintf(r, fmt, "resets", buf);
+
+	ap_rputs("<em>Hash table</em>\n", r);
+	sprintf(buf, "%ld", (long) stats.nfailures);
+	ap_rprintf(r, fmt, "failed insertions", buf);
+	if (!quick) {
+	    ap_atomic inuse;
+
+	    sprintf(buf, "%d", nentries);
+	    ap_rprintf(r, fmt, "entries", buf);
+	    sprintf(buf, "%d", ndupent);
+	    ap_rprintf(r, fmt, "duplicate entries", buf);
+	    sprintf(buf, "%d/%d (%.2f%%)", nbuckets, QSC_HASH_SIZE,
+		(double) nbuckets * 100.0 / (double) QSC_HASH_SIZE);
+	    ap_rprintf(r, fmt, "bucket use", buf);
+	    sprintf(buf, "%d/%d (%.2f%%)", nbuckets, nentries,
+		nentries ? (double) nbuckets * 100.0 / (double) nentries : 0);
+	    ap_rprintf(r, fmt, "hash effectiveness", buf);
+	    sprintf(buf, "%d", longest_chain);
+	    ap_rprintf(r, fmt, "longest chain", buf);
+	    sprintf(buf, "%.1f", (double) nentries / QSC_HASH_SIZE);
+	    ap_rprintf(r, fmt, "avg. chain", buf);
+	    sprintf(buf, "%.1f", nbuckets ?
+		(double) nentries / (double) nbuckets : 0);
+	    ap_rprintf(r, fmt, "avg. nonempty chain", buf);
+
+	    ap_rputs("  Chain length histogram:\n    ", r);
+	    for (i = 1; i < QSC_HIST_SIZE; i++)
+		ap_rprintf(r, "%5d ", i);
+	    ap_rprintf(r, "%5d+\n    ", i);
+	    for (i = 1; i <= QSC_HIST_SIZE; i++)
+		ap_rprintf(r, "%5d ", histogram[i - 1]);
+	    ap_rputc('\n', r);
+
+	    ap_rputs("<em>Memory use</em> (in bytes)\n", r);
+	    sprintf(buf, "%ld", (long) sizeof *qsc);
+	    ap_rprintf(r, fmt, "table + misc", buf);
+	    sprintf(buf, "%ld", (long) nentries * sizeof (struct qsc_entry));
+	    ap_rprintf(r, fmt, "entries", buf);
+	    sprintf(buf, "%lu", (unsigned long) nuribytes);
+	    ap_rprintf(r, fmt, "URIs", buf);
+	    sprintf(buf, "%lu", (unsigned long) nheaderbytes);
+	    ap_rprintf(r, fmt, "headers", buf);
+	    inuse = qsc_inuse();
+	    sprintf(buf, "%ld/%ld (%.2f%%)", (long) inuse, (long) QSC_MAX_SIZE,
+		(double) inuse * 100.0 / (double) QSC_MAX_SIZE);
+	    ap_rprintf(r, fmt, "total", buf);
+	    sprintf(buf, "%lu", (unsigned long) nbodybytes);
+	    ap_rprintf(r, fmt, "mapped file data", buf);
+	    sprintf(buf, "%lu (%lu %d-byte pages)", (unsigned long) nbodyvas,
+		(unsigned long) (nbodyvas / pagesize), pagesize);
+	    ap_rprintf(r, fmt, "mapped file vaddrs", buf);
+	}
+
+	if (full) {
+	    ap_rputs("<em>Full entry info</em>\n", r);
+#ifndef QSC_DEBUG
+	    ap_rputs("<strong>File names available only -DQSC_DEBUG</strong>\n", r);
+#endif
+	    ap_rputs("  <em>server * URI @ hash-bucket -> keep-alive-header-bytes;non-keep-alive-header-bytes + body-bytes file-name</em>\n", r);
+
+	    for (i = 0; i < QSC_HASH_SIZE; i++) {
+		for (ep = qsc->hash_table[i]; ep; ep = ep->next) {
+		    if (ep->server->defn_name)
+			sprintf(buf, "%s:%u", ep->server->defn_name,
+			    ep->server->defn_line_number);
+		    else
+			strcpy(buf, "main");
+
+		    ap_rprintf(r, "  %s * %s @ %d -> %lu;%lu + %lu ",
+			buf, ep->uri, i,
+			(unsigned long) ep->nkaheaderbytes,
+			(unsigned long) ep->nccheaderbytes,
+			(unsigned long) ep->nbodybytes);
+#ifdef QSC_DEBUG
+		    ap_rputs(ep->filename, r);
+#else
+		    ap_rwrite("n/a", 3, r);
+#endif
+		    ap_rputc('\n', r);
+		}
+	    }
+	}
+    } else
+	ap_rputs("QSC disabled\n", r);
+
+    ap_rputs("</pre>\n", r);
+}
+
+#endif
diff -Naur apache_1.3.6+01-07/src/modules/experimental/mod_mmap_static.c apache_1.3.6+01-08/src/modules/experimental/mod_mmap_static.c
--- apache_1.3.6+01-07/src/modules/experimental/mod_mmap_static.c	Mon Jul 19 16:43:26 1999
+++ apache_1.3.6+01-08/src/modules/experimental/mod_mmap_static.c	Thu Sep  2 10:37:38 1999
@@ -322,6 +322,10 @@
     a_file **pmatch;
     a_file *match;
     int rangestatus, errstatus;
+#ifdef USE_QSC
+    char basic_header[1024];
+    size_t basic_header_len;
+#endif
 
     /* we don't handle anything but GET */
     if (r->method_number != M_GET) return DECLINED;
@@ -370,7 +374,13 @@
     }
 
     rangestatus = ap_set_byterange(r);
+
+#ifdef USE_QSC
+    basic_header_len = sizeof basic_header;
+    ap_tee_http_header(r, basic_header, &basic_header_len);
+#else
     ap_send_http_header(r);
+#endif
 
     if (!r->header_only) {
 	if (!rangestatus) {
@@ -383,6 +393,17 @@
 	    }
 	}
     }
+
+#ifdef USE_QSC
+    if (basic_header_len > 0) {
+	/* convert from bytes remaining to bytes used */
+	basic_header_len = sizeof basic_header - basic_header_len;
+
+	qsc_insert_request(r, basic_header, basic_header_len,
+	    match->mm, match->finfo.st_size, match->filename);
+    }
+#endif
+
     return OK;
 }
 
diff -Naur apache_1.3.6+01-07/src/modules/standard/mod_status.c apache_1.3.6+01-08/src/modules/standard/mod_status.c
--- apache_1.3.6+01-07/src/modules/standard/mod_status.c	Tue Jul 20 22:44:40 1999
+++ apache_1.3.6+01-08/src/modules/standard/mod_status.c	Thu Sep  2 10:38:04 1999
@@ -74,6 +74,9 @@
  * /server-status?refresh - Returns page with 1 second refresh
  * /server-status?refresh=6 - Returns page with refresh every 6 seconds
  * /server-status?auto - Returns page with data for automatic parsing
+#ifdef USE_QSC
+ * /server-status?qsc=... - Returns page with extra QSC status info
+#endif
  *
  * Mark Cox, mark@ukweb.com, November 1995
  *
@@ -209,6 +212,7 @@
 #define STAT_OPT_REFRESH	0
 #define STAT_OPT_NOTABLE	1
 #define STAT_OPT_AUTO		2
+#define STAT_OPT_QSC		3
 
 struct stat_opt {
     int id;
@@ -221,6 +225,9 @@
     {STAT_OPT_REFRESH, "refresh", "Refresh"},
     {STAT_OPT_NOTABLE, "notable", NULL},
     {STAT_OPT_AUTO, "auto", NULL},
+#ifdef USE_QSC
+    {STAT_OPT_QSC, "qsc", NULL},
+#endif
     {STAT_OPT_END, NULL, NULL}
 };
 
@@ -260,6 +267,9 @@
 #endif
     clock_t tu, ts, tcu, tcs;
     server_rec *vhost;
+#ifdef USE_QSC
+    const char *qscp = NULL;
+#endif
 
     tu = ts = tcu = tcs = 0;
 
@@ -299,6 +309,12 @@
 		    r->content_type = "text/plain";
 		    short_report = 1;
 		    break;
+#ifdef USE_QSC
+		case STAT_OPT_QSC:
+		    if (loc[strlen(status_options[i].form_data_str)] == '=')
+			qscp = &loc[strlen(status_options[i].form_data_str) + 1];
+		    break;
+#endif
 		}
 	    }
 	    i++;
@@ -748,6 +764,11 @@
     ap_rputs("you need to use the <code>ExtendedStatus On</code> directive. \n", r);
 
     }
+
+#ifdef USE_QSC
+    ap_rputs("<hr>", r);
+    qsc_status(r, qscp);
+#endif
 
     if (!short_report) {
 	ap_rputs(ap_psignature("<HR>\n",r), r);