#include "common.h"
#include "hash.h"
#include "array.h"
#include "str.h"
#include "strescape.h"
#include "ostream.h"
#include "connect-limit.h"
struct ident_pid {
const char *ident;
pid_t pid;
ARRAY_DEFINE(times, time_t);
};
struct connect_limit {
struct hash_table *ident_hash;
struct hash_table *ident_pid_hash;
};
static unsigned int ident_pid_hash(const void *p)
{
const struct ident_pid *i = p;
return str_hash(i->ident) ^ i->pid;
}
static int ident_pid_cmp(const void *p1, const void *p2)
{
const struct ident_pid *i1 = p1, *i2 = p2;
if (i1->pid < i2->pid)
return -1;
else if (i1->pid > i2->pid)
return 1;
else
return strcmp(i1->ident, i2->ident);
}
struct connect_limit *connect_limit_init(void)
{
struct connect_limit *limit;
limit = i_new(struct connect_limit, 1);
limit->ident_hash =
hash_table_create(default_pool, default_pool, 0,
str_hash, (hash_cmp_callback_t *)strcmp);
limit->ident_pid_hash =
hash_table_create(default_pool, default_pool, 0,
ident_pid_hash, ident_pid_cmp);
return limit;
}
void connect_limit_deinit(struct connect_limit **_limit)
{
struct connect_limit *limit = *_limit;
*_limit = NULL;
hash_table_destroy(&limit->ident_hash);
hash_table_destroy(&limit->ident_pid_hash);
i_free(limit);
}
unsigned int connect_limit_lookup(struct connect_limit *limit,
const char *ident)
{
void *value;
value = hash_table_lookup(limit->ident_hash, ident);
if (value == NULL)
return 0;
return POINTER_CAST_TO(value, unsigned int);
}
void connect_limit_connect(struct connect_limit *limit, pid_t pid,
const char *ident)
{
struct ident_pid *i, lookup_i;
void *key, *value;
if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value)) {
key = i_strdup(ident);
value = POINTER_CAST(1);
hash_table_insert(limit->ident_hash, key, value);
} else {
value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1);
hash_table_update(limit->ident_hash, key, value);
}
lookup_i.ident = ident;
lookup_i.pid = pid;
i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
if (i == NULL) {
i = i_new(struct ident_pid, 1);
i->ident = key;
i->pid = pid;
i_array_init(&i->times, 5);
array_append(&i->times, &ioloop_time, 1);
hash_table_insert(limit->ident_pid_hash, i, i);
} else {
array_append(&i->times, &ioloop_time, 1);
}
}
static void
connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident)
{
void *key, *value;
unsigned int new_refcount;
if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value))
i_panic("connect limit hash tables are inconsistent");
new_refcount = POINTER_CAST_TO(value, unsigned int) - 1;
if (new_refcount > 0) {
value = POINTER_CAST(new_refcount);
hash_table_update(limit->ident_hash, key, value);
} else {
hash_table_remove(limit->ident_hash, key);
i_free(key);
}
}
void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
const char *ident)
{
struct ident_pid *i, lookup_i;
lookup_i.ident = ident;
lookup_i.pid = pid;
i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
if (i == NULL) {
i_error("connect limit: disconnection for unknown "
"pid %s + ident %s", dec2str(pid), ident);
return;
}
array_delete(&i->times, 0, 1);
if (array_count(&i->times) == 0) {
hash_table_remove(limit->ident_pid_hash, i);
array_free(&i->times);
i_free(i);
}
connect_limit_ident_hash_unref(limit, ident);
}
void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid)
{
struct hash_iterate_context *iter;
struct ident_pid *i;
void *key, *value;
const time_t *tp;
iter = hash_table_iterate_init(limit->ident_pid_hash);
while (hash_table_iterate(iter, &key, &value)) {
i = key;
if (i->pid == pid) {
hash_table_remove(limit->ident_pid_hash, i);
array_foreach(&i->times, tp) {
connect_limit_ident_hash_unref(limit, i->ident);
}
array_free(&i->times);
i_free(i);
}
}
hash_table_iterate_deinit(&iter);
}
void connect_limit_dump(struct connect_limit *limit, struct ostream *output,
bool dump_elapsed)
{
struct hash_iterate_context *iter;
void *key, *value;
string_t *str = t_str_new(256);
iter = hash_table_iterate_init(limit->ident_pid_hash);
while (hash_table_iterate(iter, &key, &value)) {
struct ident_pid *i = key;
str_truncate(str, 0);
str_tabescape_write(str, i->ident);
str_printfa(str, "\t%ld\t%u", (long)i->pid,
array_count(&i->times));
if (dump_elapsed) {
time_t earliest = ioloop_time;
const time_t *tp;
array_foreach(&i->times, tp) {
if (earliest > *tp)
earliest = *tp;
}
str_printfa(str, "\t%lu",
(unsigned long) (ioloop_time - earliest));
}
str_append_c(str, '\n');
if (o_stream_send(output, str_data(str), str_len(str)) < 0)
break;
}
hash_table_iterate_deinit(&iter);
(void)o_stream_send(output, "\n", 1);
}