upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / filters / mod_ext_filter.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_ext_filter allows Unix-style filters to filter http content.
19  */
20
21 #include "httpd.h"
22 #include "http_config.h"
23 #include "http_log.h"
24 #include "http_protocol.h"
25 #define CORE_PRIVATE
26 #include "http_core.h"
27 #include "apr_buckets.h"
28 #include "util_filter.h"
29 #include "util_script.h"
30 #include "util_time.h"
31 #include "apr_strings.h"
32 #include "apr_hash.h"
33 #include "apr_lib.h"
34 #include "apr_poll.h"
35 #define APR_WANT_STRFUNC
36 #include "apr_want.h"
37
38 typedef struct ef_server_t {
39     apr_pool_t *p;
40     apr_hash_t *h;
41 } ef_server_t;
42
43 typedef struct ef_filter_t {
44     const char *name;
45     enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
46     ap_filter_type ftype;
47     const char *command;
48     const char *enable_env;
49     const char *disable_env;
50     char **args;
51     const char *intype;             /* list of IMTs we process (well, just one for now) */
52 #define INTYPE_ALL (char *)1
53     const char *outtype;            /* IMT of filtered output */
54 #define OUTTYPE_UNCHANGED (char *)1
55     int preserves_content_length;
56 } ef_filter_t;
57
58 typedef struct ef_dir_t {
59     int debug;
60     int log_stderr;
61 } ef_dir_t;
62
63 typedef struct ef_ctx_t {
64     apr_pool_t *p;
65     apr_proc_t *proc;
66     apr_procattr_t *procattr;
67     ef_dir_t *dc;
68     ef_filter_t *filter;
69     int noop;
70 #if APR_FILES_AS_SOCKETS
71     apr_pollfd_t *pollset;
72 #endif
73 } ef_ctx_t;
74
75 module AP_MODULE_DECLARE_DATA ext_filter_module;
76 static const server_rec *main_server;
77
78 static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
79
80 #define DBGLVL_SHOWOPTIONS         1
81 #define DBGLVL_ERRORCHECK          2
82 #define DBGLVL_GORY                9
83
84 #define ERRFN_USERDATA_KEY         "EXTFILTCHILDERRFN"
85
86 static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
87 {
88     ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
89
90     dc->debug = -1;
91     dc->log_stderr = -1;
92
93     return dc;
94 }
95
96 static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
97 {
98     ef_server_t *conf;
99
100     conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
101     conf->p = p;
102     conf->h = apr_hash_make(conf->p);
103     return conf;
104 }
105
106 static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
107 {
108     ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
109     ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
110
111     if (over->debug != -1) {        /* if admin coded something... */
112         a->debug = over->debug;
113     }
114     else {
115         a->debug = base->debug;
116     }
117
118     if (over->log_stderr != -1) {   /* if admin coded something... */
119         a->log_stderr = over->log_stderr;
120     }
121     else {
122         a->log_stderr = base->log_stderr;
123     }
124
125     return a;
126 }
127
128 static const char *add_options(cmd_parms *cmd, void *in_dc,
129                                const char *arg)
130 {
131     ef_dir_t *dc = in_dc;
132
133     if (!strncasecmp(arg, "DebugLevel=", 11)) {
134         dc->debug = atoi(arg + 11);
135     }
136     else if (!strcasecmp(arg, "LogStderr")) {
137         dc->log_stderr = 1;
138     }
139     else if (!strcasecmp(arg, "NoLogStderr")) {
140         dc->log_stderr = 0;
141     }
142     else {
143         return apr_pstrcat(cmd->temp_pool, 
144                            "Invalid ExtFilterOptions option: ",
145                            arg,
146                            NULL);
147     }
148
149     return NULL;
150 }
151
152 static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
153 {
154     if (**args == '"') {
155         const char *start = *args + 1;
156         char *parms;
157         int escaping = 0;
158         apr_status_t rv;
159
160         ++*args; /* move past leading " */
161         /* find true end of args string (accounting for escaped quotes) */
162         while (**args && (**args != '"' || (**args == '"' && escaping))) {
163             if (escaping) {
164                 escaping = 0;
165             }
166             else if (**args == '\\') {
167                 escaping = 1;
168             }
169             ++*args;
170         }
171         if (**args != '"') {
172             return "Expected cmd= delimiter";
173         }
174         /* copy *just* the arg string for parsing, */
175         parms = apr_pstrndup(p, start, *args - start);
176         ++*args; /* move past trailing " */
177
178         /* parse and tokenize the args. */
179         rv = apr_tokenize_to_argv(parms, &(filter->args), p);
180         if (rv != APR_SUCCESS) {
181             return "cmd= parse error";
182         }
183     }
184     else
185     {
186         /* simple path */
187         /* Allocate space for two argv pointers and parse the args. */
188         filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
189         filter->args[0] = ap_getword_white(p, args);
190         filter->args[1] = NULL; /* end of args */
191     }
192     if (!filter->args[0]) {
193         return "Invalid cmd= parameter";
194     }
195     filter->command = filter->args[0];
196     
197     return NULL;
198 }
199
200 static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
201 {
202     ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
203                                              &ext_filter_module);
204     const char *token;
205     const char *name;
206     ef_filter_t *filter;
207
208     name = ap_getword_white(cmd->pool, &args);
209     if (!name) {
210         return "Filter name not found";
211     }
212
213     if (apr_hash_get(conf->h, name, APR_HASH_KEY_STRING)) {
214         return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
215                             name);
216     }
217
218     filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
219     filter->name = name;
220     filter->mode = OUTPUT_FILTER;
221     filter->ftype = AP_FTYPE_RESOURCE;
222     apr_hash_set(conf->h, name, APR_HASH_KEY_STRING, filter);
223
224     while (*args) {
225         while (apr_isspace(*args)) {
226             ++args;
227         }
228
229         /* Nasty parsing...  I wish I could simply use ap_getword_white()
230          * here and then look at the token, but ap_getword_white() doesn't
231          * do the right thing when we have cmd="word word word"
232          */
233         if (!strncasecmp(args, "preservescontentlength", 22)) {
234             token = ap_getword_white(cmd->pool, &args);
235             if (!strcasecmp(token, "preservescontentlength")) {
236                 filter->preserves_content_length = 1;
237             }
238             else {
239                 return apr_psprintf(cmd->pool, 
240                                     "mangled argument `%s'",
241                                     token);
242             }
243             continue;
244         }
245
246         if (!strncasecmp(args, "mode=", 5)) {
247             args += 5;
248             token = ap_getword_white(cmd->pool, &args);
249             if (!strcasecmp(token, "output")) {
250                 filter->mode = OUTPUT_FILTER;
251             }
252             else if (!strcasecmp(token, "input")) {
253                 filter->mode = INPUT_FILTER;
254             }
255             else {
256                 return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
257                                     token);
258             }
259             continue;
260         }
261
262         if (!strncasecmp(args, "ftype=", 6)) {
263             args += 6;
264             token = ap_getword_white(cmd->pool, &args);
265             filter->ftype = atoi(token);
266             continue;
267         }
268
269         if (!strncasecmp(args, "enableenv=", 10)) {
270             args += 10;
271             token = ap_getword_white(cmd->pool, &args);
272             filter->enable_env = token;
273             continue;
274         }
275         
276         if (!strncasecmp(args, "disableenv=", 11)) {
277             args += 11;
278             token = ap_getword_white(cmd->pool, &args);
279             filter->disable_env = token;
280             continue;
281         }
282         
283         if (!strncasecmp(args, "intype=", 7)) {
284             args += 7;
285             filter->intype = ap_getword_white(cmd->pool, &args);
286             continue;
287         }
288
289         if (!strncasecmp(args, "outtype=", 8)) {
290             args += 8;
291             filter->outtype = ap_getword_white(cmd->pool, &args);
292             continue;
293         }
294
295         if (!strncasecmp(args, "cmd=", 4)) {
296             args += 4;
297             if ((token = parse_cmd(cmd->pool, &args, filter))) {
298                 return token;
299             }
300             continue;
301         }
302
303         return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
304                             args);
305     }
306
307     /* parsing is done...  register the filter 
308      */
309     if (filter->mode == OUTPUT_FILTER) {
310         /* XXX need a way to ensure uniqueness among all filters */
311         ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
312     }
313 #if 0              /* no input filters yet */
314     else if (filter->mode == INPUT_FILTER) {
315         /* XXX need a way to ensure uniqueness among all filters */
316         ap_register_input_filter(filter->name, ef_input_filter, NULL, AP_FTYPE_RESOURCE);
317     }
318 #endif
319     else {
320         ap_assert(1 != 1); /* we set the field wrong somehow */
321     }
322
323     return NULL;
324 }
325
326 static const command_rec cmds[] =
327 {
328     AP_INIT_ITERATE("ExtFilterOptions",
329                     add_options,
330                     NULL,
331                     ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
332                     "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
333     AP_INIT_RAW_ARGS("ExtFilterDefine",
334                      define_filter,
335                      NULL,
336                      RSRC_CONF,
337                      "Define an external filter"),
338     {NULL}
339 };
340
341 static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
342 {
343     main_server = main_s;
344     return OK;
345 }
346
347 static void register_hooks(apr_pool_t *p)
348 {
349     ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
350 }
351
352 static apr_status_t set_resource_limits(request_rec *r, 
353                                         apr_procattr_t *procattr)
354 {
355 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
356     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
357     core_dir_config *conf = 
358         (core_dir_config *)ap_get_module_config(r->per_dir_config,
359                                                 &core_module);
360     apr_status_t rv;
361
362 #ifdef RLIMIT_CPU
363     rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
364     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
365 #endif
366 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
367     rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
368     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
369 #endif
370 #ifdef RLIMIT_NPROC
371     rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
372     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
373 #endif
374
375 #endif /* if at least one limit defined */
376
377     return APR_SUCCESS;
378 }
379
380 static apr_status_t ef_close_file(void *vfile)
381 {
382     return apr_file_close(vfile);
383 }
384
385 static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
386 {
387     request_rec *r;
388     void *vr;
389     apr_file_t *stderr_log;
390     char errbuf[200];
391     char time_str[APR_CTIME_LEN];
392
393     apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
394     r = vr;
395     apr_file_open_stderr(&stderr_log, pool);
396     ap_recent_ctime(time_str, apr_time_now());
397     apr_file_printf(stderr_log,
398                     "[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
399                     time_str,
400                     r->connection->remote_ip,
401                     err,
402                     apr_strerror(err, errbuf, sizeof(errbuf)),
403                     description);
404 }
405
406 /* init_ext_filter_process: get the external filter process going
407  * This is per-filter-instance (i.e., per-request) initialization.
408  */
409 static apr_status_t init_ext_filter_process(ap_filter_t *f)
410 {
411     ef_ctx_t *ctx = f->ctx;
412     apr_status_t rc;
413     ef_dir_t *dc = ctx->dc;
414     const char * const *env;
415
416     ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
417
418     rc = apr_procattr_create(&ctx->procattr, ctx->p);
419     ap_assert(rc == APR_SUCCESS);
420
421     rc = apr_procattr_io_set(ctx->procattr,
422                             APR_CHILD_BLOCK,
423                             APR_CHILD_BLOCK,
424                             APR_CHILD_BLOCK);
425     ap_assert(rc == APR_SUCCESS);
426
427     rc = set_resource_limits(f->r, ctx->procattr);
428     ap_assert(rc == APR_SUCCESS);
429
430     if (dc->log_stderr > 0) {
431         rc = apr_procattr_child_err_set(ctx->procattr,
432                                       f->r->server->error_log, /* stderr in child */
433                                       NULL);
434         ap_assert(rc == APR_SUCCESS);
435     }
436
437     rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
438     ap_assert(rc == APR_SUCCESS);
439     apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
440     
441     if (dc->debug >= DBGLVL_ERRORCHECK) {
442         rc = apr_procattr_error_check_set(ctx->procattr, 1);
443         ap_assert(rc == APR_SUCCESS);
444     }
445     
446     /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
447      * and QUERY_STRING_UNESCAPED
448      */
449     ap_add_cgi_vars(f->r);
450     ap_add_common_vars(f->r);
451     apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
452     apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
453     if (f->r->args) {
454             /* QUERY_STRING is added by ap_add_cgi_vars */
455         char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
456         ap_unescape_url(arg_copy);
457         apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
458                        ap_escape_shell_cmd(f->r->pool, arg_copy));
459     }
460
461     env = (const char * const *) ap_create_environment(ctx->p,
462                                                        f->r->subprocess_env);
463
464     rc = apr_proc_create(ctx->proc, 
465                             ctx->filter->command, 
466                             (const char * const *)ctx->filter->args, 
467                             env, /* environment */
468                             ctx->procattr, 
469                             ctx->p);
470     if (rc != APR_SUCCESS) {
471         ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
472                       "couldn't create child process to run `%s'",
473                       ctx->filter->command);
474         return rc;
475     }
476
477     apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
478
479     /* We don't want the handle to the child's stdin inherited by any
480      * other processes created by httpd.  Otherwise, when we close our
481      * handle, the child won't see EOF because another handle will still
482      * be open.
483      */
484
485     apr_pool_cleanup_register(ctx->p, ctx->proc->in, 
486                          apr_pool_cleanup_null, /* other mechanism */
487                          ef_close_file);
488
489 #if APR_FILES_AS_SOCKETS
490     {
491         apr_socket_t *newsock;
492
493         rc = apr_poll_setup(&ctx->pollset, 2, ctx->p);
494         ap_assert(rc == APR_SUCCESS);
495         rc = apr_socket_from_file(&newsock, ctx->proc->in);
496         ap_assert(rc == APR_SUCCESS);
497         rc = apr_poll_socket_add(ctx->pollset, newsock, APR_POLLOUT);
498         ap_assert(rc == APR_SUCCESS);
499         rc = apr_socket_from_file(&newsock, ctx->proc->out);
500         ap_assert(rc == APR_SUCCESS);
501         rc = apr_poll_socket_add(ctx->pollset, newsock, APR_POLLIN);
502         ap_assert(rc == APR_SUCCESS);
503     }
504 #endif
505
506     return APR_SUCCESS;
507 }
508
509 static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
510 {
511     const char *debug_str = dc->debug == -1 ? 
512         "DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
513     const char *log_stderr_str = dc->log_stderr < 1 ?
514         "NoLogStderr" : "LogStderr";
515     const char *preserve_content_length_str = filter->preserves_content_length ?
516         "PreservesContentLength" : "!PreserveContentLength";
517     const char *intype_str = !filter->intype ?
518         "*/*" : filter->intype;
519     const char *outtype_str = !filter->outtype ?
520         "(unchanged)" : filter->outtype;
521     
522     return apr_psprintf(p,
523                         "ExtFilterOptions %s %s %s ExtFilterInType %s "
524                         "ExtFilterOuttype %s",
525                         debug_str, log_stderr_str, preserve_content_length_str,
526                         intype_str, outtype_str);
527 }
528
529 static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
530 {
531     ef_server_t *sc;
532     ef_filter_t *f;
533
534     sc = ap_get_module_config(s->module_config, &ext_filter_module);
535     f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
536     if (!f && s != main_server) {
537         s = main_server;
538         sc = ap_get_module_config(s->module_config, &ext_filter_module);
539         f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
540     }
541     return f;
542 }
543
544 static apr_status_t init_filter_instance(ap_filter_t *f)
545 {
546     ef_ctx_t *ctx;
547     ef_dir_t *dc;
548     apr_status_t rv;
549
550     f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
551     dc = ap_get_module_config(f->r->per_dir_config,
552                               &ext_filter_module);
553     ctx->dc = dc;
554     /* look for the user-defined filter */
555     ctx->filter = find_filter_def(f->r->server, f->frec->name);
556     if (!ctx->filter) {
557         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
558                       "couldn't find definition of filter '%s'",
559                       f->frec->name);
560         return APR_EINVAL;
561     }
562     ctx->p = f->r->pool;
563     if (ctx->filter->intype &&
564         ctx->filter->intype != INTYPE_ALL) {
565         if (!f->r->content_type) {
566             ctx->noop = 1;
567         }
568         else {
569             const char *ctypes = f->r->content_type;
570             const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
571
572             if (strcasecmp(ctx->filter->intype, ctype)) {
573                 /* wrong IMT for us; don't mess with the output */
574                 ctx->noop = 1;
575             }
576         }
577     }
578     if (ctx->filter->enable_env &&
579         !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
580         /* an environment variable that enables the filter isn't set; bail */
581         ctx->noop = 1;
582     }
583     if (ctx->filter->disable_env &&
584         apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
585         /* an environment variable that disables the filter is set; bail */
586         ctx->noop = 1;
587     }
588     if (!ctx->noop) {
589         rv = init_ext_filter_process(f);
590         if (rv != APR_SUCCESS) {
591             return rv;
592         }
593         if (ctx->filter->outtype &&
594             ctx->filter->outtype != OUTTYPE_UNCHANGED) {
595             ap_set_content_type(f->r, ctx->filter->outtype);
596         }
597         if (ctx->filter->preserves_content_length != 1) {
598             /* nasty, but needed to avoid confusing the browser 
599              */
600             apr_table_unset(f->r->headers_out, "Content-Length");
601         }
602     }
603
604     if (dc->debug >= DBGLVL_SHOWOPTIONS) {
605         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
606                       "%sfiltering `%s' of type `%s' through `%s', cfg %s",
607                       ctx->noop ? "NOT " : "",
608                       f->r->uri ? f->r->uri : f->r->filename,
609                       f->r->content_type ? f->r->content_type : "(unspecified)",
610                       ctx->filter->command,
611                       get_cfg_string(dc, ctx->filter, f->r->pool));
612     }
613
614     return APR_SUCCESS;
615 }
616
617 /* drain_available_output(): 
618  *
619  * if any data is available from the filter, read it and pass it
620  * to the next filter
621  */
622 static apr_status_t drain_available_output(ap_filter_t *f)
623 {
624     request_rec *r = f->r;
625     conn_rec *c = r->connection;
626     ef_ctx_t *ctx = f->ctx;
627     ef_dir_t *dc = ctx->dc;
628     apr_size_t len;
629     char buf[4096];
630     apr_status_t rv;
631     apr_bucket_brigade *bb;
632     apr_bucket *b;
633
634     while (1) {
635         len = sizeof(buf);
636         rv = apr_file_read(ctx->proc->out,
637                       buf,
638                       &len);
639         if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
640             dc->debug >= DBGLVL_GORY) {
641             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
642                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
643                           !rv ? len : -1);
644         }
645         if (rv != APR_SUCCESS) {
646             return rv;
647         }
648         bb = apr_brigade_create(r->pool, c->bucket_alloc);
649         b = apr_bucket_transient_create(buf, len, c->bucket_alloc);
650         APR_BRIGADE_INSERT_TAIL(bb, b);
651         if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
652             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
653                           "ap_pass_brigade()");
654             return rv;
655         }
656     }
657     /* we should never get here; if we do, a bogus error message would be
658      * the least of our problems 
659      */
660     return APR_ANONYMOUS;
661 }
662
663 static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data, 
664                                         apr_size_t len)
665 {
666     ef_ctx_t *ctx = f->ctx;
667     ef_dir_t *dc = ctx->dc;
668     apr_status_t rv;
669     apr_size_t bytes_written = 0;
670     apr_size_t tmplen;
671     
672     do {
673         tmplen = len - bytes_written;
674         rv = apr_file_write(ctx->proc->in,
675                        (const char *)data + bytes_written,
676                        &tmplen);
677         bytes_written += tmplen;
678         if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
679             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
680                           "apr_file_write(child input), len %" APR_SIZE_T_FMT,
681                           tmplen);
682             return rv;
683         }
684         if (APR_STATUS_IS_EAGAIN(rv)) {
685             /* XXX handle blocking conditions here...  if we block, we need 
686              * to read data from the child process and pass it down to the
687              * next filter!
688              */
689             rv = drain_available_output(f);
690             if (APR_STATUS_IS_EAGAIN(rv)) {
691 #if APR_FILES_AS_SOCKETS
692                 int num_events;
693                 
694                 rv = apr_poll(ctx->pollset, 2,
695                               &num_events, f->r->server->timeout);
696                 if (rv || dc->debug >= DBGLVL_GORY) {
697                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
698                                   rv, f->r, "apr_poll()");
699                 }
700                 if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) { 
701                     /* some error such as APR_TIMEUP */
702                     return rv;
703                 }
704 #else /* APR_FILES_AS_SOCKETS */
705                 /* Yuck... I'd really like to wait until I can read
706                  * or write, but instead I have to sleep and try again 
707                  */
708                 apr_sleep(100000); /* 100 milliseconds */
709                 if (dc->debug >= DBGLVL_GORY) {
710                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 
711                                   0, f->r, "apr_sleep()");
712                 }
713 #endif /* APR_FILES_AS_SOCKETS */
714             }
715             else if (rv != APR_SUCCESS) {
716                 return rv;
717             }
718         }
719     } while (bytes_written < len);
720     return rv;
721 }
722
723 static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
724 {
725     request_rec *r = f->r;
726     conn_rec *c = r->connection;
727     ef_ctx_t *ctx = f->ctx;
728     apr_bucket *b;
729     ef_dir_t *dc;
730     apr_size_t len;
731     const char *data;
732     apr_status_t rv;
733     char buf[4096];
734     apr_bucket *eos = NULL;
735
736     if (!ctx) {
737         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
738             return rv;
739         }
740         ctx = f->ctx;
741     }
742     if (ctx->noop) {
743         ap_remove_output_filter(f);
744         return ap_pass_brigade(f->next, bb);
745     }
746     dc = ctx->dc;
747
748     APR_BRIGADE_FOREACH(b, bb) {
749
750         if (APR_BUCKET_IS_EOS(b)) {
751             eos = b;
752             break;
753         }
754
755         rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
756         if (rv != APR_SUCCESS) {
757             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
758             return rv;
759         }
760
761         /* Good cast, we just tested len isn't negative */
762         if (len > 0 &&
763             (rv = pass_data_to_filter(f, data, (apr_size_t)len)) 
764                 != APR_SUCCESS) {
765             return rv;
766         }
767     }
768
769     apr_brigade_destroy(bb);
770
771     /* XXX What we *really* need to do once we've hit eos is create a pipe bucket
772      * from the child output pipe and pass down the pipe bucket + eos.
773      */
774     if (eos) {
775         /* close the child's stdin to signal that no more data is coming;
776          * that will cause the child to finish generating output
777          */
778         if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
779             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
780                           "apr_file_close(child input)");
781             return rv;
782         }
783         /* since we've seen eos and closed the child's stdin, set the proper pipe 
784          * timeout; we don't care if we don't return from apr_file_read() for a while... 
785          */
786         rv = apr_file_pipe_timeout_set(ctx->proc->out, 
787                                        r->server->timeout);
788         if (rv) {
789             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
790                           "apr_file_pipe_timeout_set(child output)");
791             return rv;
792         }
793     }
794
795     do {
796         len = sizeof(buf);
797         rv = apr_file_read(ctx->proc->out,
798                       buf,
799                       &len);
800         if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
801             dc->debug >= DBGLVL_GORY) {
802             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
803                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
804                           !rv ? len : -1);
805         }
806         if (APR_STATUS_IS_EAGAIN(rv)) {
807             if (eos) {
808                 /* should not occur, because we have an APR timeout in place */
809                 AP_DEBUG_ASSERT(1 != 1);
810             }
811             return APR_SUCCESS;
812         }
813         
814         if (rv == APR_SUCCESS) {
815             bb = apr_brigade_create(r->pool, c->bucket_alloc);
816             b = apr_bucket_transient_create(buf, len, c->bucket_alloc);
817             APR_BRIGADE_INSERT_TAIL(bb, b);
818             if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
819                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
820                               "ap_pass_brigade(filtered buffer) failed");
821                 return rv;
822             }
823         }
824     } while (rv == APR_SUCCESS);
825
826     if (!APR_STATUS_IS_EOF(rv)) {
827         return rv;
828     }
829
830     if (eos) {
831         /* pass down eos */
832         bb = apr_brigade_create(r->pool, c->bucket_alloc);
833         b = apr_bucket_eos_create(c->bucket_alloc);
834         APR_BRIGADE_INSERT_TAIL(bb, b);
835         if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
836             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
837                           "ap_pass_brigade(eos) failed");
838             return rv;
839         }
840     }
841
842     return APR_SUCCESS;
843 }
844
845 #if 0
846 static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb, 
847                            ap_input_mode_t mode, apr_read_type_e block,
848                            apr_off_t readbytes)
849 {
850     apr_status_t rv;
851     apr_bucket *b;
852     char *buf;
853     apr_ssize_t len;
854     char *zero;
855
856     rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
857     if (rv != APR_SUCCESS) {
858         return rv;
859     }
860
861     APR_BRIGADE_FOREACH(b, bb) {
862         if (!APR_BUCKET_IS_EOS(b)) {
863             if ((rv = apr_bucket_read(b, (const char **)&buf, &len, APR_BLOCK_READ)) != APR_SUCCESS) {
864                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, "apr_bucket_read() failed");
865                 return rv;
866             }
867             ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "apr_bucket_read -> %d bytes",
868                          len);
869             while ((zero = memchr(buf, '0', len))) {
870                 *zero = 'a';
871             }
872         }
873         else
874             ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "got eos bucket");
875     }
876
877     return rv;
878 }
879 #endif
880
881 module AP_MODULE_DECLARE_DATA ext_filter_module =
882 {
883     STANDARD20_MODULE_STUFF,
884     create_ef_dir_conf,
885     merge_ef_dir_conf,
886     create_ef_server_conf,
887     NULL,
888     cmds,
889     register_hooks
890 };