@ -0,0 +1,4 @@ | |||
*.d | |||
*.o | |||
ctstats | |||
ctstats.log |
@ -0,0 +1,21 @@ | |||
CFLAGS := -O2 -pipe -Wall | |||
LDFLAGS := -lpthread -lnfnetlink -lnetfilter_conntrack | |||
OBJS := configfile.o conntrack.o database.o hashtable.o logging.o ctstats.o | |||
all: ctstats | |||
ctstats: $(OBJS) | |||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ | |||
%.o: %.c | |||
$(CC) $(CFLAGS) -c $< -o $@ | |||
%.d: %.c | |||
$(CC) $(CFLAGS) -MM -c $< -o $@ | |||
clean: | |||
rm -f ctstats *.d *.o *.log | |||
DEPS := $(wildcard *.c) | |||
-include $(DEPS:.c=.d) |
@ -0,0 +1,200 @@ | |||
/*************************************************************************** | |||
* Copyright (C) 06/2006 by Olaf Rempel * | |||
* razzor@kopf-tisch.de * | |||
* * | |||
* 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. * | |||
***************************************************************************/ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <sys/socket.h> | |||
#include <netinet/in.h> | |||
#include <netinet/ip.h> | |||
#include <arpa/inet.h> | |||
#include "configfile.h" | |||
#include "list.h" | |||
#include "logging.h" | |||
#define BUFSIZE 1024 | |||
struct conf_section { | |||
struct list_head list; | |||
struct list_head tupel_list; | |||
char *name; | |||
}; | |||
struct conf_tupel { | |||
struct list_head list; | |||
char *option; | |||
char *parameter; | |||
}; | |||
static LIST_HEAD(config_list); | |||
static struct conf_section * config_add_section(const char *name) | |||
{ | |||
struct conf_section *section; | |||
section = malloc(sizeof(struct conf_section) + strlen(name)); | |||
if (section == NULL) | |||
return NULL; | |||
INIT_LIST_HEAD(§ion->list); | |||
INIT_LIST_HEAD(§ion->tupel_list); | |||
section->name = strdup(name); | |||
if (section->name == NULL) { | |||
free(section); | |||
return NULL; | |||
} | |||
list_add_tail(§ion->list, &config_list); | |||
return section; | |||
} | |||
static int config_add_tupel(struct conf_section *section, const char *option, const char *parameter) | |||
{ | |||
struct conf_tupel *tupel = malloc(sizeof(struct conf_tupel)); | |||
if (tupel == NULL) | |||
return -1; | |||
INIT_LIST_HEAD(&tupel->list); | |||
tupel->option = strdup(option); | |||
tupel->parameter = strdup(parameter); | |||
if (tupel->option == NULL || tupel->parameter == NULL) { | |||
free(tupel); | |||
return -1; | |||
} | |||
list_add_tail(&tupel->list, §ion->tupel_list); | |||
return 0; | |||
} | |||
int config_parse(const char *config) | |||
{ | |||
FILE *fz = fopen(config, "r"); | |||
if (fz == NULL) { | |||
log_print(LOG_ERROR, "config_parse(): %s", config); | |||
return -1; | |||
} | |||
char *line = malloc(BUFSIZE); | |||
if (line == NULL) { | |||
log_print(LOG_ERROR, "config_parse(): out of memory"); | |||
fclose(fz); | |||
return -1; | |||
} | |||
int linenum = 0; | |||
struct conf_section *section = NULL; | |||
while (fgets(line, BUFSIZE, fz) != NULL) { | |||
linenum++; | |||
if (line[0] == '#' || line[0] <= ' ') { | |||
continue; | |||
} else if (line[0] == '[') { | |||
char *tok = strtok(line +1, " ]\n"); | |||
if (tok == NULL || (section = config_add_section(tok)) == NULL) { | |||
log_print(LOG_WARN, "config_parse(): invalid section in row %d", linenum); | |||
free(line); | |||
fclose(fz); | |||
return -1; | |||
} | |||
continue; | |||
} else if (section == NULL) { | |||
log_print(LOG_WARN, "config_parse(): missing section in row %d", linenum); | |||
free(line); | |||
fclose(fz); | |||
return -1; | |||
} | |||
char *tok = strtok(line, " \n"); | |||
if (tok != NULL) { | |||
char *tok2; | |||
while ((tok2 = strtok(NULL, " \n"))) { | |||
if (config_add_tupel(section, tok, tok2) != 0) | |||
log_print(LOG_WARN, "config_parse(): invalid row %d", linenum); | |||
} | |||
} | |||
} | |||
fclose(fz); | |||
free(line); | |||
return 0; | |||
} | |||
static struct conf_section * config_get_section(const char *name) | |||
{ | |||
struct conf_section *section; | |||
list_for_each_entry(section, &config_list, list) { | |||
if (!strcmp(section->name, name)) | |||
return section; | |||
} | |||
return NULL; | |||
} | |||
char * config_get_string(const char *section_str, const char *option, char *def) | |||
{ | |||
struct conf_section *section = config_get_section(section_str); | |||
if (section != NULL) { | |||
struct conf_tupel *tupel; | |||
list_for_each_entry(tupel, §ion->tupel_list, list) { | |||
if (!strcmp(tupel->option, option)) | |||
return tupel->parameter; | |||
} | |||
} | |||
if (def != NULL) | |||
log_print(LOG_WARN, "config [%s:%s] not found, using default: '%s'", | |||
section_str, option, def); | |||
return def; | |||
} | |||
int config_get_int(const char *section, const char *option, int def) | |||
{ | |||
char *ret = config_get_string(section, option, NULL); | |||
if (ret == NULL) { | |||
log_print(LOG_WARN, "config [%s:%s] not found, using default: '%d'", | |||
section, option, def); | |||
return def; | |||
} | |||
return atoi(ret); | |||
} | |||
int config_get_strings(const char *section_str, const char *option, | |||
int (*callback)(const char *value, void *privdata), | |||
void *privdata) | |||
{ | |||
struct conf_section *section = config_get_section(section_str); | |||
if (section == NULL) | |||
return -1; | |||
int cnt = 0; | |||
struct conf_tupel *tupel; | |||
list_for_each_entry(tupel, §ion->tupel_list, list) { | |||
if (!strcmp(tupel->option, option)) | |||
if (callback(tupel->parameter, privdata) == 0) | |||
cnt++; | |||
} | |||
return cnt; | |||
} |
@ -0,0 +1,16 @@ | |||
#ifndef _CONFIG_H_ | |||
#define _CONFIG_H_ | |||
#include <netinet/in.h> | |||
int config_parse(const char *config); | |||
char * config_get_string(const char *section_str, const char *option, char *def); | |||
int config_get_int(const char *section, const char *option, int def); | |||
int config_get_strings(const char *section_str, const char *option, | |||
int (*callback)(const char *value, void *privdata), | |||
void *privdata); | |||
#endif /* _CONFIG_H_ */ |
@ -0,0 +1,114 @@ | |||
#include <stdlib.h> | |||
#include <time.h> | |||
#include <pthread.h> | |||
#include <libnfnetlink/libnfnetlink.h> | |||
#include <libnetfilter_conntrack/libnetfilter_conntrack.h> | |||
#include "configfile.h" | |||
#include "logging.h" | |||
#include "hashtable.h" | |||
#define DEFAULT_HASHSIZE 127 | |||
static pthread_t ct_thread; | |||
static struct nfct_handle *cth; | |||
static struct hash_table *table[2]; | |||
static int hash_select = 0; | |||
static int conntrack_event_cb(void *arg, unsigned int flags, int type, void *privdata) | |||
{ | |||
struct nfct_conntrack *ct = (struct nfct_conntrack *)arg; | |||
if (ct->tuple[NFCT_DIR_ORIGINAL].l3protonum != AF_INET) | |||
return 0; | |||
struct hash_entry *entry = malloc(sizeof(struct hash_entry)); | |||
if (entry == NULL) { | |||
log_print(LOG_WARN, "conntrack_event_cb: out of memory"); | |||
return -1; | |||
} | |||
entry->src_ip = ct->tuple[NFCT_DIR_ORIGINAL].src.v4; | |||
entry->protonum = ct->tuple[NFCT_DIR_ORIGINAL].protonum; | |||
entry->dst_port = ct->tuple[NFCT_DIR_ORIGINAL].l4dst.tcp.port; | |||
entry->src_bytes = ct->counters[NFCT_DIR_ORIGINAL].bytes; | |||
entry->dst_bytes = ct->counters[NFCT_DIR_REPLY].bytes; | |||
hash_add(table[hash_select], entry); | |||
return 0; | |||
} | |||
int conntrack_dump_hash(void (*callback)(struct hash_entry *entry, void *privdata), void *privdata) | |||
{ | |||
/* | |||
* switch hashtable | |||
* -> conntrack_event_cb will use the empty hash next time, and we dump now the full one | |||
* -> if conntrack_event_cb is running *now* (on the full hash), we have to wait a bit | |||
* -> but since we are faster than dump intervall, conntrack_event_cb never waits | |||
*/ | |||
int used_hash = hash_select; | |||
hash_select ^= 0x01; | |||
purge_hash(table[used_hash], callback, privdata); | |||
return 0; | |||
} | |||
int conntrack_init(void) | |||
{ | |||
cth = nfct_open(CONNTRACK, NF_NETLINK_CONNTRACK_DESTROY); | |||
if (cth == NULL) { | |||
log_print(LOG_ERROR, "conntrack_init: nfct_open()"); | |||
return -1; | |||
} | |||
int size = config_get_int("global", "hashsize", DEFAULT_HASHSIZE); | |||
table[0] = create_hash(size); | |||
if (table[0] == NULL) { | |||
log_print(LOG_ERROR, "conntrack_init: create_hash(0)"); | |||
nfct_close(cth); | |||
return -1; | |||
} | |||
table[1] = create_hash(size); | |||
if (table[1] == NULL) { | |||
log_print(LOG_ERROR, "conntrack_init: create_hash(1)"); | |||
destroy_hash(table[0]); | |||
nfct_close(cth); | |||
return -1; | |||
} | |||
nfct_register_callback(cth, conntrack_event_cb, NULL); | |||
return 0; | |||
} | |||
static int event_thread(void *arg) | |||
{ | |||
nfct_event_conntrack(cth); | |||
return 0; | |||
} | |||
int conntrack_start_event_thread(void) | |||
{ | |||
/* startup conntrack event listener */ | |||
pthread_attr_t attr; | |||
pthread_attr_init(&attr); | |||
pthread_attr_setstacksize(&attr, 65536); | |||
pthread_create(&ct_thread, &attr, (void *)&event_thread, NULL); | |||
return 0; | |||
} | |||
int conntrack_close(void) | |||
{ | |||
destroy_hash(table[1]); | |||
destroy_hash(table[0]); | |||
nfct_close(cth); | |||
return 0; | |||
} |
@ -0,0 +1,12 @@ | |||
#ifndef _CONNTRACK_H_ | |||
#define _CONNTRACK_H_ | |||
#include "hashtable.h" | |||
int conntrack_init(void); | |||
int conntrack_start_event_thread(void); | |||
int conntrack_close(void); | |||
int conntrack_dump_hash(void (*callback)(struct hash_entry *entry, void *privdata), void *privdata); | |||
#endif // _CONNTRACK_H_ |
@ -0,0 +1,88 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <getopt.h> | |||
#include "configfile.h" | |||
#include "conntrack.h" | |||
#include "database.h" | |||
#include "logging.h" | |||
#define DEFAULT_CONFIG "ctstats.conf" | |||
#define DEFAULT_LOGFILE "ctstats.log" | |||
#define DEFAULT_INTERVALL 300 | |||
static struct option opts[] = { | |||
{"config", 1, 0, 'c'}, | |||
{"debug", 0, 0, 'd'}, | |||
{"help", 0, 0, 'h'}, | |||
{0, 0, 0, 0} | |||
}; | |||
int main(int argc, char *argv[]) | |||
{ | |||
char *config = DEFAULT_CONFIG; | |||
int code, arg = 0, debug = 0; | |||
do { | |||
code = getopt_long(argc, argv, "c:dh", opts, &arg); | |||
switch (code) { | |||
case 'c': /* config */ | |||
config = optarg; | |||
break; | |||
case 'd': /* debug */ | |||
debug = 1; | |||
break; | |||
case 'h': /* help */ | |||
printf("Usage: ctstat [options]\n" | |||
"Options: \n" | |||
" --config -c configfile use this configfile\n" | |||
" --debug -d do not fork and log to stderr\n" | |||
" --help -h this help\n" | |||
"\n"); | |||
exit(0); | |||
break; | |||
case '?': /* error */ | |||
exit(-1); | |||
break; | |||
default: /* unknown / all options parsed */ | |||
break; | |||
} | |||
} while (code != -1); | |||
/* parse config file */ | |||
if (config_parse(config)) | |||
exit(1); | |||
/* init netlink, hashtables, mutexes */ | |||
if (conntrack_init()) | |||
exit(1); | |||
/* init database connection */ | |||
if (database_init()) { | |||
conntrack_close(); | |||
exit(1); | |||
} | |||
conntrack_start_event_thread(); | |||
int intervall = config_get_int("global", "intervall", DEFAULT_INTERVALL); | |||
while (1) { | |||
sleep(intervall); | |||
/* fill database */ | |||
database_analyse(); | |||
} | |||
/* how to stop? */ | |||
database_close(); | |||
conntrack_close(); | |||
return 0; | |||
} |
@ -0,0 +1,10 @@ | |||
[global] | |||
intervall 30 | |||
hashsize 127 | |||
logfile ctstats.log | |||
[mysql] | |||
hostname localhost | |||
username ctstats | |||
password ctstats | |||
database ctstats |
@ -0,0 +1,52 @@ | |||
#include <sys/socket.h> | |||
#include <netinet/in.h> | |||
#include <arpa/inet.h> | |||
#include "conntrack.h" | |||
#include "database.h" | |||
#include "hashtable.h" | |||
#include "logging.h" | |||
static void purge_hash_cb(struct hash_entry *entry, void *privdata) | |||
{ | |||
struct in_addr src_ip = { .s_addr = entry->src_ip }; | |||
char *proto; | |||
switch (entry->protonum) { | |||
case IPPROTO_TCP: | |||
proto = "tcp "; | |||
break; | |||
case IPPROTO_UDP: | |||
proto = "udp "; | |||
break; | |||
case IPPROTO_ICMP: | |||
proto = "icmp"; | |||
break; | |||
default: | |||
proto = "unkn"; | |||
break; | |||
} | |||
log_print(LOG_DEBUG, "%4s %15s:%5d -> %8llu / %8llu (%u)", | |||
proto, inet_ntoa(src_ip), ntohs(entry->dst_port), | |||
entry->src_bytes, entry->dst_bytes, entry->count); | |||
} | |||
int database_analyse(void) | |||
{ | |||
conntrack_dump_hash(purge_hash_cb, NULL); | |||
return 0; | |||
} | |||
int database_init(void) | |||
{ | |||
return 0; | |||
} | |||
int database_close(void) | |||
{ | |||
return 0; | |||
} |
@ -0,0 +1,8 @@ | |||
#ifndef _DATABASE_H_ | |||
#define _DATABASE_H_ | |||
int database_analyse(void); | |||
int database_init(void); | |||
int database_close(void); | |||
#endif // _DATABASE_H_ |
@ -0,0 +1,126 @@ | |||
#include <stdlib.h> | |||
#include <inttypes.h> | |||
#include <pthread.h> | |||
#include "hashtable.h" | |||
/* see linux/include/linux/jhash.h for details */ | |||
#define JHASH_GOLDEN_RATIO 0x9e3779b9 | |||
#define __jhash_mix(a, b, c) \ | |||
{ \ | |||
a -= b; a -= c; a ^= (c>>13); \ | |||
b -= c; b -= a; b ^= (a<<8); \ | |||
c -= a; c -= b; c ^= (b>>13); \ | |||
a -= b; a -= c; a ^= (c>>12); \ | |||
b -= c; b -= a; b ^= (a<<16); \ | |||
c -= a; c -= b; c ^= (b>>5); \ | |||
a -= b; a -= c; a ^= (c>>3); \ | |||
b -= c; b -= a; b ^= (a<<10); \ | |||
c -= a; c -= b; c ^= (b>>15); \ | |||
} | |||
struct hash_table * create_hash(uint32_t buckets) | |||
{ | |||
struct hash_table *table; | |||
uint32_t i; | |||
table = malloc(sizeof(struct hash_table) + | |||
(sizeof(struct hash_entry *) * buckets)); | |||
if (table == NULL) | |||
return NULL; | |||
for (i = 0; i < buckets; i++) | |||
table->bucket[i] = NULL; | |||
table->buckets = buckets; | |||
table->hash_rnd = 0x56789ABC; | |||
pthread_mutex_init(&table->mutex, NULL); | |||
return table; | |||
} | |||
void purge_hash(struct hash_table *table, | |||
void (*callback)(struct hash_entry *entry, void *privdata), | |||
void *privdata) | |||
{ | |||
uint32_t i; | |||
pthread_mutex_lock(&table->mutex); | |||
for (i = 0; i < table->buckets; i++) { | |||
struct hash_entry *entry = table->bucket[i]; | |||
while (entry != NULL) { | |||
struct hash_entry *tmp = entry; | |||
if (callback != NULL) | |||
callback(entry, privdata); | |||
entry = entry->next; | |||
free(tmp); | |||
} | |||
table->bucket[i] = NULL; | |||
} | |||
pthread_mutex_unlock(&table->mutex); | |||
} | |||
void destroy_hash(struct hash_table *table) | |||
{ | |||
purge_hash(table, NULL, NULL); | |||
pthread_mutex_destroy(&table->mutex); | |||
free(table); | |||
} | |||
static uint32_t calc_hashkey(struct hash_entry *entry, uint32_t initval) | |||
{ | |||
uint32_t a = entry->src_ip; | |||
uint32_t b = entry->protonum; | |||
uint32_t c = entry->dst_port; | |||
a += JHASH_GOLDEN_RATIO; | |||
b += JHASH_GOLDEN_RATIO; | |||
c += initval; | |||
__jhash_mix(a, b, c); | |||
return c; | |||
} | |||
static int cmp_entry(struct hash_entry *a, struct hash_entry *b) | |||
{ | |||
return (a->src_ip ^ b->src_ip) | | |||
(a->protonum ^ b->protonum) | | |||
(a->dst_port ^ b->dst_port); | |||
} | |||
void hash_add(struct hash_table *table, struct hash_entry *entry) | |||
{ | |||
uint32_t key = calc_hashkey(entry, table->hash_rnd) % table->buckets; | |||
pthread_mutex_lock(&table->mutex); | |||
struct hash_entry *search = table->bucket[key]; | |||
/* search collision-list */ | |||
while (search != NULL) { | |||
if (cmp_entry(entry, search) == 0) { | |||
/* add values */ | |||
search->src_bytes += entry->src_bytes; | |||
search->dst_bytes += entry->dst_bytes; | |||
search->count++; | |||
pthread_mutex_unlock(&table->mutex); | |||
/* free entry */ | |||
free(entry); | |||
return; | |||
} | |||
search = search->next; | |||
} | |||
/* insert new bucket */ | |||
entry->count = 1; | |||
entry->next = table->bucket[key]; | |||
table->bucket[key] = entry; | |||
pthread_mutex_unlock(&table->mutex); | |||
} |
@ -0,0 +1,35 @@ | |||
#ifndef _HASHTABLE_H_ | |||
#define _HASHTABLE_H_ | |||
#include <inttypes.h> | |||
#include <pthread.h> | |||
struct hash_entry { | |||
struct hash_entry *next; | |||
uint32_t src_ip; | |||
uint16_t protonum; | |||
uint16_t dst_port; | |||
uint64_t src_bytes; | |||
uint64_t dst_bytes; | |||
uint32_t count; | |||
}; | |||
struct hash_table { | |||
uint32_t buckets; | |||
uint32_t hash_rnd; | |||
pthread_mutex_t mutex; | |||
struct hash_entry *bucket[0]; | |||
}; | |||
struct hash_table * create_hash(uint32_t buckets); | |||
void purge_hash(struct hash_table *table, | |||
void (*callback)(struct hash_entry *entry, void *privdata), | |||
void *privdata); | |||
void destroy_hash(struct hash_table *table); | |||
void hash_add(struct hash_table *table, struct hash_entry *entry); | |||
#endif /* _HASHTABLE_H_ */ |
@ -0,0 +1,268 @@ | |||
#ifndef _LIST_H_ | |||
#define _LIST_H_ | |||
/* | |||
* stolen from linux kernel 2.6.11 (http://kernel.org/) | |||
* linux/include/linux/stddef.h (offsetoff) | |||
* linux/include/linux/kernel.h (container_of) | |||
* linux/include/linux/list.h (*list*) | |||
* linux/include/linux/netfilter_ipv4/listhelp.h (LIST_FIND) | |||
* | |||
* modified by Olaf Rempel <razzor@kopf-tisch.de> | |||
*/ | |||
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) | |||
#define container_of(ptr, type, member) ({ \ | |||
const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | |||
(type *)( (char *)__mptr - offsetof(type,member) );}) | |||
struct list_head { | |||
struct list_head *next, *prev; | |||
}; | |||
#define LIST_HEAD_INIT(name) { &(name), &(name) } | |||
#define LIST_HEAD(name) \ | |||
struct list_head name = LIST_HEAD_INIT(name) | |||
#define INIT_LIST_HEAD(ptr) do { \ | |||
(ptr)->next = (ptr); (ptr)->prev = (ptr); \ | |||
} while (0) | |||
/* | |||
* Insert a new entry between two known consecutive entries. | |||
* | |||
* This is only for internal list manipulation where we know | |||
* the prev/next entries already! | |||
*/ | |||
static inline void __list_add(struct list_head *new, | |||
struct list_head *prev, | |||
struct list_head *next) | |||
{ | |||
next->prev = new; | |||
new->next = next; | |||
new->prev = prev; | |||
prev->next = new; | |||
} | |||
/* | |||
* list_add - add a new entry | |||
* @new: new entry to be added | |||
* @head: list head to add it after | |||
* | |||
* Insert a new entry after the specified head. | |||
* This is good for implementing stacks. | |||
*/ | |||
static inline void list_add(struct list_head *new, struct list_head *head) | |||
{ | |||
__list_add(new, head, head->next); | |||
} | |||
/* | |||
* list_add_tail - add a new entry | |||
* @new: new entry to be added | |||
* @head: list head to add it before | |||
* | |||
* Insert a new entry before the specified head. | |||
* This is useful for implementing queues. | |||
*/ | |||
static inline void list_add_tail(struct list_head *new, struct list_head *head) | |||
{ | |||
__list_add(new, head->prev, head); | |||
} | |||
/* | |||
* Delete a list entry by making the prev/next entries | |||
* point to each other. | |||
* | |||
* This is only for internal list manipulation where we know | |||
* the prev/next entries already! | |||
*/ | |||
static inline void __list_del(struct list_head * prev, struct list_head * next) | |||
{ | |||
next->prev = prev; | |||
prev->next = next; | |||
} | |||
/* | |||
* list_del - deletes entry from list. | |||
* @entry: the element to delete from the list. | |||
* Note: list_empty on entry does not return true after this, the entry is | |||
* in an undefined state. | |||
*/ | |||
static inline void list_del(struct list_head *entry) | |||
{ | |||
__list_del(entry->prev, entry->next); | |||
entry->next = NULL; | |||
entry->prev = NULL; | |||
} | |||
/* | |||
* list_del_init - deletes entry from list and reinitialize it. | |||
* entry: the element to delete from the list. | |||
*/ | |||
static inline void list_del_init(struct list_head *entry) | |||
{ | |||
__list_del(entry->prev, entry->next); | |||
INIT_LIST_HEAD(entry); | |||
} | |||
/* | |||
* list_move - delete from one list and add as another's head | |||
* @list: the entry to move | |||
* @head: the head that will precede our entry | |||
*/ | |||
static inline void list_move(struct list_head *list, struct list_head *head) | |||
{ | |||
__list_del(list->prev, list->next); | |||
list_add(list, head); | |||
} | |||
/* | |||
* list_move_tail - delete from one list and add as another's tail | |||
* @list: the entry to move | |||
* @head: the head that will follow our entry | |||
*/ | |||
static inline void list_move_tail(struct list_head *list, | |||
struct list_head *head) | |||
{ | |||
__list_del(list->prev, list->next); | |||
list_add_tail(list, head); | |||
} | |||
/* | |||
* list_empty - tests whether a list is empty | |||
* @head: the list to test. | |||
*/ | |||
static inline int list_empty(const struct list_head *head) | |||
{ | |||
return head->next == head; | |||
} | |||
static inline void __list_splice(struct list_head *list, | |||
struct list_head *head) | |||
{ | |||
struct list_head *first = list->next; | |||
struct list_head *last = list->prev; | |||
struct list_head *at = head->next; | |||
first->prev = head; | |||
head->next = first; | |||
last->next = at; | |||
at->prev = last; | |||
} | |||
/* | |||
* list_splice - join two lists | |||
* @list: the new list to add. | |||
* @head: the place to add it in the first list. | |||
*/ | |||
static inline void list_splice(struct list_head *list, struct list_head *head) | |||
{ | |||
if (!list_empty(list)) | |||
__list_splice(list, head); | |||
} | |||
/* | |||
* list_splice_init - join two lists and reinitialise the emptied list. | |||
* @list: the new list to add. | |||
* @head: the place to add it in the first list. | |||
* | |||
* The list at @list is reinitialised | |||
*/ | |||
static inline void list_splice_init(struct list_head *list, | |||
struct list_head *head) | |||
{ | |||
if (!list_empty(list)) { | |||
__list_splice(list, head); | |||
INIT_LIST_HEAD(list); | |||
} | |||
} | |||
/* | |||
* list_entry - get the struct for this entry | |||
* @ptr: the &struct list_head pointer. | |||
* @type: the type of the struct this is embedded in. | |||
* @member: the name of the list_struct within the struct. | |||
*/ | |||
#define list_entry(ptr, type, member) \ | |||
container_of(ptr, type, member) | |||
/* | |||
* list_for_each - iterate over a list | |||
* @pos: the &struct list_head to use as a loop counter. | |||
* @head: the head for your list. | |||
*/ | |||
#define list_for_each(pos, head) \ | |||
for (pos = (head)->next; pos != (head); pos = pos->next) | |||
/* | |||
* list_for_each_prev - iterate over a list backwards | |||
* @pos: the &struct list_head to use as a loop counter. | |||
* @head: the head for your list. | |||
*/ | |||
#define list_for_each_prev(pos, head) \ | |||
for (pos = (head)->prev; pos != (head); pos = pos->prev) | |||
/* | |||
* list_for_each_safe - iterate over a list safe against removal of list entry | |||
* @pos: the &struct list_head to use as a loop counter. | |||
* @n: another &struct list_head to use as temporary storage | |||
* @head: the head for your list. | |||
*/ | |||
#define list_for_each_safe(pos, n, head) \ | |||
for (pos = (head)->next, n = pos->next; pos != (head); \ | |||
pos = n, n = pos->next) | |||
/* | |||
* list_for_each_entry - iterate over list of given type | |||
* @pos: the type * to use as a loop counter. | |||
* @head: the head for your list. | |||
* @member: the name of the list_struct within the struct. | |||
*/ | |||
#define list_for_each_entry(pos, head, member) \ | |||
for (pos = list_entry((head)->next, typeof(*pos), member); \ | |||
&pos->member != (head); \ | |||
pos = list_entry(pos->member.next, typeof(*pos), member)) | |||
/* | |||
* list_for_each_entry_reverse - iterate backwards over list of given type. | |||
* @pos: the type * to use as a loop counter. | |||
* @head: the head for your list. | |||
* @member: the name of the list_struct within the struct. | |||
*/ | |||
#define list_for_each_entry_reverse(pos, head, member) \ | |||
for (pos = list_entry((head)->prev, typeof(*pos), member); \ | |||
&pos->member != (head); \ | |||
pos = list_entry(pos->member.prev, typeof(*pos), member)) | |||
/* | |||
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry | |||
* @pos: the type * to use as a loop counter. | |||
* @n: another type * to use as temporary storage | |||
* @head: the head for your list. | |||
* @member: the name of the list_struct within the struct. | |||
*/ | |||
#define list_for_each_entry_safe(pos, n, head, member) \ | |||
for (pos = list_entry((head)->next, typeof(*pos), member), \ | |||
n = list_entry(pos->member.next, typeof(*pos), member); \ | |||
&pos->member != (head); \ | |||
pos = n, n = list_entry(n->member.next, typeof(*n), member)) | |||
/* Return pointer to first true entry, if any, or NULL. A macro | |||
required to allow inlining of cmpfn. */ | |||
#define LIST_FIND(head, cmpfn, type, args...) \ | |||
({ \ | |||
const struct list_head *__i, *__j = NULL; \ | |||
\ | |||
list_for_each(__i, (head)) \ | |||
if (cmpfn((const type)__i , ## args)) { \ | |||
__j = __i; \ | |||
break; \ | |||
} \ | |||
(type)__j; \ | |||
}) | |||
#endif /* _LIST_H_ */ |
@ -0,0 +1,103 @@ | |||
/*************************************************************************** | |||
* Copyright (C) 06/2006 by Olaf Rempel * | |||
* razzor@kopf-tisch.de * | |||
* * | |||
* 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. * | |||
***************************************************************************/ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <time.h> | |||
#include <stdarg.h> | |||
#include <errno.h> | |||
#include <string.h> | |||
#include "logging.h" | |||
#define BUFSIZE 8192 | |||
static FILE *log_fd = NULL; | |||
static char *buffer = NULL; | |||
void log_print(int prio, const char *fmt, ...) | |||
{ | |||
va_list az; | |||
int len; | |||
if (buffer == NULL) { | |||
buffer = malloc(BUFSIZE); | |||
if (buffer == NULL) { | |||
fprintf(stderr, "log_print: out of memory\nBailing out!\n"); | |||
exit(-1); | |||
} | |||
} | |||
va_start(az, fmt); | |||
len = vsnprintf(buffer, BUFSIZE, fmt, az); | |||
va_end(az); | |||
if (len < 0 || len >= BUFSIZE) { | |||
log_print(LOG_ERROR, "log_print: arguments too long"); | |||
errno = 0; | |||
return; | |||
} | |||
if (errno) { | |||
strncpy(buffer + len, ": ", BUFSIZE - len); | |||
len += 2; | |||
strncpy(buffer + len, strerror(errno), BUFSIZE - len); | |||
} | |||
if (log_fd) { | |||
char tbuf[64]; | |||
time_t tzgr; | |||
time(&tzgr); | |||
strftime(tbuf, sizeof(tbuf), "%b %d %H:%M:%S :", localtime(&tzgr)); | |||
fprintf(log_fd, "%s %s\n", tbuf, buffer); | |||
fflush(log_fd); | |||
} else { | |||
fprintf(stderr, "%s\n", buffer); | |||
} | |||
errno = 0; | |||
} | |||
static void log_close(void) | |||
{ | |||
if (buffer) | |||
free(buffer); | |||
fclose(log_fd); | |||
} | |||
int log_init(char *logfile) | |||
{ | |||
log_fd = fopen(logfile, "a"); | |||
if (log_fd == NULL) { | |||
log_print(LOG_ERROR, "log_open('%s'): %s", logfile); | |||
return 0; | |||
} | |||
if (atexit(log_close) != 0) { | |||
log_print(LOG_ERROR, "log_open(): atexit()"); | |||
return 0; | |||
} | |||
log_print(LOG_EVERYTIME, "=========================="); | |||
return 1; | |||
} |
@ -0,0 +1,16 @@ | |||
#ifndef _LOGGING_H_ | |||
#define _LOGGING_H_ | |||
#define LOG_DEBUG 5 | |||
#define LOG_INFO 4 | |||
#define LOG_NOTICE 3 | |||
#define LOG_WARN 2 | |||
#define LOG_ERROR 1 | |||
#define LOG_CRIT 0 | |||
#define LOG_EVERYTIME 0 | |||
int log_init(char *logfile); | |||
void log_print(int prio, const char *fmt, ... ); | |||
#endif /* _LOGGING_H_ */ |