source: subversion/applications/utils/mod_tile/mod_tile.c @ 27187

Last change on this file since 27187 was 27187, checked in by frederik, 8 years ago

Add a new "bulk mode" to mod_tile. If your server is in bulk mode, it will
never mark tiles dirty and never re-render dirty tiles, and it will always
enqueue tiles at "bulk" priority.

File size: 62.2 KB
Line 
1#include "apr.h"
2#include "apr_strings.h"
3#include "apr_thread_proc.h"    /* for RLIMIT stuff */
4#include "apr_optional.h"
5#include "apr_buckets.h"
6#include "apr_lib.h"
7#include "apr_poll.h"
8
9#define APR_WANT_STRFUNC
10#define APR_WANT_MEMFUNC
11#include "apr_want.h"
12
13#include "util_filter.h"
14#include "ap_config.h"
15#include "httpd.h"
16#include "http_config.h"
17#include "http_request.h"
18#include "http_core.h"
19#include "http_protocol.h"
20#include "http_main.h"
21#include "http_log.h"
22#include "util_script.h"
23#include "ap_mpm.h"
24#include "mod_core.h"
25#include "mod_cgi.h"
26#include "util_md5.h"
27
28module AP_MODULE_DECLARE_DATA tile_module;
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <stdarg.h>
34#include <sys/types.h>
35#include <sys/socket.h>
36#include <sys/un.h>
37#include <unistd.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <limits.h>
41#include <time.h>
42#include <netinet/in.h>
43#include <arpa/inet.h>
44
45
46#include "gen_tile.h"
47#include "protocol.h"
48#include "render_config.h"
49#include "store.h"
50#include "dir_utils.h"
51#include "mod_tile.h"
52
53
54
55#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
56#include "unixd.h"
57#define MOD_TILE_SET_MUTEX_PERMS /* XXX Apache should define something */
58#endif
59
60
61
62apr_shm_t *stats_shm;
63apr_shm_t *delaypool_shm;
64char *shmfilename;
65char *shmfilename_delaypool;
66apr_global_mutex_t *stats_mutex;
67apr_global_mutex_t *delay_mutex;
68char *mutexfilename;
69
70static int error_message(request_rec *r, const char *format, ...)
71                 __attribute__ ((format (printf, 2, 3)));
72
73static int error_message(request_rec *r, const char *format, ...)
74{
75    va_list ap;
76    va_start(ap, format);
77    int len;
78    char *msg;
79
80    len = vasprintf(&msg, format, ap);
81
82    if (msg) {
83        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
84        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg);
85        r->content_type = "text/plain";
86        if (!r->header_only)
87            ap_rputs(msg, r);
88        free(msg);
89    }
90
91    return OK;
92}
93
94int socket_init(request_rec *r)
95{
96    ap_conf_vector_t *sconf = r->server->module_config;
97    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
98
99    int fd;
100    struct sockaddr_un addr;
101
102    fd = socket(PF_UNIX, SOCK_STREAM, 0);
103    if (fd < 0) {
104        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "failed to create unix socket");
105        return FD_INVALID;
106    }
107
108    bzero(&addr, sizeof(addr));
109    addr.sun_family = AF_UNIX;
110    strncpy(addr.sun_path, scfg->renderd_socket_name, sizeof(addr.sun_path));
111
112    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
113        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "socket connect failed for: %s", scfg->renderd_socket_name);
114        close(fd);
115        return FD_INVALID;
116    }
117    return fd;
118}
119
120int request_tile(request_rec *r, struct protocol *cmd, int renderImmediately)
121{
122    int fd;
123    int ret = 0;
124    int retry = 1;
125    struct protocol resp;
126
127    ap_conf_vector_t *sconf = r->server->module_config;
128    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
129
130    fd = socket_init(r);
131
132    if (fd == FD_INVALID) {
133        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "Failed to connect to renderer");
134        return 0;
135    }
136
137    // cmd has already been partial filled, fill in the rest
138    cmd->ver = PROTO_VER;
139    switch (renderImmediately) {
140    case 0: { cmd->cmd = cmdDirty; break;}
141    case 1: { cmd->cmd = cmdRender; break;}
142    case 2: { cmd->cmd = cmdRenderPrio; break;}
143    }
144
145    if (scfg->bulkMode) cmd->cmd = cmdRenderBulk; 
146
147    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Requesting style(%s) z(%d) x(%d) y(%d) from renderer with priority %d", cmd->xmlname, cmd->z, cmd->x, cmd->y, cmd->cmd);
148    do {
149        ret = send(fd, cmd, sizeof(struct protocol), 0);
150
151        if (ret == sizeof(struct protocol))
152            break;
153
154        close(fd);
155        if (errno != EPIPE)
156            return 0;
157
158        fd = socket_init(r);
159        if (fd == FD_INVALID)
160            return 0;
161    } while (retry--);
162
163    if (renderImmediately) {
164        struct timeval tv = {(renderImmediately > 1?scfg->request_timeout_priority:scfg->request_timeout), 0 };
165        fd_set rx;
166        int s;
167
168        while (1) {
169            FD_ZERO(&rx);
170            FD_SET(fd, &rx);
171            s = select(fd+1, &rx, NULL, NULL, &tv);
172            if (s == 1) {
173                bzero(&resp, sizeof(struct protocol));
174                ret = recv(fd, &resp, sizeof(struct protocol), 0);
175                if (ret != sizeof(struct protocol)) {
176                    //perror("recv error");
177                    break;
178                }
179
180                if (cmd->x == resp.x && cmd->y == resp.y && cmd->z == resp.z && !strcmp(cmd->xmlname, resp.xmlname)) {
181                    close(fd);
182                    if (resp.cmd == cmdDone)
183                        return 1;
184                    else
185                        return 0;
186                } else {
187                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
188                       "Response does not match request: xml(%s,%s) z(%d,%d) x(%d,%d) y(%d,%d)", cmd->xmlname,
189                       resp.xmlname, cmd->z, resp.z, cmd->x, resp.x, cmd->y, resp.y);
190                }
191            } else {
192                break;
193            }
194        }
195    }
196
197    close(fd);
198    return 0;
199}
200
201static apr_time_t getPlanetTime(request_rec *r)
202{
203    static apr_time_t last_check;
204    static apr_time_t planet_timestamp;
205    static pthread_mutex_t planet_lock = PTHREAD_MUTEX_INITIALIZER;
206    apr_time_t now = r->request_time;
207    struct apr_finfo_t s;
208
209    pthread_mutex_lock(&planet_lock);
210    // Only check for updates periodically
211    if (now < last_check + apr_time_from_sec(300)) {
212        pthread_mutex_unlock(&planet_lock);
213        return planet_timestamp;
214    }
215
216    ap_conf_vector_t *sconf = r->server->module_config;
217    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
218    char filename[PATH_MAX];
219    snprintf(filename, PATH_MAX-1, "%s/%s", scfg->tile_dir, PLANET_TIMESTAMP);
220
221    last_check = now;
222    if (apr_stat(&s, filename, APR_FINFO_MIN, r->pool) != APR_SUCCESS) {
223        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Planet timestamp file (%s) is missing", filename);
224        // Make something up
225        planet_timestamp = now - apr_time_from_sec(3 * 24 * 60 * 60);
226    } else {
227        if (s.mtime != planet_timestamp) {
228            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated");
229            planet_timestamp = s.mtime;
230        }
231    }
232    pthread_mutex_unlock(&planet_lock);
233    return planet_timestamp;
234}
235
236static enum tileState tile_state_once(request_rec *r)
237{
238    apr_status_t rv;
239    apr_finfo_t *finfo = &r->finfo;
240
241    if (!(finfo->valid & APR_FINFO_MTIME)) {
242        rv = apr_stat(finfo, r->filename, APR_FINFO_MIN, r->pool);
243        if (rv != APR_SUCCESS)
244            return tileMissing;
245    }
246
247    if (finfo->mtime < getPlanetTime(r))
248        return tileOld;
249
250    return tileCurrent;
251}
252
253static enum tileState tile_state(request_rec *r, struct protocol *cmd)
254{
255    enum tileState state = tile_state_once(r);
256#ifdef METATILEFALLBACK
257    if (state == tileMissing) {
258        ap_conf_vector_t *sconf = r->server->module_config;
259        tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
260
261        // Try fallback to plain PNG
262        char path[PATH_MAX];
263        xyz_to_path(path, sizeof(path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
264        r->filename = apr_pstrdup(r->pool, path);
265        state = tile_state_once(r);
266        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "png fallback %d/%d/%d",x,y,z);
267
268        if (state == tileMissing) {
269            // PNG not available either, if it gets rendered, it'll now be a .meta
270            xyz_to_meta(path, sizeof(path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
271            r->filename = apr_pstrdup(r->pool, path);
272        }
273    }
274#endif
275    return state;
276}
277
278static void add_expiry(request_rec *r, struct protocol * cmd)
279{
280    apr_time_t holdoff;
281    apr_table_t *t = r->headers_out;
282    enum tileState state = tile_state(r, cmd);
283    apr_finfo_t *finfo = &r->finfo;
284    char *timestr;
285    long int planetTimestamp, maxAge, minCache, lastModified;
286
287    ap_conf_vector_t *sconf = r->server->module_config;
288    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
289
290    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
291                  r->handler, r->uri, r->filename, r->path_info);
292
293    /* If the hostname matches the "extended caching hostname" then set the cache age accordingly */
294    if ((scfg->cache_extended_hostname[0] != 0) && (strstr(r->hostname,
295            scfg->cache_extended_hostname) != NULL)) {
296        maxAge = scfg->cache_extended_duration;
297    } else {
298
299        /* Test if the tile we are serving is out of date, then set a low maxAge*/
300        if (state == tileOld) {
301            holdoff = (scfg->cache_duration_dirty / 2) * (rand() / (RAND_MAX
302                    + 1.0));
303            maxAge = scfg->cache_duration_dirty + holdoff;
304        } else {
305            // cache heuristic based on zoom level
306            if (cmd->z > MAX_ZOOM) {
307                minCache = 0;
308                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
309                        "z (%i) is larger than MAXZOOM %i\n", cmd->z, MAX_ZOOM);
310            } else {
311                minCache = scfg->mincachetime[cmd->z];
312            }
313            // Time to the next known complete rerender
314            planetTimestamp = apr_time_sec(getPlanetTime(r)
315                    + apr_time_from_sec(PLANET_INTERVAL) - r->request_time);
316            // Time since the last render of this tile
317            lastModified = (int) (((double) apr_time_sec(r->request_time
318                    - finfo->mtime))
319                    * scfg->cache_duration_last_modified_factor);
320            // Add a random jitter of 3 hours to space out cache expiry
321            holdoff = (3 * 60 * 60) * (rand() / (RAND_MAX + 1.0));
322
323            maxAge = MAX(minCache, planetTimestamp);
324            maxAge = MAX(maxAge, lastModified);
325            maxAge += holdoff;
326
327            ap_log_rerror(
328                    APLOG_MARK,
329                    APLOG_DEBUG,
330                    0,
331                    r,
332                    "caching heuristics: next planet render %ld; zoom level based %ld; last modified %ld\n",
333                    planetTimestamp, minCache, lastModified);
334        }
335
336        maxAge = MIN(maxAge, scfg->cache_duration_max);
337    }
338
339    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Setting tiles maxAge to %ld\n", maxAge);
340
341    apr_table_mergen(t, "Cache-Control",
342                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
343                     maxAge));
344    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
345    apr_rfc822_date(timestr, (apr_time_from_sec(maxAge) + r->request_time));
346    apr_table_setn(t, "Expires", timestr);
347}
348
349double get_load_avg(request_rec *r)
350{
351    double loadavg[1];
352    int n = getloadavg(loadavg, 1);
353
354    if (n < 1)
355        return 1000;
356    else
357        return loadavg[0];
358}
359
360static int get_global_lock(request_rec *r, apr_global_mutex_t * mutex) {
361    apr_status_t rs;
362    int camped;
363
364    for (camped = 0; camped < MAXCAMP; camped++) {
365        rs = apr_global_mutex_trylock(mutex);
366        if (APR_STATUS_IS_EBUSY(rs)) {
367            apr_sleep(CAMPOUT);
368        } else if (rs == APR_SUCCESS) {
369            return 1;
370        } else if (APR_STATUS_IS_ENOTIMPL(rs)) {
371            /* If it's not implemented, just hang in the mutex. */
372            rs = apr_global_mutex_lock(mutex);
373            if (rs == APR_SUCCESS) {
374                return 1;
375            } else {
376                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Could not get hardlock");
377                return 0;
378            }
379        } else {
380            ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "Unknown return status from trylock");
381            return 0;
382        }
383    }
384    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Timedout trylock");
385    return 0;
386}
387
388static int incRespCounter(int resp, request_rec *r, struct protocol * cmd) {
389    stats_data *stats;
390
391    ap_conf_vector_t *sconf = r->server->module_config;
392    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
393
394    if (!scfg->enableGlobalStats) {
395        /* If tile stats reporting is not enable
396         * pretend we correctly updated the counter to
397         * not fill the logs with warnings about failed
398         * stats
399         */
400        return 1;
401    }
402
403    if (get_global_lock(r, stats_mutex) != 0) {
404        stats = (stats_data *)apr_shm_baseaddr_get(stats_shm);
405        switch (resp) {
406        case OK: {
407            stats->noResp200++;
408                        if (cmd != NULL) {
409                                stats->noRespZoom[cmd->z]++;
410                        }
411            break;
412        }
413        case HTTP_NOT_MODIFIED: {
414            stats->noResp304++;
415                        if (cmd != NULL) {
416                                stats->noRespZoom[cmd->z]++;
417                        }
418            break;
419        }
420        case HTTP_NOT_FOUND: {
421            stats->noResp404++;
422            break;
423        }
424                case HTTP_SERVICE_UNAVAILABLE: {
425            stats->noResp503++;
426            break;
427        }
428        case HTTP_INTERNAL_SERVER_ERROR: {
429            stats->noResp5XX++;
430            break;
431        }
432        default: {
433            stats->noRespOther++;
434        }
435
436        }
437        apr_global_mutex_unlock(stats_mutex);
438        /* Swallowing the result because what are we going to do with it at
439         * this stage?
440         */
441        return 1;
442    } else {
443        return 0;
444    }
445}
446
447static int incFreshCounter(int status, request_rec *r) {
448    stats_data *stats;
449
450    ap_conf_vector_t *sconf = r->server->module_config;
451    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
452
453    if (!scfg->enableGlobalStats) {
454        /* If tile stats reporting is not enable
455         * pretend we correctly updated the counter to
456         * not fill the logs with warnings about failed
457         * stats
458         */
459        return 1;
460    }
461
462    if (get_global_lock(r, stats_mutex) != 0) {
463        stats = (stats_data *)apr_shm_baseaddr_get(stats_shm);
464        switch (status) {
465        case FRESH: {
466            stats->noFreshCache++;
467            break;
468        }
469        case FRESH_RENDER: {
470            stats->noFreshRender++;
471            break;
472        }
473        case OLD: {
474            stats->noOldCache++;
475            break;
476        }
477        case OLD_RENDER: {
478            stats->noOldRender++;
479            break;
480        }
481        }
482        apr_global_mutex_unlock(stats_mutex);
483        /* Swallowing the result because what are we going to do with it at
484         * this stage?
485         */
486        return 1;
487    } else {
488        return 0;
489    }
490}
491
492static int delay_allowed(request_rec *r, enum tileState state) {
493        delaypool * delayp;
494        int delay = 0;
495        int i,j;
496
497    ap_conf_vector_t *sconf = r->server->module_config;
498        tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
499        delayp = (delaypool *)apr_shm_baseaddr_get(delaypool_shm);
500
501        /* TODO: fix IPv6 compatibility */
502        in_addr_t ip = inet_addr(r->connection->remote_ip);
503       
504        int hashkey = ip % DELAY_HASHTABLE_WHITELIST_SIZE;
505        if (delayp->whitelist[hashkey] == ip) {
506                return 1;
507        }
508
509        /* If a delaypool fillup is ongoing, just skip accounting to not block on a lock */
510        if (delayp->locked) {
511                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "skipping delay pool accounting, during fillup procedure\n");
512                return 1;
513        }
514
515       
516        hashkey = ip % DELAY_HASHTABLE_SIZE;
517       
518        if (get_global_lock(r,delay_mutex) == 0) {
519                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Could not acquire lock, skipping delay pool accounting\n");
520                return 1;
521        };
522        if (delayp->users[hashkey].ip_addr == ip) {
523                /* Repeat the process to determin if we have tockens in the bucket, as the fillup only runs once a client hits an empty bucket,
524                   so in the mean time, the bucket might have been filled */
525                for (j = 0; j < 3; j++) {
526                        //ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking delays: Current poolsize: %i tiles and %i renders\n", delayp->users[hashkey].available_tiles, delayp->users[hashkey].available_render_req);
527                        delay = 0;
528                        if (delayp->users[hashkey].available_tiles > 0) {
529                                delayp->users[hashkey].available_tiles--;
530                        } else {
531                                delay = 1;
532                        }
533                        if (state == tileMissing) {
534                                if (delayp->users[hashkey].available_render_req > 0) {
535                                        delayp->users[hashkey].available_render_req--;
536                                } else {
537                                        delay = 2;
538                                }
539                        }
540
541                        if (delay > 0) {
542                                /* If we are on the second round, we really  hit an empty delaypool, timeout for a while to slow down clients */
543                                if (j > 0) {
544                                        apr_global_mutex_unlock(delay_mutex);
545                                        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Delaypool: Client %s has hit its limits, throttling (%i)\n", r->connection->remote_ip, delay);
546                                        sleep(CLIENT_PENALTY);
547                                        if (get_global_lock(r,delay_mutex) == 0) {
548                                                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Could not acquire lock, but had to delay\n");
549                                                return 0;
550                                        };
551                                }
552                                /* We hit an empty bucket, so run the bucket fillup procedure to check if new tokens should have arrived in the mean time. */
553                                apr_time_t now = apr_time_now();
554                                int tiles_topup = (now - delayp->last_tile_fillup) / scfg->delaypoolTileRate;
555                                int render_topup = (now - delayp->last_render_fillup) / scfg->delaypoolRenderRate;
556                                //ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Filling up pools with %i tiles and %i renders\n", tiles_topup, render_topup);
557                                if ((tiles_topup > 0) || (render_topup > 0)) {
558                                        delayp->locked = 1;
559                                        for (i = 0; i < DELAY_HASHTABLE_SIZE; i++) {
560                                                delayp->users[i].available_tiles += tiles_topup;
561                                                delayp->users[i].available_render_req += render_topup;
562                                                if (delayp->users[i].available_tiles > scfg->delaypoolTileSize) {
563                                                        delayp->users[i].available_tiles = scfg->delaypoolTileSize;
564                                                }
565                                                if (delayp->users[i].available_render_req > scfg->delaypoolRenderSize) {
566                                                        delayp->users[i].available_render_req = scfg->delaypoolRenderSize;
567                                                }
568                                        }
569                                        delayp->locked = 0;
570                                }
571                                delayp->last_tile_fillup += scfg->delaypoolTileRate*tiles_topup;
572                                delayp->last_render_fillup += scfg->delaypoolRenderRate*render_topup;                           
573                               
574                        } else {
575                                break;
576                        }
577                }
578        } else {
579                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Creating a new delaypool for ip %s, overwriting %s\n", r->connection->remote_ip, inet_ntoa(inet_makeaddr(delayp->users[hashkey].ip_addr,delayp->users[hashkey].ip_addr)));
580                delayp->users[hashkey].ip_addr = ip;
581                delayp->users[hashkey].available_tiles = scfg->delaypoolTileSize;
582            delayp->users[hashkey].available_render_req = scfg->delaypoolRenderSize;
583                delay = 0;
584        }
585        apr_global_mutex_unlock(delay_mutex);
586
587        if (delay > 0) {
588                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Delaypool: Client %s has hit its limits, rejecting (%i)\n", r->connection->remote_ip, delay);
589                return 0;
590        } else {
591                return 1;
592        }
593}
594
595static int tile_handler_dirty(request_rec *r)
596{
597    if(strcmp(r->handler, "tile_dirty"))
598        return DECLINED;
599
600    struct protocol * cmd = (struct protocol *)ap_get_module_config(r->request_config, &tile_module);
601    if (cmd == NULL)
602        return DECLINED;
603
604    ap_conf_vector_t *sconf = r->server->module_config;
605    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
606        if (scfg->bulkMode) return OK;
607
608    request_tile(r, cmd, 0);
609    return error_message(r, "Tile submitted for rendering\n");
610}
611
612static int tile_storage_hook(request_rec *r)
613{
614//    char abs_path[PATH_MAX];
615    int avg;
616    int renderPrio = 0;
617    enum tileState state;
618
619    if (!r->handler)
620        return DECLINED;
621
622        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "tile_storage_hook: handler(%s), uri(%s), filename(%s), path_info(%s)",
623                  r->handler, r->uri, r->filename, r->path_info);
624
625    // Any status request is OK. tile_dirty also doesn't need to be handled, as tile_handler_dirty will take care of it
626    if (!strcmp(r->handler, "tile_status") || !strcmp(r->handler, "tile_dirty") || !strcmp(r->handler, "tile_mod_stats"))
627        return OK;
628
629    if (strcmp(r->handler, "tile_serve"))
630        return DECLINED;
631
632    struct protocol * cmd = (struct protocol *)ap_get_module_config(r->request_config, &tile_module);
633    if (cmd == NULL)
634        return DECLINED;
635
636/*
637should already be done
638    // Generate the tile filename
639#ifdef METATILE
640    ap_conf_vector_t *sconf = r->server->module_config;
641    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
642    xyz_to_meta(abs_path, sizeof(abs_path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
643#else
644    xyz_to_path(abs_path, sizeof(abs_path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
645#endif
646    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "abs_path(%s)", abs_path);
647    r->filename = apr_pstrdup(r->pool, abs_path);
648*/
649    avg = get_load_avg(r);
650    state = tile_state(r, cmd);
651
652    ap_conf_vector_t *sconf = r->server->module_config;
653    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
654
655        if (scfg->enableTileThrottling && !delay_allowed(r, state)) {
656                if (!incRespCounter(HTTP_SERVICE_UNAVAILABLE, r, cmd)) {
657                   ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
658                        "Failed to increase response stats counter");
659        }
660        return HTTP_SERVICE_UNAVAILABLE;               
661        }
662
663    switch (state) {
664        case tileCurrent:
665            if (!incFreshCounter(FRESH, r)) {
666                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
667                    "Failed to increase fresh stats counter");
668            }
669            return OK;
670            break;
671        case tileOld:
672            if (scfg->bulkMode) {
673                return OK;
674            } else if (avg > scfg->max_load_old) {
675               // Too much load to render it now, mark dirty but return old tile
676               request_tile(r, cmd, 0);
677               ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Load larger max_load_old (%d). Mark dirty and deliver from cache.", scfg->max_load_old);
678               if (!incFreshCounter(OLD, r)) {
679                   ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
680                        "Failed to increase fresh stats counter");
681               }
682               return OK;
683            }
684            renderPrio = 1;
685            break;
686        case tileMissing:
687            if (avg > scfg->max_load_missing) {
688               request_tile(r, cmd, 0);
689               ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Load larger max_load_missing (%d). Return HTTP_NOT_FOUND.", scfg->max_load_missing);
690               if (!incRespCounter(HTTP_NOT_FOUND, r, cmd)) {
691                   ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
692                        "Failed to increase response stats counter");
693               }
694               return HTTP_NOT_FOUND;
695            }
696            renderPrio = 2;
697            break;
698    }
699
700    if (request_tile(r, cmd, renderPrio)) {
701        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Update file info abs_path(%s)", r->filename);
702        // Need to update fileinfo for new rendered tile
703        apr_stat(&r->finfo, r->filename, APR_FINFO_MIN, r->pool);
704        if (!incFreshCounter(FRESH_RENDER, r)) {
705            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
706                    "Failed to increase fresh stats counter");
707        }
708        return OK;
709    }
710
711    if (state == tileOld) {
712        if (!incFreshCounter(OLD_RENDER, r)) {
713            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
714                    "Failed to increase fresh stats counter");
715        }
716        return OK;
717    }
718    if (!incRespCounter(HTTP_NOT_FOUND, r, cmd)) {
719        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
720                "Failed to increase response stats counter");
721    }
722
723    return HTTP_NOT_FOUND;
724}
725
726static int tile_handler_status(request_rec *r)
727{
728    enum tileState state;
729    char time_str[APR_CTIME_LEN];
730
731    if(strcmp(r->handler, "tile_status"))
732        return DECLINED;
733
734    struct protocol * cmd = (struct protocol *)ap_get_module_config(r->request_config, &tile_module);
735    if (cmd == NULL){
736        sleep(CLIENT_PENALTY);
737        return HTTP_NOT_FOUND;
738    }
739
740    state = tile_state(r, cmd);
741    if (state == tileMissing)
742        return error_message(r, "Unable to find a tile at %s\n", r->filename);
743    apr_ctime(time_str, r->finfo.mtime);
744
745    return error_message(r, "Tile is %s. Last rendered at %s\n", (state == tileOld) ? "due to be rendered" : "clean", time_str);
746}
747
748static int tile_handler_mod_stats(request_rec *r)
749{
750    stats_data * stats;
751    stats_data local_stats;
752        int i;
753
754    if (strcmp(r->handler, "tile_mod_stats"))
755        return DECLINED;
756
757    ap_conf_vector_t *sconf = r->server->module_config;
758    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
759
760    if (!scfg->enableGlobalStats) {
761        return error_message(r, "Stats are not enabled for this server");
762    }
763
764    if (get_global_lock(r,stats_mutex) != 0) {
765        //Copy over the global counter variable into
766        //local variables, that we can immediately
767        //release the lock again
768        stats = (stats_data *) apr_shm_baseaddr_get(stats_shm);
769        memcpy(&local_stats, stats, sizeof(stats_data));
770        apr_global_mutex_unlock(stats_mutex);
771    } else {
772        return error_message(r, "Failed to acquire lock, can't display stats");
773    }
774
775    ap_rprintf(r, "NoResp200: %li\n", local_stats.noResp200);
776    ap_rprintf(r, "NoResp304: %li\n", local_stats.noResp304);
777    ap_rprintf(r, "NoResp404: %li\n", local_stats.noResp404);
778        ap_rprintf(r, "NoResp503: %li\n", local_stats.noResp503);
779    ap_rprintf(r, "NoResp5XX: %li\n", local_stats.noResp5XX);
780    ap_rprintf(r, "NoRespOther: %li\n", local_stats.noRespOther);
781    ap_rprintf(r, "NoFreshCache: %li\n", local_stats.noFreshCache);
782    ap_rprintf(r, "NoOldCache: %li\n", local_stats.noOldCache);
783    ap_rprintf(r, "NoFreshRender: %li\n", local_stats.noFreshRender);
784    ap_rprintf(r, "NoOldRender: %li\n", local_stats.noOldRender);
785        for (i = 0; i <= MAX_ZOOM; i++) {
786                ap_rprintf(r, "NoRespZoom%02i: %li\n", i, local_stats.noRespZoom[i]);
787        }
788
789
790
791    return OK;
792}
793
794static int tile_handler_serve(request_rec *r)
795{
796    const int tile_max = MAX_SIZE;
797    unsigned char *buf;
798    int len;
799    apr_status_t errstatus;
800
801    if(strcmp(r->handler, "tile_serve"))
802        return DECLINED;
803
804    struct protocol * cmd = (struct protocol *)ap_get_module_config(r->request_config, &tile_module);
805    if (cmd == NULL){
806        sleep(CLIENT_PENALTY);
807        if (!incRespCounter(HTTP_NOT_FOUND, r, cmd)) {
808            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
809                    "Failed to increase response stats counter");
810        }
811        return HTTP_NOT_FOUND;
812    }
813
814    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_handler_serve: xml(%s) z(%d) x(%d) y(%d)", cmd->xmlname, cmd->z, cmd->x, cmd->y);
815
816    // FIXME: It is a waste to do the malloc + read if we are fulfilling a HEAD or returning a 304.
817    buf = malloc(tile_max);
818    if (!buf) {
819        if (!incRespCounter(HTTP_INTERNAL_SERVER_ERROR, r, cmd)) {
820            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
821                    "Failed to increase response stats counter");
822        }
823        return HTTP_INTERNAL_SERVER_ERROR;
824    }
825
826    len = tile_read(cmd->xmlname, cmd->x, cmd->y, cmd->z, buf, tile_max);
827    if (len > 0) {
828#if 0
829        // Set default Last-Modified and Etag headers
830        ap_update_mtime(r, r->finfo.mtime);
831        ap_set_last_modified(r);
832        ap_set_etag(r);
833#else
834        // Use MD5 hash as only cache attribute.
835        // If a tile is re-rendered and produces the same output
836        // then we can continue to use the previous cached copy
837        char *md5 = ap_md5_binary(r->pool, buf, len);
838        apr_table_setn(r->headers_out, "ETag",
839                        apr_psprintf(r->pool, "\"%s\"", md5));
840#endif
841        ap_set_content_type(r, "image/png");
842        ap_set_content_length(r, len);
843        add_expiry(r, cmd);
844        if ((errstatus = ap_meets_conditions(r)) != OK) {
845            free(buf);
846            if (!incRespCounter(errstatus, r, cmd)) {
847                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
848                        "Failed to increase response stats counter");
849            }
850            return errstatus;
851        } else {
852            ap_rwrite(buf, len, r);
853            free(buf);
854            if (!incRespCounter(errstatus, r, cmd)) {
855                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
856                        "Failed to increase response stats counter");
857            }
858            return OK;
859        }
860    }
861    free(buf);
862    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "len = %d", len);
863    if (!incRespCounter(HTTP_NOT_FOUND, r, cmd)) {
864        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
865                "Failed to increase response stats counter");
866    }
867    return DECLINED;
868}
869
870static int tile_translate(request_rec *r)
871{
872    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: uri(%s)", r->uri);
873
874    int i,n,limit,oob;
875    char option[11];
876
877    ap_conf_vector_t *sconf = r->server->module_config;
878    tile_server_conf *scfg = ap_get_module_config(sconf, &tile_module);
879
880    tile_config_rec *tile_configs = (tile_config_rec *) scfg->configs->elts;
881
882    /*
883     * The page /mod_tile returns global stats about the number of tiles
884     * handled and in what state those tiles were.
885     * This should probably not be hard coded
886     */
887    if (!strncmp("/mod_tile", r->uri, strlen("/mod_tile"))) {
888        r->handler = "tile_mod_stats";
889        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
890                "tile_translate: retrieving global mod_tile stats");
891        return OK;
892    }
893
894    for (i = 0; i < scfg->configs->nelts; ++i) {
895        tile_config_rec *tile_config = &tile_configs[i];
896
897        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: testing baseuri(%s) name(%s)", tile_config->baseuri, tile_config->xmlname);
898
899        if (!strncmp(tile_config->baseuri, r->uri, strlen(tile_config->baseuri))) {
900
901            struct protocol * cmd = (struct protocol *) apr_pcalloc(r->pool, sizeof(struct protocol));
902            bzero(cmd, sizeof(struct protocol));
903
904            n = sscanf(r->uri+strlen(tile_config->baseuri), "%d/%d/%d.png/%10s", &(cmd->z), &(cmd->x), &(cmd->y), option);
905            if (n < 3) {
906                                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: Invalid URL for tilelayer %s", tile_config->xmlname);
907                                return DECLINED;
908                        }
909
910            oob = (cmd->z < 0 || cmd->z > MAX_ZOOM);
911            if (!oob) {
912                 // valid x/y for tiles are 0 ... 2^zoom-1
913                 limit = (1 << cmd->z) - 1;
914                 oob =  (cmd->x < 0 || cmd->x > limit || cmd->y < 0 || cmd->y > limit);
915            }
916
917            if (oob) {
918                                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: request for %s was outside of allowed bounds", tile_config->xmlname);
919                sleep(CLIENT_PENALTY);
920                //Don't increase stats counter here,
921                //As we are interested in valid tiles only
922                return HTTP_NOT_FOUND;
923            }
924
925            strcpy(cmd->xmlname, tile_config->xmlname);
926
927            // Store a copy for later
928            ap_set_module_config(r->request_config, &tile_module, cmd);
929
930            // Generate the tile filename?
931            char abs_path[PATH_MAX];
932#ifdef METATILE
933            xyz_to_meta(abs_path, sizeof(abs_path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
934#else
935            xyz_to_path(abs_path, sizeof(abs_path), scfg->tile_dir, cmd->xmlname, cmd->x, cmd->y, cmd->z);
936#endif
937            r->filename = apr_pstrdup(r->pool, abs_path);
938
939            if (n == 4) {
940                if (!strcmp(option, "status")) r->handler = "tile_status";
941                else if (!strcmp(option, "dirty")) r->handler = "tile_dirty";
942                else return DECLINED;
943            } else {
944                r->handler = "tile_serve";
945            }
946
947            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "tile_translate: op(%s) xml(%s) z(%d) x(%d) y(%d)", r->handler , cmd->xmlname, cmd->z, cmd->x, cmd->y);
948
949            return OK;
950        }
951    }
952        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: No suitable tile layer found");
953    return DECLINED;
954}
955
956/*
957 * This routine is called in the parent, so we'll set up the shared
958 * memory segment and mutex here.
959 */
960
961static int mod_tile_post_config(apr_pool_t *pconf, apr_pool_t *plog,
962                             apr_pool_t *ptemp, server_rec *s)
963{
964    void *data; /* These two help ensure that we only init once. */
965    const char *userdata_key = "mod_tile_init_module";
966    apr_status_t rs;
967    stats_data *stats;
968        delaypool *delayp;
969        int i;
970
971
972    /*
973     * The following checks if this routine has been called before.
974     * This is necessary because the parent process gets initialized
975     * a couple of times as the server starts up, and we don't want
976     * to create any more mutexes and shared memory segments than
977     * we're actually going to use.
978     */
979    apr_pool_userdata_get(&data, userdata_key, s->process->pool);
980    if (!data) {
981        apr_pool_userdata_set((const void *) 1, userdata_key,
982                              apr_pool_cleanup_null, s->process->pool);
983        return OK;
984    } /* Kilroy was here */
985
986    /* Create the shared memory segment */
987
988    /*
989     * Create a unique filename using our pid. This information is
990     * stashed in the global variable so the children inherit it.
991     * TODO get the location from the environment $TMPDIR or somesuch.
992     */
993    shmfilename = apr_psprintf(pconf, "/tmp/httpd_shm.%ld", (long int)getpid());
994        shmfilename_delaypool = apr_psprintf(pconf, "/tmp/httpd_shm_delay.%ld", (long int)getpid());
995
996    /* Now create that segment */
997    rs = apr_shm_create(&stats_shm, sizeof(stats_data),
998                        (const char *) shmfilename, pconf);
999    if (rs != APR_SUCCESS) {
1000        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
1001                     "Failed to create shared memory segment on file %s",
1002                     shmfilename);
1003        return HTTP_INTERNAL_SERVER_ERROR;
1004    }
1005
1006        rs = apr_shm_create(&delaypool_shm, sizeof(delaypool),
1007                        (const char *) shmfilename_delaypool, pconf);
1008    if (rs != APR_SUCCESS) {
1009        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
1010                     "Failed to create shared memory segment on file %s",
1011                     shmfilename_delaypool);
1012        return HTTP_INTERNAL_SERVER_ERROR;
1013    }
1014
1015    /* Created it, now let's zero it out */
1016    stats = (stats_data *)apr_shm_baseaddr_get(stats_shm);
1017    stats->noResp200 = 0;
1018    stats->noResp304 = 0;
1019    stats->noResp404 = 0;
1020        stats->noResp503 = 0;
1021    stats->noResp5XX = 0;
1022        for (i = 0; i <= MAX_ZOOM; i++) {
1023                stats->noRespZoom[i] = 0;
1024        }
1025    stats->noRespOther = 0;
1026    stats->noFreshCache = 0;
1027    stats->noFreshRender = 0;
1028    stats->noOldCache = 0;
1029    stats->noOldRender = 0;
1030
1031        delayp = (delaypool *)apr_shm_baseaddr_get(delaypool_shm);
1032       
1033        delayp->last_tile_fillup = apr_time_now();
1034        delayp->last_render_fillup = apr_time_now();
1035
1036        for (i = 0; i < DELAY_HASHTABLE_SIZE; i++) {
1037                delayp->users[i].ip_addr = (in_addr_t)0;
1038                delayp->users[i].available_tiles = 0;
1039                delayp->users[i].available_render_req = 0;
1040        }
1041        for (i = 0; i < DELAY_HASHTABLE_WHITELIST_SIZE; i++) {
1042                delayp->whitelist[i] = (in_addr_t)0;
1043        }
1044        /* TODO: need a way to initialise the delaypool whitelist */
1045
1046
1047    /* Create global mutex */
1048
1049    /*
1050     * Create another unique filename to lock upon. Note that
1051     * depending on OS and locking mechanism of choice, the file
1052     * may or may not be actually created.
1053     */
1054    mutexfilename = apr_psprintf(pconf, "/tmp/httpd_mutex.%ld",
1055                                 (long int) getpid());
1056
1057    rs = apr_global_mutex_create(&stats_mutex, (const char *) mutexfilename,
1058                                 APR_LOCK_DEFAULT, pconf);
1059    if (rs != APR_SUCCESS) {
1060        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
1061                     "Failed to create mutex on file %s",
1062                     mutexfilename);
1063        return HTTP_INTERNAL_SERVER_ERROR;
1064    }
1065
1066#ifdef MOD_TILE_SET_MUTEX_PERMS
1067    rs = unixd_set_global_mutex_perms(stats_mutex);
1068    if (rs != APR_SUCCESS) {
1069        ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
1070                     "Parent could not set permissions on mod_tile "
1071                     "mutex: check User and Group directives");
1072        return HTTP_INTERNAL_SERVER_ERROR;
1073    }
1074#endif /* MOD_TILE_SET_MUTEX_PERMS */
1075
1076    /*
1077     * Create another unique filename to lock upon. Note that
1078     * depending on OS and locking mechanism of choice, the file
1079     * may or may not be actually created.
1080     */
1081    mutexfilename = apr_psprintf(pconf, "/tmp/httpd_mutex_delay.%ld",
1082                                 (long int) getpid());
1083
1084    rs = apr_global_mutex_create(&delay_mutex, (const char *) mutexfilename,
1085                                 APR_LOCK_DEFAULT, pconf);
1086    if (rs != APR_SUCCESS) {
1087        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
1088                     "Failed to create mutex on file %s",
1089                     mutexfilename);
1090        return HTTP_INTERNAL_SERVER_ERROR;
1091    }
1092
1093#ifdef MOD_TILE_SET_MUTEX_PERMS
1094    rs = unixd_set_global_mutex_perms(delay_mutex);
1095    if (rs != APR_SUCCESS) {
1096        ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
1097                     "Parent could not set permissions on mod_tile "
1098                     "mutex: check User and Group directives");
1099        return HTTP_INTERNAL_SERVER_ERROR;
1100    }
1101#endif /* MOD_TILE_SET_MUTEX_PERMS */
1102
1103    return OK;
1104}
1105
1106
1107/*
1108 * This routine gets called when a child inits. We use it to attach
1109 * to the shared memory segment, and reinitialize the mutex.
1110 */
1111
1112static void mod_tile_child_init(apr_pool_t *p, server_rec *s)
1113{
1114    apr_status_t rs;
1115
1116     /*
1117      * Re-open the mutex for the child. Note we're reusing
1118      * the mutex pointer global here.
1119      */
1120     rs = apr_global_mutex_child_init(&stats_mutex,
1121                                      (const char *) mutexfilename,
1122                                      p);
1123     if (rs != APR_SUCCESS) {
1124         ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
1125                     "Failed to reopen mutex on file %s",
1126                     shmfilename);
1127         /* There's really nothing else we can do here, since
1128          * This routine doesn't return a status. */
1129         exit(1); /* Ugly, but what else? */
1130     }
1131}
1132
1133static void register_hooks(__attribute__((unused)) apr_pool_t *p)
1134{
1135    ap_hook_post_config(mod_tile_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1136    ap_hook_child_init(mod_tile_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1137    ap_hook_handler(tile_handler_serve, NULL, NULL, APR_HOOK_MIDDLE);
1138    ap_hook_handler(tile_handler_dirty, NULL, NULL, APR_HOOK_MIDDLE);
1139    ap_hook_handler(tile_handler_status, NULL, NULL, APR_HOOK_MIDDLE);
1140    ap_hook_handler(tile_handler_mod_stats, NULL, NULL, APR_HOOK_MIDDLE);
1141    ap_hook_translate_name(tile_translate, NULL, NULL, APR_HOOK_MIDDLE);
1142    ap_hook_map_to_storage(tile_storage_hook, NULL, NULL, APR_HOOK_FIRST);
1143}
1144
1145static const char *_add_tile_config(cmd_parms *cmd, void *mconfig, const char *baseuri, const char *name, int minzoom, int maxzoom)
1146{
1147    if (strlen(name) == 0) {
1148        return "ConfigName value must not be null";
1149    }
1150
1151
1152    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1153    tile_config_rec *tilecfg = apr_array_push(scfg->configs);
1154
1155    // Ensure URI string ends with a trailing slash
1156    int urilen = strlen(baseuri);
1157
1158    if (urilen==0)
1159      snprintf(tilecfg->baseuri, PATH_MAX, "/");
1160    else if (baseuri[urilen-1] != '/')
1161      snprintf(tilecfg->baseuri, PATH_MAX, "%s/", baseuri);
1162    else
1163      snprintf(tilecfg->baseuri, PATH_MAX, "%s", baseuri);
1164
1165    strncpy(tilecfg->xmlname, name, XMLCONFIG_MAX-1);
1166    tilecfg->xmlname[XMLCONFIG_MAX-1] = 0;
1167    tilecfg->minzoom = minzoom;
1168    tilecfg->maxzoom = maxzoom;
1169
1170        ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, cmd->server,
1171                    "Loading tile config %s at %s for zooms %i - %i from tile directory %s",
1172                                 name, baseuri, minzoom, maxzoom, scfg->tile_dir);
1173
1174
1175    return NULL;
1176}
1177
1178static const char *add_tile_config(cmd_parms *cmd, void *mconfig, const char *baseuri, const char *name)
1179{
1180    return _add_tile_config(cmd, mconfig, baseuri, name, 0, MAX_ZOOM);
1181}
1182
1183static const char *load_tile_config(cmd_parms *cmd, void *mconfig, const char *conffile)
1184{
1185    FILE * hini ;
1186    char filename[PATH_MAX];
1187    char xmlname[XMLCONFIG_MAX];
1188    char line[INILINE_MAX];
1189    char key[INILINE_MAX];
1190    char value[INILINE_MAX];
1191    const char * result;
1192
1193    if (strlen(conffile) == 0) {
1194        strcpy(filename, RENDERD_CONFIG);
1195    } else {
1196        strcpy(filename, conffile);
1197    }
1198
1199    // Load the config
1200    if ((hini=fopen(filename, "r"))==NULL) {
1201        return "Unable to open config file";
1202    }
1203
1204    while (fgets(line, INILINE_MAX, hini)!=NULL) {
1205        if (line[0] == '#') continue;
1206        if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = 0;
1207        if (line[0] == '[') {
1208            if (strlen(line) >= XMLCONFIG_MAX){
1209                return "XML name too long";
1210            }
1211            sscanf(line, "[%[^]]", xmlname);
1212        } else if (sscanf(line, "%[^=]=%[^;#]", key, value) == 2
1213               ||  sscanf(line, "%[^=]=\"%[^\"]\"", key, value) == 2) {
1214
1215            if (!strcmp(key, "URI")){
1216                if (strlen(value) >= PATH_MAX){
1217                    return "URI too long";
1218                }
1219                result = add_tile_config(cmd, mconfig, value, xmlname);
1220                if (result != NULL) return result;
1221            }
1222        }
1223    }
1224    fclose(hini);
1225    return NULL;
1226}
1227
1228static const char *mod_tile_request_timeout_config(cmd_parms *cmd, void *mconfig, const char *request_timeout_string)
1229{
1230    int request_timeout;
1231
1232    if (sscanf(request_timeout_string, "%d", &request_timeout) != 1) {
1233        return "ModTileRequestTimeout needs integer argument";
1234    }
1235
1236    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1237    scfg->request_timeout = request_timeout;
1238    return NULL;
1239}
1240
1241static const char *mod_tile_request_timeout_missing_config(cmd_parms *cmd, void *mconfig, const char *request_timeout_string)
1242{
1243    int request_timeout;
1244
1245    if (sscanf(request_timeout_string, "%d", &request_timeout) != 1) {
1246        return "ModTileMissingRequestTimeout needs integer argument";
1247    }
1248
1249    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1250    scfg->request_timeout_priority = request_timeout;
1251    return NULL;
1252}
1253
1254static const char *mod_tile_max_load_old_config(cmd_parms *cmd, void *mconfig, const char *max_load_old_string)
1255{
1256    int max_load_old;
1257
1258    if (sscanf(max_load_old_string, "%d", &max_load_old) != 1) {
1259        return "ModTileMaxLoadOld needs integer argument";
1260    }
1261
1262    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1263    scfg->max_load_old = max_load_old;
1264    return NULL;
1265}
1266
1267static const char *mod_tile_max_load_missing_config(cmd_parms *cmd, void *mconfig, const char *max_load_missing_string)
1268{
1269    int max_load_missing;
1270
1271    if (sscanf(max_load_missing_string, "%d", &max_load_missing) != 1) {
1272        return "ModTileMaxLoadMissing needs integer argument";
1273    }
1274
1275    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1276    scfg->max_load_missing = max_load_missing;
1277    return NULL;
1278}
1279
1280static const char *mod_tile_renderd_socket_name_config(cmd_parms *cmd, void *mconfig, const char *renderd_socket_name_string)
1281{
1282    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1283    strncpy(scfg->renderd_socket_name, renderd_socket_name_string, PATH_MAX-1);
1284    scfg->renderd_socket_name[PATH_MAX-1] = 0;
1285    return NULL;
1286}
1287
1288static const char *mod_tile_tile_dir_config(cmd_parms *cmd, void *mconfig, const char *tile_dir_string)
1289{
1290    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1291    strncpy(scfg->tile_dir, tile_dir_string, PATH_MAX-1);
1292    scfg->tile_dir[PATH_MAX-1] = 0;
1293    return NULL;
1294}
1295
1296static const char *mod_tile_cache_extended_host_name_config(cmd_parms *cmd, void *mconfig, const char *cache_extended_hostname)
1297{
1298    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1299    strncpy(scfg->cache_extended_hostname, cache_extended_hostname, PATH_MAX-1);
1300    scfg->cache_extended_hostname[PATH_MAX-1] = 0;
1301
1302    return NULL;
1303}
1304
1305static const char *mod_tile_cache_extended_duration_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_string)
1306{
1307    int cache_duration;
1308    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1309    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1310            return "ModTileCacheExtendedDuration needs integer argument";
1311    }
1312    scfg->cache_extended_duration = cache_duration;
1313
1314    return NULL;
1315}
1316
1317static const char *mod_tile_cache_lastmod_factor_config(cmd_parms *cmd, void *mconfig, const char *modified_factor_string)
1318{
1319    float modified_factor;
1320    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config,
1321            &tile_module);
1322    if (sscanf(modified_factor_string, "%f", &modified_factor) != 1) {
1323        return "ModTileCacheLastModifiedFactor needs float argument";
1324    }
1325    scfg->cache_duration_last_modified_factor = modified_factor;
1326    return NULL;
1327}
1328
1329static const char *mod_tile_cache_duration_max_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_string)
1330{
1331    int cache_duration;
1332    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config,
1333            &tile_module);
1334    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1335        return "ModTileCacheDurationMax needs integer argument";
1336    }
1337    scfg->cache_duration_max = cache_duration;
1338    return NULL;
1339}
1340
1341static const char *mod_tile_cache_duration_dirty_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_string)
1342{
1343    int cache_duration;
1344    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config,
1345            &tile_module);
1346    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1347        return "ModTileCacheDurationDirty needs integer argument";
1348    }
1349    scfg->cache_duration_dirty = cache_duration;
1350    return NULL;
1351}
1352
1353static const char *mod_tile_cache_duration_minimum_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_string)
1354{
1355    int cache_duration;
1356    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config,
1357            &tile_module);
1358    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1359        return "ModTileCacheDurationMinimum needs integer argument";
1360    }
1361    scfg->cache_duration_minimum = cache_duration;
1362    return NULL;
1363}
1364
1365static const char *mod_tile_cache_duration_low_config(cmd_parms *cmd, void *mconfig, const char *zoom_level_string, const char *cache_duration_string)
1366{
1367    int zoom_level;
1368    int cache_duration;
1369    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1370    if (sscanf(zoom_level_string, "%d", &zoom_level) != 1) {
1371            return "ModTileCacheDurationLowZoom needs integer argument";
1372    }
1373    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1374            return "ModTileCacheDurationLowZoom needs integer argument";
1375    }
1376    scfg->cache_level_low_zoom = zoom_level;
1377    scfg->cache_duration_low_zoom = cache_duration;
1378
1379    return NULL;
1380}
1381static const char *mod_tile_cache_duration_medium_config(cmd_parms *cmd, void *mconfig, const char *zoom_level_string, const char *cache_duration_string)
1382{
1383    int zoom_level;
1384    int cache_duration;
1385    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1386    if (sscanf(zoom_level_string, "%d", &zoom_level) != 1) {
1387            return "ModTileCacheDurationMediumZoom needs integer argument";
1388    }
1389    if (sscanf(cache_duration_string, "%d", &cache_duration) != 1) {
1390            return "ModTileCacheDurationMediumZoom needs integer argument";
1391    }
1392    scfg->cache_level_medium_zoom = zoom_level;
1393    scfg->cache_duration_medium_zoom = cache_duration;
1394
1395    return NULL;
1396}
1397
1398static const char *mod_tile_enable_stats(cmd_parms *cmd, void *mconfig, int enableStats)
1399{
1400    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1401    scfg->enableGlobalStats = enableStats;
1402    return NULL;
1403}
1404
1405static const char *mod_tile_enable_throttling(cmd_parms *cmd, void *mconfig, int enableThrottling)
1406{
1407    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1408    scfg->enableTileThrottling = enableThrottling;
1409    return NULL;
1410}
1411
1412static const char *mod_tile_bulk_mode(cmd_parms *cmd, void *mconfig, int bulkMode)
1413{
1414    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1415    scfg->bulkMode = bulkMode;
1416    return NULL;
1417}
1418
1419static const char *mod_tile_delaypool_tiles_config(cmd_parms *cmd, void *mconfig, const char *bucketsize_string, const char *topuprate_string)
1420{
1421    int bucketsize;
1422    float topuprate;
1423
1424    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1425    if (sscanf(bucketsize_string, "%d", &bucketsize) != 1) {
1426            return "ModTileThrottlingTiles needs two numerical arguments, the first one must be integer";
1427    }
1428    if (sscanf(topuprate_string, "%f", &topuprate) != 1) {
1429            return "ModTileThrottlingTiles needs two numerical arguments, the first one must be integer";
1430    }
1431    scfg->delaypoolTileSize = bucketsize;
1432
1433        /*Convert topup rate into microseconds per tile */
1434    scfg->delaypoolTileRate = (long)(1000000.0/topuprate);
1435
1436    return NULL;
1437}
1438
1439static const char *mod_tile_delaypool_render_config(cmd_parms *cmd, void *mconfig, const char *bucketsize_string, const char *topuprate_string)
1440{
1441    int bucketsize;
1442    float topuprate;
1443
1444    tile_server_conf *scfg = ap_get_module_config(cmd->server->module_config, &tile_module);
1445    if (sscanf(bucketsize_string, "%d", &bucketsize) != 1) {
1446            return "ModTileThrottlingRenders needs two numerical arguments, the first one must be integer";
1447    }
1448    if (sscanf(topuprate_string, "%f", &topuprate) != 1) {
1449            return "ModTileThrottlingRenders needs two numerical arguments, the first one must be integer";
1450    }
1451    scfg->delaypoolRenderSize = bucketsize;
1452
1453        /*Convert topup rate into microseconds per tile */
1454    scfg->delaypoolRenderRate = (long)(1000000.0/topuprate);
1455
1456    return NULL;
1457}
1458
1459static void *create_tile_config(apr_pool_t *p, server_rec *s)
1460{
1461    tile_server_conf * scfg = (tile_server_conf *) apr_pcalloc(p, sizeof(tile_server_conf));
1462
1463    scfg->configs = apr_array_make(p, 4, sizeof(tile_config_rec));
1464    scfg->request_timeout = REQUEST_TIMEOUT;
1465    scfg->request_timeout_priority = REQUEST_TIMEOUT;
1466    scfg->max_load_old = MAX_LOAD_OLD;
1467    scfg->max_load_missing = MAX_LOAD_MISSING;
1468    strncpy(scfg->renderd_socket_name, RENDER_SOCKET, PATH_MAX-1);
1469    scfg->renderd_socket_name[PATH_MAX-1] = 0;
1470    strncpy(scfg->tile_dir, HASH_PATH, PATH_MAX-1);
1471    scfg->tile_dir[PATH_MAX-1] = 0;
1472        memset(&(scfg->cache_extended_hostname),0,PATH_MAX);
1473        scfg->cache_extended_duration = 0;
1474    scfg->cache_duration_dirty = 15*60;
1475    scfg->cache_duration_last_modified_factor = 0.0;
1476    scfg->cache_duration_max = 7*24*60*60;
1477    scfg->cache_duration_minimum = 3*60*60;
1478    scfg->cache_duration_low_zoom = 6*24*60*60;
1479    scfg->cache_duration_medium_zoom = 1*24*60*60;
1480    scfg->cache_level_low_zoom = 0;
1481    scfg->cache_level_medium_zoom = 0;
1482    scfg->enableGlobalStats = 1;
1483        scfg->enableTileThrottling = 0;
1484        scfg->delaypoolTileSize = AVAILABLE_TILE_BUCKET_SIZE;
1485        scfg->delaypoolTileRate = RENDER_TOPUP_RATE;
1486        scfg->delaypoolRenderSize = AVAILABLE_RENDER_BUCKET_SIZE;
1487        scfg->delaypoolRenderRate = RENDER_TOPUP_RATE;
1488        scfg->bulkMode = 0;
1489
1490
1491    return scfg;
1492}
1493
1494static void *merge_tile_config(apr_pool_t *p, void *basev, void *overridesv)
1495{
1496    int i;
1497    tile_server_conf * scfg = (tile_server_conf *) apr_pcalloc(p, sizeof(tile_server_conf));
1498    tile_server_conf * scfg_base = (tile_server_conf *) basev;
1499    tile_server_conf * scfg_over = (tile_server_conf *) overridesv;
1500
1501    scfg->configs = apr_array_append(p, scfg_base->configs, scfg_over->configs);
1502    scfg->request_timeout = scfg_over->request_timeout;
1503        scfg->request_timeout_priority = scfg_over->request_timeout_priority;
1504    scfg->max_load_old = scfg_over->max_load_old;
1505    scfg->max_load_missing = scfg_over->max_load_missing;
1506    strncpy(scfg->renderd_socket_name, scfg_over->renderd_socket_name, PATH_MAX-1);
1507    scfg->renderd_socket_name[PATH_MAX-1] = 0;
1508    strncpy(scfg->tile_dir, scfg_over->tile_dir, PATH_MAX-1);
1509    scfg->tile_dir[PATH_MAX-1] = 0;
1510    strncpy(scfg->cache_extended_hostname, scfg_over->cache_extended_hostname, PATH_MAX-1);
1511    scfg->cache_extended_hostname[PATH_MAX-1] = 0;
1512    scfg->cache_extended_duration = scfg_over->cache_extended_duration;
1513    scfg->cache_duration_dirty = scfg_over->cache_duration_dirty;
1514    scfg->cache_duration_last_modified_factor = scfg_over->cache_duration_last_modified_factor;
1515    scfg->cache_duration_max = scfg_over->cache_duration_max;
1516    scfg->cache_duration_minimum = scfg_over->cache_duration_minimum;
1517    scfg->cache_duration_low_zoom = scfg_over->cache_duration_low_zoom;
1518    scfg->cache_duration_medium_zoom = scfg_over->cache_duration_medium_zoom;
1519    scfg->cache_level_low_zoom = scfg_over->cache_level_low_zoom;
1520    scfg->cache_level_medium_zoom = scfg_over->cache_level_medium_zoom;
1521    scfg->enableGlobalStats = scfg_over->enableGlobalStats;
1522        scfg->enableTileThrottling = scfg_over->enableTileThrottling;
1523        scfg->delaypoolTileSize = scfg_over->delaypoolTileSize;
1524        scfg->delaypoolTileRate = scfg_over->delaypoolTileRate;
1525        scfg->delaypoolRenderSize = scfg_over->delaypoolRenderSize;
1526        scfg->delaypoolRenderRate = scfg_over->delaypoolRenderRate;
1527        scfg->bulkMode = scfg_over->bulkMode;
1528
1529    //Construct a table of minimum cache times per zoom level
1530    for (i = 0; i <= MAX_ZOOM; i++) {
1531        if (i <= scfg->cache_level_low_zoom) {
1532            scfg->mincachetime[i] = scfg->cache_duration_low_zoom;
1533        } else if (i <= scfg->cache_level_medium_zoom) {
1534            scfg->mincachetime[i] = scfg->cache_duration_medium_zoom;
1535        } else {
1536            scfg->mincachetime[i] = scfg->cache_duration_minimum;
1537        }
1538    }
1539
1540    return scfg;
1541}
1542
1543static const command_rec tile_cmds[] =
1544{
1545    AP_INIT_TAKE1(
1546        "LoadTileConfigFile",            /* directive name */
1547        load_tile_config,                /* config action routine */
1548        NULL,                            /* argument to include in call */
1549        OR_OPTIONS,                      /* where available */
1550        "load an entire renderd config file"  /* directive description */
1551    ),
1552    AP_INIT_TAKE2(
1553        "AddTileConfig",                 /* directive name */
1554        add_tile_config,                 /* config action routine */
1555        NULL,                            /* argument to include in call */
1556        OR_OPTIONS,                      /* where available */
1557        "path and name of renderd config to use"  /* directive description */
1558    ),
1559    AP_INIT_TAKE1(
1560        "ModTileRequestTimeout",         /* directive name */
1561        mod_tile_request_timeout_config, /* config action routine */
1562        NULL,                            /* argument to include in call */
1563        OR_OPTIONS,                      /* where available */
1564        "Set timeout in seconds on mod_tile requests"  /* directive description */
1565    ),
1566    AP_INIT_TAKE1(
1567        "ModTileMissingRequestTimeout",         /* directive name */
1568        mod_tile_request_timeout_missing_config, /* config action routine */
1569        NULL,                            /* argument to include in call */
1570        OR_OPTIONS,                      /* where available */
1571        "Set timeout in seconds on missing mod_tile requests"  /* directive description */
1572    ),
1573    AP_INIT_TAKE1(
1574        "ModTileMaxLoadOld",             /* directive name */
1575        mod_tile_max_load_old_config,    /* config action routine */
1576        NULL,                            /* argument to include in call */
1577        OR_OPTIONS,                      /* where available */
1578        "Set max load for rendering old tiles"  /* directive description */
1579    ),
1580    AP_INIT_TAKE1(
1581        "ModTileMaxLoadMissing",         /* directive name */
1582        mod_tile_max_load_missing_config, /* config action routine */
1583        NULL,                            /* argument to include in call */
1584        OR_OPTIONS,                      /* where available */
1585        "Set max load for rendering missing tiles"  /* directive description */
1586    ),
1587    AP_INIT_TAKE1(
1588        "ModTileRenderdSocketName",      /* directive name */
1589        mod_tile_renderd_socket_name_config, /* config action routine */
1590        NULL,                            /* argument to include in call */
1591        OR_OPTIONS,                      /* where available */
1592        "Set name of unix domain socket for connecting to rendering daemon"  /* directive description */
1593    ),
1594    AP_INIT_TAKE1(
1595        "ModTileTileDir",                /* directive name */
1596        mod_tile_tile_dir_config,        /* config action routine */
1597        NULL,                            /* argument to include in call */
1598        OR_OPTIONS,                      /* where available */
1599        "Set name of tile cache directory"  /* directive description */
1600    ),
1601        AP_INIT_TAKE1(
1602        "ModTileCacheExtendedHostName",                /* directive name */
1603        mod_tile_cache_extended_host_name_config,        /* config action routine */
1604        NULL,                            /* argument to include in call */
1605        OR_OPTIONS,                      /* where available */
1606        "set hostname for extended period caching"  /* directive description */
1607    ),
1608        AP_INIT_TAKE1(
1609        "ModTileCacheExtendedDuration",                /* directive name */
1610        mod_tile_cache_extended_duration_config,        /* config action routine */
1611        NULL,                            /* argument to include in call */
1612        OR_OPTIONS,                      /* where available */
1613        "set length of extended period caching"  /* directive description */
1614    ),
1615    AP_INIT_TAKE1(
1616        "ModTileCacheDurationMax",                /* directive name */
1617        mod_tile_cache_duration_max_config,        /* config action routine */
1618        NULL,                            /* argument to include in call */
1619        OR_OPTIONS,                      /* where available */
1620        "Set the maximum cache expiry in seconds"  /* directive description */
1621    ),
1622    AP_INIT_TAKE1(
1623        "ModTileCacheDurationDirty",                    /* directive name */
1624        mod_tile_cache_duration_dirty_config,           /* config action routine */
1625        NULL,                                           /* argument to include in call */
1626        OR_OPTIONS,                                     /* where available */
1627        "Set the cache expiry for serving dirty tiles"  /* directive description */
1628    ),
1629    AP_INIT_TAKE1(
1630        "ModTileCacheDurationMinimum",          /* directive name */
1631        mod_tile_cache_duration_minimum_config, /* config action routine */
1632        NULL,                                   /* argument to include in call */
1633        OR_OPTIONS,                             /* where available */
1634        "Set the minimum cache expiry"          /* directive description */
1635    ),
1636    AP_INIT_TAKE1(
1637        "ModTileCacheLastModifiedFactor",       /* directive name */
1638        mod_tile_cache_lastmod_factor_config,   /* config action routine */
1639        NULL,                                   /* argument to include in call */
1640        OR_OPTIONS,                             /* where available */
1641        "Set the factor by which the last modified determins cache expiry" /* directive description */
1642    ),
1643    AP_INIT_TAKE2(
1644        "ModTileCacheDurationLowZoom",       /* directive name */
1645        mod_tile_cache_duration_low_config,                 /* config action routine */
1646        NULL,                            /* argument to include in call */
1647        OR_OPTIONS,                      /* where available */
1648        "Set the minimum cache duration and zoom level for low zoom tiles"  /* directive description */
1649    ),
1650    AP_INIT_TAKE2(
1651        "ModTileCacheDurationMediumZoom",       /* directive name */
1652        mod_tile_cache_duration_medium_config,                 /* config action routine */
1653        NULL,                            /* argument to include in call */
1654        OR_OPTIONS,                      /* where available */
1655        "Set the minimum cache duration and zoom level for medium zoom tiles"  /* directive description */
1656    ),
1657    AP_INIT_FLAG(
1658        "ModTileEnableStats",       /* directive name */
1659        mod_tile_enable_stats,                 /* config action routine */
1660        NULL,                            /* argument to include in call */
1661        OR_OPTIONS,                      /* where available */
1662        "On Off - enable of keeping stats about what mod_tile is serving"  /* directive description */
1663    ),
1664        AP_INIT_FLAG(
1665        "ModTileEnableTileThrottling",       /* directive name */
1666        mod_tile_enable_throttling,                 /* config action routine */
1667        NULL,                            /* argument to include in call */
1668        OR_OPTIONS,                      /* where available */
1669        "On Off - enable of throttling of IPs that excessively download tiles such as scrapers"  /* directive description */
1670    ),
1671        AP_INIT_TAKE2(
1672        "ModTileThrottlingTiles",       /* directive name */
1673        mod_tile_delaypool_tiles_config,                 /* config action routine */
1674        NULL,                            /* argument to include in call */
1675        OR_OPTIONS,                      /* where available */
1676        "Set the initial bucket size (number of tiles) and top up rate (tiles per second) for throttling tile request per IP"  /* directive description */
1677    ),
1678        AP_INIT_TAKE2(
1679        "ModTileThrottlingRenders",       /* directive name */
1680        mod_tile_delaypool_render_config,                 /* config action routine */
1681        NULL,                            /* argument to include in call */
1682        OR_OPTIONS,                      /* where available */
1683        "Set the initial bucket size (number of tiles) and top up rate (tiles per second) for throttling tile request per IP"  /* directive description */
1684    ),
1685        AP_INIT_FLAG(
1686        "ModTileBulkMode",       /* directive name */
1687        mod_tile_bulk_mode,                 /* config action routine */
1688        NULL,                            /* argument to include in call */
1689        OR_OPTIONS,                      /* where available */
1690        "On Off - make all requests to renderd with bulk render priority, never mark tiles dirty"  /* directive description */
1691    ),
1692    {NULL}
1693};
1694
1695module AP_MODULE_DECLARE_DATA tile_module =
1696{
1697    STANDARD20_MODULE_STUFF,
1698    NULL,                                /* dir config creater */
1699    NULL,                                /* dir merger --- default is to override */
1700    create_tile_config,                  /* server config */
1701    merge_tile_config,                   /* merge server config */
1702    tile_cmds,                           /* command apr_table_t */
1703    register_hooks                       /* register hooks */
1704};
1705
Note: See TracBrowser for help on using the repository browser.