mISDNuser/capi20/capi_obj.c

624 lines
15 KiB
C

/*
* capi_obj.c
*
* Author Karsten Keil <kkeil@linux-pingi.de>
*
* Copyright 2012 by Karsten Keil <kkeil@linux-pingi.de>
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
* version 2.1 as published by the Free Software Foundation.
*
* This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
*
*/
#include "m_capi.h"
#include "mc_buffer.h"
#include "../lib/include/helper.h"
static pthread_rwlock_t danglinglock;
static struct mCAPIobj *danglinglist;
static pthread_mutex_t uniqLock = PTHREAD_MUTEX_INITIALIZER;
static unsigned int uniqID = 1;
static pthread_mutex_t rootLock = PTHREAD_MUTEX_INITIALIZER;
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
#define cobj_dbg(fmt, ...) do {\
if (file && (MIDEBUG_CAPIOBJ & mI_debug_mask))\
mi_printf(file, lineno, __func__, MISDN_LIBDEBUG_DEBUG, fmt, ##__VA_ARGS__);\
} while (0)
#define coref_dbg(fmt, ...) do {\
if (file && (MIDEBUG_CAPIOBJ & mI_debug_mask))\
mi_printf(file, lineno, __func__, MISDN_LIBDEBUG_DEBUG, fmt, ##__VA_ARGS__);\
} while (0)
#define cobj_err(fmt, ...) mi_printf(file, lineno, __func__, MISDN_LIBDEBUG_ERROR, fmt, ##__VA_ARGS__)
#define cobj_warn(fmt, ...) mi_printf(file, lineno, __func__, MISDN_LIBDEBUG_WARN, fmt, ##__VA_ARGS__)
#else
#define cobj_dbg(fmt, ...) dprint(MIDEBUG_CAPIOBJ, fmt, ##__VA_ARGS__)
#define coref_dbg(fmt, ...) do {} while (0)
#define cobj_err(fmt, ...) eprint(fmt, ##__VA_ARGS__)
#define cobj_warn(fmt, ...) wprint(fmt, ##__VA_ARGS__)
#endif
static int ShutdownNow = 0;
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
#define cobj_free(c) __cobj_free(c, __FILE__, __LINE__)
static void __cobj_free(struct mCAPIobj *, const char *, int);
#else
static void cobj_free(struct mCAPIobj *);
#endif
#ifdef MISDN_CAPIOBJ_NO_FREE
static struct mCAPIobj *freelist;
static int freelistCnt = 0;
#endif
void free_capiobject(struct mCAPIobj *co, void *ptr)
{
struct mCAPIobj *c;
if (ShutdownNow) {
free(ptr);
return;
}
if (co->freed) {
eprint("%s: uid=%i double free\n", CAPIobjIDstr(co), co->uid);
return;
}
co->freed = 1;
#ifdef MISDN_CAPIOBJ_NO_FREE
co->freep = ptr;
#endif
if (co->unlisted) {
pthread_rwlock_wrlock(&danglinglock);
c = danglinglist;
while(c) {
if (c == co) {
danglinglist = co->nextD;
break;
}
if (c->nextD == co) {
c->nextD = co->nextD;
break;
}
c = c->nextD;
}
if (!c) {
eprint("%s: not in dangling list corrupted ?\n", CAPIobjIDstr(co));
}
#ifdef MISDN_CAPIOBJ_NO_FREE
co->nextD = freelist;
freelist = co;
freelistCnt++;
#endif
pthread_rwlock_unlock(&danglinglock);
} else {
iprint("%s: not unlisted\n", CAPIobjIDstr(co));
#ifdef MISDN_CAPIOBJ_NO_FREE
pthread_rwlock_wrlock(&danglinglock);
co->nextD = freelist;
freelist = co;
freelistCnt++;
pthread_rwlock_unlock(&danglinglock);
#endif
}
#ifndef MISDN_CAPIOBJ_NO_FREE
free(ptr);
#endif
}
void CAPIobj_init(void)
{
pthread_rwlock_init(&danglinglock, NULL);
danglinglist = NULL;
#ifdef MISDN_CAPIOBJ_NO_FREE
freelist = NULL;
#endif
}
void CAPIobj_exit(void)
{
struct mCAPIobj *co, *cn;
pthread_rwlock_wrlock(&danglinglock);
ShutdownNow = 1;
co = danglinglist;
while (co) {
cn = co->nextD;
eprint("%s: uid=%i refcnt %d in dangling list - freeing now\n", CAPIobjIDstr(co), co->uid, co->refcnt);
co->cleaned = 1;
cobj_free(co);
co = cn;
}
#ifdef MISDN_CAPIOBJ_NO_FREE
co = freelist;
while (co) {
cn = co->nextD;
eprint("%s: uid=%d refcnt %d in free list - freeing now\n", CAPIobjIDstr(co), co->uid, co->refcnt);
free(co->freep);
co = cn;
}
#endif
pthread_rwlock_unlock(&danglinglock);
}
void dump_cobjects(void)
{
struct mCAPIobj *co;
if (pthread_rwlock_tryrdlock(&danglinglock)) {
wprint("Cannot read lock dangling list for dumping\n");
return;
}
co = danglinglist;
iprint("Next unique ID=%i\n", uniqID);
if (!co)
iprint("No items in dangling list\n");
while (co) {
iprint("%s: uid=%i refcnt %d in dangling list\n", CAPIobjIDstr(co), co->uid, co->refcnt);
co = co->nextD;
}
pthread_rwlock_unlock(&danglinglock);
#ifdef MISDN_CAPIOBJ_NO_FREE
iprint("%d items in freelist\n", freelistCnt);
#endif
}
#ifdef MISDN_CAPIOBJ_NO_FREE
void dump_cobjects_free(void)
{
struct mCAPIobj *co;
if (pthread_rwlock_tryrdlock(&danglinglock)) {
wprint("Cannot read lock dangling list for dumping\n");
return;
}
co = freelist;
if (!co)
iprint("No items in free list\n");
while (co) {
iprint("%s: uid=%i refcnt %d in free list\n", CAPIobjIDstr(co), co->uid, co->refcnt);
co = co->nextD;
}
pthread_rwlock_unlock(&danglinglock);
}
#endif
void cobj_unlisted(struct mCAPIobj *co)
{
if (ShutdownNow)
return;
if (co->unlisted) {
eprint("%s: refcnt %d double unlist\n", CAPIobjIDstr(co), co->refcnt);
return;
}
co->unlisted = 1;
pthread_rwlock_wrlock(&danglinglock);
co->nextD = danglinglist;
danglinglist = co;
pthread_rwlock_unlock(&danglinglock);
}
const char *__eCAPIobjtype_s[] = {
"None",
"Root",
"Application",
"lController",
"PLCI",
"lPLCI",
"NCCI",
"Fax",
"Undef",
"Null object"
};
const char *CAPIobjt2str(struct mCAPIobj *co)
{
unsigned int i;
if (co) {
i = co->type;
if (i > Cot_Last)
i = 1 + Cot_Last;
} else
i = 2 + Cot_Last;
return __eCAPIobjtype_s[i];
}
static const char *__CAPIobjt2str(enum eCAPIobjtype cot)
{
unsigned int i;
i = cot;
if (i > Cot_Last)
i = 1 + Cot_Last;
return __eCAPIobjtype_s[i];
}
const char *CAPIobjIDstr(struct mCAPIobj *co)
{
int used;
char stat[8], *p, *s;
if (co) {
s = co->idstr;
if (co->type > Cot_Last) {
used = snprintf(s, CAPIobj_IDSIZE, "%s%d id:%x id2:%x", CAPIobjt2str(co), co->type, co->id, co->id2);
} else {
switch (co->type) {
case Cot_None:
used = snprintf(s, CAPIobj_IDSIZE, "NoneObj id:%x-%x", co->id, co->id2);
break;
case Cot_Root:
used = snprintf(s, CAPIobj_IDSIZE, "RootObj id:%x-%x", co->id, co->id2);
break;
case Cot_Application:
used = snprintf(s, CAPIobj_IDSIZE, "Appl-id:%d", co->id2);
break;
case Cot_lController:
used = snprintf(s, CAPIobj_IDSIZE, "LContr-%02d Appl-%03d", co->id, co->id2);
break;
case Cot_PLCI:
used = snprintf(s, CAPIobj_IDSIZE, " PLCI%04x PID:%08x", co->id, co->id2);
break;
case Cot_lPLCI:
used = snprintf(s, CAPIobj_IDSIZE, " LPLCI%04x Appl-%03d", co->id, co->id2);
break;
case Cot_NCCI:
used = snprintf(s, CAPIobj_IDSIZE, "NCCI%06x Appl-%03d", co->id, co->id2);
break;
case Cot_FAX:
used = snprintf(s, CAPIobj_IDSIZE, " FAX%06x B%d", co->id, co->id2);
break;
}
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
used += snprintf(&s[used], CAPIobj_IDSIZE - used, " *%d", co->refcnt);
#endif
p = stat;
if (co->cleaned)
*p++ = 'C';
if (co->unlisted)
*p++ = co->parent ? 'u' : 'U';
if (co->freeing)
*p++ = (co->freed) ? 'F' : 'f';
*p = 0;
if (p != stat) {
used += snprintf(&s[used], CAPIobj_IDSIZE - used, " %s", stat);
if (used >= CAPIobj_IDSIZE)
wprint("Overflow %d >= %d\n", used, CAPIobj_IDSIZE);
}
}
} else {
s = (char *)CAPIobjt2str(NULL);
}
return s;
}
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
#undef cobj_free
#define cobj_free(c) __cobj_free(c, file, lineno)
static void __cobj_free(struct mCAPIobj *co, const char *file, int lineno)
{
if (!file)
file = "(none)";
#else
static void cobj_free(struct mCAPIobj *co)
{
#endif
if (co->freeing) {
cobj_err("%s: uid=%i refcnt %d called again -- double free attempt\n", CAPIobjIDstr(co), co->uid, co->refcnt);
return;
}
co->freeing = 1;
cobj_dbg("%s: uid=%i freeing now\n", CAPIobjIDstr(co), co->uid);
/* sanity check */
if (co->itemcnt || co->listhead) {
cobj_err("%s: Still %d items in %slist\n", CAPIobjIDstr(co), co->itemcnt, co->listhead ? "" : "NULL ");
}
switch(co->type) {
case Cot_None:
case Cot_Root:
/* we never free these */
wprint("%s: try to free\n", CAPIobjIDstr(co));
break;
case Cot_Application:
Free_Application(co);
break;
case Cot_lController:
Free_lController(co);
break;
case Cot_PLCI:
Free_PLCI(co);
break;
case Cot_lPLCI:
Free_lPLCI(co);
break;
case Cot_NCCI:
Free_NCCI(co);
break;
case Cot_FAX:
Free_Faxobject(co);
break;
}
};
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
struct mCAPIobj *__get_cobj(struct mCAPIobj *co, const char *file, int lineno)
#else
struct mCAPIobj *get_cobj(struct mCAPIobj *co)
#endif
{
struct mCAPIobj *p;
if (co) {
p = co->parent;
if (p) {
pthread_rwlock_wrlock(&p->lock);
} else {
if (co->type != Cot_Root) { /* has no parent */
cobj_err("%s: parent not assigned\n", CAPIobjIDstr(co));
return NULL;
}
pthread_mutex_lock(&rootLock);
}
if (co->cleaned) {
cobj_warn("%s: pending free detected - do not get object\n", CAPIobjIDstr(co));
co = NULL;
} else {
if (co->freeing)
cobj_err("Currently freeing %s refcnt: %d\n", CAPIobjIDstr(co), co->refcnt);
co->refcnt++;
coref_dbg("%s\n", CAPIobjIDstr(co));
}
if (p)
pthread_rwlock_unlock(&p->lock);
else
pthread_mutex_unlock(&rootLock);
} else
coref_dbg("No CAPIobj\n");
return co;
}
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
int __put_cobj(struct mCAPIobj *co, const char *file, int lineno)
#else
int put_cobj(struct mCAPIobj *co)
#endif
{
struct mCAPIobj *p;
int ret = -ENODEV;
if (co) {
if (co->freeing)
cobj_err("Currently freeing %s refcnt: %d\n", CAPIobjIDstr(co), co->refcnt);
p = co->parent;
if (p) {
pthread_rwlock_wrlock(&p->lock);
coref_dbg("%s\n", CAPIobjIDstr(co));
co->refcnt--;
if (co->refcnt < 0) {
cobj_err("%s: refcnt reached %d - list items:%d\n", CAPIobjIDstr(co), co->refcnt, co->itemcnt);
}
ret = co->refcnt;
if (co->cleaned && co->refcnt <= 0) { /* last ref */
pthread_rwlock_unlock(&p->lock);
/* OK not perfect but scheduling here should prevent us from access of freed memory, if a other thread
still pending on the lock - a cleaned object is not longer listed and do not return success in get_obj()
so getting a new reference should not happen after this point */
sched_yield();
cobj_free(co);
} else {
pthread_rwlock_unlock(&p->lock);
if (co->cleaned) {
switch (co->type) {
case Cot_Application: /* Application has a special put handler */
Put_Application_cleaned(co);
break;
default:
break;
}
}
}
} else {
if (co->type == Cot_Root) { /* has no parent */
pthread_mutex_lock(&rootLock);
coref_dbg("%s\n", CAPIobjIDstr(co));
co->refcnt--;
ret = co->refcnt;
pthread_mutex_unlock(&rootLock);
} else
cobj_warn("%s: parent not assigned\n", CAPIobjIDstr(co));
}
} else
cobj_dbg("No CAPIobj\n");
return ret;
}
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
struct mCAPIobj *__get_next_cobj(struct mCAPIobj *parent, struct mCAPIobj *cur, const char *file, int lineno)
#else
struct mCAPIobj *get_next_cobj(struct mCAPIobj *parent, struct mCAPIobj *cur)
#endif
{
struct mCAPIobj *next;
if (parent) {
pthread_rwlock_wrlock(&parent->lock);
if (cur)
next = cur->next;
else
next = parent->listhead;
if (next) {
if (next->cleaned) {
cobj_warn("%s: pending free detected - do not get next\n", CAPIobjIDstr(next));
next = NULL;
} else {
next->refcnt++;
coref_dbg("%s: next %s\n", CAPIobjIDstr(cur), CAPIobjIDstr(next));
}
}
pthread_rwlock_unlock(&parent->lock);
if (parent->freeing)
cobj_err("Currently freeing %s refcnt: %d Next: %s\n", CAPIobjIDstr(parent), parent->refcnt, CAPIobjIDstr(next));
} else
next = NULL;
if (cur) {
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
if (next) {
__put_cobj(cur, NULL, lineno);
} else {
__put_cobj(cur, file, lineno);
}
#else
put_cobj(cur);
#endif
}
return next;
}
static unsigned int get_uniqID(void)
{
unsigned int uid;
pthread_mutex_lock(&uniqLock);
uid = uniqID++;
pthread_mutex_unlock(&uniqLock);
return uid;
}
int init_cobj(struct mCAPIobj *co, struct mCAPIobj *parent, enum eCAPIobjtype cot, unsigned int id, unsigned int id2)
{
int ret;
ret = pthread_rwlock_init(&co->lock, NULL);
co->type = cot;
co->uid = get_uniqID();
if (parent)
co->parent = get_cobj(parent);
else
co->parent = NULL;
co->id = id;
co->id2 = id2;
co->refcnt = 1;
dprint(MIDEBUG_CAPIOBJ, "%s: uid=%i initialized\n", CAPIobjIDstr(co), co->uid);
return ret;
}
int init_cobj_registered(struct mCAPIobj *co, struct mCAPIobj *parent, enum eCAPIobjtype cot, unsigned int idmask)
{
unsigned int id, m = 0, lastid;
int ret = 0;
pthread_rwlock_wrlock(&parent->lock);
co->type = cot;
co->uid = get_uniqID();
id = 0;
if (parent->listhead)
lastid = parent->listhead->id;
else
lastid = 0;
if (cot == Cot_PLCI) {
id = NextFreePLCI(parent);
if (!id)
ret = -EBUSY;
} else {
if (idmask) {
for (m = 0xff; m < 0xff000000; m <<= 8) {
if ((m & idmask) == m) {
id &= idmask;
} else {
id += (lastid & m) + (idmask & m);
if ((id & m) == m) {
ret = -EBUSY; /* overflow */
break;
}
idmask &= ~m;
}
}
}
}
if (ret) {
pthread_rwlock_unlock(&parent->lock);
wprint("%s: uid=%i new id overflow lastid %x id %x test mask %x\n", CAPIobjt2str(co), co->uid, lastid, id, m);
return ret;
}
co->id = id | (idmask & parent->id);
ret = pthread_rwlock_init(&co->lock, NULL);
if (ret == 0) {
co->next = parent->listhead;
parent->listhead = co;
parent->itemcnt++;
co->refcnt = 2;
} else {
eprint("%s: error %s on init lock\n", CAPIobjt2str(co), strerror(errno));
return ret;
}
pthread_rwlock_unlock(&parent->lock);
if (ret == 0)
co->parent = get_cobj(parent);
dprint(MIDEBUG_CAPIOBJ, "%s: uid=%i initialized\n", CAPIobjIDstr(co), co->uid);
return ret;
}
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
int __delist_cobj(struct mCAPIobj *co, const char *file, int lineno)
#else
int delist_cobj(struct mCAPIobj *co)
#endif
{
int r, ret = -EINVAL;
int old __attribute__((unused));
enum eCAPIobjtype pt __attribute__((unused));
struct mCAPIobj *p, *c;
unsigned int uid;
if (co) {
p = co->parent;
if (p) {
pthread_rwlock_wrlock(&p->lock);
c = p->listhead;
old = p->itemcnt;
while (c) {
if (c == co) {
p->listhead = co->next;
break;
}
if (c->next == co) {
c->next = co->next;
break;
}
c = c->next;
}
if (c) {
p->itemcnt--;
cobj_unlisted(co);
}
ret = p->itemcnt;
r = co->refcnt;
uid = co->uid;
pt = p->type;
pthread_rwlock_unlock(&p->lock);
if (c)
r = put_cobj(co);
if (r > 0)
coref_dbg("%s: parent %s items %d->%d\n", CAPIobjIDstr(co), __CAPIobjt2str(pt), old, ret);
else
coref_dbg("Object uid=%i delisted and freed parent %s items %d->%d\n", uid, __CAPIobjt2str(pt), old, ret);
} else
cobj_warn("%s: no parent assigned\n", CAPIobjt2str(co));
}
return ret;
}