/* multi-counts.c
multi-counts -- month by month hit counter called by cgiwrap
copyright (C) 1999 by Richard L. Hawes
email: rhawes@dma.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*@unused@*/ /*@observer@*/
static char const *rcsid __attribute__ ((unused)) = \
"$Id: multi-counts.c,v 1.25 2000/01/31 00:46:35 hr Exp $" ;
/**
usage:
Textual mode:
{
}
accesses so far this month (Time zone: EDT).
Graphical mode (hidden):
called from a script:
- - -
#!/bin/sh
exec /home/rhawes/html/bin/multi-counts "$1"
- - -
Counts can be logged with log-counts (version > 0.33). Counts can be
summarized in a web page with stat-counts. Both programs are included
with this program.
This is a month by month hit counter that may be called by cgiwrap.
It uses a directory in cgi-bin for storing 2 or 3 files for each month.
The first value in the query string is the mode. `t' is for textual mode
(the default), and `g' is for for graphical mode. The second value in
the query string is the counter number. The counter number can be any
number from 1 (the default) to 255 allowing for keeping track of up to
255 seperate counts.
The Graphical mode sends a hidden gif image.
The text mode prints out the count or ? if not determined.
As soon as possible, it redirects stdin, stdout, and stderr to end pipe
to web server.
Errors go into counts/mh_year-month.err. The return value is the line
number where an error occurred when an error occurs in the function die.
Otherwise, the return value is zero.
It does not wait to lock the count bytes in `counts/mc_year-month.hc'; It
just tries once (non-blocking) and then appends the counter number byte
to another file (counts/mc_year-month.lhc). This prevents an overload of
programs waiting to lock a count region and gobbling up time and memory.
latest version is at: http://www.dma.org/~rhawes/programs/multi-counts.tgz
includes annotations for lclint
not used:
-tagprefix e^& +tagprefixexclude
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*@
-booltype bool
+allmacros
-constantprefix c^& +constantprefixexclude
-enumprefix m^& +enumprefixexclude
-externalprefix x^& +externalprefixexclude
-filestaticprefix s^& +filestaticprefixexclude
-globalprefix g^& +globalprefixexclude
-iterprefix i^& +iterprefixexclude
-localprefix l^& +localprefixexclude
-typeprefix t^& +typeprefixexclude
-uncheckedmacroprefix ~* +uncheckedmacroprefixexclude
@*/
#include "bool.h"
#include "defs.h"
#include "cerr.h"
/*
* types
* *****/
typedef int tCount;
typedef unsigned char tCounterId;
typedef struct eControl {
bool textual;
tCounterId ctr_id;
} tControl;
/*
* constants
* *********/
#define BUFF_LEN 64
/*
* Macros
* ******/
#define bufcat(buf, str1, str2) { strcpy(buf, str1); strcat(buf, str2); }
#define nohup() if(EOF==fclose(stdout)) { cerr_set(mEmc_CLOSE_OUT); }
#define DIE(message) { cerr_set(message); goto ERROR; }
#define DIE_IF(action) { action; if(mEmc_NONE != cerr_get()) \
{ goto ERROR; } }
/*
* Functions
* *********/
static inline void show_text(tCount hits)
/*@globals internalState, fileSystem, stdout, errno,
gEcerr;@*/
/*@modifies fileSystem, *stdout, errno,
gEcerr@*/
{
if( EOF == puts("Content-type: text/plain\n")) {
cerr_set(mEmc_STDOUT);
}
else if( 0 < hits) {
/* add commas */
char buffer[BUFF_LEN], *str;
int digit = 0;
div_t num;
str = &buffer[BUFF_LEN - 1];
*str = '\0';
num.quot = hits;
while( 0 != num.quot) {
if(3 < ++digit) {
*(--str) = ',';
digit = 1;
}
num = div(num.quot, 10);
*(--str) = (char)(num.rem + (int)'0');
}
if(EOF == fputs(str, stdout)) {
cerr_set(mEmc_STDOUT);
}
}
else {
if(EOF == fputs("?", stdout)) {
cerr_set(mEmc_STDOUT);
}
}
nohup();
}
static inline bool counting(const char *base_name, const tControl cntrl)
/*@globals internalState, fileSystem, errno, stdout,
gEcerr;@*/
/*@modifies internalState, fileSystem, errno, *stdout,
gEcerr;@*/
{
int cnt_fd;
off_t pos;
char cnt_data[BUFF_LEN];
tCount hits = 0;
bool lock_fail = TRUE;
struct flock lock_data = { (short)F_WRLCK, (short)SEEK_CUR, 0,
(off_t)sizeof(hits), 0 } ;
pos = (off_t)(sizeof(hits) * ((int)cntrl.ctr_id - MC_MIN_CTR));
bufcat(cnt_data, base_name, MC_DATA_SUFX);
if(-1 == (cnt_fd =
open(cnt_data, (int)((unsigned)O_RDWR|(unsigned)O_CREAT), 0666)))
DIE(mEmc_OPEN_DATA);
if(-1 == lseek(cnt_fd, pos, SEEK_SET))
DIE(mEmc_SEEK_DATA);
if(!(lock_fail = (-1 == fcntl(cnt_fd, F_SETLK, &lock_data)))) {
if(0 > read(cnt_fd, &hits, sizeof(hits))) {
DIE(mEmc_READ_DATA);
}
hits++;
if( hits <= 0 ) {
hits = 1;
cerr_set(mEmc_OVRFLW);
}
if(-1 == lseek(cnt_fd, pos, SEEK_SET))
DIE(mEmc_SEEK_DATA);
if(sizeof(hits) != (size_t)write(cnt_fd, &hits, sizeof(hits))) {
DIE(mEmc_WRITE_DATA);
}
}
if(0 > close(cnt_fd)) {
cerr_set(mEmc_CLOSE_DATA);
}
else {
if( cntrl.textual ) {
show_text(hits);
}
}
ERROR:
return lock_fail;
}
static inline void log_cnt(const char *base_name, const tCounterId ctr_id)
/*@globals fileSystem, errno, gEcerr;@*/
/*@modifies fileSystem, errno, gEcerr@*/
{
FILE *cnt_log_strm;
char cnt_log[BUFF_LEN];
bufcat(cnt_log, base_name, MC_LOG_SUFX);
cnt_log_strm = fopen(cnt_log, "ab");
if(NULL == cnt_log_strm) {
cerr_set(mEmc_OPEN_LOG);
}
else if(EOF == putc((int)ctr_id, cnt_log_strm)) {
cerr_set(mEmc_APPEND_LOG);
}
else {
if(0 != fclose(cnt_log_strm)) {
cerr_set(mEmc_CLOSE_LOG);
}
}
}
static inline void show_graphic(void)
/*@globals internalState, fileSystem, stdout, errno,
gEcerr;@*/
/*@modifies internalState, fileSystem, *stdout, errno,
gEcerr;@*/
{
#define GIF_LEN 68
static char const hidden_gif[] = {
'C', 'o', 'n', 't', 'e', 'n', 't', '-',
't', 'y', 'p', 'e', ':', ' ', 'i', 'm',
'a', 'g', 'e', '/', 'g', 'i', 'f', '\n',
'\n', '\107', '\111', '\106', '\070', '\071', '\141', '\001',
'\000', '\001', '\000', '\360', '\000', '\000', '\000', '\000',
'\000', '\000', '\000', '\000', '\041', '\371', '\004', '\001',
'\000', '\000', '\001', '\000', '\054', '\000', '\000', '\000',
'\000', '\001', '\000', '\001', '\000', '\000', '\002', '\002',
'\114', '\001', '\000', '\073'
} ;
register short int i;
for(i=0; i< GIF_LEN; i++) {
if( EOF == putchar(hidden_gif[i])) {
DIE(mEmc_STDOUT);
}
}
nohup();
ERROR:
return;
}
static inline tControl get_query(char *vectr[])
/*@globals errno, gEcerr;@*/
/*@modifies errno, gEcerr@*/
{
tControl cntrl = {TRUE, (tCounterId)MC_MIN_CTR};
char *query;
bool
got_mode = FALSE,
got_ctr_id = FALSE;
while(NULL != (query=*(++vectr))) {
if( '\0' == query[0] ) {
continue;
} else if( ('t' == query[0]) || ('g' == query[0])) {
if(got_mode) {
cerr_set(mEmc_DUP_MODE);
break;
} else if('\0' != query[1]) {
cerr_set(mEmc_INVLD_MODE);
break;
} else {
got_mode = TRUE;
cntrl.textual = ('t' == query[0]);
}
} else if( ('0' <= query[0]) && ('9' >= query[0]) ) {
long num;
char *endp;
if(got_ctr_id) {
cerr_set(mEmc_DUP_CNTR_ID);
break;
}
got_ctr_id = TRUE;
num = strtol(query, &endp, 10);
if( ( num < MC_MIN_CTR ) || ( num > MC_MAX_CTR ) || (*endp != '\0') ) {
cerr_set(mEmc_INVLD_CNTR_ID);
break;
}
else {
cntrl.ctr_id = (tCounterId)num;
}
} else {
cerr_set(mEmc_INVLD_ARG);
break;
}
}
return cntrl;
}
static inline void init(char *base_name, /*@null@*/ struct tm *local_time )
/*@globals internalState, fileSystem, errno, stdin, stderr,
gEcerr;@*/
/*@modifies internalState, fileSystem, errno, *stdin, *stderr,
gEcerr, *base_name;@*/
{
char cnt_err[BUFF_LEN];
if(NULL == local_time) {
cerr_set(mEmc_NULL_PTR);
}
else {
(void)sprintf(base_name, MC_PREFIX "_%04i-%02i",
(local_time->tm_year + 1900), (local_time->tm_mon + 1));
bufcat(cnt_err, base_name, MC_ERR_SUFX);
if(0 != chdir(MC_DATA_DIR)) {
cerr_set(mEmc_CHDIR);
} else if(freopen(cnt_err, "a", stderr) == NULL) {
cerr_set(mEmc_REDRCT_ERR);
}
else if( SIG_ERR == (void (*)(int))signal(SIGHUP, SIG_IGN)) {
cerr_set(mEmc_NO_SIG_SET);
}
else {
if(fclose(stdin)==EOF) {
cerr_set(mEmc_CLOSE_IN);
}
}
}
}
int main(/*@unused@*/ int argc, char *argv[])
/*@globals internalState, fileSystem, errno, stdin, stdout, stderr,
gEcerr;@*/
/*@modifies internalState, fileSystem, *stdin,*stdout,*stderr, errno,
gEcerr;@*/
{
/*
* variables
************/
time_t current_time;
/*@null@*/ /*@observer@*/
struct tm *local_time;
bool lock_fail;
/* static */
static tControl cntrl;
static char
base_name[BUFF_LEN];
/*
* begin
************/
bool_initMod();
cerr_initMod();
current_time = time(NULL);
DIE_IF(init(base_name, local_time = localtime( ¤t_time )));
DIE_IF(cntrl = get_query(argv));
if( ! cntrl.textual ) {
DIE_IF(show_graphic());
}
DIE_IF(lock_fail = counting( base_name, cntrl));
if( lock_fail ) {
/* record hit count in log */
DIE_IF(log_cnt(base_name, cntrl.ctr_id));
}
#ifdef TEST
/* test that the server does not wait */
sleep(30);
#endif
exit(EXIT_SUCCESS);
ERROR:
{
static char date[BUFF_LEN];
if(NULL != local_time) {
(void)strftime(date, BUFF_LEN, "%c", local_time);
}
(void)fprintf(stderr, "%s : %u : ", date, (unsigned int)cntrl.ctr_id );
}
if(0 != errno) {
perror(cerr_msg(cerr_get()));
}
else {
(void)fputs(cerr_msg(cerr_get()), stderr);
(void)fputc('\n', stderr);
}
exit(EXIT_FAILURE);
}