mirror of
https://github.com/apache/httpd.git
synced 2025-04-18 22:24:07 +03:00
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1542416 13f79535-47bb-0310-9956-ffa450edef68
775 lines
23 KiB
C
775 lines
23 KiB
C
/**
|
|
* Licensed 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Originally written @ BBC by Graham Leggett
|
|
* Copyright 2009-2011 British Broadcasting Corporation
|
|
*
|
|
*/
|
|
|
|
#include "apr.h"
|
|
#include "apr_lib.h"
|
|
#include "apr_buckets.h"
|
|
#include "apr_file_io.h"
|
|
#include "apr_file_info.h"
|
|
#include "apr_hash.h"
|
|
#include "apr_poll.h"
|
|
#include "apr_portable.h"
|
|
#include "apr_getopt.h"
|
|
#include "apr_signal.h"
|
|
#include "apr_strings.h"
|
|
#include "apr_uuid.h"
|
|
#if APR_HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#if APR_HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "ap_release.h"
|
|
|
|
#define DEFAULT_MAXLINES 0
|
|
#define DEFAULT_MAXSIZE 0
|
|
#define DEFAULT_AGE 0 * 1000 * 1000
|
|
#define DEFAULT_PREFIX 0
|
|
#define DEFAULT_NONBLOCK 0
|
|
|
|
typedef struct file_rec
|
|
{
|
|
apr_pool_t *pool;
|
|
apr_file_t *file_err;
|
|
apr_file_t *file_in;
|
|
apr_file_t *file_out;
|
|
const char *directory;
|
|
apr_bucket_alloc_t *alloc;
|
|
apr_bucket_brigade *bb;
|
|
apr_hash_t *request_uuids;
|
|
apr_hash_t *response_uuids;
|
|
apr_hash_t *filters;
|
|
int limit;
|
|
apr_size_t skipped_bytes;
|
|
apr_size_t dropped_fragments;
|
|
apr_time_t start;
|
|
apr_time_t end;
|
|
} file_rec;
|
|
|
|
typedef struct uuid_rec
|
|
{
|
|
apr_pool_t *pool;
|
|
const char *uuid;
|
|
file_rec *file;
|
|
apr_uint64_t count;
|
|
apr_time_t last;
|
|
apr_size_t offset;
|
|
int direction;
|
|
} uuid_rec;
|
|
|
|
typedef struct filter_rec
|
|
{
|
|
apr_pool_t *pool;
|
|
const char *prefix;
|
|
apr_size_t len;
|
|
} filter_rec;
|
|
|
|
typedef struct header_rec
|
|
{
|
|
apr_size_t len;
|
|
apr_time_t timestamp;
|
|
int direction;
|
|
char uuid[APR_UUID_FORMATTED_LENGTH + 1];
|
|
apr_uint64_t count;
|
|
uuid_rec *rec;
|
|
} header_rec;
|
|
|
|
static const apr_getopt_option_t
|
|
cmdline_opts[] =
|
|
{
|
|
/* commands */
|
|
{
|
|
"file",
|
|
'f',
|
|
1,
|
|
" --file, -f <name>\t\t\tFile to read the firehose from.\n\t\t\t\t\tDefaults to stdin." },
|
|
{
|
|
"output-directory",
|
|
'd',
|
|
1,
|
|
" --output-directory, -o <name>\tDirectory to write demuxed connections\n\t\t\t\t\tto." },
|
|
{
|
|
"uuid",
|
|
'u',
|
|
1,
|
|
" --uuid, -u <uuid>\t\t\tThe UUID of the connection to\n\t\t\t\t\tdemultiplex. Can be specified more\n\t\t\t\t\tthan once." },
|
|
/* { "output-host", 'h', 1,
|
|
" --output-host, -h <hostname>\tHostname to write demuxed connections to." },*/
|
|
/* {
|
|
"speed",
|
|
's',
|
|
1,
|
|
" --speed, -s <factor>\tSpeed up or slow down demuxing\n\t\t\t\tby the given factor." },*/
|
|
{ "help", 258, 0, " --help, -h\t\t\t\tThis help text." },
|
|
{ "version", 257, 0,
|
|
" --version\t\t\t\tDisplay the version of the program." },
|
|
{ NULL } };
|
|
|
|
#define HELP_HEADER "Usage : %s [options] [prefix1 [prefix2 ...]]\n\n" \
|
|
"Firehose demultiplexes the given stream of multiplexed connections, and\n" \
|
|
"writes each connection to a file, or to a socket as appropriate.\n" \
|
|
"\n" \
|
|
"When writing to files, each connection is placed into a dedicated file\n" \
|
|
"named after the UUID of the connection within the stream. Separate files\n" \
|
|
"will be created if requests and responses are found in the stream.\n" \
|
|
"\n" \
|
|
"If an optional prefix is specified as a parameter, connections that start\n" \
|
|
"with the given prefix will be included. The prefix needs to fit completely\n" \
|
|
"within the first fragment for a successful match to occur.\n" \
|
|
"\n"
|
|
/* "When writing to a socket, new connections\n"
|
|
* "are opened for each connection in the stream, allowing it to be possible to\n"
|
|
* "'replay' traffic recorded by one server to other server.\n"
|
|
* "\n\n"
|
|
*/
|
|
#define HELP_FOOTER ""
|
|
|
|
/**
|
|
* Who are we again?
|
|
*/
|
|
static void version(const char * const progname)
|
|
{
|
|
printf("%s (%s)\n", progname, AP_SERVER_VERSION);
|
|
}
|
|
|
|
/**
|
|
* Help the long suffering end user.
|
|
*/
|
|
static void help(const char *argv, const char * header, const char *footer,
|
|
const apr_getopt_option_t opts[])
|
|
{
|
|
int i = 0;
|
|
|
|
if (header) {
|
|
printf(header, argv);
|
|
}
|
|
|
|
while (opts[i].name) {
|
|
printf("%s\n", opts[i].description);
|
|
i++;
|
|
}
|
|
|
|
if (footer) {
|
|
printf("%s\n", footer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup a uuid record. Removes the record from the uuid hashtable in files.
|
|
*/
|
|
static apr_status_t cleanup_uuid_rec(void *dummy)
|
|
{
|
|
uuid_rec *rec = (uuid_rec *) dummy;
|
|
|
|
if (rec->direction == '>') {
|
|
apr_hash_set(rec->file->response_uuids, rec->uuid, APR_HASH_KEY_STRING,
|
|
NULL);
|
|
}
|
|
if (rec->direction == '<') {
|
|
apr_hash_set(rec->file->request_uuids, rec->uuid, APR_HASH_KEY_STRING,
|
|
NULL);
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Create a uuid record, register a cleanup for it's destruction.
|
|
*/
|
|
static apr_status_t make_uuid_rec(file_rec *file, header_rec *header,
|
|
uuid_rec **ptr)
|
|
{
|
|
apr_pool_t *pool;
|
|
uuid_rec *rec;
|
|
apr_pool_create(&pool, file->pool);
|
|
|
|
rec = apr_pcalloc(pool, sizeof(uuid_rec));
|
|
rec->pool = pool;
|
|
rec->file = file;
|
|
rec->uuid = apr_pstrdup(pool, header->uuid);
|
|
rec->count = 0;
|
|
rec->last = header->timestamp;
|
|
rec->direction = header->direction;
|
|
|
|
if (header->direction == '>') {
|
|
apr_hash_set(file->response_uuids, rec->uuid, APR_HASH_KEY_STRING, rec);
|
|
}
|
|
if (header->direction == '<') {
|
|
apr_hash_set(file->request_uuids, rec->uuid, APR_HASH_KEY_STRING, rec);
|
|
}
|
|
|
|
apr_pool_cleanup_register(pool, rec, cleanup_uuid_rec, cleanup_uuid_rec);
|
|
|
|
*ptr = rec;
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Process the end of the fragment body.
|
|
*
|
|
* This function renames the completed stream to it's final name.
|
|
*/
|
|
static apr_status_t finalise_body(file_rec *file, header_rec *header)
|
|
{
|
|
apr_status_t status;
|
|
char *nfrom, *nto, *from, *to;
|
|
apr_pool_t *pool;
|
|
|
|
apr_pool_create(&pool, file->pool);
|
|
|
|
to = apr_pstrcat(pool, header->uuid, header->direction == '>' ? ".response"
|
|
: ".request", NULL);
|
|
from = apr_pstrcat(pool, to, ".part", NULL);
|
|
|
|
status = apr_filepath_merge(&nfrom, file->directory, from,
|
|
APR_FILEPATH_SECUREROOT, pool);
|
|
if (APR_SUCCESS == status) {
|
|
status = apr_filepath_merge(&nto, file->directory, to,
|
|
APR_FILEPATH_SECUREROOT, pool);
|
|
if (APR_SUCCESS == status) {
|
|
if (APR_SUCCESS == (status = apr_file_mtime_set(nfrom, file->end, pool))) {
|
|
if (APR_SUCCESS != (status = apr_file_rename(nfrom, nto, pool))) {
|
|
apr_file_printf(
|
|
file->file_err,
|
|
"Could not rename file '%s' to '%s' for fragment write: %pm\n",
|
|
nfrom, nto, &status);
|
|
}
|
|
}
|
|
else {
|
|
apr_file_printf(
|
|
file->file_err,
|
|
"Could not set mtime on file '%s' to '%" APR_TIME_T_FMT "' for fragment write: %pm\n",
|
|
nfrom, file->end, &status);
|
|
}
|
|
}
|
|
else {
|
|
apr_file_printf(file->file_err,
|
|
"Could not merge directory '%s' with file '%s': %pm\n",
|
|
file->directory, to, &status);
|
|
}
|
|
}
|
|
else {
|
|
apr_file_printf(file->file_err,
|
|
"Could not merge directory '%s' with file '%s': %pm\n",
|
|
file->directory, from, &status);
|
|
}
|
|
|
|
apr_pool_destroy(pool);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Check if the fragment matches on of the prefixes.
|
|
*/
|
|
static int check_prefix(file_rec *file, header_rec *header, const char *str,
|
|
apr_size_t len)
|
|
{
|
|
apr_hash_index_t *hi;
|
|
void *val;
|
|
apr_pool_t *pool;
|
|
int match = -1;
|
|
|
|
apr_pool_create(&pool, file->pool);
|
|
|
|
for (hi = apr_hash_first(pool, file->filters); hi; hi = apr_hash_next(hi)) {
|
|
filter_rec *filter;
|
|
apr_hash_this(hi, NULL, NULL, &val);
|
|
filter = (filter_rec *) val;
|
|
|
|
if (len > filter->len && !strncmp(filter->prefix, str, filter->len)) {
|
|
match = 1;
|
|
break;
|
|
}
|
|
match = 0;
|
|
}
|
|
|
|
apr_pool_destroy(pool);
|
|
|
|
return match;
|
|
}
|
|
|
|
/**
|
|
* Process part of the fragment body, given the header parameters.
|
|
*
|
|
* Currently, we append it to a file named after the UUID of the connection.
|
|
*
|
|
* The file is opened on demand and closed when done, so that we are
|
|
* guaranteed never to hit a file handle limit (within reason).
|
|
*/
|
|
static apr_status_t process_body(file_rec *file, header_rec *header,
|
|
const char *str, apr_size_t len)
|
|
{
|
|
apr_status_t status;
|
|
char *native, *name;
|
|
apr_pool_t *pool;
|
|
apr_file_t *handle;
|
|
|
|
if (!file->start) {
|
|
file->start = header->timestamp;
|
|
}
|
|
file->end = header->timestamp;
|
|
|
|
apr_pool_create(&pool, file->pool);
|
|
|
|
name
|
|
= apr_pstrcat(pool, header->uuid,
|
|
header->direction == '>' ? ".response.part"
|
|
: ".request.part", NULL);
|
|
|
|
status = apr_filepath_merge(&native, file->directory, name,
|
|
APR_FILEPATH_SECUREROOT, pool);
|
|
if (APR_SUCCESS == status) {
|
|
if (APR_SUCCESS == (status = apr_file_open(&handle, native, APR_WRITE
|
|
| APR_CREATE | APR_APPEND, APR_OS_DEFAULT, pool))) {
|
|
if (APR_SUCCESS != (status = apr_file_write_full(handle, str, len,
|
|
NULL))) {
|
|
apr_file_printf(file->file_err,
|
|
"Could not write fragment body to file '%s': %pm\n",
|
|
native, &status);
|
|
}
|
|
}
|
|
else {
|
|
apr_file_printf(file->file_err,
|
|
"Could not open file '%s' for fragment write: %pm\n",
|
|
native, &status);
|
|
}
|
|
}
|
|
else {
|
|
apr_file_printf(file->file_err,
|
|
"Could not merge directory '%s' with file '%s': %pm\n",
|
|
file->directory, name, &status);
|
|
}
|
|
|
|
apr_pool_destroy(pool);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Parse a chunk extension, detect overflow.
|
|
* There are two error cases:
|
|
* 1) If the conversion would require too many bits, a -1 is returned.
|
|
* 2) If the conversion used the correct number of bits, but an overflow
|
|
* caused only the sign bit to flip, then that negative number is
|
|
* returned.
|
|
* In general, any negative number can be considered an overflow error.
|
|
*/
|
|
static apr_status_t read_hex(const char **buf, apr_uint64_t *val)
|
|
{
|
|
const char *b = *buf;
|
|
apr_uint64_t chunksize = 0;
|
|
apr_size_t chunkbits = sizeof(apr_uint64_t) * 8;
|
|
|
|
if (!apr_isxdigit(*b)) {
|
|
return APR_EGENERAL;
|
|
}
|
|
/* Skip leading zeros */
|
|
while (*b == '0') {
|
|
++b;
|
|
}
|
|
|
|
while (apr_isxdigit(*b) && (chunkbits > 0)) {
|
|
int xvalue = 0;
|
|
|
|
if (*b >= '0' && *b <= '9') {
|
|
xvalue = *b - '0';
|
|
}
|
|
else if (*b >= 'A' && *b <= 'F') {
|
|
xvalue = *b - 'A' + 0xa;
|
|
}
|
|
else if (*b >= 'a' && *b <= 'f') {
|
|
xvalue = *b - 'a' + 0xa;
|
|
}
|
|
|
|
chunksize = (chunksize << 4) | xvalue;
|
|
chunkbits -= 4;
|
|
++b;
|
|
}
|
|
*buf = b;
|
|
if (apr_isxdigit(*b) && (chunkbits <= 0)) {
|
|
/* overflow */
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
*val = chunksize;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Parse what might be a fragment header line.
|
|
*
|
|
* If the parse doesn't match for any reason, an error is returned, otherwise
|
|
* APR_SUCCESS.
|
|
*
|
|
* The header structure will be filled with the header values as parsed.
|
|
*/
|
|
static apr_status_t process_header(file_rec *file, header_rec *header,
|
|
const char *str, apr_size_t len)
|
|
{
|
|
apr_uint64_t val;
|
|
apr_status_t status;
|
|
int i;
|
|
apr_uuid_t raw;
|
|
const char *end = str + len;
|
|
|
|
if (APR_SUCCESS != (status = read_hex(&str, &val))) {
|
|
return status;
|
|
}
|
|
header->len = val;
|
|
|
|
if (!apr_isspace(*(str++))) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
if (APR_SUCCESS != (status = read_hex(&str, &val))) {
|
|
return status;
|
|
}
|
|
header->timestamp = val;
|
|
|
|
if (!apr_isspace(*(str++))) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
if (*str != '<' && *str != '>') {
|
|
return APR_EGENERAL;
|
|
}
|
|
header->direction = *str;
|
|
str++;
|
|
|
|
if (!apr_isspace(*(str++))) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
for (i = 0; str[i] && i < APR_UUID_FORMATTED_LENGTH; i++) {
|
|
header->uuid[i] = str[i];
|
|
}
|
|
header->uuid[i] = 0;
|
|
if (apr_uuid_parse(&raw, header->uuid)) {
|
|
return APR_EGENERAL;
|
|
}
|
|
str += i;
|
|
|
|
if (!apr_isspace(*(str++))) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
if (APR_SUCCESS != (status = read_hex(&str, &val))) {
|
|
return status;
|
|
}
|
|
header->count = val;
|
|
|
|
if ((*(str++) != '\r')) {
|
|
return APR_EGENERAL;
|
|
}
|
|
if ((*(str++) != '\n')) {
|
|
return APR_EGENERAL;
|
|
}
|
|
if (str != end) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Suck on the file/pipe, and demux any fragments on the incoming stream.
|
|
*
|
|
* If EOF is detected, this function returns.
|
|
*/
|
|
static apr_status_t demux(file_rec *file)
|
|
{
|
|
apr_size_t len = 0;
|
|
apr_status_t status = APR_SUCCESS;
|
|
apr_bucket *b, *e;
|
|
apr_bucket_brigade *bb, *obb;
|
|
int footer = 0;
|
|
const char *buf;
|
|
|
|
bb = apr_brigade_create(file->pool, file->alloc);
|
|
obb = apr_brigade_create(file->pool, file->alloc);
|
|
b = apr_bucket_pipe_create(file->file_in, file->alloc);
|
|
|
|
APR_BRIGADE_INSERT_HEAD(bb, b);
|
|
|
|
do {
|
|
|
|
/* when the pipe is closed, the pipe disappears from the brigade */
|
|
if (APR_BRIGADE_EMPTY(bb)) {
|
|
break;
|
|
}
|
|
|
|
status = apr_brigade_split_line(obb, bb, APR_BLOCK_READ,
|
|
HUGE_STRING_LEN);
|
|
|
|
if (APR_SUCCESS == status || APR_EOF == status) {
|
|
char str[HUGE_STRING_LEN];
|
|
len = HUGE_STRING_LEN;
|
|
|
|
apr_brigade_flatten(obb, str, &len);
|
|
|
|
apr_brigade_cleanup(obb);
|
|
|
|
if (len == HUGE_STRING_LEN) {
|
|
file->skipped_bytes += len;
|
|
continue;
|
|
}
|
|
else if (footer) {
|
|
if (len == 2 && str[0] == '\r' && str[1] == '\n') {
|
|
footer = 0;
|
|
continue;
|
|
}
|
|
file->skipped_bytes += len;
|
|
}
|
|
else if (len > 0) {
|
|
header_rec header;
|
|
status = process_header(file, &header, str, len);
|
|
if (APR_SUCCESS != status) {
|
|
file->skipped_bytes += len;
|
|
continue;
|
|
}
|
|
else {
|
|
int ignore = 0;
|
|
|
|
header.rec = NULL;
|
|
if (header.direction == '>') {
|
|
header.rec = apr_hash_get(file->response_uuids,
|
|
header.uuid, APR_HASH_KEY_STRING);
|
|
}
|
|
if (header.direction == '<') {
|
|
header.rec = apr_hash_get(file->request_uuids,
|
|
header.uuid, APR_HASH_KEY_STRING);
|
|
}
|
|
if (header.rec) {
|
|
/* does the count match what is expected? */
|
|
if (header.count != header.rec->count) {
|
|
file->dropped_fragments++;
|
|
ignore = 1;
|
|
}
|
|
}
|
|
else {
|
|
/* must we ignore unknown uuids? */
|
|
if (file->limit) {
|
|
ignore = 1;
|
|
}
|
|
|
|
/* is the counter not what we expect? */
|
|
else if (header.count != 0) {
|
|
file->skipped_bytes += len;
|
|
ignore = 1;
|
|
}
|
|
|
|
/* otherwise, make a new uuid */
|
|
else {
|
|
make_uuid_rec(file, &header, &header.rec);
|
|
}
|
|
}
|
|
|
|
if (header.len) {
|
|
if (APR_SUCCESS != (status = apr_brigade_partition(bb,
|
|
header.len, &e))) {
|
|
apr_file_printf(
|
|
file->file_err,
|
|
"Could not read fragment body from input file: %pm\n", &status);
|
|
break;
|
|
}
|
|
while ((b = APR_BRIGADE_FIRST(bb)) && e != b) {
|
|
apr_bucket_read(b, &buf, &len, APR_READ_BLOCK);
|
|
if (!ignore && !header.count && !check_prefix(file,
|
|
&header, buf, len)) {
|
|
ignore = 1;
|
|
}
|
|
if (!ignore) {
|
|
status = process_body(file, &header, buf, len);
|
|
header.rec->offset += len;
|
|
}
|
|
if (ignore || APR_SUCCESS != status) {
|
|
apr_bucket_delete(b);
|
|
file->skipped_bytes += len;
|
|
continue;
|
|
}
|
|
apr_bucket_delete(b);
|
|
}
|
|
if (!ignore) {
|
|
header.rec->count++;
|
|
}
|
|
footer = 1;
|
|
continue;
|
|
}
|
|
else {
|
|
/* an empty header means end-of-connection */
|
|
if (header.rec) {
|
|
if (!ignore) {
|
|
if (!header.count) {
|
|
status = process_body(file, &header, "", 0);
|
|
}
|
|
status = finalise_body(file, &header);
|
|
}
|
|
apr_pool_destroy(header.rec->pool);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
apr_file_printf(file->file_err,
|
|
"Could not read fragment header from input file: %pm\n", &status);
|
|
break;
|
|
}
|
|
|
|
} while (1);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Start the application.
|
|
*/
|
|
int main(int argc, const char * const argv[])
|
|
{
|
|
apr_status_t status;
|
|
apr_pool_t *pool;
|
|
apr_getopt_t *opt;
|
|
int optch;
|
|
const char *optarg;
|
|
|
|
file_rec *file;
|
|
|
|
/* lets get APR off the ground, and make sure it terminates cleanly */
|
|
if (APR_SUCCESS != (status = apr_app_initialize(&argc, &argv, NULL))) {
|
|
return 1;
|
|
}
|
|
atexit(apr_terminate);
|
|
|
|
if (APR_SUCCESS != (status = apr_pool_create(&pool, NULL))) {
|
|
return 1;
|
|
}
|
|
|
|
#ifdef SIGPIPE
|
|
apr_signal_block(SIGPIPE);
|
|
#endif
|
|
|
|
file = apr_pcalloc(pool, sizeof(file_rec));
|
|
apr_file_open_stderr(&file->file_err, pool);
|
|
apr_file_open_stdin(&file->file_in, pool);
|
|
apr_file_open_stdout(&file->file_out, pool);
|
|
|
|
file->pool = pool;
|
|
file->alloc = apr_bucket_alloc_create(pool);
|
|
file->bb = apr_brigade_create(pool, file->alloc);
|
|
file->request_uuids = apr_hash_make(pool);
|
|
file->response_uuids = apr_hash_make(pool);
|
|
file->filters = apr_hash_make(pool);
|
|
|
|
apr_getopt_init(&opt, pool, argc, argv);
|
|
while ((status = apr_getopt_long(opt, cmdline_opts, &optch, &optarg))
|
|
== APR_SUCCESS) {
|
|
|
|
switch (optch) {
|
|
case 'f': {
|
|
status = apr_file_open(&file->file_in, optarg, APR_FOPEN_READ,
|
|
APR_OS_DEFAULT, pool);
|
|
if (status != APR_SUCCESS) {
|
|
apr_file_printf(file->file_err,
|
|
"Could not open file '%s' for read: %pm\n", optarg, &status);
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
case 'd': {
|
|
apr_finfo_t finfo;
|
|
status = apr_stat(&finfo, optarg, APR_FINFO_TYPE, pool);
|
|
if (status != APR_SUCCESS) {
|
|
apr_file_printf(file->file_err,
|
|
"Directory '%s' could not be found: %pm\n", optarg, &status);
|
|
return 1;
|
|
}
|
|
if (finfo.filetype != APR_DIR) {
|
|
apr_file_printf(file->file_err,
|
|
"Path '%s' isn't a directory\n", optarg);
|
|
return 1;
|
|
}
|
|
file->directory = optarg;
|
|
break;
|
|
}
|
|
case 'u': {
|
|
apr_pool_t *pchild;
|
|
uuid_rec *rec;
|
|
apr_pool_create(&pchild, pool);
|
|
rec = apr_pcalloc(pchild, sizeof(uuid_rec));
|
|
rec->pool = pchild;
|
|
rec->uuid = optarg;
|
|
apr_hash_set(file->request_uuids, optarg, APR_HASH_KEY_STRING, rec);
|
|
apr_hash_set(file->response_uuids, optarg, APR_HASH_KEY_STRING, rec);
|
|
file->limit++;
|
|
break;
|
|
}
|
|
case 257: {
|
|
version(argv[0]);
|
|
return 0;
|
|
}
|
|
case 258: {
|
|
help(argv[0], HELP_HEADER, HELP_FOOTER, cmdline_opts);
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
if (APR_SUCCESS != status && APR_EOF != status) {
|
|
return 1;
|
|
}
|
|
|
|
/* read filters from the command line */
|
|
while (opt->ind < argc) {
|
|
apr_pool_t *pchild;
|
|
filter_rec *filter;
|
|
apr_pool_create(&pchild, pool);
|
|
filter = apr_pcalloc(pchild, sizeof(filter_rec));
|
|
filter->pool = pchild;
|
|
filter->prefix = opt->argv[opt->ind];
|
|
filter->len = strlen(opt->argv[opt->ind]);
|
|
apr_hash_set(file->filters, opt->argv[opt->ind], APR_HASH_KEY_STRING,
|
|
filter);
|
|
opt->ind++;
|
|
}
|
|
|
|
status = demux(file);
|
|
|
|
/* warn people if any non blocking writes failed */
|
|
if (file->skipped_bytes || file->dropped_fragments) {
|
|
apr_file_printf(
|
|
file->file_err,
|
|
"Warning: %" APR_SIZE_T_FMT " bytes skipped, %" APR_SIZE_T_FMT " fragments dropped.\n",
|
|
file->skipped_bytes, file->dropped_fragments);
|
|
}
|
|
|
|
if (APR_SUCCESS != status) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|