diff options
Diffstat (limited to 'src/libs/indmanager/ind_manager.c')
-rw-r--r-- | src/libs/indmanager/ind_manager.c | 1286 |
1 files changed, 1286 insertions, 0 deletions
diff --git a/src/libs/indmanager/ind_manager.c b/src/libs/indmanager/ind_manager.c new file mode 100644 index 0000000..7009e3b --- /dev/null +++ b/src/libs/indmanager/ind_manager.c @@ -0,0 +1,1286 @@ +/* + * Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Roman Rakus <rrakus@redhat.com> + */ + +#include "ind_manager.h" + +#include <cmpi/cmpidt.h> +#include <cmpi/cmpift.h> +#include <cmpi/cmpimacs.h> + +#include <string.h> +#include <stdio.h> +#include <pthread.h> +#include <time.h> + +/* Debugging macros */ +#define DEBUG(...) \ + _im_debug(manager->broker, __FILE__, __func__, __LINE__, __VA_ARGS__) +#include <stdarg.h> + +static inline void _im_debug(const CMPIBroker *b, const char *file, const char *func, + int line, const char *format, ...) +{ + char text[2048] = ""; + snprintf(text, 2047, "%s:%d(%s):", file, line, func); + va_list args; + va_start(args, format); + vsnprintf(text+strlen(text), 2047-strlen(text), format, args); + va_end(args); + CMTraceMessage(b, CMPI_LEV_VERBOSE, "LMI indication manager", text, NULL); +} + +// Get char * representation of name space from object path +#define NS_STR_FROM_OP(op) CMGetCharPtr(CMGetNameSpace((op), NULL)) + +// Get char * representation of class name from object path +#define CN_STR_FROM_OP(op) CMGetCharPtr(CMGetClassName((op), NULL)) + +typedef struct _IMPolledDiff { + CMPIInstance *iold; + CMPIInstance *inew; + struct _IMPolledDiff *next; +} IMPolledDiff; + +typedef struct _IMPolledDiffs { + IMPolledDiff *first; +} IMPolledDiffs; + +typedef enum _IMIDiff { + IM_I_SAME, // Instances are the same + IM_I_DIFFERENT, // Instances are different in key properties or value types + IM_I_CHANGE // Instances are different in non-key properties +} IMIDiff; + +static IMIDiff compare_instances(CMPIInstance *, CMPIInstance *); + +/* CMPI rc handler */ +CMPIStatus im_rc; + +/* + * Compare 2 object paths + * return true if they are same, false otherwise + * is using ft->toString() + */ +static bool compare_objectpaths(CMPIObjectPath *op1, CMPIObjectPath *op2) +{ + return (strcmp(CMGetCharsPtr(CMObjectPathToString(op1, NULL), NULL), + CMGetCharsPtr(CMObjectPathToString(op2, NULL), NULL)) == 0); +} + +/* + * Compare 2 cmpi data + * return true if they are the same, false otherwise + * note: inspired by value.c:sfcb_comp_CMPIValue() from sblim-sfcb + */ +static bool compare_values(CMPIValue *v1, CMPIValue *v2, CMPIType type) +{ +// Checks for NULL, but it is incorrect +#define CHECK_PTRS \ + if (!v1->array && !v2->array) {\ + return true;\ + }\ + if (!v1->array || !v2->array) {\ + return false;\ + }\ + + if (type & CMPI_ARRAY) { + CHECK_PTRS; + CMPICount c = CMGetArrayCount(v1->array, NULL); + if (c != CMGetArrayCount(v2->array, NULL)) { + return false; + } + for (; c > 0; c--) { + CMPIValue val1 = CMGetArrayElementAt(v1->array, c-1, NULL).value; + CMPIValue val2 = CMGetArrayElementAt(v2->array, c-1, NULL).value; + if (!compare_values(&val1, &val2, type & ~CMPI_ARRAY)) { + return false; + } + } + return true; + } + switch (type) { + case CMPI_boolean: + case CMPI_uint8: + case CMPI_sint8: + return (v1->Byte == v2->Byte); + case CMPI_char16: + case CMPI_uint16: + case CMPI_sint16: + return (v1->Short == v2->Short); + case CMPI_uint32: + case CMPI_sint32: + return (v1->Int == v2->Int); + case CMPI_real32: + return (v1->Float == v2->Float); + case CMPI_uint64: + case CMPI_sint64: + return (v1->Long == v2->Long); + case CMPI_real64: + return(v1->Double == v2->Double); + case CMPI_instance: + CHECK_PTRS; + return (compare_instances(v1->inst, v2->inst) == IM_I_SAME); + case CMPI_ref: + CHECK_PTRS; + return (compare_objectpaths(v1->ref, v2->ref)); + case CMPI_string: + CHECK_PTRS; + return (strcmp(CMGetCharsPtr(v1->string, NULL), + CMGetCharsPtr(v2->string, NULL)) == 0); + case CMPI_dateTime: + CHECK_PTRS; + return (strcmp(CMGetCharsPtr( + CMGetStringFormat(v1->dateTime, NULL), NULL), + CMGetCharsPtr( + CMGetStringFormat(v2->dateTime, NULL), NULL)) == 0); + default: + return true; + } + return true; +} + +/* + * Compare 2 instances. + * They can be same, different in key properties or different in non-key props + */ +static IMIDiff compare_instances(CMPIInstance *i1, CMPIInstance *i2) +{ + CMPIString *prop_name = NULL; + // At first compare key properties. They will differ in most cases + // Then compare non-key properties + CMPIObjectPath *op1 = CMGetObjectPath(i1, NULL); + CMPIObjectPath *op2 = CMGetObjectPath(i2, NULL); + int pcount1 = CMGetKeyCount(op1, NULL); + int pcount2 = CMGetKeyCount(op2, NULL); + + if (pcount1 != pcount2) { + // They differ in count of key properties +// DEBUG("compare instances: Different count of properties"); + return IM_I_DIFFERENT; + } + int i; + CMPIData data1, data2; + // Check key properties one by one + for (i = 0; i < pcount1; i++) { + data1 = CMGetKeyAt(op1, i, &prop_name, NULL); + data2 = CMGetKey(op2, CMGetCharsPtr(prop_name, NULL), NULL); + // XXX - is state really CMPI_badValue? + if (data2.state == CMPI_badValue || data1.type != data2.type || + !compare_values(&data1.value, &data2.value, data1.type)) { + return IM_I_DIFFERENT; + } + } + // Check all properties one by one + pcount1 = CMGetPropertyCount(i1, NULL); + pcount2 = CMGetPropertyCount(i2, NULL); + if (pcount1 != pcount2) { + return IM_I_DIFFERENT; + } + for (i = 0; i < pcount1; i++) { + data1 = CMGetPropertyAt(i1, i, &prop_name, NULL); + data2 = CMGetProperty(i2, CMGetCharsPtr(prop_name, NULL), NULL); + // XXX - is state really CMPI_badValue? + if (data2.state == CMPI_badValue || data1.type != data2.type) { + return IM_I_DIFFERENT; + } else if (!compare_values(&data1.value, &data2.value, data1.type)) { + return IM_I_CHANGE; + } + } + return IM_I_SAME; +} + + +/* + * Remove (free()) the first polled diff from diff list + * returns nothing + * Does not perform any checks + */ +/* Called with lock held */ +static void remove_diff(IMPolledDiffs *d) +{ + IMPolledDiff *first = d->first; + if (first) { + d->first = first->next; + free (first); + } +} + +/* + * add instance pair to the list of diffs + * First argument is diffs list. + * Returns true when ok, false if not and set IMError + */ +/* Called with lock held */ +static bool add_diff(IMPolledDiffs *d, CMPIInstance *newi, CMPIInstance *oldi, + IMError *err) +{ + IMPolledDiff *nd = malloc(sizeof(IMPolledDiff)); + if (!nd) { + *err = IM_ERR_MALLOC; + return false; + } + nd->inew = newi; + nd->iold = oldi; + nd->next = NULL; + if (!d->first) { + d->first = nd; + } else { + IMPolledDiff *last = d->first; + while (last->next) last = last->next; + last->next = nd; + } + return true; +} + +/* + * Generate diffs based on polled enumerations. + * Generated diff will contain: + * - pair => there is a change + * - old NULL and new value => new instance created + * - old value and new NULL => instance deletion + * Return true when ok, false if not and set IMError + */ +/* Called with lock held */ +static bool gen_diffs(IMManager *manager, IMPolledDiffs *d, IMError *err) +{ + DEBUG("gen diffs called"); + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!manager->enums) { + return false; + } + IMEnumerationPair *pair = manager->enums->first; + while (pair) { +// DEBUG("gen diffs - next pair"); + CMPIArray *penum = pair->prev_enum; + CMPIArray *tenum = pair->this_enum; + if (penum && tenum) { + int pcount = CMGetArrayCount(penum, NULL); + int tcount = CMGetArrayCount(tenum, NULL); + bool p_used[pcount], t_used[tcount]; + bzero(p_used, pcount); + bzero(t_used, tcount); + int pi = 0, ti = 0; + CMPIInstance *newi = NULL; + CMPIInstance *oldi = NULL; + bool do_compare = true; + for (pi = 0; pi < pcount; pi++) { + oldi = CMGetArrayElementAt(penum, pi, NULL).value.inst; + do_compare = true; + for (ti = 0; ti < tcount && do_compare; ti++) { + if (t_used[ti] == true) continue; + newi = CMGetArrayElementAt(tenum, ti, NULL).value.inst; // XXX are them really in same order, on the same index? + switch (compare_instances(oldi, newi)) { + case IM_I_SAME: + DEBUG("gen diffs - same instances"); + do_compare = false; + t_used[ti] = true; + p_used[pi] = true; + break; + case IM_I_CHANGE: + DEBUG("gen diffs - change in instances"); + if (manager->type == IM_IND_MODIFICATION) { + if (!add_diff(d, newi, oldi, err)) { + return false; + } + } + do_compare = false; + t_used[ti] = true; + p_used[pi] = true; + break; + case IM_I_DIFFERENT: + DEBUG("gen diffs - different instances"); + default: break; + } + } + } + if (manager->type == IM_IND_DELETION) { + for (pi = 0; pi < pcount; pi++) { + if (p_used[pi] == false) { + DEBUG("gen diffs - deleted instance"); + if (!add_diff(d, NULL, + CMGetArrayElementAt(penum, pi, NULL).value.inst, + err)) { + return false; + } + } + } + } + if (manager->type == IM_IND_CREATION) { + for (ti = 0; ti < tcount; ti++) { + if (t_used[ti] == false) { + DEBUG("gen diffs - created instance"); + if (!add_diff(d, + CMGetArrayElementAt(tenum, ti, NULL).value.inst, + NULL, err)) { + return false; + } + } + } + } + + } + pair = pair->next; + } + DEBUG("gen diffs returning true"); + return true; +} + + +/* helper functions */ + +/* + * Extract object path from select expression + * returns match for the first occurence of ISA operator + */ +static CMPIObjectPath *get_object_path(const CMPIBroker *broker, CMPISelectExp *se) +{ + CMPISelectCond *sc = CMGetDoc(se, NULL); + CMPICount scount = CMGetSubCondCountAndType(sc,NULL,NULL); + unsigned int i, j; + for (i = 0; i < scount; i++) { + CMPISubCond *subcond = CMGetSubCondAt(sc,i,NULL); + CMPICount pcount = CMGetPredicateCount(subcond,NULL); + for (j = 0; j < pcount; j++) { + CMPIPredicate *pred = CMGetPredicateAt(subcond,j,NULL); + CMPIType type; + CMPIPredOp op; + CMPIString *lhs=NULL; + CMPIString *rhs=NULL; + CMGetPredicateData(pred,&type,&op,&lhs,&rhs); + if (op == CMPI_PredOp_Isa) { + return CMNewObjectPath(broker, "root/cimv2", + CMGetCharsPtr(rhs, NULL), NULL); + } + } + } + return NULL; +} + +// compare 2 object paths, return true when equal, false if not +// equality is set by comparing class names and namespaces +static bool equal_ops(CMPIObjectPath *op1, CMPIObjectPath *op2) +{ + return (strcmp(NS_STR_FROM_OP(op1), NS_STR_FROM_OP(op2)) == 0 && + strcmp(CN_STR_FROM_OP(op1), CN_STR_FROM_OP(op2)) == 0); +} + +// Pair enumerations. This enum will become prev enum and new this enum +// will be created by new obtained by object path +/* Called with lock held */ +static bool pair_enumeration(IMManager *manager, IMEnumerationPair *epair) +{ + CMPIEnumeration *aux_e = CBEnumInstances(manager->broker, + manager->ctx_manage, epair->op, NULL, NULL); + if (!aux_e) { + return false; + } + CMPIArray *new_e = CMClone(CMToArray(aux_e, NULL), NULL); + if (!epair->prev_enum && !epair->this_enum) { + epair->prev_enum = CMClone(new_e, NULL); + epair->this_enum = new_e; + return true; + } + if (epair->prev_enum) { + CMRelease(epair->prev_enum); + } + epair->prev_enum = epair->this_enum; + epair->this_enum = new_e; + return true; +} + +// When the filter is added there is no enum pairs created +// This function will generate pairs where they are missing +/* Called with lock held */ +static bool first_poll(IMManager *manager, IMError *err) +{ + DEBUG("IM first poll called"); + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!manager->enums) { + // Nothing to poll + return true; + } + IMEnumerationPair *epair = NULL; + for (epair = manager->enums->first; epair; epair = epair->next) { + if (epair->this_enum == NULL && epair->prev_enum == NULL) { + if (!pair_enumeration(manager, epair)) { + return false; + } + } + } + return true; +} + +// Try to find enumeration with given object path +// if found increase reference count +// if not found add it to the end of list with ref count = 1 +/* Called with lock held */ +static bool add_enumeration(IMManager *manager, CMPIObjectPath *op, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!op) { + *err = IM_ERR_OP; + return false; + } + if (!manager->enums) { + if (!(manager->enums = malloc(sizeof(IMEnumerations)))) { + *err = IM_ERR_MALLOC; + return false; + } + manager->enums->first = NULL; + } + IMEnumerationPair *e = manager->enums->first; + IMEnumerationPair *last = NULL; + while (e) { // find last enumeration + if (compare_objectpaths(op, e->op)) { + e->ref_count++; + return true; + } + last = e; + e = e->next; + } + IMEnumerationPair *new_ime = malloc(sizeof(IMEnumerationPair)); + if (!new_ime) { + *err = IM_ERR_MALLOC; + return false; + } + new_ime->next = NULL; + new_ime->op = CMClone(op, NULL); + new_ime->ref_count = 1; + new_ime->prev_enum = NULL; + new_ime->this_enum = NULL; + if (manager->ctx_manage) { + /* Fill the enumeration with values valid at the time the filter was registered and + * only if we're adding new filter when indications have been started already */ + pair_enumeration(manager, new_ime); + } + if (!last) { // there isn't any enumeration yet + manager->enums->first = new_ime; + } else { + last->next = new_ime; + } + return true; +} + +/* Called with lock held */ +static bool _im_poll(IMManager *manager, IMError *err) +{ + DEBUG("IM poll called"); + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!manager->enums) { + // Nothing to poll + DEBUG("IM poll nothing to poll"); + return true; + } + IMEnumerationPair *epair = NULL; + // go thru all enumerations' object path + // Use object path to enumerate instances + DEBUG("IM poll will go thru all filters"); + for (epair = manager->enums->first; epair; epair = epair->next) { + DEBUG("IM poll next enumeration"); + pair_enumeration(manager, epair); + } + DEBUG("IM poll returning true"); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +static bool send_creation_indication(CMPIInstance *ind_inst, CMPIInstance *new_inst, + IMManager *manager) +{ + DEBUG("Instance creation indication to be send"); + if (!manager || !ind_inst || !new_inst) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &new_inst, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +static bool send_deletion_indication(CMPIInstance *ind_inst, CMPIInstance *old_inst, + IMManager *manager) +{ + DEBUG("Instance deletion indication to be send"); + if (!manager || !ind_inst || !old_inst) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &old_inst, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +static bool send_modification_indication(CMPIInstance *ind_inst, CMPIInstance *new, + CMPIInstance *old, IMManager *manager) +{ + DEBUG("Instance modification indication to be send"); + if (!manager || !ind_inst || !new || !old) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &new, CMPI_instance); + CMSetProperty(ind_inst, "PreviousInstance", &old, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Will extract class name from select expression + * example: SELECT * FROM LMI_AccountInstCreationIndication WHERE ... + * will return LMI_AccountInstCreationIndication + * returns NULL when not found or on error + * Caller has to free memory. + */ +static char *get_classname(CMPISelectExp *se) +{ + if (!se) { + return NULL; + } +// TODO remove when working +#if 0 + char *select = (char*)CMGetCharsPtr(CMGetSelExpString(se, NULL), NULL); + char *after_from = strcasestr(select, "from") + strlen("from"); + char *start = after_from + strspn(after_from, " "); + char *end = index(start,' '); + char *cname = malloc(end-start+1); + bzero(cname, end-start+1); + return memcpy(cname, start, end-start); +#else + char *select = (char*)CMGetCharsPtr(CMGetSelExpString(se, NULL), NULL); + char *rest = NULL, *token = NULL, *ptr = select; + if (!ptr) { + return NULL; + } + for (; ptr || strcasecmp(token, "from") != 0; ptr = NULL) { + token = strtok_r(ptr, " ", &rest); + if (!token) { + break; + } + } + if (token) { + token = strtok_r(ptr, " ", &rest); + return strdup(token); + } + return NULL; +#endif +} + +/* + * Main send indication function. Will decide by type of indication what + * to send. + * Returns true when everything is OK. False otherwise. + */ +/* Called with lock held */ +static bool send_indication(CMPIInstance *old, CMPIInstance *new, IMManager *manager) +{ + if (!manager) { + return false; + } + + CMPIObjectPath *op = CMNewObjectPath( + manager->broker, + CMGetCharsPtr(CMGetNameSpace(manager->polling ? manager->enums->first->op : CMGetObjectPath(new ? new : old, &im_rc), NULL), NULL), + manager->filters->class_name, + NULL); + DEBUG("Will send indication with this OP: %s", + CMGetCharsPtr(CDToString(manager->broker, op, NULL), NULL)); + CMPIInstance *ind_inst = CMNewInstance(manager->broker, op, NULL); + DEBUG("Will send indication with this instance): %s", + CMGetCharsPtr(CDToString(manager->broker, ind_inst, NULL), NULL), NULL); + DEBUG("Old instance: %p - %s", old, + CMGetCharsPtr(CDToString(manager->broker, old, NULL), NULL), NULL); + DEBUG("New instance: %p - %s", new, + CMGetCharsPtr(CDToString(manager->broker, new, NULL), NULL), NULL); + + CMPIDateTime *date = CMNewDateTimeFromBinary(manager->broker, + time(NULL) * 1000000, + 0, NULL); + CMSetProperty(ind_inst, "IndicationTime", &date, CMPI_dateTime); + + switch (manager->type) { + case IM_IND_CREATION: + if (!new) { + return false; + } + send_creation_indication(ind_inst, new, manager); + break; + case IM_IND_DELETION: + if (!old) { + return false; + } + send_deletion_indication(ind_inst, old, manager); + break; + case IM_IND_MODIFICATION: + if (!old || !new) { + return false; + } + send_modification_indication(ind_inst, new, old, manager); + break; + } + CMRelease(date); + return true; +} + +/* + * Run in separate thread + */ +static IMError manage(IMManager *manager) +{ + IMError err = IM_ERR_OK; // TODO - How to handle potential errors? + IMPolledDiffs diffs = {NULL}; + CMPIInstance *iold = NULL, *inew = NULL; + + while (1) { + DEBUG("manage thread in infinite loop"); + // wait until manager is running + pthread_mutex_lock(&manager->_t_mutex); + if (manager->cancelled) { + pthread_mutex_unlock(&manager->_t_mutex); + return IM_ERR_CANCELLED; + } + while (!manager->running) { + DEBUG("manage thread waiting to indication start"); + pthread_cond_wait(&manager->_t_cond, &manager->_t_mutex); + } + + if (manager->polling) { + if (!first_poll(manager, &err)) { + pthread_mutex_unlock(&manager->_t_mutex); + return err; + } + } + pthread_mutex_unlock(&manager->_t_mutex); + + DEBUG("manage thread calling watcher"); + if(!manager->watcher(&manager->data)) { + continue; + } + pthread_mutex_lock(&manager->_t_mutex); + if (manager->cancelled) { + pthread_mutex_unlock(&manager->_t_mutex); + return IM_ERR_CANCELLED; + } + if (manager->polling) { + // poll enumerations + if (!_im_poll(manager, &err)) { + pthread_mutex_unlock(&manager->_t_mutex); + return err; + } + // create list of diffs + if (!gen_diffs(manager, &diffs, &err)) { + while (diffs.first) + remove_diff(&diffs); + pthread_mutex_unlock(&manager->_t_mutex); + return err; + } + manager->data = &diffs; + } + DEBUG("manage thread calling gather"); + while (manager->gather(manager, &iold, &inew, manager->data)) { + send_indication(iold, inew, manager); + } + pthread_mutex_unlock(&manager->_t_mutex); + } +} + +static void manage_cleanup(void *data) +{ + IMManager *manager = (IMManager *)data; + + DEBUG("manage thread - detaching thread manager context = %p", manager->ctx_manage); + CBDetachThread(manager->broker, manager->ctx_manage); +} + +/* Wrapper thread function, to isolate code control flow due to sensitive pthread_cleanup_ macros */ +static void *manage_wrapper(void *data) +{ + // TODO: Switch Enable/Disable state on some places? + IMManager *manager = (IMManager *)data; + IMError err; + + DEBUG("manage thread started"); + + /* Allow thread cancellation */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + + DEBUG("manage thread - attaching thread manager context = %p", manager->ctx_manage); + CBAttachThread(manager->broker, manager->ctx_manage); + + /* Main execution block closed in a cleanup handler */ + pthread_cleanup_push(manage_cleanup, data); + err = manage(manager); + pthread_cleanup_pop(1); + + DEBUG("manage thread stopped"); + + return (void *)err; +} + +/* + * Default gather function using polling + * It is going thru all polled instances. If there is some pair (previous enum + * and this enum) + */ +/* Called with lock held */ +static bool default_gather (const IMManager *manager, CMPIInstance **old_inst, + CMPIInstance **new_inst, void* data) +{ + // passed data is a list of diffs + // It is going to be called in loop + // return true if now we are going to create indication + // return false if there will be no indication and loop will break + IMPolledDiffs *diffs = (IMPolledDiffs *) data; + if (diffs->first) { + *new_inst = diffs->first->inew; + *old_inst = diffs->first->iold; + remove_diff(diffs); + return true; + } + return false; +} + +/* + * Remove every polled enums + * true if ok, false if not + */ +static bool remove_all_enums(IMManager *manager, IMError *err) +{ + if (!manager->enums) { + return true; + } + IMEnumerationPair *current = manager->enums->first; + IMEnumerationPair *next = NULL; + while (current) { + next = current->next; + CMRelease(current->prev_enum); + CMRelease(current->this_enum); + CMRelease(current->op); + free(current); + current = next; + } + free(manager->enums); + manager->enums = NULL; + return true; +} + +/* + * true if ok + * false if not + */ +static bool remove_all_filters(IMManager *manager, IMError *err) +{ + if (!manager->filters) { + return true; + } + IMFilter *current = manager->filters->first; + IMFilter *next = NULL; + while (current) { + next = current->next; + free(current->select_exp_string); + CMRelease(current->op); + free(current); + current = next; + } + free(manager->filters->class_name); + free(manager->filters); + manager->filters = NULL; + + if (!remove_all_enums(manager, err)) { + return false; + } + + return true; +} + +IMManager* im_create_manager(IMInstGather gather, IMFilterChecker f_checker, + bool polling, IMEventWatcher watcher, + IMIndType type, const CMPIBroker *broker, + IMError *err) +{ + if ((!polling && !gather) || (polling && gather) ) { + *err = IM_ERR_GATHER; + return NULL; + } + if (!watcher) { + *err = IM_ERR_WATCHER; + return NULL; + } + if (!broker) { + *err = IM_ERR_BROKER; + return NULL; + } + IMManager* manager = malloc(sizeof(IMManager)); + if (!manager) { + *err = IM_ERR_MALLOC; + return NULL; + } + manager->type = type; + manager->gather = gather ? gather : default_gather; + manager->watcher = watcher; + manager->filters = NULL; + manager->running = false; + manager->polling = polling; + manager->cancelled = false; + manager->broker = broker; + manager->f_checker = f_checker; + manager->enums = NULL; + manager->ctx_main = NULL; + manager->ctx_manage = NULL; + manager->data = NULL; + DEBUG("Manager created"); + + if (pthread_cond_init(&manager->_t_cond, NULL) || + pthread_mutex_init(&manager->_t_mutex, NULL)) { + *err = IM_ERR_THREAD; + free(manager); + return NULL; + } + + return manager; +} + +bool im_destroy_manager(IMManager *manager, const CMPIContext *ctx, + IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + if (manager->running) + im_stop_ind(manager, ctx, err); + /* No need for locking as all running threads should be stopped at this point */ + if (!remove_all_filters(manager, err)) + return false; + DEBUG("Destroying manager"); + if (pthread_mutex_destroy(&manager->_t_mutex) || + pthread_cond_destroy(&manager->_t_cond)) { + *err = IM_ERR_THREAD; + return false; + } + free(manager); + return true; +} + +static bool default_ind_filter_cb(IMManager *manager, const CMPISelectExp *filter) +{ + /* Looks for a class to be subscribed and matches it against a list of + * allowed classes. Filter query may contain more conditions, only those + * with a ISA operator are looked for. + */ + CMPIStatus st; + unsigned int i; + + CMPISelectCond *sec = CMGetDoc(filter, &st); + if (!sec) + return false; + CMPICount count = CMGetSubCondCountAndType(sec, NULL, &st); + if (count != 1) + return false; + CMPISubCond *sub = CMGetSubCondAt(sec, 0, &st); + if (!sub) + return false; + count = CMGetPredicateCount(sub, &st); + if (count == 0) + return false; + + for (i = 0; i < count; i++) { + CMPIType type; + CMPIPredOp op; + CMPIString *lhs = NULL; + CMPIString *rhs = NULL; + CMPIPredicate *pred = CMGetPredicateAt(sub, i, &st); + if (!pred) + return false; + st = CMGetPredicateData(pred, &type, &op, &lhs, &rhs); + if (st.rc != CMPI_RC_OK || op != CMPI_PredOp_Isa) + continue; + const char *rhs_str = CMGetCharsPtr(rhs, &st); + if (!rhs_str) + continue; + while (manager->f_allowed_classes[i]) { + if (strcasecmp(rhs_str, manager->f_allowed_classes[i++]) == 0) + return true; + } + } + return false; +} + +bool im_verify_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return NULL; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return NULL; + } + if (!filter) { + *err = IM_ERR_FILTER; + return NULL; + } + if (!manager->f_checker && !manager->f_allowed_classes) { + *err = IM_ERR_FILTER_CHECKER; + return NULL; + } + manager->ctx_main = ctx; + if (manager->f_checker) { + DEBUG("Verifying filter. Manager = %p, filter checker = %p", manager, manager->f_checker); + return manager->f_checker(filter); + } else { + DEBUG("Verifying filter. Manager = %p, using default filter checker", manager); + return default_ind_filter_cb(manager, filter); + } +} + +/* + * Because Pegasus cannot easily clone nor copy select expression + * we need to dig internals and store them directly. + * Return true when ok, or NULL and error set + * appropiately + */ +/* Called with lock held */ +static bool _im_add_filter(IMManager *manager, CMPISelectExp *se, IMError *err) +{ + if (!se) { + *err = IM_ERR_SELECT_EXP; + return false; + } + + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + + CMPIString *str = CMGetSelExpString(se, &im_rc); + if (!str) { + *err = IM_ERR_CMPI_RC; + return false; + } + + const char *query = CMGetCharsPtr(str, &im_rc); + if (!query) { + *err = IM_ERR_CMPI_RC; + return false; + } + char *ses = NULL; + if (!(ses = strdup(query))) { + *err = IM_ERR_MALLOC; + return false; + } + CMPIObjectPath *op = get_object_path(manager->broker, se); + if (!op) { + *err = IM_ERR_SELECT_EXP; + free(ses); + return false; + } + + if(!manager->filters) { + DEBUG("This is the first filter creation"); + IMFilters *filters = malloc(sizeof(IMFilters)); + if (!filters) { + *err = IM_ERR_MALLOC; + CMRelease(op); + free(ses); + return false; + } + if (!(filters->class_name = get_classname(se))) { + *err = IM_ERR_MALLOC; + CMRelease(op); + free(ses); + free(filters); + return false; + } + filters->first = NULL; + manager->filters = filters; + } + + DEBUG("Adding filter to the list"); + IMFilter *current = manager->filters->first; + IMFilter *prev = NULL; + while (current) { + prev = current; + current = current->next; + } + IMFilter *next = malloc(sizeof(IMFilter)); + if (!next) { + *err = IM_ERR_MALLOC; + CMRelease(op); + free(ses); + return false; + } + next->next = NULL; + next->select_exp_string = ses; + next->op = CMClone(op, NULL); + if (prev) { + prev->next = next; + } else { + manager->filters->first = next; + } + CMRelease(op); + + return true; +} + +bool im_add_filter(IMManager *manager, CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + pthread_mutex_lock(&manager->_t_mutex); + if (!ctx) { + *err = IM_ERR_CONTEXT; + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + if (!filter) { + *err = IM_ERR_FILTER; + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + manager->ctx_main = ctx; + DEBUG("Adding filter"); + if (!_im_add_filter(manager, filter, err)) { + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + if (manager->polling) { + if (!add_enumeration(manager, + get_object_path(manager->broker, filter), + err)) { + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + } + pthread_mutex_unlock(&manager->_t_mutex); + return true; +} + +bool im_register_filter_classes(IMManager *manager, + const char** allowed_classes, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + manager->f_allowed_classes = allowed_classes; + return true; +} + +/* + * Decrease reference count for polled enumerations. If count is 0, remove + * the whole enumerations. + * Returns true/false on success/fail and set err. + */ +/* Called with lock held */ +static bool maybe_remove_polled(IMManager *manager, CMPIObjectPath *op, IMError *err) +{ + if (!manager->enums || !manager->enums->first) { + *err = IM_ERR_NOT_FOUND; + return false; + } + IMEnumerationPair *current = manager->enums->first; + IMEnumerationPair *prev = NULL; + while (current) { + if (compare_objectpaths(op, current->op)) { + if (--current->ref_count == 0) { + if (prev) { + prev->next = current->next; + } else { + manager->enums->first = current->next; + } + if (current->prev_enum) + CMRelease(current->prev_enum); + if (current->this_enum) + CMRelease(current->this_enum); + CMRelease(current->op); + } + return true; + } + prev = current; + current = current->next; + } + *err = IM_ERR_NOT_FOUND; + return false; +} + +bool im_remove_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + if (!filter) { + *err = IM_ERR_FILTER; + return false; + } + pthread_mutex_lock(&manager->_t_mutex); + if (!manager->filters) { + pthread_mutex_unlock(&manager->_t_mutex); + *err = IM_ERR_NOT_FOUND; + return false; + } + manager->ctx_main = ctx; + DEBUG("Removing filter"); + IMFilter *current = manager->filters->first; + IMFilter *prev = NULL; + while (current) { + if (strcmp(current->select_exp_string, + CMGetCharsPtr(CMGetSelExpString(filter, NULL), NULL)) == 0) { + if (prev) { + prev->next = current->next; + } else { + manager->filters->first = current->next; + } + if (manager->polling && !maybe_remove_polled(manager, current->op, + err)) { + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + CMRelease(current->op); + free(current->select_exp_string); + free(current); + pthread_mutex_unlock(&manager->_t_mutex); + return true; + } + prev = current; + current = current->next; + } + *err = IM_ERR_NOT_FOUND; + pthread_mutex_unlock(&manager->_t_mutex); + return false; +} + +// start indications +bool im_start_ind(IMManager *manager, const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + if (manager->running) { + DEBUG("WARNING: thread already exists"); + *err = IM_ERR_THREAD; + return false; + } + manager->cancelled = false; + manager->ctx_main = ctx; + manager->ctx_manage = CBPrepareAttachThread(manager->broker, + manager->ctx_main); + DEBUG("Creating second thread"); + if (pthread_create(&manager->_t_manage, NULL, manage_wrapper, manager)) { + *err = IM_ERR_THREAD; + return false; + } + DEBUG("Starting indications"); + manager->running = true; + + pthread_cond_signal(&manager->_t_cond); + return true; +} + +// stop indications +bool im_stop_ind(IMManager *manager, const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + manager->ctx_main = ctx; + DEBUG("Stopping indications"); + manager->running = false; + + /* First lock the mutex so we are sure the thread does not hold it. */ + pthread_mutex_lock(&manager->_t_mutex); + /* Then cancel the thread in deferred mode and set a private flag. + * The thread may be doing unlocked stuff that will cancel just fine + * or may be waiting for mutex acquisition where it will cancel and + * unlock right after that using our private flag. + */ + manager->cancelled = true; + /* Note that mutex functions ARE NOT cancellation points! */ + if (pthread_cancel(manager->_t_manage)) { + *err = IM_ERR_THREAD; + pthread_mutex_unlock(&manager->_t_mutex); + return false; + } + + /* Unlock the mutex and give the thread chance to cancel properly. */ + pthread_mutex_unlock(&manager->_t_mutex); + + /* Wait until thread cancellation is finished. */ + if (pthread_join(manager->_t_manage, NULL)) { + *err = IM_ERR_THREAD; + return false; + } + + /* Cleanup */ + remove_all_enums(manager, err); + manager->data = NULL; + manager->ctx_manage = NULL; + manager->cancelled = false; + + return true; +} + |