You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/uid_avp_db/extra_attrs.c

541 lines
13 KiB

#include "extra_attrs.h"
#include "uid_avp_db.h"
#include "../../usr_avp.h"
#include "../../sr_module.h"
#include "../../ut.h"
#include "../../mem/mem.h"
#include "../../mem/shm_mem.h"
#include "../../lock_ops.h"
#include "../../script_cb.h"
#include "../../hashes.h"
#define set_str_val(f,s) (f).v.lstr=(s); \
(f).flags = 0;
#define set_str_val_ex(f,s) if ((s).len) { (f).v.lstr=(s); (f).flags = 0; } \
else (f).flags|=DB_NULL;
#define set_int_val(f,t) (f).v.int4=t;\
(f).flags=0;
#define get_str_val(rvi,dst) do{if(!(rvi.flags&DB_NULL)){dst=rvi.v.lstr;} else dst.len = 0;}while(0)
#define get_int_val(rvi,dst) do{if(!(rvi.flags&DB_NULL)){dst=rvi.v.int4;} else dst = 0;}while(0)
typedef struct _registered_table_t {
char *id;
char *table_name;
/* column names */
char *key_column;
char *name_column;
char *type_column;
char *value_column;
char *flags_column;
char *flag_name;
/* pregenerated queries */
db_cmd_t *query;
db_cmd_t *remove;
db_cmd_t *add;
avp_flags_t flag;
int group_mutex_idx;
struct _registered_table_t *next;
char buf[1]; /* buffer for strings allocated with the structure */
} registered_table_t;
static registered_table_t *tables = NULL;
registered_table_t *find_registered_table(const char *id)
{
registered_table_t *t = tables;
while (t) {
if (strcmp(t->id, id) == 0) return t;
t = t->next;
}
return NULL;
}
char *get_token(char *s, str *name, str *value)
{
enum { reading_name, reading_value } state = reading_name;
/* returns 'token' which has the form name[=value][,]
* replaces separators ,= by binary 0 to allow char* strings */
name->s = s;
name->len = 0;
value->s = NULL;
value->len = 0;
while (*s) {
switch (state) {
case reading_name:
switch (*s) {
case '=':
case ':':
state = reading_value;
value->s = s + 1;
*s = 0; /* replace separator */
break;
case ',':
*s = 0; /* replace separator */
return s + 1;
default: name->len++;
}
break;
case reading_value:
if (*s == ',') {
*s = 0; /* replace separator */
return s + 1;
}
else value->len++;
break;
}
s++;
}
return NULL; /* everything read */
}
static int cmp_s(str *a, str *b)
{
int i;
/* Warning: none string can be NULL! */
if (a->len != b->len) return -1;
if (!a->len) return 0; /* equal - empty */
for (i = 0; i < a->len; i++)
if (a->s[i] != b->s[i]) return 1;
return 0;
}
static inline void cpy(char *dst, str *s)
{
memcpy(dst, s->s, s->len);
dst[s->len] = 0;
}
/* adds new 'extra attribute group' (it adds new table for it) */
int declare_attr_group(modparam_t type, char* _param)
{
registered_table_t *rt;
str name, value;
str param;
char *p;
static str table = STR_STATIC_INIT("table");
static str flag = STR_STATIC_INIT("flag");
static str id = STR_STATIC_INIT("id");
static str key_column = STR_STATIC_INIT("key_column");
static str name_column = STR_STATIC_INIT("name_column");
static str value_column = STR_STATIC_INIT("value_column");
static str type_column = STR_STATIC_INIT("type_column");
static str flags_column = STR_STATIC_INIT("flags_column");
if (!(type & PARAM_STR)) {
ERR("Invalid parameter type\n");
return -1;
}
if (!_param) {
ERR("invalid parameter value\n");
return -1;
}
param = *((str*)_param);
DBG("group def: %.*s\n", param.len, param.s);
rt = pkg_malloc(param.len + sizeof(*rt) + 1);
if (!rt) {
ERR("can't allocate PKG memory\n");
return -1;
}
memset(rt, 0, sizeof(*rt));
cpy(rt->buf, &param);
/* default column names */
rt->key_column = "id";
rt->name_column = "name";
rt->type_column = "type";
rt->value_column = "value";
rt->flags_column = "flags";
/* parse the string */
p = rt->buf;
do {
p = get_token(p, &name, &value);
if (cmp_s(&name, &table) == 0) rt->table_name = value.s;
else if (cmp_s(&name, &flag) == 0) rt->flag_name = value.s;
else if (cmp_s(&name, &id) == 0) rt->id = value.s;
else if (cmp_s(&name, &key_column) == 0) rt->key_column = value.s;
else if (cmp_s(&name, &name_column) == 0) rt->name_column = value.s;
else if (cmp_s(&name, &type_column) == 0) rt->type_column = value.s;
else if (cmp_s(&name, &value_column) == 0) rt->value_column = value.s;
else if (cmp_s(&name, &flags_column) == 0) rt->flags_column = value.s;
} while (p);
if ((!rt->id) || (!rt->flag_name)) {
ERR("at least attribute group ID and flags must ve given\n");
return -1;
}
/* insert new element into registered tables */
rt->flag = register_avpflag(rt->flag_name);
if (!rt->flag) {
ERR("can't register AVP flag: %s\n", rt->flag_name);
pkg_free(rt);
return -1;
}
/* append to the beggining - it doesn't depend on the order */
rt->next = tables;
tables = rt;
return 0;
}
/** Initialize all queries needed by 'extra attributes' for given table.
* Variable default_res holds columns which can used by read_attrs. */
static int init_queries(db_ctx_t *ctx, registered_table_t *t)
{
db_fld_t match[] = {
{ .name = t->key_column, .type = DB_STR, .op = DB_EQ },
{ .name = NULL }
};
db_fld_t query_res[] = {
/* Warning: be careful here - the query must have the same result
* as query for user/uri AVPs to be readable by read_attrs */
{ .name = t->name_column, .type = DB_STR, .op = DB_EQ },
{ .name = t->type_column, .type = DB_INT, .op = DB_EQ },
{ .name = t->value_column, .type = DB_STR, .op = DB_EQ },
{ .name = t->flags_column, .type = DB_BITMAP, .op = DB_EQ },
{ .name = NULL }
};
db_fld_t add_values[] = {
{ .name = t->key_column, .type = DB_STR, .op = DB_EQ },
{ .name = t->name_column, .type = DB_STR, .op = DB_EQ },
{ .name = t->type_column, .type = DB_INT, .op = DB_EQ },
{ .name = t->value_column, .type = DB_STR, .op = DB_EQ },
{ .name = t->flags_column, .type = DB_BITMAP, .op = DB_EQ },
{ .name = NULL }
};
t->query = db_cmd(DB_GET, ctx, t->table_name, query_res, match, NULL);
t->remove = db_cmd(DB_DEL, ctx, t->table_name, NULL, match, NULL);
t->add = db_cmd(DB_PUT, ctx, t->table_name, NULL, NULL, add_values);
if (t->query && t->remove && t->add) return 0;
else return -1; /* not all queries were initialized */
}
int init_extra_avp_queries(db_ctx_t *ctx)
{
registered_table_t *t = tables;
while (t) {
if (init_queries(ctx, t) < 0) return -1;
t = t->next;
}
return 0;
}
static void get_avp_value_ex(avp_t *avp, str *dst, int *type) {
avp_value_t val;
/* Warning! it uses static buffer from int2str !!! */
get_avp_val(avp, &val);
if (avp->flags & AVP_VAL_STR) {
*dst = val.s;
*type = AVP_VAL_STR;
}
else { /* probably (!) number */
dst->s = int2str(val.n, &dst->len);
*type = 0;
}
}
/** Saves attribute into DB with given ID. The ID must not be NULL. The
* use_table must be called outside of this function (from interface
* functions) */
static inline int save_avp(registered_table_t *t, avp_t *avp, str *id) /* id MUST NOT be NULL */
{
str *s, v;
int type;
static str empty = STR_STATIC_INIT("");
set_str_val(t->add->vals[0], *id);
s = get_avp_name(avp);
if (!s) s = &empty;
set_str_val(t->add->vals[1], *s);
get_avp_value_ex(avp, &v, &type);
set_int_val(t->add->vals[2], type);
set_str_val(t->add->vals[3], v);
set_int_val(t->add->vals[4], avp->flags & (AVP_CLASS_ALL | AVP_TRACK_ALL | AVP_NAME_STR | AVP_VAL_STR));
if (db_exec(NULL, t->add) < 0) {
ERR("Can't insert record into DB\n");
return -1;
}
return 0;
}
/** Loads all attributes with given ID. The ID must not be NULL. */
static int read_avps(db_res_t *res, avp_flags_t flag) /* id must not be NULL */
{
db_rec_t *row;
row = db_first(res);
while (row) {
int flags = 0;
int type = 0;
str value = STR_NULL;
avp_value_t val;
avp_name_t name;
get_str_val(row->fld[0], name.s);
get_int_val(row->fld[1], type);
get_str_val(row->fld[2], value);
get_int_val(row->fld[3], flags);
if (flags & SRDB_LOAD_SER) {
if (type == AVP_VAL_STR) val.s = value;
else str2int(&value, (unsigned int *)&val.n); /* FIXME */
flags |= flag;
/* FIXME: avps probably should be removed before they are added,
* but this should be done in add_avp and not here! */
add_avp(flags, name, val);
}
row = db_next(res);
}
return 0;
}
/** Removes all attributes with given ID. The ID must not be NULL. */
static inline int remove_all_avps(registered_table_t *t, str *id)
{
set_str_val(t->remove->match[0], *id);
if (db_exec(NULL, t->remove) < 0) {
ERR("can't remove attrs\n");
return -1;
}
return 0;
}
/* ----- interface functions ----- */
int load_extra_attrs(struct sip_msg* msg, char* _table, char* _id)
{
registered_table_t *t;
db_res_t *res = NULL;
str id;
t = (registered_table_t *)_table;
if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
ERR("invalid parameter value\n");
return -1;
}
set_str_val(t->query->match[0], id);
if (db_exec(&res, t->query) < 0) {
ERR("DB query failed\n");
return -1;
}
if (res) {
read_avps(res, t->flag);
db_res_free(res);
}
return 1;
}
int remove_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
{
str id;
registered_table_t *t;
t = (registered_table_t *)_table;
if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
ERR("invalid parameter value\n");
return -1;
}
remove_all_avps(t, &id);
return 1;
}
int save_extra_attrs(struct sip_msg* msg, char* _table, char *_id)
{
str id;
int i;
struct usr_avp *avp;
static unsigned short lists[] = {
AVP_CLASS_USER | AVP_TRACK_FROM,
AVP_CLASS_USER | AVP_TRACK_TO,
AVP_CLASS_URI | AVP_TRACK_FROM,
AVP_CLASS_URI | AVP_TRACK_TO,
0
};
registered_table_t *t;
t = (registered_table_t *)_table;
if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
ERR("invalid parameter value\n");
return -1;
}
/* delete all attrs under given id */
remove_all_avps(t, &id);
/* save all attrs flagged with flag under id */
for (i = 0; lists[i]; i++) {
for (avp = get_avp_list(lists[i]); avp; avp = avp->next) {
if ((avp->flags & t->flag) != 0) save_avp(t, avp, &id);
}
}
return 1;
}
int extra_attrs_fixup(void** param, int param_no)
{
registered_table_t *t;
switch (param_no) {
case 1: /* try to find registered table, error if not found */
t = find_registered_table(*param);
if (!t) {
ERR("can't find attribute group with id: %s\n", (char*)*param);
return -1;
}
*param = (void*)t;
break;
case 2: return fixup_var_str_2(param, param_no);
}
return 0;
}
/******* locking *******/
#define LOCK_CNT 32
gen_lock_t *locks = NULL; /* set of mutexes allocated in shared memory */
int lock_counters[LOCK_CNT]; /* set of counters (each proces has its own counters) */
static int avpdb_post_script_cb(struct sip_msg *msg, unsigned int flags, void *param) {
int i;
for (i=0; i<LOCK_CNT; i++) {
if (lock_counters[i] > 0) {
if (auto_unlock) {
DEBUG("post script auto unlock extra attrs <%d>\n", i);
lock_release(&locks[i]);
lock_counters[i]=0;
} else {
BUG("script writer didn't unlock extra attrs !!!\n");
return 1;
}
}
}
return 1;
}
int init_extra_avp_locks()
{
int i;
registered_table_t *t = tables;
/* zero all 'lock counters' */
memset(lock_counters, 0, sizeof(lock_counters));
locks = shm_malloc(sizeof(gen_lock_t) * LOCK_CNT);
if (!locks) {
ERR("can't allocate mutexes\n");
return -1;
}
for (i = 0; i < LOCK_CNT; i++) {
lock_init(&locks[i]);
}
/* initializes mutexes for extra AVPs */
i = 0;
while (t) {
t->group_mutex_idx = get_hash1_raw(t->table_name, strlen(t->table_name)) % LOCK_CNT;
t = t->next;
}
register_script_cb(avpdb_post_script_cb, REQUEST_CB | ONREPLY_CB | POST_SCRIPT_CB, 0);
return 0;
}
static inline int find_mutex(registered_table_t *t, str *id)
{
/* hash(table_name) + hash(id) */
return ((t->group_mutex_idx + get_hash1_raw(id->s, id->len)) % LOCK_CNT);
}
int lock_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
{
str id;
registered_table_t *t;
int mutex_idx;
t = (registered_table_t *)_table;
if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
ERR("invalid parameter value\n");
return -1;
}
/* find right mutex according to id/table */
mutex_idx = find_mutex(t, &id);
if (lock_counters[mutex_idx] > 0) {
/* mutex is already locked by this process */
lock_counters[mutex_idx]++;
}
else {
/* the mutex was not locked => lock it and set counter */
lock_get(&locks[mutex_idx]);
lock_counters[mutex_idx] = 1;
}
return 1;
}
int unlock_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
{
str id;
registered_table_t *t;
int mutex_idx;
t = (registered_table_t *)_table;
if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
ERR("invalid parameter value\n");
return -1;
}
/* find right mutex according to id/table */
mutex_idx = find_mutex(t, &id);
if (lock_counters[mutex_idx] > 1) {
/* mutex is locked more times by this process */
lock_counters[mutex_idx]--;
}
else if (lock_counters[mutex_idx] == 1) {
/* the mutex is locked once => unlock it and reset counter */
lock_release(&locks[mutex_idx]);
lock_counters[mutex_idx] = 0;
}
else {
BUG("trying to unlock without lock group=\"%s\" id=\"%.*s\"\n", t->id, id.len, id.s);
}
return 1;
}