/*
* xferstats, a logfile parser and report generator
* Copyright 1997-2000 Phil Schwan <phil@off.net>
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#else
# error You did not run configure, did you?
#endif
#ifdef HAVE_STRING_H
# include <string.h> /* needed everywhere */
#endif
#ifdef HAVE_CTYPE_H
# include <ctype.h> /* needed for isdigit, tolower, isalpha */
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#include <stdlib.h> /* needed everywhere */
#include <stdio.h> /* needed everywhere */
#include <time.h>
#include <glib.h>
#include "xferstats.h"
const char DAYS[7][3] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const char LONG_DAYS[7][10] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
const char LONG_MONTHS[12][10] = {"January", "February", "March", "April",
"May", "June", "July", "August", "September",
"October", "November", "December"};
const char MONTHS[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
"Aug", "Sep", "Oct", "Nov", "Dec"};
/* don't ask ... */
const char NUMBERS[31][3] = {"01", "02", "03", "04", "05", "06", "07", "08",
"09", "10", "11", "12", "13", "14", "15", "16",
"17", "18", "19", "20", "21", "22", "23", "24",
"25", "26", "27", "28", "29", "30", "31"};
const char T_NAMES[6][10] = {"file", "dir", "host", "TLD", "domain", "size"};
typedef struct _GHashNode GHashNode;
struct _GHashNode
{
gpointer key;
gpointer value;
GHashNode *next;
};
struct _GHashTable
{
gint size;
gint nnodes;
gint frozen;
GHashNode **nodes;
GHashFunc hash_func;
GCompareFunc key_compare_func;
};
/* Very self-explanitory, converts an abbreviated month string to an integer.
* In the interest of efficiency, it returns as soon as it can exclude other
* months. Therefore, "Jan", "Jar", "Jagawagavoovoo" all return 1 */
static inline int
month2int(char *month)
{
switch (month[0])
{
case 'J':
switch (month[1])
{
case 'a':
return 1;
case 'u':
switch (month[2])
{
case 'n':
return 6;
case 'l':
return 7;
}
}
case 'F':
return 2;
case 'M':
switch (month[2])
{
case 'r':
return 3;
case 'y':
return 5;
}
case 'A':
switch (month[1])
{
case 'p':
return 4;
case 'u':
return 8;
}
case 'S':
return 9;
case 'O':
return 10;
case 'N':
return 11;
case 'D':
return 12;
}
return 0;
} /* month2int */
/* this routine compares two date strings and tells us which is earlier. it
* returns:
*
* 0 for identical days
* 1 for date1 being more in the future than date2
* -1 for date1 being more in the past than date2 */
inline int
compare_dates(char date1[25], char date2[25])
{
int dw1, dw2, len = strlen(date1);
char foo[5] = {0};
/* check the year */
if (len == 24)
{
memcpy(foo, date1 + 20, 4);
dw1 = atoi(foo);
memcpy(foo, date2 + 20, 4);
dw2 = atoi(foo);
}
else if (len == 15)
{
memcpy(foo, date1 + 11, 4);
dw1 = atoi(foo);
memcpy(foo, date2 + 11, 4);
dw2 = atoi(foo);
}
else
return 0;
#ifdef DEBUG
fprintf(stderr, "Year1: %d Year2: %d\n", dw1, dw2);
#endif
if (dw1 > dw2)
return 1;
else if (dw2 > dw1)
return -1;
/* check the month */
dw1 = month2int(date1 + 4);
dw2 = month2int(date2 + 4);
#ifdef DEBUG
fprintf(stderr, "Month1: %d Month2: %d\n", dw1, dw2);
#endif
if (dw1 > dw2)
return 1;
else if (dw2 > dw1)
return -1;
/* lastly, check the day */
foo[0] = date1[8];
foo[1] = date1[9];
foo[2] = '\0';
dw1 = atoi(foo);
foo[0] = date2[8];
foo[1] = date2[9];
dw2 = atoi(foo);
#ifdef DEBUG
fprintf(stderr, "Day1: %d Day2: %d\n", dw1, dw2);
#endif
if (dw1 == dw2)
return 0;
if (dw1 > dw2)
return 1;
else
return -1;
} /* compare_dates */
static inline int
day2int(char *day)
{
switch (day[0])
{
case 'S':
switch (day[1])
{
case 'u':
return 1;
case 'a':
return 7;
}
case 'M':
return 2;
case 'T':
switch (day[1])
{
case 'u':
return 3;
case 'h':
return 5;
}
case 'W':
return 4;
case 'F':
return 6;
}
return 0;
} /* day2int */
int
xf_sort_by_data(const xfmisc_t ** a, const xfmisc_t ** b)
{
if ((*a)->data > (*b)->data)
return -1;
else if ((*a)->data < (*b)->data)
return 1;
else
return 0;
} /* xf_sort_by_data */
int
xf_sort_by_file_count(const xfmisc_t ** a, const xfmisc_t ** b)
{
if ((*a)->file_count > (*b)->file_count)
return -1;
else if ((*a)->file_count < (*b)->file_count)
return 1;
else
return 0;
} /* xf_sort_by_file_count */
int
xf_sort_by_file_size(const xfmisc_t ** a, const xfmisc_t ** b)
{
if (strlen((*a)->name) > strlen((*b)->name))
return -1;
else if (strlen((*a)->name) < strlen((*b)->name))
return 1;
else
return 0;
} /* xf_sort_by_file_size */
int
xf_sort_by_throughput(const xfmisc_t ** a, const xfmisc_t ** b)
{
if ((*a)->data / (*a)->seconds > (*b)->data / (*b)->seconds)
return -1;
else if ((*a)->data / (*a)->seconds < (*b)->data / (*b)->seconds)
return 1;
else
return 0;
} /* xf_sort_by_throughput */
int
xf_sort_alphabetical(const xfmisc_t ** a, const xfmisc_t ** b)
{
return strcmp((*a)->name, (*b)->name);
} /* xf_sort_alphabetical */
int
xf_sort_by_day(const xfmisc_t ** a, const xfmisc_t ** b)
{
return compare_dates((char *)(*a)->name, (char *)(*b)->name);
} /* xf_sort_by_file_size */
int
xf_sort_by_number(const xfmisc_t ** a, const xfmisc_t ** b)
{
if (a < b)
return -1;
else if (b < a)
return 1;
else
return 0;
if ((*a)->name[0] < (*b)->name[0])
return -1;
else if ((*a)->name[0] > (*b)->name[0])
return 1;
else
if ((*a)->name[1] < (*b)->name[1])
return -1;
else if ((*a)->name[1] > (*b)->name[1])
return 1;
else
return 0;
} /* xf_sort_by_number */
static inline void **
xf_make_array(GHashTable * ht, int * count)
{
void ** array;
gint i, j;
GHashNode * node;
array = g_malloc(sizeof(void *) * ((*count = ht->nnodes) + 1));
for (i = j = 0; i < ht->size; i++)
for (node = ht->nodes[i]; node; node = node->next)
array[j++] = node->value;
array[j] = NULL;
return array;
} /* xf_make_array */
/* This cute little macro is used to check/set the value in the hash tables in
* generate_misc_data(). Maybe things would be cleaner if I wrote some more
* macros instead of duplicating so much code in that function :) */
/*
* What a disaster. What was I thinking, 3 years ago, when I wrote this?
* Was I high?
*/
#define xf_set_value(ht, item, current_data_ptr, other_data_ptr, com, fc) \
{ \
if ((current_data_ptr = g_hash_table_lookup(ht, item))) \
{ \
current_data_ptr->file_count += fc; \
current_data_ptr->seconds += other_data_ptr->seconds; \
current_data_ptr->data += other_data_ptr->data; \
current_data_ptr->completed_count += com; \
} \
else \
{ \
current_data_ptr = G_MEM_CHUNK_ALLOC(pointers->xfmisc_chunk); \
current_data_ptr->name = g_strdup(item); \
current_data_ptr->data = other_data_ptr->data; \
current_data_ptr->seconds = other_data_ptr->seconds; \
current_data_ptr->file_count = fc; \
current_data_ptr->completed_count = com; \
\
g_hash_table_insert(ht, current_data_ptr->name, current_data_ptr); \
} \
}
/* This function runs down the list starting with first_ftp_line and stuffs
* data into the pointers->hourly_data[0-23] structs. */
void
generate_hourly_data(pointers_t * pointers, ftp_entry_t * first_ftp_line)
{
ftp_entry_t * ftp_line;
int index;
char hour_str[3] = {0};
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning hourly data generation...\n");
#endif
for (ftp_line = first_ftp_line; ftp_line; ftp_line = ftp_line->next_ptr)
{
/* pluck the hour from the date into a string */
hour_str[0] = ftp_line->date[11];
hour_str[1] = ftp_line->date[12];
/* convert it to an integer */
index = atoi(hour_str);
/* add it to the already-compiled data in the array */
pointers->hour_data[index]->seconds += ftp_line->seconds;
pointers->hour_data[index]->file_count++;
pointers->hour_data[index]->data += ftp_line->data;
}
if (pointers->done) {
switch(pointers->config->hourly_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_number;
break;
}
qsort((void *)pointers->hour_data, 24, (size_t) sizeof(void *), sort_func);
}
#if 0
if (pointers->config->additive_db)
{
FILE * db;
if (!(db = fopen("hourly.xdb", "w")))
return;
if (fwrite(pointers->hour_data, sizeof(xfmult_t), 24, db) < 24)
{
perror("generate_hourly_data: fwrite(hour_data)");
return;
}
fclose(db);
}
#endif
#ifdef DEBUGS
fprintf(stderr, "Hourly data generation complete.\n");
#endif
} /* generate_hourly_data */
void
generate_monthly_data(pointers_t * pointers, ftp_entry_t * first_ftp_line)
{
xfmisc_t * day = NULL;
ftp_entry_t * ftp_line;
int index, count;
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning monthly data generation...\n");
#endif
if (!pointers->config->number_daily_stats && day)
{
#ifdef DEBUGS
fprintf(stderr, " Using previously generated daily data...\n");
#endif
for (count = 0; (day = pointers->first_day_ptr[count]); count++)
{
if ((index = month2int(day->name + 4) - 1) >= 0)
{
/* add it to the already-compiled data in the array */
pointers->month_data[index]->seconds += day->seconds;
pointers->month_data[index]->file_count += day->file_count;
pointers->month_data[index]->data += day->data;
}
}
}
else
for (ftp_line = first_ftp_line; ftp_line; ftp_line = ftp_line->next_ptr)
if ((index = month2int(ftp_line->date + 4) - 1) >= 0)
{
/* add it to the already-compiled data in the array */
pointers->month_data[index]->seconds += ftp_line->seconds;
pointers->month_data[index]->file_count++;
pointers->month_data[index]->data += ftp_line->data;
}
if (pointers->done) {
switch(pointers->config->monthly_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_number;
}
qsort((void *)pointers->month_data, 12, (size_t)sizeof(void *), sort_func);
}
#if 0
if (pointers->config->additive_db)
{
FILE * db;
if (!(db = fopen("monthly.xdb", "w")))
return;
if (fwrite(pointers->month_data, sizeof(xfmult_t), 12, db) < 12)
{
perror("generate_monthly_data: fwrite(month_data)");
return;
}
fclose(db);
}
#endif
#ifdef DEBUGS
fprintf(stderr, "Monthly data generation complete.\n");
#endif
} /* generate_monthly_data */
void
generate_dom_data(pointers_t * pointers, ftp_entry_t * first_ftp_line)
{
ftp_entry_t * ftp_line;
xfmisc_t * day = NULL;
int index, count;
char day_str[3] = {0};
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning day-of-the-month data generation...\n");
#endif
if (!pointers->config->number_daily_stats && day &&
!pointers->config->chunk_input)
{
#ifdef DEBUGS
fprintf(stderr, " Using previously generated daily data...\n");
#endif
for (count = 0; (day = pointers->first_day_ptr[count]); count++)
{
/* pluck the day from the date into a string */
strncpy(day_str, day->name+8, 2);
/* convert it to an appropriate type and decrement it */
index = atoi(day_str);
if (--index >= 0 && index <= 30)
{
/* add it to the already-compiled data in the array */
pointers->dom_data[index]->seconds += day->seconds;
pointers->dom_data[index]->file_count += day->file_count;
pointers->dom_data[index]->data += day->data;
}
#ifdef DEBUG
else
fprintf(stderr, "generate_dom_data: Invalid day: %d\n", index);
#endif
}
}
else
for (ftp_line = first_ftp_line; ftp_line; ftp_line = ftp_line->next_ptr)
{
/* pluck the day from the date into a string */
strncpy(day_str, ftp_line->date + 8, 2);
/* convert it to an appropriate type and decrement it */
index = atoi(day_str);
if (--index >= 0 && index <= 30)
{
/* add it to the already-compiled data in the array */
pointers->dom_data[index]->seconds += ftp_line->seconds;
pointers->dom_data[index]->file_count += 1;
pointers->dom_data[index]->data += ftp_line->data;
}
#ifdef DEBUG
else
fprintf(stderr, "generate_dom_data: Invalid day: %d\n", index);
#endif
}
if (pointers->done) {
switch(pointers->config->dom_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_number;
}
qsort((void *)pointers->dom_data, 31, (size_t) sizeof(void *), sort_func);
}
#if 0
if (pointers->config->additive_db)
{
FILE * db;
if (!(db = fopen("dom.xdb", "w")))
return;
if (fwrite(pointers->dom_data, sizeof(xfmult_t), 31, db) < 31)
{
perror("generate_dom_data: fwrite(dom_data)");
return;
}
fclose(db);
}
#endif
#ifdef DEBUGS
fprintf(stderr, "Day-of-the-month data generation complete.\n");
#endif
} /* generate_dom_data */
/* this function creates the structures for the day-of-the-week report. */
void
generate_dow_data(pointers_t * pointers, ftp_entry_t * first_ftp_line)
{
ftp_entry_t * ftp_line;
xfmisc_t * day = NULL;
int index, count;
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning day-of-the-week data generation...\n");
#endif
if (!pointers->config->number_daily_stats && day &&
!pointers->config->chunk_input)
{
#ifdef DEBUGS
fprintf(stderr, " Using previously generated daily data...\n");
#endif
for (count = 0; (day = pointers->first_day_ptr[count]); count++)
if ((index = day2int(day->name) - 1) >= 0)
{
/* add it to the already-compiled data in the array */
pointers->weekday_data[index]->seconds += day->seconds;
pointers->weekday_data[index]->file_count += day->file_count;
pointers->weekday_data[index]->data += day->data;
}
#ifdef DEBUG
else
fprintf(stderr, "Invalid day in date \"%s\"\n", day->name);
#endif
}
else
for (ftp_line = first_ftp_line; ftp_line; ftp_line = ftp_line->next_ptr)
if ((index = day2int(ftp_line->date) - 1) >= 0)
{
/* add it to the already-compiled data in the array */
pointers->weekday_data[index]->seconds += ftp_line->seconds;
pointers->weekday_data[index]->file_count += 1;
pointers->weekday_data[index]->data += ftp_line->data;
}
#ifdef DEBUG
else
fprintf(stderr, "Invalid day in date \"%s\"\n", day->name);
#endif
if (pointers->done) {
switch(pointers->config->dow_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
default:
sort_func = (void *)xf_sort_by_number;
}
qsort((void *)pointers->weekday_data, 7, (size_t)sizeof(void *),sort_func);
}
#if 0
if (pointers->config->additive_db)
{
FILE * db;
if (!(db = fopen("dow.xdb", "w")))
return;
if (fwrite(pointers->weekday_data, sizeof(xfmult_t), 7, db) < 7)
{
perror("generate_dow_data: fwrite(weekday_data)");
return;
}
fclose(db);
}
#endif
#ifdef DEBUGS
fprintf(stderr, "Day-of-the-week data generation complete.\n");
#endif
} /* generate_dow_data */
void
generate_daily_data(pointers_t * pointers, ftp_entry_t * first_ftp_line)
{
ftp_entry_t * ftp_line = first_ftp_line;
xfmisc_t * current_day_ptr;
char temp_date[16];
static GHashTable * ht = NULL;
int count;
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning daily data generation...\n");
#endif
if (!ht)
ht = g_hash_table_new(g_str_hash, g_str_equal);
/* If [low, high]_date hasn't already been set (perhaps by the additive DB
* code, or a (god forbid) previous execution of this function) set them to
* something so that we can compare later */
if (!pointers->low_date[0])
memcpy(pointers->low_date, ftp_line->date, 25);
if (!pointers->high_date[0])
memcpy(pointers->high_date, ftp_line->date, 25);
for (; ftp_line; ftp_line = ftp_line->next_ptr)
{
pointers->file_count++;
pointers->data += ftp_line->data;
if (ftp_line->complete == 'c') {
pointers->complete_count++;
}
if (compare_dates(ftp_line->date, pointers->high_date) > 0)
memcpy(pointers->high_date, ftp_line->date, 25);
else if (compare_dates(ftp_line->date, pointers->low_date) < 0)
memcpy(pointers->low_date, ftp_line->date, 25);
memcpy(temp_date, ftp_line->date, 10);
memcpy(temp_date + 10, ftp_line->date + 19, 5);
temp_date[15] = '\0';
xf_set_value(ht, temp_date, current_day_ptr, ftp_line,
(ftp_line->complete == 'c' ? 1 : 0), 1);
}
if (pointers->done) {
pointers->first_day_ptr = (xfmisc_t **)xf_make_array(ht, &count);
g_hash_table_destroy(ht);
switch(pointers->config->daily_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_day;
break;
}
qsort((void *)pointers->first_day_ptr, (size_t) count,
(size_t) sizeof(void *), sort_func);
}
#if 0
if (pointers->config->additive_db)
{
FILE * db;
if (!(db = fopen("daily.xdb", "w")))
return;
for (current_day_ptr = pointers->first_day_ptr; current_day_ptr;
current_day_ptr = current_day_ptr->next_ptr)
{
if (fwrite(current_day_ptr->name, sizeof(char),
sizeof(current_day_ptr->name) / sizeof(char), db) <
sizeof(current_day_ptr->name) / sizeof(char))
{
perror("generate_daily_data: fwrite(name)");
return;
}
if (fwrite(¤t_day_ptr->data, sizeof(current_day_ptr->data), 1,
db) < 1)
{
perror("generate_daily_data: fwrite(data)");
return;
}
if (fwrite(¤t_day_ptr->file_count,
sizeof(current_day_ptr->file_count), 1, db) < 1)
{
perror("generate_daily_data: fwrite(file_count)");
return;
}
if (fwrite(¤t_day_ptr->seconds,
sizeof(current_day_ptr->seconds), 1, db) < 1)
{
perror("generate_daily_data: fwrite(seconds)");
return;
}
}
fclose(db);
if (!(db = fopen("totals.xdb", "w")))
return;
if (fwrite(pointers->low_date, sizeof(char),
sizeof(pointers->low_date) / sizeof(char), db) <
sizeof(pointers->low_date) / sizeof(char))
{
perror("generate_daily_data: fwrite(low_date)");
return;
}
if (fwrite(pointers->high_date, sizeof(char),
sizeof(pointers->high_date) / sizeof(char), db) <
sizeof(pointers->high_date) / sizeof(char))
{
perror("generate_daily_data: fwrite(high_date)");
return;
}
if (fwrite(&pointers->data, sizeof(pointers->data), 1, db)
< 1)
{
perror("generate_daily_data: fwrite(data)");
return;
}
if (fwrite(&pointers->file_count, sizeof(pointers->file_count), 1, db)
< 1)
{
perror("generate_daily_data: fwrite(file_count)");
return;
}
fclose(db);
}
#endif
#ifdef DEBUGS
fprintf(stderr, "Daily data generation complete.\n");
#endif
} /* generate_daily_data */
/* This is probably the worst function in xferstats to read or change.
* It performs size functions: file, directory, host, domains, TLD and file
* size data generation because of the similar nature in which they're
* processed. Passed in addition to the pointers struct is the type (defined
* in xferstats.h). */
void
generate_misc_data(pointers_t *pointers, ftp_entry_t * first_ftp_line,
int type)
{
GHashTable * ht;
ftp_entry_t * ftp_line = first_ftp_line;
xfmisc_t * other_data_ptr, * current_data_ptr = NULL;
int a, count = 1;
void ** pointers_ptr = NULL, ** array;
char name[MAXPATHLEN], * tmpchar, * prevchar;
int (*sort_func)(const void *, const void *) = NULL;
#ifdef DEBUGS
fprintf(stderr, "Beginning %s data generation...(%ld)\n", T_NAMES[type],
(long) time(NULL));
#endif
switch (type) {
case T_FILE:
pointers_ptr = pointers->first_file_ptr;
if (!pointers->file_ht)
ht = pointers->file_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->file_ht;
break;
case T_DIR:
pointers_ptr = pointers->first_dir_ptr;
if (!pointers->dir_ht)
ht = pointers->dir_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->dir_ht;
break;
case T_HOST:
pointers_ptr = pointers->first_host_ptr;
if (!pointers->host_ht)
ht = pointers->host_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->host_ht;
break;
case T_TLD:
pointers_ptr = pointers->first_tld_ptr;
if (!pointers->tld_ht)
ht = pointers->tld_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->tld_ht;
break;
case T_DOMAIN:
pointers_ptr = pointers->first_domain_ptr;
if (!pointers->domain_ht)
ht = pointers->domain_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->domain_ht;
break;
case T_SIZE:
pointers_ptr = pointers->first_size_ptr;
if (!pointers->size_ht)
ht = pointers->size_ht = g_hash_table_new(g_str_hash, g_str_equal);
else
ht = pointers->size_ht;
break;
default:
fprintf(stderr, "generate_misc_data: invalid type. if you get this, "
"please mail\n phil@off.net with the version number (%s). "
"This is important!\n", VERSION);
return;
}
if (type == T_DOMAIN && pointers->config->host_traffic &&
pointers->first_host_ptr && !pointers->config->number_host_stats &&
!pointers->config->chunk_input)
{
/* get domain data from host data--since it's already partly-compiled, it
* saves time, as there will be fewer (or at the very least, the same
* number of) records. */
#ifdef DEBUGS
fprintf(stderr, " Using previously generated host data...\n");
#endif
for (other_data_ptr = (xfmisc_t *)pointers->first_host_ptr[0];
other_data_ptr;
other_data_ptr = (xfmisc_t *)pointers->first_host_ptr[count++])
{
/* check to see if it's resolved first */
if ((tmpchar = strrchr(other_data_ptr->name, '.')) &&
tmpchar + 1)
{
#ifdef DEBUG2
fprintf(stderr, "First letter of TLD: %d:%c\n",
*(tmpchar + 1), *(tmpchar + 1));
#endif
if (isdigit((int)*(tmpchar + 1)))
strncpy(name, "unresolved", sizeof(name) - 1);
else
{
/* now check to see if we have another '.' */
if (tmpchar == other_data_ptr->name)
{
/* tmpchar is already the first character (why there
* would be any sort of valid domain name that BEGINS
* with a period is beyond me... */
/* convert all to lower case */
for(prevchar = tmpchar + 1;
(*prevchar = tolower((int)*prevchar)); prevchar++);
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
}
else
{
*tmpchar = '\0';
if (!(prevchar = strrchr(other_data_ptr->name, '.')))
{
/* there is only one '.' in the host */
*tmpchar = '.';
/* convert all to lower case */
for(prevchar = other_data_ptr->name;
(*prevchar = tolower((int)*prevchar));
prevchar++)
;
strncpy(name, other_data_ptr->name,
MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
}
else
{
/* take everything after that '.' */
*tmpchar = '.';
tmpchar = prevchar;
/* convert all to lower case */
for(prevchar = tmpchar + 1;
(*prevchar = tolower((int)*prevchar));
prevchar++)
;
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
}
}
}
}
else
{
/* No '.' in the name, eh? Probably something that's in
* /etc/hosts. *ching* Unresolved. */
strncpy(name, "unresolved", sizeof(name) - 1);
}
xf_set_value(ht, name, current_data_ptr, other_data_ptr,
other_data_ptr->completed_count,
other_data_ptr->file_count);
}
}
else if (type == T_DIR && pointers->config->file_traffic &&
pointers->first_file_ptr && !pointers->config->number_file_stats &&
!pointers->config->chunk_input)
{
/* Take the dir data from the file data, for the same reason as above */
#ifdef DEBUGS
fprintf(stderr, " Using previously generated file data...\n");
#endif
for (other_data_ptr = (xfmisc_t *)pointers->first_file_ptr[0];
other_data_ptr;
other_data_ptr = (xfmisc_t *)pointers->first_file_ptr[count++])
{
strncpy(name, other_data_ptr->name, MAXPATHLEN - 1);
/* if we're doing directory processing, cut off at least the
* filename (and probably some directories) based on
* pointers->config->depth */
if (pointers->config->depth)
{
if ((tmpchar = strrchr(name, '/')))
*(tmpchar + 1) = '\0';
for (a = 1, prevchar = name + 1, tmpchar = name;
a <= pointers->config->depth; prevchar = tmpchar, a++)
{
if ((tmpchar = strchr(tmpchar + 1, '/')) == NULL)
{
*prevchar = '\0';
break;
}
if (a == pointers->config->depth)
*tmpchar = '\0';
}
}
else
if ((tmpchar = strrchr(name, '/')))
*tmpchar = '\0';
#ifdef DEBUG2
fprintf(stderr, "path: %s dir: %s\n", other_data_ptr->name, name);
#endif
xf_set_value(ht, name, current_data_ptr, other_data_ptr,
other_data_ptr->completed_count,
other_data_ptr->file_count);
}
}
else if (type == T_TLD && pointers->config->domain_traffic &&
pointers->first_domain_ptr &&
!pointers->config->number_domain_stats &&
!pointers->config->chunk_input)
{
/* Take the TLD data from the domain data, for the same reason as the
* two above blocks... */
#ifdef DEBUGS
fprintf(stderr, " Using previously generated domain data...\n");
#endif
for (other_data_ptr = (xfmisc_t *)pointers->first_domain_ptr[0];
other_data_ptr;
other_data_ptr = (xfmisc_t *)pointers->first_domain_ptr[count++])
{
strncpy(name, other_data_ptr->name, MAXHOSTNAMELEN - 1);
/* if we're doing TLD processing, we only want what's after the
* last '.' */
if ((tmpchar = strrchr(name, '.')) &&
tmpchar + 1)
{
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
#ifdef DEBUG2
fprintf(stderr, "First letter of TLD: %d:%c\n", name[0],
name[0]);
#endif
if (isdigit((int)name[0]))
strncpy(name, "unresolved", sizeof(name) - 1);
else
/* convert all to lower case */
for(tmpchar = name; (*tmpchar = tolower((int)*tmpchar));
tmpchar++);
}
else
strncpy(name, "unresolved", sizeof(name) - 1);
#ifdef DEBUG2
fprintf(stderr, "host: %s TLD: %s\n", other_data_ptr->name, name);
#endif
xf_set_value(ht, name, current_data_ptr, other_data_ptr,
other_data_ptr->completed_count,
other_data_ptr->file_count);
}
}
else
{
for (; ftp_line; ftp_line = ftp_line->next_ptr)
{
/* if we're doing file or host processing, all we need to do is
* strncpy the name and go */
switch (type)
{
case T_FILE:
strncpy(name, ftp_line->path, MAXPATHLEN - 1);
break;
case T_HOST:
strncpy(name, ftp_line->host, MAXHOSTNAMELEN - 1);
break;
case T_DIR:
strncpy(name, ftp_line->path, MAXPATHLEN - 1);
/* if we're doing directory processing, cut off at least the
* filename (and probably some directories) based on
* pointers->config->depth */
if (pointers->config->depth)
{
if ((tmpchar = strrchr(name, '/')))
*(tmpchar + 1) = '\0';
for (a = 1, prevchar = name + 1, tmpchar = name;
a <= pointers->config->depth;
prevchar = tmpchar, a++)
{
if ((tmpchar = strchr(tmpchar + 1, '/')) == NULL)
{
*prevchar = '\0';
break;
}
if (a == pointers->config->depth)
*tmpchar = '\0';
}
}
else
if ((tmpchar = strrchr(name, '/')))
*tmpchar = '\0';
#ifdef DEBUG2
fprintf(stderr, "date: %s path: %s dir: %s\n", ftp_line->date,
ftp_line->path, name);
#endif
break;
case T_TLD:
strncpy(name, ftp_line->host, MAXHOSTNAMELEN - 1);
/* if we're doing TLD processing, we only want what's after the
* last '.' */
if ((tmpchar = strrchr(name, '.')) &&
tmpchar + 1)
{
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
#ifdef DEBUG2
fprintf(stderr, "First letter of TLD: %d:%c\n", name[0],
name[0]);
#endif
if (isdigit((int)name[0]))
strncpy(name, "unresolved", sizeof(name) - 1);
else
/* convert all to lower case */
for(tmpchar = name; (*tmpchar = tolower((int)*tmpchar));
tmpchar++);
}
else
strncpy(name, "unresolved", sizeof(name) - 1);
#ifdef DEBUG2
fprintf(stderr, "host: %s TLD: %s\n", ftp_line->host, name);
#endif
break;
case T_DOMAIN:
/* if we're doing domain processing, check to see if it's
* resolved first */
if ((tmpchar = strrchr(ftp_line->host, '.')) &&
tmpchar + 1)
{
#ifdef DEBUG2
fprintf(stderr, "First letter of TLD: %d:%c\n",
*(tmpchar + 1), *(tmpchar + 1));
#endif
if (isdigit((int)*(tmpchar + 1)))
{
strncpy(name, "unresolved", sizeof(name) - 1);
break;
}
}
else
{
/* No '.' in the name, eh? Probably something that's in
* /etc/hosts. *ching* Unresolved. */
strncpy(name, "unresolved", sizeof(name) - 1);
break;
}
/* now check to see if we have another '.' */
if (tmpchar == ftp_line->host)
{
/* tmpchar is already the first character (why there would
* be any sort of valid domain name that BEGINS with a period
* is beyond me... */
/* convert all to lower case */
for(prevchar = tmpchar + 1;
(*prevchar = tolower((int)*prevchar)); prevchar++);
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
break;
}
*tmpchar = '\0';
if ((prevchar = strrchr(ftp_line->host, '.')) == NULL)
{
/* there is only one '.' in the host */
*tmpchar = '.';
/* convert all to lower case */
for(prevchar = ftp_line->host;
(*prevchar = tolower((int)*prevchar)); prevchar++);
strncpy(name, ftp_line->host, MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
}
else
{
/* take everything after that '.' */
*tmpchar = '.';
tmpchar = prevchar;
/* convert all to lower case */
for(prevchar = tmpchar + 1;
(*prevchar = tolower((int)*prevchar)); prevchar++);
strncpy(name, tmpchar + 1, MAXHOSTNAMELEN - 1);
name[MAXHOSTNAMELEN - 1] = '\0';
}
break;
case T_SIZE:
g_snprintf(name, sizeof(name) - 1, "%lu0", ftp_line->data);
name[0] = '1';
for (tmpchar = name + 1; *(tmpchar + 1); tmpchar++)
*tmpchar = '0';
break;
} /* switch(type) */
xf_set_value(ht, name, current_data_ptr, ftp_line,
(ftp_line->complete == 'c' ? 1 : 0), 1);
}
}
if (pointers->done) {
#ifdef DEBUG
fprintf(stderr, "Starting to sort...\n");
#endif
/* In recognition of time wasted working on sorting only to replace it all
* with a basic libc function, I give you the following quote:
*
* "There are people out there who will do this, and they are very scary."
* -- Alan Cox, "Space Aliens Ate My Mouse", Linux Expo, May 30 1998
*/
array = xf_make_array(ht, &count);
g_hash_table_destroy(ht);
switch(type) {
case T_FILE:
switch(pointers->config->file_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 2:
sort_func = (void *)xf_sort_alphabetical;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_file_count;
break;
}
break;
case T_HOST:
switch(pointers->config->host_sort_pref) {
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 2:
sort_func = (void *)xf_sort_alphabetical;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_data;
break;
}
break;
case T_DIR:
switch(pointers->config->dir_sort_pref) {
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 2:
sort_func = (void *)xf_sort_alphabetical;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_data;
break;
}
break;
case T_TLD:
switch(pointers->config->tld_sort_pref) {
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 2:
sort_func = (void *)xf_sort_alphabetical;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_data;
break;
}
break;
case T_DOMAIN:
switch(pointers->config->domain_sort_pref) {
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 2:
sort_func = (void *)xf_sort_alphabetical;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_data;
break;
}
break;
case T_SIZE:
switch(pointers->config->size_sort_pref) {
case 0:
sort_func = (void *)xf_sort_by_data;
break;
case 1:
sort_func = (void *)xf_sort_by_file_count;
break;
case 3:
sort_func = (void *)xf_sort_by_throughput;
break;
default:
sort_func = (void *)xf_sort_by_file_size;
break;
}
break;
}
qsort((void *)array, (size_t) count, (size_t) sizeof(void *), sort_func);
switch(type) {
case T_FILE:
pointers->first_file_ptr = array;
break;
case T_HOST:
pointers->first_host_ptr = array;
break;
case T_DIR:
pointers->first_dir_ptr = array;
break;
case T_TLD:
pointers->first_tld_ptr = array;
break;
case T_DOMAIN:
pointers->first_domain_ptr = array;
break;
case T_SIZE:
pointers->first_size_ptr = array;
break;
}
if (pointers->config->additive_db) {
FILE * db;
switch(type) {
case T_FILE:
if (!(db = fopen("file.xdb", "w")))
return;
break;
case T_HOST:
if (!(db = fopen("host.xdb", "w")))
return;
break;
case T_DIR:
if (!(db = fopen("dir.xdb", "w")))
return;
break;
case T_TLD:
if (!(db = fopen("tld.xdb", "w")))
return;
break;
case T_DOMAIN:
if (!(db = fopen("domain.xdb", "w")))
return;
break;
case T_SIZE:
if (!(db = fopen("size.xdb", "w")))
return;
break;
default:
return;
}
for (current_data_ptr = array[count = 0]; current_data_ptr;
current_data_ptr = array[++count]) {
if (fwrite(current_data_ptr->name, sizeof(char),
strlen(current_data_ptr->name) + 1, db) <
strlen(current_data_ptr->name) + 1) {
perror("generate_misc_data: fwrite(name)");
return;
}
if (fwrite(¤t_data_ptr->data, sizeof(current_data_ptr->data),
1, db) < 1) {
perror("generate_misc_data: fwrite(data)");
return;
}
if (fwrite(¤t_data_ptr->file_count,
sizeof(current_data_ptr->file_count), 1, db) < 1) {
perror("generate_misc_data: fwrite(file_count)");
return;
}
if (fwrite(¤t_data_ptr->seconds,
sizeof(current_data_ptr->seconds), 1, db) < 1) {
perror("generate_misc_data: fwrite(seconds)");
return;
}
}
fclose(db);
}
}
#ifdef DEBUGS
fprintf(stderr, "%s data generation complete (%ld)\n", T_NAMES[type],
(long) time(NULL));
#endif
} /* generate_misc_data */
void
parse_xferstats_databases(pointers_t * pointers)
{
FILE * db;
char tmpstr[MAXPATHLEN];
int type;
if ((db = fopen("totals.xdb", "r")))
{
#ifdef DEBUGS
fprintf(stderr, "Reading databases from previous runs.");
#endif
if (fread(pointers->low_date, sizeof(char), sizeof(pointers->low_date) /
sizeof(char), db) < sizeof(pointers->low_date) / sizeof(char))
perror("parse_xferstats_databases: fread(low_date)");
else if (fread(pointers->high_date, sizeof(char),
sizeof(pointers->high_date) / sizeof(char), db) <
sizeof(pointers->high_date) / sizeof(char))
perror("parse_xferstats_databases: fread(high_date)");
else if (fread(&pointers->data, sizeof(pointers->data), 1, db) < 1)
perror("parse_xferstats_databases: fread(pointers->data)");
else if (fread(&pointers->file_count, sizeof(pointers->file_count), 1,
db) < 1)
perror("parse_xferstats_databases: fread(pointers->file_count)");
fclose(db);
}
else
return;
#if 0
{
perror("parse_xferstats_databases: fopen(totals.xdb)");
return;
}
#endif
#if 0
if ((db = fopen("daily.xdb", "r")))
{
xfmisc_t * current_day_ptr = pointers->first_day_ptr, * new_day_ptr;
#ifdef DEBUGS
fputc('.', stderr);
#endif
if (current_day_ptr)
for (; current_day_ptr->next_ptr;
current_day_ptr = current_day_ptr->next_ptr)
;
while (!feof(db))
{
new_day_ptr = G_MEM_CHUNK_ALLOC(pointers->xfmisc_chunk);
if (fread(new_day_ptr->name, sizeof(char),
sizeof(new_day_ptr->name) / sizeof(char), db) <
sizeof(new_day_ptr->name) / sizeof(char))
{
#ifndef SMFS
g_free(new_day_ptr);
#endif
}
else if (fread(&new_day_ptr->data, sizeof(new_day_ptr->data), 1, db)
< 1)
perror("parse_xferstats_databases: fread(day->data)");
else if (fread(&new_day_ptr->file_count,
sizeof(new_day_ptr->file_count), 1, db) < 1)
perror("parse_xferstats_databases: fread(day->file_count)");
else if (fread(&new_day_ptr->seconds, sizeof(new_day_ptr->seconds),
1, db) < 1)
perror("parse_xferstats_databases: fread(day->seconds)");
else
{
if (current_day_ptr)
{
current_day_ptr->next_ptr = new_day_ptr;
pointers->last_day_ptr = current_day_ptr = new_day_ptr;
}
else
pointers->last_day_ptr = pointers->first_day_ptr =
current_day_ptr = new_day_ptr;
new_day_ptr->next_ptr = NULL;
}
}
fclose(db);
}
else
{
perror("parse_xferstats_databases: fopen(daily.xdb)");
return;
}
#endif
#if 0
if ((db = fopen("dow.xdb", "r")))
{
#ifdef DEBUGS
fputc('.', stderr);
#endif
if (fread(pointers->weekday_data, sizeof(xfmult_t), 7, db) < 7)
{
perror("parse_xferstats_databases: fread(weekday_data)");
return;
}
fclose(db);
}
else
{
perror("parse_xferstats_databases: fopen(dow.xdb)");
return;
}
if ((db = fopen("hourly.xdb", "r")))
{
#ifdef DEBUGS
fputc('.', stderr);
#endif
if (fread(pointers->hour_data, sizeof(xfmult_t), 24, db) < 24)
{
perror("parse_xferstats_databases: fread(hour_data)");
return;
}
fclose(db);
}
else
{
perror("parse_xferstats_databases: fopen(hourly.xdb)");
return;
}
if ((db = fopen("monthly.xdb", "r")))
{
#ifdef DEBUGS
fputc('.', stderr);
#endif
if (fread(pointers->month_data, sizeof(xfmult_t), 12, db) < 12)
{
perror("parse_xferstats_databases: fread(month_data)");
return;
}
fclose(db);
}
else
{
perror("parse_xferstats_databases: fopen(monthly.xdb)");
return;
}
#endif
for (type = T_FILE; type <= T_SIZE; type++)
{
xfmisc_t * new_data_ptr;
void *** pointers_ptr, ** current_data_ptr;
int num = 0, size = 128;
switch(type)
{
case T_FILE:
if (!(db = fopen("file.xdb", "r")))
return;
pointers_ptr = &pointers->first_file_ptr;
current_data_ptr = pointers->first_file_ptr;
break;
case T_DIR:
if (!(db = fopen("dir.xdb", "r")))
return;
pointers_ptr = &pointers->first_dir_ptr;
current_data_ptr = pointers->first_dir_ptr;
break;
case T_HOST:
if (!(db = fopen("host.xdb", "r")))
return;
pointers_ptr = &pointers->first_host_ptr;
current_data_ptr = pointers->first_host_ptr;
break;
case T_TLD:
if (!(db = fopen("tld.xdb", "r")))
return;
pointers_ptr = &pointers->first_tld_ptr;
current_data_ptr = pointers->first_tld_ptr;
break;
case T_DOMAIN:
if (!(db = fopen("domain.xdb", "r")))
return;
pointers_ptr = &pointers->first_domain_ptr;
current_data_ptr = pointers->first_domain_ptr;
break;
case T_SIZE:
if (!(db = fopen("size.xdb", "r")))
return;
pointers_ptr = &pointers->first_size_ptr;
current_data_ptr = pointers->first_size_ptr;
break;
default:
return;
break;
}
#ifdef DEBUGS
fputc('.', stderr);
#endif
if (current_data_ptr)
for (; current_data_ptr; current_data_ptr = *pointers_ptr[++num])
num++;
while (!feof(db))
{
new_data_ptr = G_MEM_CHUNK_ALLOC(pointers->xfmisc_chunk);
fgets(tmpstr, sizeof(tmpstr), db);
if (!tmpstr[0])
{
#ifndef SMFS
g_free(new_data_ptr);
#endif
}
else
{
new_data_ptr->name = g_strdup(tmpstr);
printf("tmpstr: %s\n", tmpstr);
if (fread(&new_data_ptr->data, sizeof(new_data_ptr->data), 1, db)
< 1)
{
printf("(%d) type: %d\n",
tmpstr[0], type);
perror("parse_xferstats_databases: fread(misc->data)");
}
else if (fread(&new_data_ptr->file_count,
sizeof(new_data_ptr->file_count), 1, db) < 1)
perror("parse_xferstats_databases: fread(misc->file_count)");
else if (fread(&new_data_ptr->seconds,
sizeof(new_data_ptr->seconds), 1, db) < 1)
perror("parse_xferstats_databases: fread(misc->seconds)");
else
{
if (*pointers_ptr)
{
if (num >= size - 1)
*pointers_ptr =
(void **)g_realloc(*pointers_ptr, (size += 128)
* sizeof(void *));
}
else
*pointers_ptr = g_malloc(128 * sizeof(void *));
(*pointers_ptr)[num++] = (void *)new_data_ptr;
}
}
}
if (*pointers_ptr)
(*pointers_ptr)[num + 1] = NULL;
fclose(db);
}
#ifdef DEBUGS
fputc('\n', stderr);
#endif
} /* parse_xferstats_databases */
static void
usage(char bad_arg)
{
if (bad_arg)
printf("Invalid argument -- %c\n", bad_arg);
printf("%s\n", VERSION);
printf("usage: xferstats [options] [filename(s)]\n\n"
"Any command line arguments inside <> are required, inside [] are optional. All\n"
"defaults listed assume that the configuration file has not changed these\n"
"settings. If in doubt, check the configuration file or explicitly set them on\n"
"the command line.\n"
"\n"
" - get the log from a file (default)\n"
" + get the log from stdin\n"
" -c <config file> specify a path and filename for the configuration file\n"
" -T <number> logfile type (wu-ftpd = 0, ncftpd = 1)\n"
"\n"
"The following options are enabled with a \"+\" and disabled with a \"-\". See the\n"
"man page for more information. Any arguments apply only to enabling:\n"
"\n"
" C Strict data validity checking\n"
" H HTML output\n"
" n no HTML headers option (see man page)\n"
" s single-page output (see man page)\n"
" r real user data\n"
" a anonymous user data\n"
" g guest user data\n"
" i inbound traffic data\n"
" u outbound traffic data\n"
" h report on hourly traffic\n"
" m report on monthly traffic\n"
" S report on file size traffic\n"
" w report on days-of-the-week traffic\n"
" M report on days-of-the-month traffic\n"
" f [number] report on file traffic\n"
" d [number] report on directory traffic\n"
" t [number] report on top-level domain traffic\n"
" O [number] report on domain traffic\n"
" o [number] report on host traffic\n"
"\n"
"\n"
" +L <number> limit the report-by-day report to <number> listings\n"
" +A include all users, all reports\n"
#if 0 /* This will have to wait for another version */
" +T <TLD> report only on traffic from the <TLD> top-level domain\n"
#endif
" -D <number> depth of path detail for directories (default 3)\n"
" +D <directory> directory to report on, for example: +D /pub will\n"
" report only on paths under /pub\n"
" +B <timestamp> the starting date for all reports (all prior transfers\n"
" will be filtered). Jan DD HH:MM:ss YYYY\n"
" +E <timestamp> the ending date, in the same format\n"
" +P <prefix> strip the specified prefix from pathnames\n"
"\n"
" -v, --version display version information\n"
" --help this listing\n"
"\n"
"Report bugs to phil@off.net\n");
} /* usage */
static void
check_cmd_arg(int argc, char * argv[], int * i, int * j, unsigned int * value,
char * arg_str)
{
long foo;
if (argv[*i] + *j + 1 == '\0')
{
if (*i < (argc - 1) && *argv[*i + 1] != '-' && *argv[*i + 1] != '+')
{
if ((foo = atoi(argv[++*i])) < 0)
{
fprintf(stderr, "fatal: the %s parameter accepts only an "
"optional number (>= 0) following it.\n", arg_str);
exit(1);
}
else
{
*value = foo;
*j = strlen(argv[*i]);
}
}
}
else
if ((foo = atoi(argv[*i] + *j + 1)) < 0)
{
fprintf(stderr, "fatal: the %s parameter accepts only an optional "
"number (>= 0) following it.\n", arg_str);
exit(1);
}
else
{
*value = foo;
*j = strlen(argv[*i]);
}
} /* check_cmd_arg */
void
parse_cmdline(int argc, char *argv[], config_t * config)
{
int i = 1, j, cmdline_logfile = 0;
char c, no_more_options = 0, * logfile_name;
long foo;
only_dir_t * item;
GSList * clist;
for (; i < argc; i++)
{
if (!no_more_options && !strncmp(argv[i], "--", 2))
{
/* argv[i] contains a long option (or is --) */
if (argv[i][2] == '\0')
no_more_options = 1;
else if (!strcmp(argv[i]+2, "help"))
{
usage('\0');
exit(0);
}
else if (!strcmp(argv[i]+2, "version"))
{
printf("%s\n", VERSION);
printf("Copyright (C) 1997-1999 Phil Schwan\n");
exit(0);
}
}
else if (!no_more_options && *argv[i] == '-')
{
/* argv[i] contains a disabling option */
if (*(argv[i] + (j = 1)) == '\0')
config->use_stdin = 0;
while ((c = *(argv[i] + j)) != '\0')
{
switch (c)
{
case 'c':
/* this (config_file) was parsed in get_config_arg */
if (i < argc - 1)
/* set 'j' to be the pointer to the null at the end of the
* -next- argument (the one that contains the config file).
* I'm not going to explain this in detail every time I
* do this :) */
j = strlen(argv[++i]);
continue;
case 'T':
if (i < argc - 1 && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
if (!g_strcasecmp(argv[i + 1], "wu-ftpd") ||
!g_strcasecmp(argv[i + 1], "wuftpd") ||
!g_strcasecmp(argv[i + 1], "wu-ftp") ||
!g_strcasecmp(argv[i + 1], "wuftp"))
config->log_type = 0;
else if (!g_strcasecmp(argv[i + 1], "ncftpd") ||
!g_strcasecmp(argv[i + 1], "ncftp"))
config->log_type = 1;
else if (!g_strcasecmp(argv[i + 1], "apache"))
config->log_type = 2;
else
{
fprintf(stderr, "fatal: the -T parameter requires a "
"log type following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
}
else
{
fprintf(stderr, "fatal: the -T parameter requires a log "
"type following it.\n");
exit(1);
}
case 'C':
config->strict_check = 0;
break;
case 'H':
config->html_output = 0;
break;
case 'n':
config->no_html_headers = 0;
break;
case 's':
if (config->single_page)
g_free(config->single_page);
config->single_page = NULL;
break;
case 'r':
config->real_traffic = 0;
break;
case 'a':
config->anon_traffic = 0;
break;
case 'g':
config->guest_traffic = 0;
break;
case 'i':
config->inbound = 0;
break;
case 'u':
config->outbound = 0;
break;
case 'h':
config->hourly_traffic = 0;
break;
case 'w':
config->dow_traffic = 0;
break;
case 'M':
config->dom_traffic = 0;
break;
case 't':
config->tld_traffic = 0;
break;
case 'O':
config->domain_traffic = 0;
break;
case 'o':
config->host_traffic = 0;
break;
case 'm':
config->monthly_traffic = 0;
break;
case 'S':
config->size_traffic = 0;
break;
case 'd':
config->dir_traffic = 0;
break;
case 'f':
config->file_traffic = 0;
break;
case 'D':
/* we duplicate the stuff in check_cmd_arg because this
* arguments requires (instead of being optional) a
* value > 0 (instead of >= 0) */
if (*(argv[i] + j + 1) == '\0')
{
/* this is the end of this block of -xXxD so the argument
* is in the next arg... */
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
if ((foo = atoi(argv[i + 1])) < 0)
{
fprintf(stderr, "fatal: the -D parameter "
"requires a number (> 0) following "
"it.\n");
exit(1);
}
else
{
config->depth = foo;
j = strlen(argv[++i]);
}
}
else
{
fprintf(stderr, "fatal: the -D parameter requires a "
"number (> 0) following it.\n");
exit(1);
}
}
else
if ((foo = atoi(argv[i] + j + 1)) < 0)
{
fprintf(stderr, "fatal: the -D parameter requires a "
"number (> 0) following it.\n");
exit(1);
}
else
{
config->depth = foo;
j = strlen(argv[i]);
}
continue;
case 'v':
printf("%s\n", VERSION);
printf("Copyright (C) 1997-1999 Phil Schwan\n");
exit(0);
default:
usage(argv[i][j]);
exit(1);
}
j++;
}
}
else if (!no_more_options && *argv[i] == '+')
{
/* argv[i] contains an enabling option */
if (*(argv[i] + (j = 1)) == '\0')
config->use_stdin = 1;
while ((c = *(argv[i] + j)) != '\0')
{
switch (c)
{
case 'C':
config->strict_check = 2;
break;
case 'a':
config->anon_traffic = 2;
break;
case 'g':
config->guest_traffic = 2;
break;
case 'r':
config->real_traffic = 2;
break;
case 'i':
config->inbound = 2;
break;
case 'u':
config->outbound = 2;
break;
case 'h':
config->hourly_traffic = 2;
break;
case 'w':
config->dow_traffic = 2;
break;
case 'M':
config->dom_traffic = 2;
break;
case 'm':
config->monthly_traffic = 2;
break;
case 'S':
config->size_traffic = 2;
break;
case 'd':
config->dir_traffic = 2;
check_cmd_arg(argc, argv, &i, &j,
&config->number_dir_stats, "+d");
continue;
case 'O':
config->domain_traffic = 2;
check_cmd_arg(argc, argv, &i, &j,
&config->number_domain_stats, "+O");
continue;
case 'o':
config->host_traffic = 2;
check_cmd_arg(argc, argv, &i, &j,
&config->number_host_stats, "+o");
continue;
case 't':
config->tld_traffic = 2;
check_cmd_arg(argc, argv, &i, &j,
&config->number_tld_stats, "+d");
continue;
case 'f':
config->file_traffic = 2;
check_cmd_arg(argc, argv, &i, &j,
&config->number_file_stats, "+f");
continue;
case 'L':
check_cmd_arg(argc, argv, &i, &j,
&config->number_daily_stats, "+L");
continue;
case 'R':
check_cmd_arg(argc, argv, &i, &j,
&config->max_report_size, "+R");
continue;
case 'H':
config->html_output = 1;
break;
case 'n':
config->no_html_headers = 1;
break;
case 's':
if (config->single_page)
g_free(config->single_page);
config->single_page = g_strdup("xferstats.html");
break;
case 'D':
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
item = g_malloc(sizeof(only_dir_t));
item->dir = g_strdup(argv[i + 1]);
item->len = strlen(argv[i + 1]);
config->only_dir = g_slist_append(config->only_dir,
item);
}
else
{
fprintf(stderr, "fatal: the +D parameter requires a "
"path following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
case 'P':
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
/* FIXME: allow a list of stripped prefixes? */
if (config->strip_prefix != NULL)
g_free(config->strip_prefix);
config->strip_prefix =
g_malloc((config->strip_prefix_len =
strlen(argv[i + 1])) + 1);
strcpy(config->strip_prefix, argv[i + 1]);
}
else
{
fprintf(stderr, "fatal: the +P parameter requires a "
"string following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
case 'B':
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
char * bar = argv[i + 1];
if (strlen(argv[i + 1]) != 20)
{
fprintf(stderr, "fatal: the date must be in the "
"Jan DD HH:MM:ss YYYY format\n");
exit(1);
}
if (bar[3] != ' ' || bar[6] != ' ' ||
bar[15] != ' ' || bar[9] != ':' ||
bar[12] != ':' || !isdigit(bar[5]) ||
!isdigit(bar[7]) || !isdigit(bar[8]) ||
!isdigit(bar[10]) || !isdigit(bar[11]) ||
!isdigit(bar[13]) || !isdigit(bar[14]) ||
!isdigit(bar[16]) || !isdigit(bar[17]) ||
!isdigit(bar[18]) || !isdigit(bar[19]) ||
!(bar[4] == ' ' || bar[4] == '0' ||
bar[4] == '1' || bar[4] == '2' ||
bar[4] == '3'))
{
fprintf(stderr, "fatal: the date must be in the "
"Jan DD HH:MM:ss YYYY format\n");
exit(1);
}
memset(config->begin_date, ' ', 4);
memcpy(config->begin_date + 4, argv[i + 1], 21);
}
else
{
fprintf(stderr, "fatal: the +B parameter requires a "
"path following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
case 'E':
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
{
char * bar = argv[i + 1];
if (strlen(argv[i + 1]) != 20)
{
fprintf(stderr, "fatal: the date must be in the "
"Jan DD HH:MM:ss YYYY format\n");
exit(1);
}
if (bar[3] != ' ' || bar[6] != ' ' ||
bar[15] != ' ' || bar[9] != ':' ||
bar[12] != ':' || !isdigit(bar[5]) ||
!isdigit(bar[7]) || !isdigit(bar[8]) ||
!isdigit(bar[10]) || !isdigit(bar[11]) ||
!isdigit(bar[13]) || !isdigit(bar[14]) ||
!isdigit(bar[16]) || !isdigit(bar[17]) ||
!isdigit(bar[18]) || !isdigit(bar[19]) ||
!(bar[4] == ' ' || bar[4] == '0' ||
bar[4] == '1' || bar[4] == '2' ||
bar[4] == '3'))
{
fprintf(stderr, "fatal: the date must be in the "
"Jan DD HH:MM:ss YYYY format\n");
exit(1);
}
memset(config->end_date, ' ', 4);
memcpy(config->end_date + 4, argv[i + 1], 21);
}
else
{
fprintf(stderr, "fatal: the +E parameter requires a "
"path following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
#if 0 /* This will have to wait for another version */
case 'T':
if (i < (argc - 1) && *argv[i + 1] != '-' &&
*argv[i + 1] != '+')
if (*argv[i + 1] != '/')
{
config->only_dir = g_malloc((config->only_dir_length =
strlen(argv[i + 1]) + 1) + 1);
config->only_dir[0] = '/';
memcpy(config->only_dir + 1, argv[i + 1],
config->only_dir_length);
}
else
{
config->only_dir = g_strdup(argv[i + 1]);
config->only_dir_length = strlen(argv[i + 1]);
}
else
{
fprintf(stderr, "fatal: the +T parameter requires a "
"top-level domain following it.\n");
exit(1);
}
j = strlen(argv[++i]);
continue;
#endif
case 'A':
config->anon_traffic = 2;
config->real_traffic = 2;
config->guest_traffic = 2;
config->inbound = 2;
config->outbound = 2;
config->hourly_traffic = 2;
config->monthly_traffic = 2;
config->tld_traffic = 2;
config->host_traffic = 2;
config->domain_traffic = 2;
config->dir_traffic = 2;
config->file_traffic = 2;
config->size_traffic = 2;
config->dow_traffic = 2;
config->dom_traffic = 2;
break;
default:
usage(argv[i][j]);
exit(1);
}
j++;
}
}
else
{
/* it's a filename */
logfile_name = g_strdup(argv[i]);
if (!cmdline_logfile)
{
/* this is the first logfile that we've gotten from the command
* line. destroy any existing logfiles, as they're logfiles that
* are from the config file, and we don't want those! */
cmdline_logfile = 1;
if (config->logfiles)
{
for (clist = config->logfiles; clist; clist = clist->next)
g_free(clist->data);
g_slist_free(config->logfiles);
config->logfiles = NULL;
}
}
config->logfiles = g_slist_append(config->logfiles, logfile_name);
/* as soon as we get a filename, we're done getting options */
no_more_options = 1;
}
}
} /* parse_cmdline */
void
get_config_arg(int argc, char *argv[], pointers_t *pointers)
{
int argno = 0;
for (; argno < argc; argno++)
if (!strcmp(argv[argno], "-c") && argno < (argc - 1))
{
if (!(pointers->config->config_file = g_strdup(argv[argno + 1])))
{
perror("get_config_arg: g_strdup");
exit(0);
}
return;
}
} /* get_config_arg */
void
xferstats_init(pointers_t *pointers)
{
pointers->xfmisc_chunk = G_MEM_CHUNK_NEW("xfmisc_t mem chunk",
sizeof(xfmisc_t),
1024, G_ALLOC_ONLY);
pointers->ftpentry_chunk = G_MEM_CHUNK_NEW("ftp_entry_t mem chunk",
sizeof(ftp_entry_t),
1024, G_ALLOC_ONLY);
pointers->config->only_dir =
pointers->config->logfiles = NULL;
/* initialize our config structure with remaining default values */
pointers->config->log_type = 1;
pointers->config->number_file_stats =
pointers->config->number_daily_stats =
pointers->config->number_dir_stats =
pointers->config->number_tld_stats =
pointers->config->number_domain_stats =
pointers->config->number_host_stats = 50;
pointers->config->real_traffic =
pointers->config->anon_traffic =
pointers->config->guest_traffic =
pointers->config->inbound =
pointers->config->outbound =
pointers->config->tld_traffic =
pointers->config->domain_traffic =
pointers->config->host_traffic =
pointers->config->hourly_traffic =
pointers->config->dow_traffic =
pointers->config->dom_traffic =
pointers->config->dir_traffic =
pointers->config->file_traffic =
pointers->config->monthly_traffic =
pointers->config->size_traffic =
pointers->config->use_stdin =
pointers->config->html_output =
pointers->config->no_html_headers =
pointers->config->strip_prefix_len =
pointers->config->strict_check = 0;
/* -1 is an invalid value which will trigger the defaults */
pointers->config->file_sort_pref =
pointers->config->dir_sort_pref =
pointers->config->domain_sort_pref =
pointers->config->tld_sort_pref =
pointers->config->host_sort_pref =
pointers->config->dom_sort_pref =
pointers->config->dow_sort_pref =
pointers->config->hourly_sort_pref =
pointers->config->daily_sort_pref =
pointers->config->monthly_sort_pref =
pointers->config->size_sort_pref = -1;
pointers->config->max_report_size = 30;
pointers->config->depth = 4;
pointers->low_date[0] =
pointers->high_date[0] =
pointers->config->begin_date[0] =
pointers->config->end_date[0] = 0;
#if 0
/* This will have to wait for another version */
pointers->config->only_tld = NULL;
pointers->config->only_tld_length = 0;
#endif
pointers->config->config_file =
pointers->config->single_page =
pointers->config->graph_path =
pointers->config->link_prefix =
pointers->config->html_location =
pointers->config->strip_prefix =
pointers->config->additive_db = NULL;
/* initialize the pointers and clear the totals/dates */
pointers->first_ftp_line = NULL;
pointers->first_day_ptr = NULL;
pointers->first_tld_ptr =
pointers->first_domain_ptr =
pointers->first_host_ptr =
pointers->first_file_ptr =
pointers->first_dir_ptr =
pointers->first_size_ptr = NULL;
pointers->file_count =
pointers->data =
pointers->config->refresh =
pointers->config->chunk_input = 0;
pointers->file_ht =
pointers->tld_ht =
pointers->domain_ht =
pointers->host_ht =
pointers->dir_ht =
pointers->size_ht = NULL;
pointers->done = 0;
/* allocate and clean out these arrays. these arrays are generated in this
* fashion instead of statically so that I can use libc's qsort(). I hope
* the compiler can optimize some of this crap :). */
{
int a;
pointers->hour_data = g_malloc(24 * sizeof(void *));
for (a = 0; a < 24; a++)
{
pointers->hour_data[a] = G_MEM_CHUNK_ALLOC0(pointers->xfmisc_chunk);
pointers->hour_data[a]->name = (char *)NUMBERS[a];
}
pointers->weekday_data = g_malloc(7 * sizeof(void *));
for (a = 0; a < 7; a++)
{
pointers->weekday_data[a] = G_MEM_CHUNK_ALLOC0(pointers->xfmisc_chunk);
pointers->weekday_data[a]->name = (char *)LONG_DAYS[a];
}
pointers->dom_data = g_malloc(31 * sizeof(void *));
for (a = 0; a < 31; a++)
{
pointers->dom_data[a] = G_MEM_CHUNK_ALLOC0(pointers->xfmisc_chunk);
pointers->dom_data[a]->name = (char *)NUMBERS[a];
}
pointers->month_data = g_malloc(12 * sizeof(void *));
for (a = 0; a < 12; a++)
{
pointers->month_data[a] = G_MEM_CHUNK_ALLOC0(pointers->xfmisc_chunk);
pointers->month_data[a]->name = (char *)LONG_MONTHS[a];
}
}
} /* xferstats_init */
int
main(int argc, char **argv)
{
pointers_t * pointers;
char * default_logfile;
int first = 1;
/* unbuffer stdout and stderr so output isn't lost */
setbuf(stdout, NULL);
setbuf(stderr, NULL);
/* allocate memory for our pointers */
pointers = g_malloc(sizeof(pointers_t));
pointers->config = g_malloc(sizeof(config_t));
/* init lots and lots of vars */
xferstats_init(pointers);
/* grab the -c <configfile> argument if it exists */
get_config_arg(argc, argv, pointers);
/* do the config-file thing */
init_config(pointers);
/* change the config structure based on command line args */
parse_cmdline(argc, argv, pointers->config);
/* make sure our max report size is something sane (>= 10 or 0) */
if (pointers->config->max_report_size &&
pointers->config->max_report_size < 10)
pointers->config->max_report_size = 10;
/* load any databases that may exist */
if (pointers->config->additive_db)
{
parse_xferstats_databases(pointers);
/* something else--if we have additive_db turned on, we must zero out the
* stat limits so that all are processed */
pointers->config->number_file_stats =
pointers->config->number_daily_stats =
pointers->config->number_dir_stats =
pointers->config->number_tld_stats =
pointers->config->number_domain_stats =
pointers->config->number_host_stats = 0;
}
if (!pointers->config->logfiles)
{
/* no logfiles have been specified in the config file or on the command
* line, add the default */
pointers->config->logfiles = NULL;
if (!(default_logfile = g_strdup(FILENAME)))
{
perror("main: g_strdup(FILENAME)");
exit(1);
}
pointers->config->logfiles = g_slist_append(pointers->config->logfiles,
default_logfile);
}
/* load the xferlog into ftp_line structures */
while (!pointers->done) {
switch(pointers->config->log_type) {
case 0:
if (!parse_wuftpd_logfile(pointers))
pointers->done = 1;
break;
case 1:
if (!parse_ncftpd_logfile(pointers))
pointers->done = 1;
break;
case 2:
if (!parse_apache_logfile(pointers))
pointers->done = 1;
break;
}
G_BLOW_CHUNKS();
if (first && !pointers->first_ftp_line) {
fprintf(stderr, "No data to process.\n");
exit(0);
}
first = 0;
generate_daily_data(pointers, pointers->first_ftp_line);
if (pointers->config->dow_traffic)
generate_dow_data(pointers, pointers->first_ftp_line);
if (pointers->config->dom_traffic)
generate_dom_data(pointers, pointers->first_ftp_line);
if (pointers->config->monthly_traffic)
generate_monthly_data(pointers, pointers->first_ftp_line);
if (pointers->config->file_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_FILE);
if (pointers->config->dir_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_DIR);
if (pointers->config->host_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_HOST);
if (pointers->config->domain_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_DOMAIN);
if (pointers->config->tld_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_TLD);
if (pointers->config->hourly_traffic)
generate_hourly_data(pointers, pointers->first_ftp_line);
if (pointers->config->size_traffic)
generate_misc_data(pointers, pointers->first_ftp_line, T_SIZE);
}
display_daily_totals(pointers);
if (pointers->config->dow_traffic)
display_dow_totals(pointers);
if (pointers->config->dom_traffic)
display_dom_totals(pointers);
if (pointers->config->monthly_traffic)
display_monthly_totals(pointers);
if (pointers->config->file_traffic)
display_misc_totals(pointers, T_FILE, "file.html");
if (pointers->config->dir_traffic)
display_misc_totals(pointers, T_DIR, "dir.html");
if (pointers->config->host_traffic)
display_misc_totals(pointers, T_HOST, "host.html");
if (pointers->config->domain_traffic)
display_misc_totals(pointers, T_DOMAIN, "domain.html");
if (pointers->config->tld_traffic)
display_misc_totals(pointers, T_TLD, "tld.html");
if (pointers->config->hourly_traffic)
display_hourly_totals(pointers);
if (pointers->config->size_traffic)
display_misc_totals(pointers, T_SIZE, "size.html");
#ifdef DEBUGS
/* display memory information to stderr */
g_mem_chunk_info();
g_mem_profile();
#endif
return 0;
} /* main */
syntax highlighted by Code2HTML, v. 0.9.1