|  | /* Licensed to the Apache Software Foundation (ASF) under one or more | 
|  | * contributor license agreements.  See the NOTICE file distributed with | 
|  | * this work for additional information regarding copyright ownership. | 
|  | * The ASF licenses this file to You under the Apache License, Version 2.0 | 
|  | * (the "License"); you may not use this file except in compliance with | 
|  | * the License.  You may obtain a copy of the License at | 
|  | * | 
|  | *     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Modified by djm@va.pubnix.com: | 
|  | * If no TransferLog is given explicitly, decline to log. | 
|  | * | 
|  | * This is module implements the TransferLog directive (same as the | 
|  | * common log module), and additional directives, LogFormat and CustomLog. | 
|  | * | 
|  | * | 
|  | * Syntax: | 
|  | * | 
|  | *    TransferLog fn      Logs transfers to fn in standard log format, unless | 
|  | *                        a custom format is set with LogFormat | 
|  | *    LogFormat format    Set a log format from TransferLog files | 
|  | *    CustomLog fn format | 
|  | *                        Log to file fn with format given by the format | 
|  | *                        argument | 
|  | * | 
|  | * There can be any number of TransferLog and CustomLog | 
|  | * commands. Each request will be logged to _ALL_ the | 
|  | * named files, in the appropriate format. | 
|  | * | 
|  | * If no TransferLog or CustomLog directive appears in a VirtualHost, | 
|  | * the request will be logged to the log file(s) defined outside | 
|  | * the virtual host section. If a TransferLog or CustomLog directive | 
|  | * appears in the VirtualHost section, the log files defined outside | 
|  | * the VirtualHost will _not_ be used. This makes this module compatible | 
|  | * with the CLF and config log modules, where the use of TransferLog | 
|  | * inside the VirtualHost section overrides its use outside. | 
|  | * | 
|  | * Examples: | 
|  | * | 
|  | *    TransferLog    logs/access_log | 
|  | *    <VirtualHost> | 
|  | *    LogFormat      "... custom format ..." | 
|  | *    TransferLog    log/virtual_only | 
|  | *    CustomLog      log/virtual_useragents "%t %{user-agent}i" | 
|  | *    </VirtualHost> | 
|  | * | 
|  | * This will log using CLF to access_log any requests handled by the | 
|  | * main server, while any requests to the virtual host will be logged | 
|  | * with the "... custom format..." to virtual_only _AND_ using | 
|  | * the custom user-agent log to virtual_useragents. | 
|  | * | 
|  | * Note that the NCSA referer and user-agent logs are easily added with | 
|  | * CustomLog: | 
|  | *   CustomLog   logs/referer  "%{referer}i -> %U" | 
|  | *   CustomLog   logs/agent    "%{user-agent}i" | 
|  | * | 
|  | * RefererIgnore functionality can be obtained with conditional | 
|  | * logging (SetEnvIf and CustomLog ... env=!VAR). | 
|  | * | 
|  | * But using this method allows much easier modification of the | 
|  | * log format, e.g. to log hosts along with UA: | 
|  | *   CustomLog   logs/referer "%{referer}i %U %h" | 
|  | * | 
|  | * The argument to LogFormat and CustomLog is a string, which can include | 
|  | * literal characters copied into the log files, and '%' directives as | 
|  | * follows: | 
|  | * | 
|  | * %...B:  bytes sent, excluding HTTP headers. | 
|  | * %...b:  bytes sent, excluding HTTP headers in CLF format, i.e. a '-' | 
|  | *         when no bytes where sent (rather than a '0'. | 
|  | * %...{FOOBAR}C:  The contents of the HTTP cookie FOOBAR | 
|  | * %...{FOOBAR}e:  The contents of the environment variable FOOBAR | 
|  | * %...f:  filename | 
|  | * %...h:  remote host | 
|  | * %...a:  remote IP-address | 
|  | * %...A:  local IP-address | 
|  | * %...{Foobar}i:  The contents of Foobar: header line(s) in the request | 
|  | *                 sent to the client. | 
|  | * %...k:  number of keepalive requests served over this connection | 
|  | * %...l:  remote logname (from identd, if supplied) | 
|  | * %...{Foobar}n:  The contents of note "Foobar" from another module. | 
|  | * %...{Foobar}o:  The contents of Foobar: header line(s) in the reply. | 
|  | * %...p:  the canonical port for the server | 
|  | * %...{format}p: the canonical port for the server, or the actual local | 
|  | *                or remote port | 
|  | * %...P:  the process ID of the child that serviced the request. | 
|  | * %...{format}P: the process ID or thread ID of the child/thread that | 
|  | *                serviced the request | 
|  | * %...r:  first line of request | 
|  | * %...s:  status.  For requests that got internally redirected, this | 
|  | *         is status of the *original* request --- %...>s for the last. | 
|  | * %...t:  time, in common log format time format | 
|  | * %...{format}t:  The time, in the form given by format, which should | 
|  | *                 be in strftime(3) format. | 
|  | * %...T:  the time taken to serve the request, in seconds. | 
|  | * %...{s}T:  the time taken to serve the request, in seconds, same as %T. | 
|  | * %...{us}T:  the time taken to serve the request, in micro seconds, same as %D. | 
|  | * %...{ms}T:  the time taken to serve the request, in milliseconds. | 
|  | * %...D:  the time taken to serve the request, in micro seconds. | 
|  | * %...u:  remote user (from auth; may be bogus if return status (%s) is 401) | 
|  | * %...U:  the URL path requested. | 
|  | * %...v:  the configured name of the server (i.e. which virtual host?) | 
|  | * %...V:  the server name according to the UseCanonicalName setting | 
|  | * %...m:  the request method | 
|  | * %...H:  the request protocol | 
|  | * %...q:  the query string prepended by "?", or empty if no query string | 
|  | * %...X:  Status of the connection. | 
|  | *         'X' = connection aborted before the response completed. | 
|  | *         '+' = connection may be kept alive after the response is sent. | 
|  | *         '-' = connection will be closed after the response is sent. | 
|  | *         (This directive was %...c in late versions of Apache 1.3, but | 
|  | *          this conflicted with the historical ssl %...{var}c syntax.) | 
|  | * %...L:  Log-Id of the Request (or '-' if none) | 
|  | * %...{c}L:  Log-Id of the Connection (or '-' if none) | 
|  | * | 
|  | * The '...' can be nothing at all (e.g. "%h %u %r %s %b"), or it can | 
|  | * indicate conditions for inclusion of the item (which will cause it | 
|  | * to be replaced with '-' if the condition is not met).  Note that | 
|  | * there is no escaping performed on the strings from %r, %...i and | 
|  | * %...o; some with long memories may remember that I thought this was | 
|  | * a bad idea, once upon a time, and I'm still not comfortable with | 
|  | * it, but it is difficult to see how to "do the right thing" with all | 
|  | * of '%..i', unless we URL-escape everything and break with CLF. | 
|  | * | 
|  | * The forms of condition are a list of HTTP status codes, which may | 
|  | * or may not be preceded by '!'.  Thus, '%400,501{User-agent}i' logs | 
|  | * User-agent: on 400 errors and 501 errors (Bad Request, Not | 
|  | * Implemented) only; '%!200,304,302{Referer}i' logs Referer: on all | 
|  | * requests which did *not* return some sort of normal status. | 
|  | * | 
|  | * The default LogFormat reproduces CLF; see below. | 
|  | * | 
|  | * The way this is supposed to work with virtual hosts is as follows: | 
|  | * a virtual host can have its own LogFormat, or its own TransferLog. | 
|  | * If it doesn't have its own LogFormat, it inherits from the main | 
|  | * server.  If it doesn't have its own TransferLog, it writes to the | 
|  | * same descriptor (meaning the same process for "| ..."). | 
|  | * | 
|  | * --- rst */ | 
|  |  | 
|  | #include "apr_strings.h" | 
|  | #include "apr_lib.h" | 
|  | #include "apr_hash.h" | 
|  | #include "apr_optional.h" | 
|  | #include "apr_anylock.h" | 
|  |  | 
|  | #define APR_WANT_STRFUNC | 
|  | #include "apr_want.h" | 
|  |  | 
|  | #include "ap_config.h" | 
|  | #include "mod_log_config.h" | 
|  | #include "httpd.h" | 
|  | #include "http_config.h" | 
|  | #include "http_core.h"          /* For REMOTE_NAME */ | 
|  | #include "http_log.h" | 
|  | #include "http_protocol.h" | 
|  | #include "util_time.h" | 
|  | #include "ap_mpm.h" | 
|  |  | 
|  | #if APR_HAVE_UNISTD_H | 
|  | #include <unistd.h> | 
|  | #endif | 
|  | #ifdef HAVE_LIMITS_H | 
|  | #include <limits.h> | 
|  | #endif | 
|  |  | 
|  | #define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b" | 
|  |  | 
|  | module AP_MODULE_DECLARE_DATA log_config_module; | 
|  |  | 
|  |  | 
|  | static int xfer_flags = (APR_WRITE | APR_APPEND | APR_CREATE | APR_LARGEFILE); | 
|  | static apr_fileperms_t xfer_perms = APR_OS_DEFAULT; | 
|  | static apr_hash_t *log_hash; | 
|  | static apr_status_t ap_default_log_writer(request_rec *r, | 
|  | void *handle, | 
|  | const char **strs, | 
|  | int *strl, | 
|  | int nelts, | 
|  | apr_size_t len); | 
|  | static apr_status_t ap_buffered_log_writer(request_rec *r, | 
|  | void *handle, | 
|  | const char **strs, | 
|  | int *strl, | 
|  | int nelts, | 
|  | apr_size_t len); | 
|  | static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s, | 
|  | const char* name); | 
|  | static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s, | 
|  | const char* name); | 
|  |  | 
|  | static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle); | 
|  | static ap_log_writer *ap_log_set_writer(ap_log_writer *handle); | 
|  | static ap_log_writer *log_writer = ap_default_log_writer; | 
|  | static ap_log_writer_init *log_writer_init = ap_default_log_writer_init; | 
|  | static int buffered_logs = 0; /* default unbuffered */ | 
|  | static apr_array_header_t *all_buffered_logs = NULL; | 
|  |  | 
|  | /* POSIX.1 defines PIPE_BUF as the maximum number of bytes that is | 
|  | * guaranteed to be atomic when writing a pipe.  And PIPE_BUF >= 512 | 
|  | * is guaranteed.  So we'll just guess 512 in the event the system | 
|  | * doesn't have this.  Now, for file writes there is actually no limit, | 
|  | * the entire write is atomic.  Whether all systems implement this | 
|  | * correctly is another question entirely ... so we'll just use PIPE_BUF | 
|  | * because it's probably a good guess as to what is implemented correctly | 
|  | * everywhere. | 
|  | */ | 
|  | #ifdef PIPE_BUF | 
|  | #define LOG_BUFSIZE     PIPE_BUF | 
|  | #else | 
|  | #define LOG_BUFSIZE     (512) | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * multi_log_state is our per-(virtual)-server configuration. We store | 
|  | * an array of the logs we are going to use, each of type config_log_state. | 
|  | * If a default log format is given by LogFormat, store in default_format | 
|  | * (backward compat. with mod_log_config).  We also store for each virtual | 
|  | * server a pointer to the logs specified for the main server, so that if this | 
|  | * vhost has no logs defined, we can use the main server's logs instead. | 
|  | * | 
|  | * So, for the main server, config_logs contains a list of the log files | 
|  | * and server_config_logs is empty. For a vhost, server_config_logs | 
|  | * points to the same array as config_logs in the main server, and | 
|  | * config_logs points to the array of logs defined inside this vhost, | 
|  | * which might be empty. | 
|  | */ | 
|  |  | 
|  | typedef struct { | 
|  | const char *default_format_string; | 
|  | apr_array_header_t *default_format; | 
|  | apr_array_header_t *config_logs; | 
|  | apr_array_header_t *server_config_logs; | 
|  | apr_table_t *formats; | 
|  | } multi_log_state; | 
|  |  | 
|  | /* | 
|  | * config_log_state holds the status of a single log file. fname might | 
|  | * be NULL, which means this module does no logging for this | 
|  | * request. format might be NULL, in which case the default_format | 
|  | * from the multi_log_state should be used, or if that is NULL as | 
|  | * well, use the CLF. | 
|  | * log_writer is NULL before the log file is opened and is | 
|  | * set to a opaque structure (usually a fd) after it is opened. | 
|  |  | 
|  | */ | 
|  | typedef struct { | 
|  | apr_file_t *handle; | 
|  | apr_size_t outcnt; | 
|  | char outbuf[LOG_BUFSIZE]; | 
|  | apr_anylock_t mutex; | 
|  | } buffered_log; | 
|  |  | 
|  | typedef struct { | 
|  | const char *fname; | 
|  | const char *format_string; | 
|  | apr_array_header_t *format; | 
|  | void *log_writer; | 
|  | char *condition_var; | 
|  | int inherit; | 
|  | ap_expr_info_t *condition_expr; | 
|  | /** place of definition or NULL if already checked */ | 
|  | const ap_directive_t *directive; | 
|  | } config_log_state; | 
|  |  | 
|  | /* | 
|  | * log_request_state holds request specific log data that is not | 
|  | * part of the request_rec. | 
|  | */ | 
|  | typedef struct { | 
|  | apr_time_t request_end_time; | 
|  | } log_request_state; | 
|  |  | 
|  | /* | 
|  | * Format items... | 
|  | * Note that many of these could have ap_sprintfs replaced with static buffers. | 
|  | */ | 
|  |  | 
|  | typedef struct { | 
|  | ap_log_handler_fn_t *func; | 
|  | char *arg; | 
|  | int condition_sense; | 
|  | int want_orig; | 
|  | apr_array_header_t *conditions; | 
|  | } log_format_item; | 
|  |  | 
|  | static char *pfmt(apr_pool_t *p, int i) | 
|  | { | 
|  | if (i <= 0) { | 
|  | return "-"; | 
|  | } | 
|  | else { | 
|  | return apr_itoa(p, i); | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *constant_item(request_rec *dummy, char *stuff) | 
|  | { | 
|  | return stuff; | 
|  | } | 
|  |  | 
|  | static const char *log_remote_host(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, ap_get_remote_host(r->connection, | 
|  | r->per_dir_config, | 
|  | REMOTE_NAME, NULL)); | 
|  | } | 
|  |  | 
|  | static const char *log_remote_address(request_rec *r, char *a) | 
|  | { | 
|  | if (a && !strcmp(a, "c")) { | 
|  | return r->connection->client_ip; | 
|  | } | 
|  | else { | 
|  | return r->useragent_ip; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *log_local_address(request_rec *r, char *a) | 
|  | { | 
|  | return r->connection->local_ip; | 
|  | } | 
|  |  | 
|  | static const char *log_remote_logname(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, ap_get_remote_logname(r)); | 
|  | } | 
|  |  | 
|  | static const char *log_remote_user(request_rec *r, char *a) | 
|  | { | 
|  | char *rvalue = r->user; | 
|  |  | 
|  | if (rvalue == NULL) { | 
|  | rvalue = "-"; | 
|  | } | 
|  | else if (strlen(rvalue) == 0) { | 
|  | rvalue = "\"\""; | 
|  | } | 
|  | else { | 
|  | rvalue = ap_escape_logitem(r->pool, rvalue); | 
|  | } | 
|  |  | 
|  | return rvalue; | 
|  | } | 
|  |  | 
|  | static const char *log_request_line(request_rec *r, char *a) | 
|  | { | 
|  | /* NOTE: If the original request contained a password, we | 
|  | * re-write the request line here to contain XXXXXX instead: | 
|  | * (note the truncation before the protocol string for HTTP/0.9 requests) | 
|  | * (note also that r->the_request contains the unmodified request) | 
|  | */ | 
|  | return ap_escape_logitem(r->pool, | 
|  | (r->parsed_uri.password) | 
|  | ? apr_pstrcat(r->pool, r->method, " ", | 
|  | apr_uri_unparse(r->pool, | 
|  | &r->parsed_uri, 0), | 
|  | r->assbackwards ? NULL : " ", | 
|  | r->protocol, NULL) | 
|  | : r->the_request); | 
|  | } | 
|  |  | 
|  | static const char *log_request_file(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->filename); | 
|  | } | 
|  | static const char *log_request_uri(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->uri); | 
|  | } | 
|  | static const char *log_request_method(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->method); | 
|  | } | 
|  | static const char *log_log_id(request_rec *r, char *a) | 
|  | { | 
|  | if (a && !strcmp(a, "c")) { | 
|  | return r->connection->log_id ? r->connection->log_id : "-"; | 
|  | } | 
|  | else { | 
|  | return r->log_id ? r->log_id : "-"; | 
|  | } | 
|  | } | 
|  | static const char *log_request_protocol(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->protocol); | 
|  | } | 
|  | static const char *log_request_query(request_rec *r, char *a) | 
|  | { | 
|  | return (r->args) ? apr_pstrcat(r->pool, "?", | 
|  | ap_escape_logitem(r->pool, r->args), NULL) | 
|  | : ""; | 
|  | } | 
|  | static const char *log_status(request_rec *r, char *a) | 
|  | { | 
|  | return pfmt(r->pool, r->status); | 
|  | } | 
|  |  | 
|  | static const char *log_handler(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->handler); | 
|  | } | 
|  |  | 
|  | static const char *clf_log_bytes_sent(request_rec *r, char *a) | 
|  | { | 
|  | if (!r->sent_bodyct || !r->bytes_sent) { | 
|  | return "-"; | 
|  | } | 
|  | else { | 
|  | return apr_off_t_toa(r->pool, r->bytes_sent); | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *log_bytes_sent(request_rec *r, char *a) | 
|  | { | 
|  | if (!r->sent_bodyct || !r->bytes_sent) { | 
|  | return "0"; | 
|  | } | 
|  | else { | 
|  | return apr_off_t_toa(r->pool, r->bytes_sent); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static const char *log_header_in(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, apr_table_get(r->headers_in, a)); | 
|  | } | 
|  |  | 
|  | static const char *log_trailer_in(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, apr_table_get(r->trailers_in, a)); | 
|  | } | 
|  |  | 
|  |  | 
|  | static APR_INLINE char *find_multiple_headers(apr_pool_t *pool, | 
|  | const apr_table_t *table, | 
|  | const char *key) | 
|  | { | 
|  | const apr_array_header_t *elts; | 
|  | const apr_table_entry_t *t_elt; | 
|  | const apr_table_entry_t *t_end; | 
|  | apr_size_t len; | 
|  | struct sle { | 
|  | struct sle *next; | 
|  | const char *value; | 
|  | apr_size_t len; | 
|  | } *result_list, *rp; | 
|  |  | 
|  | elts = apr_table_elts(table); | 
|  |  | 
|  | if (!elts->nelts) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | t_elt = (const apr_table_entry_t *)elts->elts; | 
|  | t_end = t_elt + elts->nelts; | 
|  | len = 1; /* \0 */ | 
|  | result_list = rp = NULL; | 
|  |  | 
|  | do { | 
|  | if (!strcasecmp(t_elt->key, key)) { | 
|  | if (!result_list) { | 
|  | result_list = rp = apr_palloc(pool, sizeof(*rp)); | 
|  | } | 
|  | else { | 
|  | rp = rp->next = apr_palloc(pool, sizeof(*rp)); | 
|  | len += 2; /* ", " */ | 
|  | } | 
|  |  | 
|  | rp->next = NULL; | 
|  | rp->value = t_elt->val; | 
|  | rp->len = strlen(rp->value); | 
|  |  | 
|  | len += rp->len; | 
|  | } | 
|  | ++t_elt; | 
|  | } while (t_elt < t_end); | 
|  |  | 
|  | if (result_list) { | 
|  | char *result = apr_palloc(pool, len); | 
|  | char *cp = result; | 
|  |  | 
|  | rp = result_list; | 
|  | while (rp) { | 
|  | if (rp != result_list) { | 
|  | *cp++ = ','; | 
|  | *cp++ = ' '; | 
|  | } | 
|  | memcpy(cp, rp->value, rp->len); | 
|  | cp += rp->len; | 
|  | rp = rp->next; | 
|  | } | 
|  | *cp = '\0'; | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static const char *log_header_out(request_rec *r, char *a) | 
|  | { | 
|  | const char *cp = NULL; | 
|  |  | 
|  | if (!strcasecmp(a, "Content-type") && r->content_type) { | 
|  | cp = ap_field_noparam(r->pool, r->content_type); | 
|  | } | 
|  | else if (!strcasecmp(a, "Set-Cookie")) { | 
|  | cp = find_multiple_headers(r->pool, r->headers_out, a); | 
|  | } | 
|  | else { | 
|  | cp = apr_table_get(r->headers_out, a); | 
|  | } | 
|  |  | 
|  | return ap_escape_logitem(r->pool, cp); | 
|  | } | 
|  |  | 
|  | static const char *log_trailer_out(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, apr_table_get(r->trailers_out, a)); | 
|  | } | 
|  |  | 
|  | static const char *log_note(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, apr_table_get(r->notes, a)); | 
|  | } | 
|  | static const char *log_env_var(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, apr_table_get(r->subprocess_env, a)); | 
|  | } | 
|  |  | 
|  | static const char *log_cookie(request_rec *r, char *a) | 
|  | { | 
|  | const char *cookies_entry; | 
|  |  | 
|  | /* | 
|  | * This supports Netscape version 0 cookies while being tolerant to | 
|  | * some properties of RFC2109/2965 version 1 cookies: | 
|  | * - case-insensitive match of cookie names | 
|  | * - white space between the tokens | 
|  | * It does not support the following version 1 features: | 
|  | * - quoted strings as cookie values | 
|  | * - commas to separate cookies | 
|  | */ | 
|  |  | 
|  | if ((cookies_entry = apr_table_get(r->headers_in, "Cookie"))) { | 
|  | char *cookie, *last1, *last2; | 
|  | char *cookies = apr_pstrdup(r->pool, cookies_entry); | 
|  |  | 
|  | while ((cookie = apr_strtok(cookies, ";", &last1))) { | 
|  | char *name = apr_strtok(cookie, "=", &last2); | 
|  | /* last2 points to the next char following an '=' delim, | 
|  | or the trailing NUL char of the string */ | 
|  | char *value = last2; | 
|  | if (name && *name &&  value && *value) { | 
|  | char *last = value - 2; | 
|  | /* Move past leading WS */ | 
|  | name += strspn(name, " \t"); | 
|  | while (last >= name && apr_isspace(*last)) { | 
|  | *last = '\0'; | 
|  | --last; | 
|  | } | 
|  |  | 
|  | if (!strcasecmp(name, a)) { | 
|  | /* last1 points to the next char following the ';' delim, | 
|  | or the trailing NUL char of the string */ | 
|  | last = last1 - (*last1 ? 2 : 1); | 
|  | /* Move past leading WS */ | 
|  | value += strspn(value, " \t"); | 
|  | while (last >= value && apr_isspace(*last)) { | 
|  | *last = '\0'; | 
|  | --last; | 
|  | } | 
|  |  | 
|  | return ap_escape_logitem(r->pool, value); | 
|  | } | 
|  | } | 
|  | /* Iterate the remaining tokens using apr_strtok(NULL, ...) */ | 
|  | cookies = NULL; | 
|  | } | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static const char *log_request_time_custom(request_rec *r, char *a, | 
|  | apr_time_exp_t *xt) | 
|  | { | 
|  | apr_size_t retcode; | 
|  | char tstr[MAX_STRING_LEN]; | 
|  | apr_strftime(tstr, &retcode, sizeof(tstr), a, xt); | 
|  | return apr_pstrdup(r->pool, tstr); | 
|  | } | 
|  |  | 
|  | #define DEFAULT_REQUEST_TIME_SIZE 32 | 
|  | typedef struct { | 
|  | unsigned t; | 
|  | char timestr[DEFAULT_REQUEST_TIME_SIZE]; | 
|  | unsigned t_validate; | 
|  | } cached_request_time; | 
|  |  | 
|  | #define TIME_FMT_CUSTOM          0 | 
|  | #define TIME_FMT_CLF             1 | 
|  | #define TIME_FMT_ABS_SEC         2 | 
|  | #define TIME_FMT_ABS_MSEC        3 | 
|  | #define TIME_FMT_ABS_USEC        4 | 
|  | #define TIME_FMT_ABS_MSEC_FRAC   5 | 
|  | #define TIME_FMT_ABS_USEC_FRAC   6 | 
|  |  | 
|  | #define TIME_CACHE_SIZE 4 | 
|  | #define TIME_CACHE_MASK 3 | 
|  | static cached_request_time request_time_cache[TIME_CACHE_SIZE]; | 
|  |  | 
|  | static apr_time_t get_request_end_time(request_rec *r) | 
|  | { | 
|  | log_request_state *state = (log_request_state *)ap_get_module_config(r->request_config, | 
|  | &log_config_module); | 
|  | if (!state) { | 
|  | state = apr_pcalloc(r->pool, sizeof(log_request_state)); | 
|  | ap_set_module_config(r->request_config, &log_config_module, state); | 
|  | } | 
|  | if (state->request_end_time == 0) { | 
|  | state->request_end_time = apr_time_now(); | 
|  | } | 
|  | return state->request_end_time; | 
|  | } | 
|  |  | 
|  |  | 
|  | static const char *log_request_time(request_rec *r, char *a) | 
|  | { | 
|  | apr_time_exp_t xt; | 
|  | apr_time_t request_time = r->request_time; | 
|  | int fmt_type = TIME_FMT_CUSTOM; | 
|  | char *fmt = a; | 
|  |  | 
|  | if (fmt && *fmt) { | 
|  | if (!strncmp(fmt, "begin", 5)) { | 
|  | fmt += 5; | 
|  | if (!*fmt) { | 
|  | fmt_type = TIME_FMT_CLF; | 
|  | } | 
|  | else if (*fmt == ':') { | 
|  | fmt++; | 
|  | a = fmt; | 
|  | } | 
|  | } | 
|  | else if (!strncmp(fmt, "end", 3)) { | 
|  | fmt += 3; | 
|  | if (!*fmt) { | 
|  | request_time = get_request_end_time(r); | 
|  | fmt_type = TIME_FMT_CLF; | 
|  | } | 
|  | else if (*fmt == ':') { | 
|  | fmt++; | 
|  | a = fmt; | 
|  | request_time = get_request_end_time(r); | 
|  | } | 
|  | } | 
|  | if (!strncmp(fmt, "msec", 4)) { | 
|  | fmt += 4; | 
|  | if (!*fmt) { | 
|  | fmt_type = TIME_FMT_ABS_MSEC; | 
|  | } | 
|  | else if (!strcmp(fmt, "_frac")) { | 
|  | fmt_type = TIME_FMT_ABS_MSEC_FRAC; | 
|  | } | 
|  | } | 
|  | else if (!strncmp(fmt, "usec", 4)) { | 
|  | fmt += 4; | 
|  | if (!*fmt) { | 
|  | fmt_type = TIME_FMT_ABS_USEC; | 
|  | } | 
|  | else if (!strcmp(fmt, "_frac")) { | 
|  | fmt_type = TIME_FMT_ABS_USEC_FRAC; | 
|  | } | 
|  | } | 
|  | else if (!strcmp(fmt, "sec")) { | 
|  | fmt_type = TIME_FMT_ABS_SEC; | 
|  | } | 
|  | else if (!*fmt) { | 
|  | fmt_type = TIME_FMT_CLF; | 
|  | } | 
|  | } | 
|  | else { | 
|  | fmt_type = TIME_FMT_CLF; | 
|  | } | 
|  |  | 
|  | if (fmt_type >= TIME_FMT_ABS_SEC) {      /* Absolute (micro-/milli-)second time | 
|  | * or msec/usec fraction | 
|  | */ | 
|  | char* buf = apr_palloc(r->pool, 20); | 
|  | switch (fmt_type) { | 
|  | case TIME_FMT_ABS_SEC: | 
|  | apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_sec(request_time)); | 
|  | break; | 
|  | case TIME_FMT_ABS_MSEC: | 
|  | apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_as_msec(request_time)); | 
|  | break; | 
|  | case TIME_FMT_ABS_USEC: | 
|  | apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, request_time); | 
|  | break; | 
|  | case TIME_FMT_ABS_MSEC_FRAC: | 
|  | apr_snprintf(buf, 20, "%03" APR_TIME_T_FMT, apr_time_msec(request_time)); | 
|  | break; | 
|  | case TIME_FMT_ABS_USEC_FRAC: | 
|  | apr_snprintf(buf, 20, "%06" APR_TIME_T_FMT, apr_time_usec(request_time)); | 
|  | break; | 
|  | default: | 
|  | return "-"; | 
|  | } | 
|  | return buf; | 
|  | } | 
|  | else if (fmt_type == TIME_FMT_CUSTOM) {  /* Custom format */ | 
|  | /* The custom time formatting uses a very large temp buffer | 
|  | * on the stack.  To avoid using so much stack space in the | 
|  | * common case where we're not using a custom format, the code | 
|  | * for the custom format in a separate function.  (That's why | 
|  | * log_request_time_custom is not inlined right here.) | 
|  | */ | 
|  | ap_explode_recent_localtime(&xt, request_time); | 
|  | return log_request_time_custom(r, a, &xt); | 
|  | } | 
|  | else {                                   /* CLF format */ | 
|  | /* This code uses the same technique as ap_explode_recent_localtime(): | 
|  | * optimistic caching with logic to detect and correct race conditions. | 
|  | * See the comments in server/util_time.c for more information. | 
|  | */ | 
|  | cached_request_time* cached_time = apr_palloc(r->pool, | 
|  | sizeof(*cached_time)); | 
|  | unsigned t_seconds = (unsigned)apr_time_sec(request_time); | 
|  | unsigned i = t_seconds & TIME_CACHE_MASK; | 
|  | *cached_time = request_time_cache[i]; | 
|  | if ((t_seconds != cached_time->t) || | 
|  | (t_seconds != cached_time->t_validate)) { | 
|  |  | 
|  | /* Invalid or old snapshot, so compute the proper time string | 
|  | * and store it in the cache | 
|  | */ | 
|  | char sign; | 
|  | int timz; | 
|  |  | 
|  | ap_explode_recent_localtime(&xt, request_time); | 
|  | timz = xt.tm_gmtoff; | 
|  | if (timz < 0) { | 
|  | timz = -timz; | 
|  | sign = '-'; | 
|  | } | 
|  | else { | 
|  | sign = '+'; | 
|  | } | 
|  | cached_time->t = t_seconds; | 
|  | apr_snprintf(cached_time->timestr, DEFAULT_REQUEST_TIME_SIZE, | 
|  | "[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]", | 
|  | xt.tm_mday, apr_month_snames[xt.tm_mon], | 
|  | xt.tm_year+1900, xt.tm_hour, xt.tm_min, xt.tm_sec, | 
|  | sign, timz / (60*60), (timz % (60*60)) / 60); | 
|  | cached_time->t_validate = t_seconds; | 
|  | request_time_cache[i] = *cached_time; | 
|  | } | 
|  | return cached_time->timestr; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *log_request_duration_microseconds(request_rec *r, char *a) | 
|  | { | 
|  | return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, | 
|  | (get_request_end_time(r) - r->request_time)); | 
|  | } | 
|  |  | 
|  | static const char *log_request_duration_scaled(request_rec *r, char *a) | 
|  | { | 
|  | apr_time_t duration = get_request_end_time(r) - r->request_time; | 
|  | if (*a == '\0' || !strcasecmp(a, "s")) { | 
|  | duration = apr_time_sec(duration); | 
|  | } | 
|  | else if (!strcasecmp(a, "ms")) { | 
|  | duration = apr_time_as_msec(duration); | 
|  | } | 
|  | else if (!strcasecmp(a, "us")) { | 
|  | } | 
|  | else { | 
|  | /* bogus format */ | 
|  | return a; | 
|  | } | 
|  | return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, duration); | 
|  | } | 
|  |  | 
|  | /* These next two routines use the canonical name:port so that log | 
|  | * parsers don't need to duplicate all the vhost parsing crud. | 
|  | */ | 
|  | static const char *log_virtual_host(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, r->server->server_hostname); | 
|  | } | 
|  |  | 
|  | static const char *log_server_port(request_rec *r, char *a) | 
|  | { | 
|  | apr_port_t port; | 
|  |  | 
|  | if (*a == '\0' || !strcasecmp(a, "canonical")) { | 
|  | port = r->server->port ? r->server->port : ap_default_port(r); | 
|  | } | 
|  | else if (!strcasecmp(a, "remote")) { | 
|  | port = r->useragent_addr->port; | 
|  | } | 
|  | else if (!strcasecmp(a, "local")) { | 
|  | port = r->connection->local_addr->port; | 
|  | } | 
|  | else { | 
|  | /* bogus format */ | 
|  | return a; | 
|  | } | 
|  | return apr_itoa(r->pool, (int)port); | 
|  | } | 
|  |  | 
|  | /* This respects the setting of UseCanonicalName so that | 
|  | * the dynamic mass virtual hosting trick works better. | 
|  | */ | 
|  | static const char *log_server_name(request_rec *r, char *a) | 
|  | { | 
|  | return ap_escape_logitem(r->pool, ap_get_server_name(r)); | 
|  | } | 
|  |  | 
|  | static const char *log_pid_tid(request_rec *r, char *a) | 
|  | { | 
|  | if (*a == '\0' || !strcasecmp(a, "pid")) { | 
|  | return ap_append_pid(r->pool, "", ""); | 
|  | } | 
|  | else if (!strcasecmp(a, "tid") || !strcasecmp(a, "hextid")) { | 
|  | #if APR_HAS_THREADS | 
|  | apr_os_thread_t tid = apr_os_thread_current(); | 
|  | #else | 
|  | int tid = 0; /* APR will format "0" anyway but an arg is needed */ | 
|  | #endif | 
|  | return apr_psprintf(r->pool, | 
|  | #if APR_MAJOR_VERSION > 1 || (APR_MAJOR_VERSION == 1 && APR_MINOR_VERSION >= 2) | 
|  | /* APR can format a thread id in hex */ | 
|  | *a == 'h' ? "%pt" : "%pT", | 
|  | #else | 
|  | /* APR is missing the feature, so always use decimal */ | 
|  | "%pT", | 
|  | #endif | 
|  | &tid); | 
|  | } | 
|  | /* bogus format */ | 
|  | return a; | 
|  | } | 
|  |  | 
|  | static const char *log_connection_status(request_rec *r, char *a) | 
|  | { | 
|  | if (r->connection->aborted) | 
|  | return "X"; | 
|  |  | 
|  | if (r->connection->keepalive == AP_CONN_KEEPALIVE && | 
|  | (!r->server->keep_alive_max || | 
|  | (r->server->keep_alive_max - r->connection->keepalives) > 0)) { | 
|  | return "+"; | 
|  | } | 
|  | return "-"; | 
|  | } | 
|  |  | 
|  | static const char *log_requests_on_connection(request_rec *r, char *a) | 
|  | { | 
|  | int num = r->connection->keepalives ? r->connection->keepalives - 1 : 0; | 
|  | return apr_itoa(r->pool, num); | 
|  | } | 
|  |  | 
|  | /***************************************************************** | 
|  | * | 
|  | * Parsing the log format string | 
|  | */ | 
|  |  | 
|  | static char *parse_log_misc_string(apr_pool_t *p, log_format_item *it, | 
|  | const char **sa) | 
|  | { | 
|  | const char *s; | 
|  | char *d; | 
|  |  | 
|  | it->func = constant_item; | 
|  | it->conditions = NULL; | 
|  |  | 
|  | s = *sa; | 
|  | while (*s && *s != '%') { | 
|  | s++; | 
|  | } | 
|  | /* | 
|  | * This might allocate a few chars extra if there's a backslash | 
|  | * escape in the format string. | 
|  | */ | 
|  | it->arg = apr_palloc(p, s - *sa + 1); | 
|  |  | 
|  | d = it->arg; | 
|  | s = *sa; | 
|  | while (*s && *s != '%') { | 
|  | if (*s != '\\') { | 
|  | *d++ = *s++; | 
|  | } | 
|  | else { | 
|  | s++; | 
|  | switch (*s) { | 
|  | case '\\': | 
|  | *d++ = '\\'; | 
|  | s++; | 
|  | break; | 
|  | case 'r': | 
|  | *d++ = '\r'; | 
|  | s++; | 
|  | break; | 
|  | case 'n': | 
|  | *d++ = '\n'; | 
|  | s++; | 
|  | break; | 
|  | case 't': | 
|  | *d++ = '\t'; | 
|  | s++; | 
|  | break; | 
|  | default: | 
|  | /* copy verbatim */ | 
|  | *d++ = '\\'; | 
|  | /* | 
|  | * Allow the loop to deal with this *s in the normal | 
|  | * fashion so that it handles end of string etc. | 
|  | * properly. | 
|  | */ | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | *d = '\0'; | 
|  |  | 
|  | *sa = s; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa) | 
|  | { | 
|  | const char *s = *sa; | 
|  | ap_log_handler *handler = NULL; | 
|  |  | 
|  | if (*s != '%') { | 
|  | return parse_log_misc_string(p, it, sa); | 
|  | } | 
|  |  | 
|  | ++s; | 
|  | it->condition_sense = 0; | 
|  | it->conditions = NULL; | 
|  |  | 
|  | if (*s == '%') { | 
|  | it->arg = "%"; | 
|  | it->func = constant_item; | 
|  | *sa = ++s; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | it->want_orig = -1; | 
|  | it->arg = "";               /* For safety's sake... */ | 
|  |  | 
|  | while (*s) { | 
|  | int i; | 
|  |  | 
|  | switch (*s) { | 
|  | case '!': | 
|  | ++s; | 
|  | it->condition_sense = !it->condition_sense; | 
|  | break; | 
|  |  | 
|  | case '<': | 
|  | ++s; | 
|  | it->want_orig = 1; | 
|  | break; | 
|  |  | 
|  | case '>': | 
|  | ++s; | 
|  | it->want_orig = 0; | 
|  | break; | 
|  |  | 
|  | case ',': | 
|  | ++s; | 
|  | break; | 
|  |  | 
|  | case '{': | 
|  | ++s; | 
|  | it->arg = ap_getword(p, &s, '}'); | 
|  | break; | 
|  |  | 
|  | case '0': | 
|  | case '1': | 
|  | case '2': | 
|  | case '3': | 
|  | case '4': | 
|  | case '5': | 
|  | case '6': | 
|  | case '7': | 
|  | case '8': | 
|  | case '9': | 
|  | i = *s - '0'; | 
|  | while (apr_isdigit(*++s)) { | 
|  | i = i * 10 + (*s) - '0'; | 
|  | } | 
|  | if (!it->conditions) { | 
|  | it->conditions = apr_array_make(p, 4, sizeof(int)); | 
|  | } | 
|  | *(int *) apr_array_push(it->conditions) = i; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | /* check for '^' + two character format first */ | 
|  | if (*s == '^' && *(s+1) && *(s+2)) { | 
|  | handler = (ap_log_handler *)apr_hash_get(log_hash, s, 3); | 
|  | if (handler) { | 
|  | s += 3; | 
|  | } | 
|  | } | 
|  | if (!handler) { | 
|  | handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1); | 
|  | } | 
|  | if (!handler) { | 
|  | char dummy[2]; | 
|  |  | 
|  | dummy[0] = s[-1]; | 
|  | dummy[1] = '\0'; | 
|  | return apr_pstrcat(p, "Unrecognized LogFormat directive %", | 
|  | dummy, NULL); | 
|  | } | 
|  | it->func = handler->func; | 
|  | if (it->want_orig == -1) { | 
|  | it->want_orig = handler->want_orig_default; | 
|  | } | 
|  | *sa = s; | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | return "Ran off end of LogFormat parsing args to some directive"; | 
|  | } | 
|  |  | 
|  | static apr_array_header_t *parse_log_string(apr_pool_t *p, const char *s, const char **err) | 
|  | { | 
|  | apr_array_header_t *a = apr_array_make(p, 30, sizeof(log_format_item)); | 
|  | char *res; | 
|  |  | 
|  | while (*s) { | 
|  | if ((res = parse_log_item(p, (log_format_item *) apr_array_push(a), &s))) { | 
|  | *err = res; | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | s = APR_EOL_STR; | 
|  | parse_log_item(p, (log_format_item *) apr_array_push(a), &s); | 
|  | return a; | 
|  | } | 
|  |  | 
|  | /***************************************************************** | 
|  | * | 
|  | * Actually logging. | 
|  | */ | 
|  |  | 
|  | static const char *process_item(request_rec *r, request_rec *orig, | 
|  | log_format_item *item) | 
|  | { | 
|  | const char *cp; | 
|  |  | 
|  | /* First, see if we need to process this thing at all... */ | 
|  |  | 
|  | if (item->conditions && item->conditions->nelts != 0) { | 
|  | int i; | 
|  | int *conds = (int *) item->conditions->elts; | 
|  | int in_list = 0; | 
|  |  | 
|  | for (i = 0; i < item->conditions->nelts; ++i) { | 
|  | if (r->status == conds[i]) { | 
|  | in_list = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((item->condition_sense && in_list) | 
|  | || (!item->condition_sense && !in_list)) { | 
|  | return "-"; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* We do.  Do it... */ | 
|  |  | 
|  | cp = (*item->func) (item->want_orig ? orig : r, item->arg); | 
|  | return cp ? cp : "-"; | 
|  | } | 
|  |  | 
|  | static void flush_log(buffered_log *buf) | 
|  | { | 
|  | if (buf->outcnt && buf->handle != NULL) { | 
|  | apr_file_write(buf->handle, buf->outbuf, &buf->outcnt); | 
|  | buf->outcnt = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static int config_log_transaction(request_rec *r, config_log_state *cls, | 
|  | apr_array_header_t *default_format) | 
|  | { | 
|  | log_format_item *items; | 
|  | const char **strs; | 
|  | int *strl; | 
|  | request_rec *orig; | 
|  | int i; | 
|  | apr_size_t len = 0; | 
|  | apr_array_header_t *format; | 
|  | char *envar; | 
|  | apr_status_t rv; | 
|  |  | 
|  | if (cls->fname == NULL) { | 
|  | return DECLINED; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * See if we've got any conditional envariable-controlled logging decisions | 
|  | * to make. | 
|  | */ | 
|  | if (cls->condition_var != NULL) { | 
|  | envar = cls->condition_var; | 
|  | if (*envar != '!') { | 
|  | if (apr_table_get(r->subprocess_env, envar) == NULL) { | 
|  | return DECLINED; | 
|  | } | 
|  | } | 
|  | else { | 
|  | if (apr_table_get(r->subprocess_env, &envar[1]) != NULL) { | 
|  | return DECLINED; | 
|  | } | 
|  | } | 
|  | } | 
|  | else if (cls->condition_expr != NULL) { | 
|  | const char *err; | 
|  | int rc = ap_expr_exec(r, cls->condition_expr, &err); | 
|  | if (rc < 0) | 
|  | ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00644) | 
|  | "Error evaluating log condition: %s", err); | 
|  | if (rc <= 0) | 
|  | return DECLINED; | 
|  | } | 
|  |  | 
|  | format = cls->format ? cls->format : default_format; | 
|  |  | 
|  | strs = apr_palloc(r->pool, sizeof(char *) * (format->nelts)); | 
|  | strl = apr_palloc(r->pool, sizeof(int) * (format->nelts)); | 
|  | items = (log_format_item *) format->elts; | 
|  |  | 
|  | orig = r; | 
|  | while (orig->prev) { | 
|  | orig = orig->prev; | 
|  | } | 
|  | while (r->next) { | 
|  | r = r->next; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < format->nelts; ++i) { | 
|  | strs[i] = process_item(r, orig, &items[i]); | 
|  | } | 
|  |  | 
|  | for (i = 0; i < format->nelts; ++i) { | 
|  | len += strl[i] = strlen(strs[i]); | 
|  | } | 
|  | if (!log_writer) { | 
|  | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00645) | 
|  | "log writer isn't correctly setup"); | 
|  | return HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  | rv = log_writer(r, cls->log_writer, strs, strl, format->nelts, len); | 
|  | if (rv != APR_SUCCESS) { | 
|  | ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00646) | 
|  | "Error writing to %s", cls->fname); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | static int multi_log_transaction(request_rec *r) | 
|  | { | 
|  | multi_log_state *mls = ap_get_module_config(r->server->module_config, | 
|  | &log_config_module); | 
|  | config_log_state *clsarray; | 
|  | int i; | 
|  |  | 
|  | /* | 
|  | * Initialize per request state | 
|  | */ | 
|  | log_request_state *state = apr_pcalloc(r->pool, sizeof(log_request_state)); | 
|  | ap_set_module_config(r->request_config, &log_config_module, state); | 
|  |  | 
|  | /* | 
|  | * Log this transaction.. | 
|  | */ | 
|  | if (mls->config_logs->nelts) { | 
|  | clsarray = (config_log_state *) mls->config_logs->elts; | 
|  | for (i = 0; i < mls->config_logs->nelts; ++i) { | 
|  | config_log_state *cls = &clsarray[i]; | 
|  |  | 
|  | config_log_transaction(r, cls, mls->default_format); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mls->server_config_logs) { | 
|  | clsarray = (config_log_state *) mls->server_config_logs->elts; | 
|  | for (i = 0; i < mls->server_config_logs->nelts; ++i) { | 
|  | config_log_state *cls = &clsarray[i]; | 
|  |  | 
|  | if (cls->inherit || !mls->config_logs->nelts) { | 
|  | config_log_transaction(r, cls, mls->default_format); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | /***************************************************************** | 
|  | * | 
|  | * Module glue... | 
|  | */ | 
|  |  | 
|  | static void *make_config_log_state(apr_pool_t *p, server_rec *s) | 
|  | { | 
|  | multi_log_state *mls; | 
|  |  | 
|  | mls = (multi_log_state *) apr_palloc(p, sizeof(multi_log_state)); | 
|  | mls->config_logs = apr_array_make(p, 1, sizeof(config_log_state)); | 
|  | mls->default_format_string = NULL; | 
|  | mls->default_format = NULL; | 
|  | mls->server_config_logs = NULL; | 
|  | mls->formats = apr_table_make(p, 4); | 
|  | apr_table_setn(mls->formats, "CLF", DEFAULT_LOG_FORMAT); | 
|  |  | 
|  | return mls; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Use the merger to simply add a pointer from the vhost log state | 
|  | * to the log of logs specified for the non-vhost configuration.  Make sure | 
|  | * vhosts inherit any globally-defined format names. | 
|  | */ | 
|  |  | 
|  | static void *merge_config_log_state(apr_pool_t *p, void *basev, void *addv) | 
|  | { | 
|  | multi_log_state *base = (multi_log_state *) basev; | 
|  | multi_log_state *add = (multi_log_state *) addv; | 
|  |  | 
|  | add->server_config_logs = base->config_logs; | 
|  | if (!add->default_format) { | 
|  | add->default_format_string = base->default_format_string; | 
|  | add->default_format = base->default_format; | 
|  | } | 
|  | add->formats = apr_table_overlay(p, base->formats, add->formats); | 
|  |  | 
|  | return add; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Set the default logfile format, or define a nickname for a format string. | 
|  | */ | 
|  | static const char *log_format(cmd_parms *cmd, void *dummy, const char *fmt, | 
|  | const char *name) | 
|  | { | 
|  | const char *err_string = NULL; | 
|  | multi_log_state *mls = ap_get_module_config(cmd->server->module_config, | 
|  | &log_config_module); | 
|  |  | 
|  | /* | 
|  | * If we were given two arguments, the second is a name to be given to the | 
|  | * format.  This syntax just defines the nickname - it doesn't actually | 
|  | * make the format the default. | 
|  | */ | 
|  | if (name != NULL) { | 
|  | parse_log_string(cmd->pool, fmt, &err_string); | 
|  | if (err_string == NULL) { | 
|  | apr_table_setn(mls->formats, name, fmt); | 
|  | } | 
|  | } | 
|  | else { | 
|  | mls->default_format_string = fmt; | 
|  | mls->default_format = parse_log_string(cmd->pool, fmt, &err_string); | 
|  | } | 
|  | return err_string; | 
|  | } | 
|  |  | 
|  |  | 
|  | static const char *add_custom_log(cmd_parms *cmd, void *dummy, const char *fn, | 
|  | const char *fmt, const char *envclause) | 
|  | { | 
|  | const char *err_string = NULL; | 
|  | multi_log_state *mls = ap_get_module_config(cmd->server->module_config, | 
|  | &log_config_module); | 
|  | config_log_state *cls; | 
|  |  | 
|  | cls = (config_log_state *) apr_array_push(mls->config_logs); | 
|  | cls->condition_var = NULL; | 
|  | cls->condition_expr = NULL; | 
|  | if (envclause != NULL) { | 
|  | if (strncasecmp(envclause, "env=", 4) == 0) { | 
|  | if ((envclause[4] == '\0') | 
|  | || ((envclause[4] == '!') && (envclause[5] == '\0'))) { | 
|  | return "missing environment variable name"; | 
|  | } | 
|  | cls->condition_var = apr_pstrdup(cmd->pool, &envclause[4]); | 
|  | } | 
|  | else if (strncasecmp(envclause, "expr=", 5) == 0) { | 
|  | const char *err; | 
|  | if ((envclause[5] == '\0')) | 
|  | return "missing condition"; | 
|  | cls->condition_expr = ap_expr_parse_cmd(cmd, &envclause[5], | 
|  | AP_EXPR_FLAG_DONT_VARY, | 
|  | &err, NULL); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  | else { | 
|  | return "error in condition clause"; | 
|  | } | 
|  | } | 
|  |  | 
|  | cls->fname = fn; | 
|  | cls->format_string = fmt; | 
|  | cls->directive = cmd->directive; | 
|  | if (fmt == NULL) { | 
|  | cls->format = NULL; | 
|  | } | 
|  | else { | 
|  | cls->format = parse_log_string(cmd->pool, fmt, &err_string); | 
|  | } | 
|  | cls->log_writer = NULL; | 
|  |  | 
|  | return err_string; | 
|  | } | 
|  |  | 
|  | static const char *add_global_log(cmd_parms *cmd, void *dummy, const char *fn, | 
|  | const char *fmt, const char *envclause) { | 
|  | multi_log_state *mls = ap_get_module_config(cmd->server->module_config, | 
|  | &log_config_module); | 
|  | config_log_state *clsarray; | 
|  | config_log_state *cls; | 
|  | const char *ret; | 
|  |  | 
|  | const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); | 
|  |  | 
|  | if (err) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Add a custom log through the normal channel */ | 
|  | ret = add_custom_log(cmd, dummy, fn, fmt, envclause); | 
|  |  | 
|  | /* Set the inherit flag unless there was some error */ | 
|  | if (ret == NULL) { | 
|  | clsarray = (config_log_state*)mls->config_logs->elts; | 
|  | cls = &clsarray[mls->config_logs->nelts-1]; | 
|  | cls->inherit = 1; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const char *set_transfer_log(cmd_parms *cmd, void *dummy, | 
|  | const char *fn) | 
|  | { | 
|  | return add_custom_log(cmd, dummy, fn, NULL, NULL); | 
|  | } | 
|  |  | 
|  | static const char *set_buffered_logs_on(cmd_parms *parms, void *dummy, int flag) | 
|  | { | 
|  | buffered_logs = flag; | 
|  | if (buffered_logs) { | 
|  | ap_log_set_writer_init(ap_buffered_log_writer_init); | 
|  | ap_log_set_writer(ap_buffered_log_writer); | 
|  | } | 
|  | else { | 
|  | ap_log_set_writer_init(ap_default_log_writer_init); | 
|  | ap_log_set_writer(ap_default_log_writer); | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  | static const command_rec config_log_cmds[] = | 
|  | { | 
|  | AP_INIT_TAKE23("CustomLog", add_custom_log, NULL, RSRC_CONF, | 
|  | "a file name, a custom log format string or format name, " | 
|  | "and an optional \"env=\" or \"expr=\" clause (see docs)"), | 
|  | AP_INIT_TAKE23("GlobalLog", add_global_log, NULL, RSRC_CONF, | 
|  | "Same as CustomLog, but forces virtualhosts to inherit the log"), | 
|  | AP_INIT_TAKE1("TransferLog", set_transfer_log, NULL, RSRC_CONF, | 
|  | "the filename of the access log"), | 
|  | AP_INIT_TAKE12("LogFormat", log_format, NULL, RSRC_CONF, | 
|  | "a log format string (see docs) and an optional format name"), | 
|  | AP_INIT_FLAG("BufferedLogs", set_buffered_logs_on, NULL, RSRC_CONF, | 
|  | "Enable Buffered Logging (experimental)"), | 
|  | {NULL} | 
|  | }; | 
|  |  | 
|  | static config_log_state *open_config_log(server_rec *s, apr_pool_t *p, | 
|  | config_log_state *cls, | 
|  | apr_array_header_t *default_format) | 
|  | { | 
|  | if (cls->log_writer != NULL) { | 
|  | return cls;             /* virtual config shared w/main server */ | 
|  | } | 
|  |  | 
|  | if (cls->fname == NULL) { | 
|  | return cls;             /* Leave it NULL to decline.  */ | 
|  | } | 
|  |  | 
|  | cls->log_writer = log_writer_init(p, s, cls->fname); | 
|  | if (cls->log_writer == NULL) | 
|  | return NULL; | 
|  |  | 
|  | return cls; | 
|  | } | 
|  |  | 
|  | static int open_multi_logs(server_rec *s, apr_pool_t *p) | 
|  | { | 
|  | int i; | 
|  | multi_log_state *mls = ap_get_module_config(s->module_config, | 
|  | &log_config_module); | 
|  | config_log_state *clsarray; | 
|  | const char *dummy; | 
|  | const char *format; | 
|  |  | 
|  | if (mls->default_format_string) { | 
|  | format = apr_table_get(mls->formats, mls->default_format_string); | 
|  | if (format) { | 
|  | mls->default_format = parse_log_string(p, format, &dummy); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!mls->default_format) { | 
|  | mls->default_format = parse_log_string(p, DEFAULT_LOG_FORMAT, &dummy); | 
|  | } | 
|  |  | 
|  | if (mls->config_logs->nelts) { | 
|  | clsarray = (config_log_state *) mls->config_logs->elts; | 
|  | for (i = 0; i < mls->config_logs->nelts; ++i) { | 
|  | config_log_state *cls = &clsarray[i]; | 
|  |  | 
|  | if (cls->format_string) { | 
|  | format = apr_table_get(mls->formats, cls->format_string); | 
|  | if (format) { | 
|  | cls->format = parse_log_string(p, format, &dummy); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!open_config_log(s, p, cls, mls->default_format)) { | 
|  | /* Failure already logged by open_config_log */ | 
|  | return DONE; | 
|  | } | 
|  | } | 
|  | } | 
|  | else if (mls->server_config_logs) { | 
|  | clsarray = (config_log_state *) mls->server_config_logs->elts; | 
|  | for (i = 0; i < mls->server_config_logs->nelts; ++i) { | 
|  | config_log_state *cls = &clsarray[i]; | 
|  |  | 
|  | if (cls->format_string) { | 
|  | format = apr_table_get(mls->formats, cls->format_string); | 
|  | if (format) { | 
|  | cls->format = parse_log_string(p, format, &dummy); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!open_config_log(s, p, cls, mls->default_format)) { | 
|  | /* Failure already logged by open_config_log */ | 
|  | return DONE; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static apr_status_t flush_all_logs(void *data) | 
|  | { | 
|  | server_rec *s = data; | 
|  | multi_log_state *mls; | 
|  | apr_array_header_t *log_list; | 
|  | config_log_state *clsarray; | 
|  | buffered_log *buf; | 
|  | int i; | 
|  |  | 
|  | if (!buffered_logs) | 
|  | return APR_SUCCESS; | 
|  |  | 
|  | for (; s; s = s->next) { | 
|  | mls = ap_get_module_config(s->module_config, &log_config_module); | 
|  | log_list = NULL; | 
|  | if (mls->config_logs->nelts) { | 
|  | log_list = mls->config_logs; | 
|  | } | 
|  | else if (mls->server_config_logs) { | 
|  | log_list = mls->server_config_logs; | 
|  | } | 
|  | if (log_list) { | 
|  | clsarray = (config_log_state *) log_list->elts; | 
|  | for (i = 0; i < log_list->nelts; ++i) { | 
|  | buf = clsarray[i].log_writer; | 
|  | flush_log(buf); | 
|  | } | 
|  | } | 
|  | } | 
|  | return APR_SUCCESS; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int init_config_log(apr_pool_t *pc, apr_pool_t *p, apr_pool_t *pt, server_rec *s) | 
|  | { | 
|  | int res; | 
|  |  | 
|  | /* First init the buffered logs array, which is needed when opening the logs. */ | 
|  | if (buffered_logs) { | 
|  | all_buffered_logs = apr_array_make(p, 5, sizeof(buffered_log *)); | 
|  | } | 
|  |  | 
|  | /* Next, do "physical" server, which gets default log fd and format | 
|  | * for the virtual servers, if they don't override... | 
|  | */ | 
|  | res = open_multi_logs(s, p); | 
|  |  | 
|  | /* Then, virtual servers */ | 
|  |  | 
|  | for (s = s->next; (res == OK) && s; s = s->next) { | 
|  | res = open_multi_logs(s, p); | 
|  | } | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static void init_child(apr_pool_t *p, server_rec *s) | 
|  | { | 
|  | int mpm_threads; | 
|  |  | 
|  | ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads); | 
|  |  | 
|  | /* Now register the last buffer flush with the cleanup engine */ | 
|  | if (buffered_logs) { | 
|  | int i; | 
|  | buffered_log **array = (buffered_log **)all_buffered_logs->elts; | 
|  |  | 
|  | apr_pool_cleanup_register(p, s, flush_all_logs, flush_all_logs); | 
|  |  | 
|  | for (i = 0; i < all_buffered_logs->nelts; i++) { | 
|  | buffered_log *this = array[i]; | 
|  |  | 
|  | #if APR_HAS_THREADS | 
|  | if (mpm_threads > 1) { | 
|  | apr_status_t rv; | 
|  |  | 
|  | this->mutex.type = apr_anylock_threadmutex; | 
|  | rv = apr_thread_mutex_create(&this->mutex.lock.tm, | 
|  | APR_THREAD_MUTEX_DEFAULT, | 
|  | p); | 
|  | if (rv != APR_SUCCESS) { | 
|  | ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00647) | 
|  | "could not initialize buffered log mutex, " | 
|  | "transfer log may become corrupted"); | 
|  | this->mutex.type = apr_anylock_none; | 
|  | } | 
|  | } | 
|  | else | 
|  | #endif | 
|  | { | 
|  | this->mutex.type = apr_anylock_none; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ap_register_log_handler(apr_pool_t *p, char *tag, | 
|  | ap_log_handler_fn_t *handler, int def) | 
|  | { | 
|  | ap_log_handler *log_struct = apr_palloc(p, sizeof(*log_struct)); | 
|  | log_struct->func = handler; | 
|  | log_struct->want_orig_default = def; | 
|  |  | 
|  | apr_hash_set(log_hash, tag, strlen(tag), (const void *)log_struct); | 
|  | } | 
|  | static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle) | 
|  | { | 
|  | ap_log_writer_init *old = log_writer_init; | 
|  | log_writer_init = handle; | 
|  |  | 
|  | return old; | 
|  |  | 
|  | } | 
|  | static ap_log_writer *ap_log_set_writer(ap_log_writer *handle) | 
|  | { | 
|  | ap_log_writer *old = log_writer; | 
|  | log_writer = handle; | 
|  |  | 
|  | return old; | 
|  | } | 
|  |  | 
|  | static apr_status_t ap_default_log_writer( request_rec *r, | 
|  | void *handle, | 
|  | const char **strs, | 
|  | int *strl, | 
|  | int nelts, | 
|  | apr_size_t len) | 
|  |  | 
|  | { | 
|  | char *str; | 
|  | char *s; | 
|  | int i; | 
|  | apr_status_t rv; | 
|  |  | 
|  | /* | 
|  | * We do this memcpy dance because write() is atomic for len < PIPE_BUF, | 
|  | * while writev() need not be. | 
|  | */ | 
|  | str = apr_palloc(r->pool, len + 1); | 
|  |  | 
|  | for (i = 0, s = str; i < nelts; ++i) { | 
|  | memcpy(s, strs[i], strl[i]); | 
|  | s += strl[i]; | 
|  | } | 
|  |  | 
|  | rv = apr_file_write((apr_file_t*)handle, str, &len); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  | static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s, | 
|  | const char* name) | 
|  | { | 
|  | if (*name == '|') { | 
|  | piped_log *pl; | 
|  |  | 
|  | pl = ap_open_piped_log(p, name + 1); | 
|  | if (pl == NULL) { | 
|  | return NULL; | 
|  | } | 
|  | return ap_piped_log_write_fd(pl); | 
|  | } | 
|  | else { | 
|  | const char *fname = ap_server_root_relative(p, name); | 
|  | apr_file_t *fd; | 
|  | apr_status_t rv; | 
|  |  | 
|  | if (!fname) { | 
|  | ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(00648) | 
|  | "invalid transfer log path %s.", name); | 
|  | return NULL; | 
|  | } | 
|  | rv = apr_file_open(&fd, fname, xfer_flags, xfer_perms, p); | 
|  | if (rv != APR_SUCCESS) { | 
|  | ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00649) | 
|  | "could not open transfer log file %s.", fname); | 
|  | return NULL; | 
|  | } | 
|  | return fd; | 
|  | } | 
|  | } | 
|  | static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s, | 
|  | const char* name) | 
|  | { | 
|  | buffered_log *b; | 
|  | b = apr_pcalloc(p, sizeof(buffered_log)); | 
|  | b->handle = ap_default_log_writer_init(p, s, name); | 
|  |  | 
|  | if (b->handle) { | 
|  | *(buffered_log **)apr_array_push(all_buffered_logs) = b; | 
|  | return b; | 
|  | } | 
|  | else | 
|  | return NULL; | 
|  | } | 
|  | static apr_status_t ap_buffered_log_writer(request_rec *r, | 
|  | void *handle, | 
|  | const char **strs, | 
|  | int *strl, | 
|  | int nelts, | 
|  | apr_size_t len) | 
|  |  | 
|  | { | 
|  | char *str; | 
|  | char *s; | 
|  | int i; | 
|  | apr_status_t rv; | 
|  | buffered_log *buf = (buffered_log*)handle; | 
|  |  | 
|  | if ((rv = APR_ANYLOCK_LOCK(&buf->mutex)) != APR_SUCCESS) { | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | if (len + buf->outcnt > LOG_BUFSIZE) { | 
|  | flush_log(buf); | 
|  | } | 
|  | if (len >= LOG_BUFSIZE) { | 
|  | apr_size_t w; | 
|  |  | 
|  | /* | 
|  | * We do this memcpy dance because write() is atomic for | 
|  | * len < PIPE_BUF, while writev() need not be. | 
|  | */ | 
|  | str = apr_palloc(r->pool, len + 1); | 
|  | for (i = 0, s = str; i < nelts; ++i) { | 
|  | memcpy(s, strs[i], strl[i]); | 
|  | s += strl[i]; | 
|  | } | 
|  | w = len; | 
|  | rv = apr_file_write(buf->handle, str, &w); | 
|  |  | 
|  | } | 
|  | else { | 
|  | for (i = 0, s = &buf->outbuf[buf->outcnt]; i < nelts; ++i) { | 
|  | memcpy(s, strs[i], strl[i]); | 
|  | s += strl[i]; | 
|  | } | 
|  | buf->outcnt += len; | 
|  | rv = APR_SUCCESS; | 
|  | } | 
|  |  | 
|  | APR_ANYLOCK_UNLOCK(&buf->mutex); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) | 
|  | { | 
|  | static APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register; | 
|  |  | 
|  | log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler); | 
|  |  | 
|  | if (log_pfn_register) { | 
|  | log_pfn_register(p, "h", log_remote_host, 0); | 
|  | log_pfn_register(p, "a", log_remote_address, 0 ); | 
|  | log_pfn_register(p, "A", log_local_address, 0 ); | 
|  | log_pfn_register(p, "l", log_remote_logname, 0); | 
|  | log_pfn_register(p, "u", log_remote_user, 0); | 
|  | log_pfn_register(p, "t", log_request_time, 0); | 
|  | log_pfn_register(p, "f", log_request_file, 0); | 
|  | log_pfn_register(p, "b", clf_log_bytes_sent, 0); | 
|  | log_pfn_register(p, "B", log_bytes_sent, 0); | 
|  | log_pfn_register(p, "i", log_header_in, 0); | 
|  | log_pfn_register(p, "o", log_header_out, 0); | 
|  | log_pfn_register(p, "n", log_note, 0); | 
|  | log_pfn_register(p, "L", log_log_id, 1); | 
|  | log_pfn_register(p, "e", log_env_var, 0); | 
|  | log_pfn_register(p, "V", log_server_name, 0); | 
|  | log_pfn_register(p, "v", log_virtual_host, 0); | 
|  | log_pfn_register(p, "p", log_server_port, 0); | 
|  | log_pfn_register(p, "P", log_pid_tid, 0); | 
|  | log_pfn_register(p, "H", log_request_protocol, 0); | 
|  | log_pfn_register(p, "m", log_request_method, 0); | 
|  | log_pfn_register(p, "q", log_request_query, 0); | 
|  | log_pfn_register(p, "X", log_connection_status, 0); | 
|  | log_pfn_register(p, "C", log_cookie, 0); | 
|  | log_pfn_register(p, "k", log_requests_on_connection, 0); | 
|  | log_pfn_register(p, "r", log_request_line, 1); | 
|  | log_pfn_register(p, "D", log_request_duration_microseconds, 1); | 
|  | log_pfn_register(p, "T", log_request_duration_scaled, 1); | 
|  | log_pfn_register(p, "U", log_request_uri, 1); | 
|  | log_pfn_register(p, "s", log_status, 1); | 
|  | log_pfn_register(p, "R", log_handler, 1); | 
|  |  | 
|  | log_pfn_register(p, "^ti", log_trailer_in, 0); | 
|  | log_pfn_register(p, "^to", log_trailer_out, 0); | 
|  | } | 
|  |  | 
|  | /* reset to default conditions */ | 
|  | ap_log_set_writer_init(ap_default_log_writer_init); | 
|  | ap_log_set_writer(ap_default_log_writer); | 
|  | buffered_logs = 0; | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | static int check_log_dir(apr_pool_t *p, server_rec *s, config_log_state *cls) | 
|  | { | 
|  | if (!cls->fname || cls->fname[0] == '|' || !cls->directive) { | 
|  | return OK; | 
|  | } | 
|  | else { | 
|  | char *abs = ap_server_root_relative(p, cls->fname); | 
|  | char *dir = ap_make_dirstr_parent(p, abs); | 
|  | apr_finfo_t finfo; | 
|  | const ap_directive_t *directive = cls->directive; | 
|  | apr_status_t rv = apr_stat(&finfo, dir, APR_FINFO_TYPE, p); | 
|  | cls->directive = NULL; /* Don't check this config_log_state again */ | 
|  | if (rv == APR_SUCCESS && finfo.filetype != APR_DIR) | 
|  | rv = APR_ENOTDIR; | 
|  | if (rv != APR_SUCCESS) { | 
|  | ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, rv, s, | 
|  | APLOGNO(02297) | 
|  | "Cannot access directory '%s' for log file '%s' " | 
|  | "defined at %s:%d", dir, cls->fname, | 
|  | directive->filename, directive->line_num); | 
|  | return !OK; | 
|  | } | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | static int log_check_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) | 
|  | { | 
|  | int rv = OK; | 
|  | while (s) { | 
|  | multi_log_state *mls = ap_get_module_config(s->module_config, | 
|  | &log_config_module); | 
|  | /* | 
|  | * We don't need to check mls->server_config_logs because it just | 
|  | * points to the parent server's mls->config_logs. | 
|  | */ | 
|  | apr_array_header_t *log_list = mls->config_logs; | 
|  | config_log_state *clsarray = (config_log_state *) log_list->elts; | 
|  | int i; | 
|  | for (i = 0; i < log_list->nelts; ++i) { | 
|  | if (check_log_dir(ptemp, s, &clsarray[i]) != OK) | 
|  | rv = !OK; | 
|  | } | 
|  |  | 
|  | s = s->next; | 
|  | } | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | static void register_hooks(apr_pool_t *p) | 
|  | { | 
|  | ap_hook_pre_config(log_pre_config,NULL,NULL,APR_HOOK_REALLY_FIRST); | 
|  | ap_hook_check_config(log_check_config,NULL,NULL,APR_HOOK_MIDDLE); | 
|  | ap_hook_child_init(init_child,NULL,NULL,APR_HOOK_MIDDLE); | 
|  | ap_hook_open_logs(init_config_log,NULL,NULL,APR_HOOK_MIDDLE); | 
|  | ap_hook_log_transaction(multi_log_transaction,NULL,NULL,APR_HOOK_MIDDLE); | 
|  |  | 
|  | /* Init log_hash before we register the optional function. It is | 
|  | * possible for the optional function, ap_register_log_handler, | 
|  | * to be called before any other mod_log_config hooks are called. | 
|  | * As a policy, we should init everything required by an optional function | 
|  | * before calling APR_REGISTER_OPTIONAL_FN. | 
|  | */ | 
|  | log_hash = apr_hash_make(p); | 
|  | APR_REGISTER_OPTIONAL_FN(ap_register_log_handler); | 
|  | APR_REGISTER_OPTIONAL_FN(ap_log_set_writer_init); | 
|  | APR_REGISTER_OPTIONAL_FN(ap_log_set_writer); | 
|  | } | 
|  |  | 
|  | AP_DECLARE_MODULE(log_config) = | 
|  | { | 
|  | STANDARD20_MODULE_STUFF, | 
|  | NULL,                       /* create per-dir config */ | 
|  | NULL,                       /* merge per-dir config */ | 
|  | make_config_log_state,      /* server config */ | 
|  | merge_config_log_state,     /* merge server config */ | 
|  | config_log_cmds,            /* command apr_table_t */ | 
|  | register_hooks              /* register hooks */ | 
|  | }; | 
|  |  |