From 2db093ea7a58b206919480c7f7a938aa57f9782e Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Sat, 24 Mar 2007 19:49:30 +0100 Subject: [PATCH] initial commit --- .gitignore | 4 + Makefile | 21 ++++ configfile.c | 200 ++++++++++++++++++++++++++++++++++++++ configfile.h | 16 +++ conntrack.c | 114 ++++++++++++++++++++++ conntrack.h | 12 +++ ctstats.c | 88 +++++++++++++++++ ctstats.conf | 10 ++ database.c | 52 ++++++++++ database.h | 8 ++ hashtable.c | 126 ++++++++++++++++++++++++ hashtable.h | 35 +++++++ list.h | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++ logging.c | 103 ++++++++++++++++++++ logging.h | 16 +++ 15 files changed, 1073 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 configfile.c create mode 100644 configfile.h create mode 100644 conntrack.c create mode 100644 conntrack.h create mode 100644 ctstats.c create mode 100644 ctstats.conf create mode 100644 database.c create mode 100644 database.h create mode 100644 hashtable.c create mode 100644 hashtable.h create mode 100644 list.h create mode 100644 logging.c create mode 100644 logging.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1be1ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.d +*.o +ctstats +ctstats.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4c2557 --- /dev/null +++ b/Makefile @@ -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) diff --git a/configfile.c b/configfile.c new file mode 100644 index 0000000..73ff3ae --- /dev/null +++ b/configfile.c @@ -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 +#include +#include +#include + +#include +#include +#include +#include + +#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; +} diff --git a/configfile.h b/configfile.h new file mode 100644 index 0000000..6528459 --- /dev/null +++ b/configfile.h @@ -0,0 +1,16 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include + +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_ */ diff --git a/conntrack.c b/conntrack.c new file mode 100644 index 0000000..1543876 --- /dev/null +++ b/conntrack.c @@ -0,0 +1,114 @@ +#include +#include +#include + +#include +#include + +#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; +} diff --git a/conntrack.h b/conntrack.h new file mode 100644 index 0000000..8396412 --- /dev/null +++ b/conntrack.h @@ -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_ diff --git a/ctstats.c b/ctstats.c new file mode 100644 index 0000000..c12b2f4 --- /dev/null +++ b/ctstats.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#include +#include + +#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; +} diff --git a/ctstats.conf b/ctstats.conf new file mode 100644 index 0000000..5789f13 --- /dev/null +++ b/ctstats.conf @@ -0,0 +1,10 @@ +[global] +intervall 30 +hashsize 127 +logfile ctstats.log + +[mysql] +hostname localhost +username ctstats +password ctstats +database ctstats diff --git a/database.c b/database.c new file mode 100644 index 0000000..a94baf1 --- /dev/null +++ b/database.c @@ -0,0 +1,52 @@ +#include +#include +#include + +#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; +} diff --git a/database.h b/database.h new file mode 100644 index 0000000..970e524 --- /dev/null +++ b/database.h @@ -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_ diff --git a/hashtable.c b/hashtable.c new file mode 100644 index 0000000..353ee16 --- /dev/null +++ b/hashtable.c @@ -0,0 +1,126 @@ +#include +#include +#include + +#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); +} diff --git a/hashtable.h b/hashtable.h new file mode 100644 index 0000000..06ddbc2 --- /dev/null +++ b/hashtable.h @@ -0,0 +1,35 @@ +#ifndef _HASHTABLE_H_ +#define _HASHTABLE_H_ + +#include +#include + +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_ */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..61f8d93 --- /dev/null +++ b/list.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 + */ +#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_ */ diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..d4fd706 --- /dev/null +++ b/logging.c @@ -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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..c6e32c0 --- /dev/null +++ b/logging.h @@ -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_ */