httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From r..@engelschall.com (Ralf S. Engelschall)
Subject Updated mod_cntr.c, for those interested
Date Wed, 16 Apr 1997 12:57:17 GMT

I'm currently fixed mod_cntr.c to make it again workable under Apache
1.2b9-dev. Just for those who are interested in or still using this nice
(non-graphics!) access counting module.

                                       Ralf S. Engelschall
                                       rse@engelschall.com
                                       www.engelschall.com

/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/*
 * URL counting
 *
 * Track all incoming requests for each URL.  Maintains a database
 * containing each URL accessed, count of times accessed, and last
 * time counter has been reset.  If the URL is a directory, then
 * the file that it is redirected to is counted.
 *
 * The values for count of times accessed, and last time counter was
 * reset are made available to the to the document via variables called
 * URL_COUNT and URL_COUNT_RESET, respectively.
 * Another variable URL_COUNT_DB is set to the database file used.
 *
 * Config file directives:
 *
 *     CounterType           File or Dbm
 *     CounterAutoAdd        On or Off  Automatically add missing URL's
 *     CounterFile           path to ascii or dbm file (relative to logs/),
 *                           there is no default
 *
 * The following pertain to a per server (or virtual server) configuration:
 *
 *     ServerCounterType     File or Dbm
 *     ServerCounterAutoAdd  On or Off  Automatically add missing URL's
 *     ServerCounterFile     path to ascii or dbm file (relative to logs/),
 *                           there is no default
 *
 * Ascii files have the format:  URL ### "date
 *
 * Note: The counter files are open/closed for each request to allow
 * URLS to be managed by external programs without shutting down the
 * web server.  The primitive locking mechanism is just the existance
 * of a lock file, which is the name of the logfile with ".lock" appended.
 *
 * Original by Brian Kolaci, bk@galaxy.net, 1-14-95
 *
 * Ported to Apache 1.2b8 16-04-1997 by 
 *     Ralf S. Engelschall
 *     rse@engelschall.com
 *     www.engelschall.com
 * Changes:
 *   - added header parse API hook
 *   - replaced the locking stuff with mod_rewrite's variant
 *   - added "const" to the command functions
 * 
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include <ndbm.h>

module cntr_module;

/*
 *  Data structions
 */
typedef enum { CntrFile, CntrDbm } CounterType;
typedef struct {
        unsigned long   count;
        char*           date;
} cntr_results;

typedef struct {
        int     cntr_default;
        CounterType     cntr_type;
        int     cntr_auto_add;
        char*   cntr_file;
} cntr_config_rec;

#define DEF_CNTRTYPE    01
#define DEF_CNTRAA      02
#define DEF_CNTRFILE    04
#define DEF_ALL         (DEF_CNTRTYPE|DEF_CNTRAA|DEF_CNTRFILE)

/*
 *  Set defaults
 */
#define DEFAULT_CNTR_TYPE       CntrDbm
#define DEFAULT_DIRCOUNTERFILE  ""
#define DEFAULT_SVRCOUNTERFILE  ""
#define DEFAULT_TIME_FORMAT     "%A, %d-%b-%y %T %Z"

/*
 * File locking
 */

#if defined(USE_FCNTL_SERIALIZED_ACCEPT)
#define USE_FCNTL 1
#include <fcntl.h>
#endif
#if defined(USE_FLOCK_SERIALIZED_ACCEPT)
#define USE_FLOCK 1
#include <sys/file.h>
#endif
#if !defined(USE_FCNTL) && !defined(USE_FLOCK)
#define USE_FLOCK 1
#ifndef MPE
#include <sys/file.h>
#endif
#ifndef LOCK_UN
#undef USE_FLOCK
#define USE_FCNTL 1
#include <fcntl.h>
#endif
#endif
#ifdef AIX
#undef USE_FLOCK
#define USE_FCNTL 1
#include <fcntl.h>
#endif

#ifdef USE_FCNTL
static struct flock   lock_it;
static struct flock unlock_it;
#endif 

static int fd_lock(int fd)
{
    int rc;

#ifdef USE_FCNTL
    lock_it.l_whence = SEEK_SET; /* from current point */
    lock_it.l_start  = 0;        /* -"- */
    lock_it.l_len    = 0;        /* until end of file */
    lock_it.l_type   = F_WRLCK;  /* set exclusive/write lock */
    lock_it.l_pid    = 0;        /* pid not actually interesting */

    while (   ((rc = fcntl(fd, F_SETLKW, &lock_it)) < 0) 
           && (errno == EINTR)                               )
        continue;
#endif
#ifdef USE_FLOCK
    while (   ((rc = flock(fd, LOCK_EX)) < 0) 
           && (errno == EINTR)               )
        continue;
#endif

    return rc;
}

static int fd_unlock(int fd)
{
    int rc;

#ifdef USE_FCNTL 
    unlock_it.l_whence = SEEK_SET; /* from current point */
    unlock_it.l_start  = 0;        /* -"- */
    unlock_it.l_len    = 0;        /* until end of file */
    unlock_it.l_type   = F_UNLCK;  /* unlock */
    unlock_it.l_pid    = 0;        /* pid not actually interesting */

    rc = fcntl(fd, F_SETLKW, &unlock_it);
#endif
#ifdef USE_FLOCK 
    rc = flock(fd, LOCK_UN);
#endif 

    return rc;
}


/*
 * Create config data structure
 */
void* create_cntr_dir_config_rec( pool* p, char* d )
{
        /*
         * Set the defaults
         */
        cntr_config_rec * rec =
                (cntr_config_rec*)pcalloc(p, sizeof(cntr_config_rec));
        rec->cntr_type = DEFAULT_CNTR_TYPE;
        rec->cntr_auto_add = 0;
        rec->cntr_file = DEFAULT_DIRCOUNTERFILE ? pstrdup(p, DEFAULT_DIRCOUNTERFILE) :
NULL;
        rec->cntr_default = DEF_ALL;

        return(rec);
}

void* create_cntr_srv_config_rec( pool* p, server_rec* d )
{
        /*
         * Set the defaults
         */
        cntr_config_rec * rec =
                (cntr_config_rec*)pcalloc(p, sizeof(cntr_config_rec));
        rec->cntr_type = DEFAULT_CNTR_TYPE;
        rec->cntr_auto_add = 0;
        rec->cntr_file = DEFAULT_SVRCOUNTERFILE ? pstrdup(p, DEFAULT_SVRCOUNTERFILE) :
NULL;
        rec->cntr_default = DEF_ALL;

        return(rec);
}

void*   merge_config_rec( pool* p, void* parent, void* sub )
{
        cntr_config_rec * par = (cntr_config_rec *)parent;
        cntr_config_rec * chld = (cntr_config_rec *)sub;
        cntr_config_rec * mrg = (cntr_config_rec *)palloc(p, sizeof(*mrg));
        if (chld->cntr_default & DEF_CNTRTYPE)
                mrg->cntr_type = par->cntr_type;
        else
                mrg->cntr_type = chld->cntr_type;
        if (chld->cntr_default & DEF_CNTRAA)
                mrg->cntr_auto_add = par->cntr_auto_add;
        else
                mrg->cntr_auto_add = chld->cntr_auto_add;
        if (chld->cntr_default & DEF_CNTRFILE)
                mrg->cntr_file = par->cntr_file;
        else
                mrg->cntr_file = chld->cntr_file;

        mrg->cntr_default = 0;
        return(mrg);
}

const char* set_cntr_type( cmd_parms* cmd, void* ct, char* arg )
{
        void* ret = NULL;
        cntr_config_rec* conf = (cntr_config_rec*)ct;

        if (!strcasecmp(arg, "file"))
                conf->cntr_type = CntrFile;
        else if (!strcasecmp(arg, "dbm"))
                conf->cntr_type = CntrDbm;
        else
                ret = "CounterType must be File or Dbm";
        conf->cntr_default &= ~DEF_CNTRTYPE;

        return(ret);
}

const char* set_cntr_autoadd( cmd_parms* cmd, void* ct, int arg )
{
        cntr_config_rec* conf = (cntr_config_rec*)ct;
        conf->cntr_auto_add = arg;
        conf->cntr_default &= ~DEF_CNTRAA;
        return(NULL);
}

const char* set_cntr_file( cmd_parms* cmd, void* ct, char* arg )
{
        void* ret = NULL;
        cntr_config_rec* conf = (cntr_config_rec*)ct;

        if (strcmp(arg, "/dev/null"))
                conf->cntr_file = server_root_relative(cmd->pool, arg);
        else
                conf->cntr_file = "";
        conf->cntr_default &= ~DEF_CNTRFILE;

        return(ret);
}

const char* set_svr_cntr_type( cmd_parms* cmd, void* ct, char* arg )
{
        return(set_cntr_type(cmd, get_module_config(cmd->server->module_config, &cntr_module),
arg));
}

const char* set_svr_cntr_autoadd( cmd_parms* cmd, void* ct, int arg )
{
        return(set_cntr_autoadd(cmd,
                get_module_config(cmd->server->module_config, &cntr_module), arg));
}

const char* set_svr_cntr_file( cmd_parms* cmd, void* ct, char* arg )
{
        return(set_cntr_file(cmd,
                get_module_config(cmd->server->module_config, &cntr_module), arg));
}

command_rec cntr_cmds[] = {
{ "CounterType", set_cntr_type, NULL, ACCESS_CONF, TAKE1, NULL },
{ "CounterAutoAdd", set_cntr_autoadd, NULL, ACCESS_CONF, FLAG, NULL },
{ "CounterFile", set_cntr_file, NULL, ACCESS_CONF, TAKE1,
    "Name of counter file or database" },

{ "ServerCounterType", set_svr_cntr_type, NULL, RSRC_CONF, TAKE1, NULL },
{ "ServerCounterAutoAdd", set_svr_cntr_autoadd, NULL, RSRC_CONF, FLAG, NULL },
{ "ServerCounterFile", set_svr_cntr_file, NULL, RSRC_CONF, TAKE1,
    "Name of counter file or database" },
{ NULL }
};

char*   cntr_incfile( pool* p, cntr_results* results,
                      cntr_config_rec* r, char* uri )
{
        char buf[HUGE_STRING_LEN];
        char buf2[HUGE_STRING_LEN];
        int found = 0;
        int len = strlen(uri);
        int buflen = 0;
        int buflen2 = 0;
        FILE* fp;
        long roffset = 0;
        long woffset;
        long fsize;

        /* Open counter file for update */
        if (!(fp = fopen(r->cntr_file,"r+"))) {
                if (!(fp = fopen(r->cntr_file,"w+"))) {
                        return(pstrcat(p, "Failed to open counter file: ",
                                r->cntr_file, NULL));
                }
        }

        /* Lock file */
        if (fd_lock(fileno(fp))) {
                fclose(fp);
                return(pstrcat(p, "Failed to lock counter file: ",
                                r->cntr_file, NULL));
        }

        /* Find end of file */
        fseek(fp, 0, SEEK_END);
        fsize = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        while (roffset < fsize) {

                roffset = ftell(fp);
                fgets(buf, sizeof(buf), fp);
                buflen = strlen(buf);

                /*
                 *  If we already found the URI, manage buffers
                 *  to effectively "push" the strings lower in the file.
                 */
                if (found) {
                        roffset = ftell(fp);
                        /* Go back and write old buffer */
                        fseek(fp, woffset, SEEK_SET);
                        fwrite(buf2, buflen2, 1, fp);
                        woffset = ftell(fp);

                        /* Are we done ? */
                        if (roffset >= fsize) {
                                fwrite(buf, buflen, 1, fp);
                                break;
                        }

                        /* Save copy of last buffer */
                        strcpy(buf2, buf);
                        buflen2 = buflen;
                        /* Move offset back to read next line */
                        fseek(fp, roffset, SEEK_SET);
                        continue;
                }

                /*
                 *  See if we found the matching URI
                 */
                if (!strncmp(uri, buf, len) && isspace(buf[len])) {
                        char* ptr = &buf[len];
                        char* q = strchr(ptr, '\n');
                        if (q)
                                *q = '\0';
                        /* Skip spaces to number */
                        while (*ptr && isspace(*ptr))
                                ptr++;
                        results->count = atol(ptr) + 1;
                        /* Skip over number */
                        while (*ptr && !isspace(*ptr))
                                ptr++;
                        /* Skip spaces to date */
                        while (*ptr && isspace(*ptr))
                                ptr++;
                        results->date = pstrdup(p, ptr);
                        found++;
                        sprintf(buf2, "%s\t%010lu\t%s\n",
                                        uri, results->count, results->date);
                        /*
                         *  If we don't increase the size of the buffer,
                         *  we can write it out now and get out of the loop.
                         *  If not, we must then "push" all of the items
                         *  down in the file.
                         */
                        buflen2 = strlen(buf2);
                        woffset = roffset;
                        if (buflen2 <= buflen) {
                                buflen2--;
                                while (buflen2 < buflen - 1)
                                        buf2[buflen2++] = ' ';
                                buf2[buflen2++] = '\n';
                                buf2[buflen2++] = '\0';
                                fseek(fp, -buflen, SEEK_CUR);
                                fwrite(buf2, buflen, 1, fp);
                                break;
                        }
                        continue;
                }
        }
        /*
         *  See if we need to append to end of file
         */
        if (!found && r->cntr_auto_add) {
                time_t t = time(0L);
                results->count = 1;
                results->date = ht_time(p, time(0L), DEFAULT_TIME_FORMAT, 0);

                /* We should already be at the bottom of the file */
                fprintf(fp, "%s\t%010lu\t%s\n", uri, results->count, results->date);
        }
        fd_unlock(fileno(fp));
        fclose(fp);

        return(OK);
}

char*   cntr_incdbm( pool* p, cntr_results* results,
                     cntr_config_rec* r, char* uri )
{
        DBM* dbm;
        datum d, q;

        results->count = 0;
        results->date = NULL;

        q.dptr = uri;
        q.dsize = strlen(q.dptr);

        if (!(dbm = dbm_open(r->cntr_file, O_RDWR|O_CREAT, 0664))) {
                return(pstrcat(p, "Failed to open counter dbmfile: ",
                                r->cntr_file, NULL));
        }

        /* Lock file */
        if (fd_lock(dbm_dirfno(dbm))) {
                dbm_close(dbm);
                return(pstrcat(p, "Failed to lock DBM counter file: ",
                                r->cntr_file, NULL));
        }

        d = dbm_fetch(dbm, q);

        /*
         *  See if found, if not check whether to add it in
         */
        if (d.dptr) {
                int len;
                char * ptr = d.dptr;
                results->count = atol(ptr);

                /* Skip over number */
                while (*ptr && !isspace(*ptr))
                        ptr++;

                /* Skip spaces to date */
                while (*ptr && isspace(*ptr))
                        ptr++;

                len = d.dsize - (ptr - d.dptr);
                results->date = pcalloc(p, len + 1);
                strncpy(results->date, ptr, len);
                results->date[len] = '\0';
        }

        /*
         *  Increment count, set default date
         */
        results->count++;
        if (results->date == NULL)
                results->date = ht_time(p, time(0L), DEFAULT_TIME_FORMAT, 0);
        /*
         * Add or update the record
         */
        if (d.dptr || r->cntr_auto_add) {
                char buf[HUGE_STRING_LEN];
                sprintf(buf, "%lu\t%s", results->count, results->date);
                d.dptr = buf;
                d.dsize = strlen(buf);

                dbm_store(dbm, q, d, DBM_REPLACE);
        }
        fd_unlock(dbm_dirfno(dbm));
        dbm_close(dbm);

        return(OK);
}

char*   cntr_inc( pool* p, cntr_results* results,
                  cntr_config_rec* r, char* uri )
{
        /* Normalize the URI stripping out double "//" */
        char* puri = pstrdup(p, uri);
        char* ptr = puri;
        while (ptr && *ptr) {
                if (*ptr == '/' && *(ptr+1) == '/') {
                        char* q = ptr + 1;
                        while (*q = *(q+1))
                                q++;
                } else
                        ptr++;
        }

        if (r->cntr_type == CntrFile)
                return cntr_incfile(p, results, r, puri);
        else if (r->cntr_type == CntrDbm)
                return cntr_incdbm(p, results, r, puri);
        else
                return NULL;
}

int     cntr_update( request_rec* r )
{
        char* buf;
        char* dbfile;
        int ret = OK;
        cntr_results * res;
        cntr_config_rec *svr, *dir;
        cntr_results *sres, *dres;

        /*
         *  Get actual file, if locally redirected
         */
        while (r->next)
                r = r->next;
        /*
         * Skip if missing URI or this is an included request
         */
        if (!r->uri || !strcmp(r->protocol, "INCLUDED"))
                return(DECLINED);
        if (!S_ISREG(r->finfo.st_mode))
                return(DECLINED);

        /*
         * Get each of the counter files
         */
        svr = get_module_config(r->server->module_config, &cntr_module);
        dir = get_module_config(r->per_dir_config, &cntr_module);

        /*
         * Return if no counter file
         */
        if (!*svr->cntr_file && !*dir->cntr_file)
                return(DECLINED);

        /*
         * Allocate result structures
         */
        sres = (cntr_results*)pcalloc(r->pool, sizeof(cntr_results));
        dres = (cntr_results*)pcalloc(r->pool, sizeof(cntr_results));

        /*
         * Bump up server configured counter
         */
        if (*svr->cntr_file)
                if (buf = cntr_inc(r->pool, sres, svr, r->uri))
                        log_error(buf, r->server);
#if 0
        /*
         * Only increment the directory config file if different
         * from the server config file.
         */
        if (*dir->cntr_file && strcasecmp(svr->cntr_file, dir->cntr_file))
                if (buf = cntr_inc(r->pool, dres, dir, r->uri))
                        log_error(buf, r->server);
#else
        /*
         * Increment the directory counter file using the full
         * file name instead of the URL
         */
        if (*dir->cntr_file)
                if (buf = cntr_inc(r->pool, dres, dir, r->filename))
                        log_error(buf, r->server);
#endif

        /*
         * Now set the environment variables, take the server config
         * as preference over the directory config.
         */
        res = (sres->count) ? sres : dres;
        dbfile = (sres->count) ? svr->cntr_file : dir->cntr_file;
        buf = pcalloc(r->pool, 16);
        sprintf(buf, "%lu", res->count);
/*
fprintf(stderr, "setting env:  %s  %s  %s\n", buf, res->date, dbfile);
*/
        table_set(r->subprocess_env, "URL_COUNT", buf);
        table_set(r->subprocess_env, "URL_COUNT_RESET", res->date);
        table_set(r->subprocess_env, "URL_COUNT_DB", dbfile);

        return ret;
}


module cntr_module = {
   STANDARD_MODULE_STUFF,
   NULL,                        /* initializer */
   create_cntr_dir_config_rec,  /* dir config creater */
   NULL,                        /* dir merger --- default is to override */
   create_cntr_srv_config_rec,  /* server config */
   NULL,                        /* merge server config */
   cntr_cmds,                   /* command table */
   NULL,                        /* handlers */
   NULL,                        /* filename translation */
   NULL,                        /* check_user_id */
   NULL,                        /* check auth */
   NULL,                        /* check access */
   NULL,                        /* type_checker */
   cntr_update,                 /* fixups */
   NULL,                        /* logger */
   NULL                         /* header parse */
};


Mime
View raw message