static int startElement_METRIC(void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; ganglia_slope_t slope = GANGLIA_SLOPE_UNSPECIFIED; struct xml_tag *xt; struct type_tag *tt; datum_t *hash_datum = NULL; datum_t *rdatum; datum_t hashkey, hashval; const char *name = NULL; const char *metricval = NULL; const char *type = NULL; int do_summary; int i, edge, carbon_ret; hash_t *summary; Metric_t *metric; if (!xmldata->host_alive ) return 0; /* Get name for hash key, and val/type for summaries. */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list(attr[i], strlen(attr[i])); if (!xt) continue; switch (xt->tag) { case NAME_TAG: name = attr[i+1]; hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; break; case VAL_TAG: metricval = attr[i+1]; break; case TYPE_TAG: type = attr[i+1]; break; case SLOPE_TAG: slope = cstr_to_slope(attr[i+1]); default: break; } } metric = &(xmldata->metric); memset((void*) metric, 0, sizeof(*metric)); /* Summarize all numeric metrics */ do_summary = 0; tt = in_type_list(type, strlen(type)); if (!tt) return 0; if (tt->type==INT || tt->type==UINT || tt->type==FLOAT) do_summary = 1; /* Only keep metric details if we are the authority on this cluster. */ if (authority_mode(xmldata)) { /* Save the data to a round robin database if the data source is alive */ fillmetric(attr, metric, type); if (metric->dmax && metric->tn > metric->dmax) return 0; if (do_summary && !xmldata->ds->dead && !xmldata->rval) { debug_msg("Updating host %s, metric %s", xmldata->hostname, name); if ( gmetad_config.write_rrds == 1 ) xmldata->rval = write_data_to_rrd(xmldata->sourcename, xmldata->hostname, name, metricval, NULL, xmldata->ds->step, xmldata->source.localtime, slope); if (gmetad_config.carbon_server) // if the user has specified a carbon server, send the metric to carbon as well carbon_ret=write_data_to_carbon(xmldata->sourcename, xmldata->hostname, name, metricval,xmldata->source.localtime); } metric->id = METRIC_NODE; metric->report_start = metric_report_start; metric->report_end = metric_report_end; edge = metric->stringslen; metric->name = addstring(metric->strings, &edge, name); metric->stringslen = edge; /* Set local idea of T0. */ metric->t0 = xmldata->now; metric->t0.tv_sec -= metric->tn; /* Trim metric structure to the correct length. */ hashval.size = sizeof(*metric) - GMETAD_FRAMESIZE + metric->stringslen; hashval.data = (void*) metric; /* Update full metric in cluster host table. */ rdatum = hash_insert(&hashkey, &hashval, xmldata->host.metrics); if (!rdatum) { err_msg("Could not insert %s metric", name); } } /* Always update summary for numeric metrics. */ if (do_summary) { summary = xmldata->source.metric_summary; hash_datum = hash_lookup(&hashkey, summary); if (!hash_datum) { if (!authority_mode(xmldata)) { metric = &(xmldata->metric); memset((void*) metric, 0, sizeof(*metric)); fillmetric(attr, metric, type); } /* else we have already filled in the metric above. */ } else { memcpy(&xmldata->metric, hash_datum->data, hash_datum->size); datum_free(hash_datum); metric = &(xmldata->metric); switch (tt->type) { case INT: case UINT: case FLOAT: metric->val.d += (double) strtod(metricval, (char**) NULL); break; default: break; } } metric->num++; metric->t0 = xmldata->now; /* tell cleanup thread we are using this */ /* Trim metric structure to the correct length. Tricky. */ hashval.size = sizeof(*metric) - GMETAD_FRAMESIZE + metric->stringslen; hashval.data = (void*) metric; /* Update metric in summary table. */ rdatum = hash_insert(&hashkey, &hashval, summary); if (!rdatum) err_msg("Could not insert %s metric", name); } return 0; }
/* XXX - There is an issue which will cause a failure if there are more than 16 EXTRA_ELEMENTs. This is a problem with the size of the data structure that is used to hold the metric information. */ static int startElement_EXTRA_ELEMENT (void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; int edge; struct xml_tag *xt; int i, name_off, value_off; Metric_t metric; char *name = getfield(xmldata->metric.strings, xmldata->metric.name); datum_t *rdatum; datum_t hashkey, hashval; datum_t *hash_datum = NULL; if (!xmldata->host_alive) return 0; /* Only keep extra element details if we are the authority on this cluster. */ if (!authority_mode(xmldata)) return 0; hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; hash_datum = hash_lookup (&hashkey, xmldata->host.metrics); if (!hash_datum) return 0; memcpy(&metric, hash_datum->data, hash_datum->size); datum_free(hash_datum); /* Check to make sure that we don't try to add more extra elements than the array can handle. */ if (metric.ednameslen >= MAX_EXTRA_ELEMENTS) { debug_msg("Can not add more extra elements for [%s]. Capacity of %d reached.", name, MAX_EXTRA_ELEMENTS); return 0; } edge = metric.stringslen; name_off = value_off = -1; for(i = 0; attr[i]; i+=2) { xt = in_xml_list(attr[i], strlen(attr[i])); if (!xt) continue; switch (xt->tag) { case NAME_TAG: name_off = i; break; case VAL_TAG: value_off = i; break; default: break; } } if ((name_off >= 0) && (value_off >= 0)) { const char *new_name = attr[name_off+1]; const char *new_value = attr[value_off+1]; metric.ednames[metric.ednameslen++] = addstring(metric.strings, &edge, new_name); metric.edvalues[metric.edvalueslen++] = addstring(metric.strings, &edge, new_value); metric.stringslen = edge; hashkey.data = (void*)name; hashkey.size = strlen(name) + 1; /* Trim metric structure to the correct length. */ hashval.size = sizeof(metric) - GMETAD_FRAMESIZE + metric.stringslen; hashval.data = (void*) &metric; /* Update full metric in cluster host table. */ rdatum = hash_insert(&hashkey, &hashval, xmldata->host.metrics); if (!rdatum) { err_msg("Could not insert %s metric", name); } else { hash_t *summary = xmldata->source.metric_summary; Metric_t sum_metric; /* do not add every SPOOF_HOST element to the summary table. if the same metric is SPOOF'd on more than ~MAX_EXTRA_ELEMENTS hosts then its summary table is destroyed. */ if ( strlen(new_name) == 10 && !strcasecmp(new_name, SPOOF_HOST) ) return 0; /* only update summary if metric is in hash */ hash_datum = hash_lookup(&hashkey, summary); if (hash_datum) { int found = FALSE; memcpy(&sum_metric, hash_datum->data, hash_datum->size); datum_free(hash_datum); for (i = 0; i < sum_metric.ednameslen; i++) { char *chk_name = getfield(sum_metric.strings, sum_metric.ednames[i]); char *chk_value = getfield(sum_metric.strings, sum_metric.edvalues[i]); /* If the name and value already exists, skip adding the strings. */ if (!strcasecmp(chk_name, new_name) && !strcasecmp(chk_value, new_value)) { found = TRUE; break; } } if (!found) { edge = sum_metric.stringslen; sum_metric.ednames[sum_metric.ednameslen++] = addstring(sum_metric.strings, &edge, new_name); sum_metric.edvalues[sum_metric.edvalueslen++] = addstring(sum_metric.strings, &edge, new_value); sum_metric.stringslen = edge; } /* Trim graph display sum_metric at (352, 208) now or when in startElement_EXTRA_ELEMENT metric structure to the correct length. Tricky. */ hashval.size = sizeof(sum_metric) - GMETAD_FRAMESIZE + sum_metric.stringslen; hashval.data = (void*) &sum_metric; /* Update metric in summary table. */ rdatum = hash_insert(&hashkey, &hashval, summary); if (!rdatum) err_msg("Could not insert summary %s metric", name); } } } return 0; }
static int startElement_CLUSTER(void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; struct xml_tag *xt; datum_t *hash_datum = NULL; datum_t hashkey; const char *name = NULL; int edge; int i; Source_t *source; /* Get name for hash key */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list (attr[i], strlen(attr[i])); if (!xt) continue; if (xt->tag == NAME_TAG) name = attr[i+1]; } /* Only keep cluster details if we are the authority on this cluster. */ if (!authority_mode(xmldata)) return 0; source = &(xmldata->source); xmldata->sourcename = realloc(xmldata->sourcename, strlen(name)+1); strcpy(xmldata->sourcename, name); hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; hash_datum = hash_lookup(&hashkey, xmldata->root); if (!hash_datum) { memset((void*) source, 0, sizeof(*source)); /* Set the identity of this host. */ source->id = CLUSTER_NODE; source->report_start = source_report_start; source->report_end = source_report_end; source->authority = hash_create(DEFAULT_CLUSTERSIZE); if (!source->authority) { err_msg("Could not create hash table for cluster %s", name); return 1; } if(gmetad_config.case_sensitive_hostnames == 0) hash_set_flags(source->authority, HASH_FLAG_IGNORE_CASE); source->metric_summary = hash_create(DEFAULT_METRICSIZE); if (!source->metric_summary) { err_msg("Could not create summary hash for cluster %s", name); return 1; } source->ds = xmldata->ds; /* Initialize the partial sum lock */ source->sum_finished = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(source->sum_finished, NULL); /* Grab the "partial sum" mutex until we are finished summarizing. */ pthread_mutex_lock(source->sum_finished); } else { memcpy(source, hash_datum->data, hash_datum->size); datum_free(hash_datum); /* We need this lock before zeroing metric sums. */ pthread_mutex_lock(source->sum_finished); source->hosts_up = 0; source->hosts_down = 0; hash_foreach(source->metric_summary, zero_out_summary, NULL); } /* Edge has the same invariant as in fillmetric(). */ edge = 0; source->owner = -1; source->latlong = -1; source->url = -1; /* Fill in cluster attributes. */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list (attr[i], strlen(attr[i])); if (!xt) continue; switch( xt->tag ) { case OWNER_TAG: source->owner = addstring(source->strings, &edge, attr[i+1]); break; case LATLONG_TAG: source->latlong = addstring(source->strings, &edge, attr[i+1]); break; case URL_TAG: source->url = addstring(source->strings, &edge, attr[i+1]); break; case LOCALTIME_TAG: source->localtime = strtoul(attr[i+1], (char **) NULL, 10); break; default: break; } } source->stringslen = edge; return 0; }
static int startElement_HOST(void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; datum_t *hash_datum = NULL; datum_t *rdatum; datum_t hashkey, hashval; struct xml_tag *xt; uint32_t tn=0; uint32_t tmax=0; uint32_t reported=0; const char *name = NULL; int edge; int i; Host_t *host; hash_t *hosts; /* Check if the host is up. */ for (i = 0; attr[i]; i+=2) { xt = in_xml_list (attr[i], strlen(attr[i])); if (!xt) continue; if (xt->tag == REPORTED_TAG) reported = strtoul(attr[i+1], (char **)NULL, 10); else if (xt->tag == TN_TAG) tn = atoi(attr[i+1]); else if (xt->tag == TMAX_TAG) tmax = atoi(attr[i+1]); else if (xt->tag == NAME_TAG) name = attr[i+1]; } /* Is this host alive? For pre-2.5.0, we use the 60-second * method, otherwise we use the host's TN and TMAX attrs. */ xmldata->host_alive = (xmldata->old || !tmax) ? abs(xmldata->source.localtime - reported) < 60 : tn < tmax * 4; if (xmldata->host_alive) xmldata->source.hosts_up++; else xmldata->source.hosts_down++; /* Only keep host details if we are the authority on this cluster. */ if (!authority_mode(xmldata)) return 0; host = &(xmldata->host); /* Use node Name for hash key (Query processing * requires a name key). */ xmldata->hostname = realloc(xmldata->hostname, strlen(name)+1); strcpy(xmldata->hostname, name); /* Convert name to lower case - host names can't be * case sensitive */ /*for(i = 0; name[i] != 0; i++) xmldata->hostname[i] = tolower(name[i]); xmldata->hostname[i] = 0; */ hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; hosts = xmldata->source.authority; hash_datum = hash_lookup (&hashkey, hosts); if (!hash_datum) { memset((void*) host, 0, sizeof(*host)); host->id = HOST_NODE; host->report_start = host_report_start; host->report_end = host_report_end; /* Only create one hash table for the host's metrics. Not user/builtin * like gmond. */ host->metrics = hash_create(DEFAULT_METRICSIZE); if (!host->metrics) { err_msg("Could not create metric hash for host %s", name); return 1; } } else { /* Copy the stored host data into our Host buffer in xmldata. */ memcpy(host, hash_datum->data, hash_datum->size); datum_free(hash_datum); } /* Edge has the same invariant as in fillmetric(). */ edge = 0; host->location = -1; host->tags = -1; host->reported = reported; host->tn = tn; host->tmax = tmax; /* sacerdoti: Host TN tracks what gmond sees. TN=0 when gmond received last * heartbeat from node. Works because clocks move at same speed, and TN is * a relative timespan. */ host->t0 = xmldata->now; host->t0.tv_sec -= host->tn; /* We will store this host in the cluster's authority table. */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list (attr[i], strlen(attr[i])); if (!xt) continue; switch( xt->tag ) { case IP_TAG: host->ip = addstring(host->strings, &edge, attr[i+1]); break; case DMAX_TAG: host->dmax = strtoul(attr[i+1], (char **)NULL, 10); break; case LOCATION_TAG: host->location = addstring(host->strings, &edge, attr[i+1]); break; case TAGS_TAG: host->tags = addstring(host->strings, &edge, attr[i+1]); break; case STARTED_TAG: host->started = strtoul(attr[i+1], (char **)NULL, 10); break; default: break; } } host->stringslen = edge; /* Trim structure to the correct length. */ hashval.size = sizeof(*host) - GMETAD_FRAMESIZE + host->stringslen; hashval.data = host; /* We dont care if this is an insert or an update. */ rdatum = hash_insert(&hashkey, &hashval, hosts); if (!rdatum) { err_msg("Could not insert host %s", name); return 1; } return 0; }
static int startElement_GRID(void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; struct xml_tag *xt; datum_t *hash_datum = NULL; datum_t hashkey; const char *name = NULL; int edge; int i; Source_t *source; /* In non-scalable mode, we ignore GRIDs. */ if (!gmetad_config.scalable_mode) return 0; /* We do not keep info on nested grids. */ if (authority_mode(xmldata)) { /* Get name for hash key */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list (attr[i], strlen(attr[i])); if (!xt) continue; if (xt->tag == NAME_TAG) { name = attr[i+1]; xmldata->sourcename = realloc(xmldata->sourcename, strlen(name)+1); strcpy(xmldata->sourcename, name); hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; } } source = &(xmldata->source); /* Query the hash table for this cluster */ hash_datum = hash_lookup(&hashkey, xmldata->root); if (!hash_datum) { /* New Cluster */ memset((void*) source, 0, sizeof(*source)); source->id = GRID_NODE; source->report_start = source_report_start; source->report_end = source_report_end; source->metric_summary = hash_create(DEFAULT_METRICSIZE); if (!source->metric_summary) { err_msg("Could not create summary hash for cluster %s", name); return 1; } source->ds = xmldata->ds; /* Initialize the partial sum lock */ source->sum_finished = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(source->sum_finished, NULL); /* Grab the "partial sum" mutex until we are finished * summarizing. */ pthread_mutex_lock(source->sum_finished); } else { /* Found Cluster. Put into our Source buffer in xmldata. */ memcpy(source, hash_datum->data, hash_datum->size); datum_free(hash_datum); /* Grab the "partial sum" mutex until we are finished * summarizing. Needs to be done asap.*/ pthread_mutex_lock(source->sum_finished); source->hosts_up = 0; source->hosts_down = 0; hash_foreach(source->metric_summary, zero_out_summary, NULL); } /* Edge has the same invariant as in fillmetric(). */ edge = 0; /* Fill in grid attributes. */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list(attr[i], strlen(attr[i])); if (!xt) continue; switch( xt->tag ) { case AUTHORITY_TAG: source->authority_ptr = addstring(source->strings, &edge, attr[i+1]); break; case LOCALTIME_TAG: source->localtime = strtoul(attr[i+1], (char **) NULL, 10); break; default: break; } } source->stringslen = edge; } /* Must happen after all processing of this tag. */ xmldata->grid_depth++; debug_msg("Found a <GRID>, depth is now %d", xmldata->grid_depth); return 0; }
static int startElement_METRICS(void *data, const char *el, const char **attr) { xmldata_t *xmldata = (xmldata_t *)data; struct xml_tag *xt; struct type_tag *tt; datum_t *hash_datum = NULL; datum_t *rdatum; datum_t hashkey, hashval; const char *name = NULL; const char *metricval = NULL; const char *metricnum = NULL; const char *type = NULL; int i; hash_t *summary; Metric_t *metric; /* In non-scalable mode, we do not process summary data. */ if (!gmetad_config.scalable_mode) return 0; /* Get name for hash key, and val/type for summaries. */ for(i = 0; attr[i]; i+=2) { xt = in_xml_list(attr[i], strlen(attr[i])); if (!xt) continue; switch (xt->tag) { case NAME_TAG: name = attr[i+1]; hashkey.data = (void*) name; hashkey.size = strlen(name) + 1; break; case TYPE_TAG: type = attr[i+1]; break; case SUM_TAG: metricval = attr[i+1]; break; case NUM_TAG: metricnum = attr[i+1]; default: break; } } summary = xmldata->source.metric_summary; hash_datum = hash_lookup(&hashkey, summary); if (!hash_datum) { metric = &(xmldata->metric); memset((void*) metric, 0, sizeof(*metric)); fillmetric(attr, metric, type); } else { memcpy(&xmldata->metric, hash_datum->data, hash_datum->size); datum_free(hash_datum); metric = &(xmldata->metric); tt = in_type_list(type, strlen(type)); if (!tt) return 0; switch (tt->type) { case INT: case UINT: case FLOAT: metric->val.d += (double) strtod(metricval, (char**) NULL); break; default: break; } metric->num += atoi(metricnum); } /* Update metric in summary table. */ hashval.size = sizeof(*metric) - GMETAD_FRAMESIZE + metric->stringslen; hashval.data = (void*) metric; summary = xmldata->source.metric_summary; rdatum = hash_insert(&hashkey, &hashval, summary); if (!rdatum) { err_msg("Could not insert %s metric", name); return 1; } return 0; }
int hdbm_file_copy(const char *filefrom, const char *fileto) /* Delete any original content of fileto and then copy the database in filefrom into fileto. Both files are closed at the end of the operation. */ { hdbm dbfrom, dbto; datum key, value; int available; FILE *file; /* Truncate the to-file to zero length or create it ... this is better than unlinking it since it may be a link or a special file */ if (!(file = fopen(fileto, "w"))) { (void) fprintf(stderr,"hdbm_file_copy: failed to truncate (to) %s\n", fileto); return 0; } (void) fclose(file); /* Open the databases */ if (!hdbm_open(filefrom, 1, &dbfrom)) { (void) fprintf(stderr,"hdbm_file_copy: failed to open (from) %s\n", filefrom); return 0; } if (!hdbm_open(fileto, 1, &dbto)) { (void) fprintf(stderr,"hdbm_file_copy: failed to open (to) %s\n", fileto); (void) hdbm_close(dbfrom); return 0; } /* Do the copy */ for (available = hdbm_first_key(dbfrom, &key); available; available = hdbm_next_key(dbfrom, &key)) { int ok = hdbm_read(dbfrom, key, &value); if (!ok) (void) fprintf(stderr, "hdbm_file_copy: failed reading from %s\n", filefrom); else { ok = hdbm_insert(dbto, key, value); datum_free(key); datum_free(value); if (!ok) (void) fprintf(stderr, "hdbm_file_copy: failed writing to %s\n", fileto); } if (!ok) { (void) hdbm_close(dbfrom); (void) hdbm_close(dbto); return 0; } } return (hdbm_close(dbfrom) && hdbm_close(dbto)); }
datum_t * hash_insert (datum_t *key, datum_t *val, hash_t *hash) { size_t i; node_t *bucket; i = hashval(key, hash); ck_rwlock_write_lock(&hash->lock[i]); bucket = &hash->node[i]; if (bucket->key == NULL) { /* This bucket hasn't been used yet */ bucket->key = datum_dup(key); if ( bucket->key == NULL ) { free(bucket); bucket = NULL; ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } bucket->val = datum_dup(val); if ( bucket->val == NULL ) { free(bucket); bucket = NULL; ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } ck_rwlock_write_unlock(&hash->lock[i]); return bucket->val; } /* This node in the hash is already in use. Collision or new data for existing key. */ for (; bucket != NULL; bucket = bucket->next) { if(bucket->key && hash_keycmp(hash, bucket->key, key)) { /* New data for an existing key */ /* Make sure we have enough space */ if ( bucket->val->size < val->size ) { /* Make sure we have enough room */ if(! (bucket->val->data = realloc(bucket->val->data, val->size)) ) { ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } bucket->val->size = val->size; } memset( bucket->val->data, 0, val->size ); memcpy( bucket->val->data, val->data, val->size ); ck_rwlock_write_unlock(&hash->lock[i]); return bucket->val; } } /* It's a Hash collision... link it in the collided bucket */ bucket = calloc(1, sizeof(*bucket)); if (bucket == NULL) { ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } bucket->key = datum_dup (key); if ( bucket->key == NULL ) { free(bucket); ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } bucket->val = datum_dup (val); if ( bucket->val == NULL ) { datum_free(bucket->key); free(bucket); ck_rwlock_write_unlock(&hash->lock[i]); return NULL; } bucket->next = hash->node[i].next; hash->node[i].next = bucket; ck_rwlock_write_unlock(&hash->lock[i]); return bucket->val; }
/* sacerdoti: This function does a tree walk while respecting the filter path. * Will return valid XML even if we have chosen a subtree. Since tree depth is * bounded, this function guarantees O(1) search time. */ static int process_path (client_t *client, char *path, datum_t *myroot, datum_t *key) { char *p, *q, *pathend; char *element; int rc, len; datum_t *found; datum_t findkey; Generic_t *node; node = (Generic_t*) myroot->data; /* Base case */ if (!path) { /* Show the subtree. */ applyfilter(client, node); if (node->children) { /* Allow this to stop early (return code = 1) */ hash_foreach(node->children, tree_report, (void*) client); } return 0; } /* Start tag */ if (node->report_start) { rc = node->report_start(node, key, client, NULL); if (rc) return 1; } /* Subtree body */ pathend = path + strlen(path); p = path+1; if (!node->children || p >= pathend) rc = process_path(client, 0, myroot, NULL); else { /* Advance to the next element along path. */ q = strchr(p, '/'); if (!q) q=pathend; /* len is limited in size by REQUESTLEN through readline() */ len = q-p; element = malloc(len + 1); if ( element == NULL ) return 1; strncpy(element, p, len); element[len] = '\0'; /* err_msg("Skipping to %s (%d)", element, len); */ /* look for element in hash table. */ findkey.data = element; findkey.size = len+1; found = hash_lookup(&findkey, node->children); if (found) { /* err_msg("Found %s", element); */ rc = process_path(client, q, found, &findkey); datum_free(found); } else if (!client->http) { /* report this element */ rc = process_path(client, 0, myroot, NULL); } else if (!strcmp(element, "*")) { /* wildcard detected -> process every child */ struct request_context ctxt; ctxt.path = q; ctxt.client = client; hash_foreach(node->children, process_path_adapter, (void*) &ctxt); } else { /* element not found */ rc = 0; } free(element); } if (rc) return 1; /* End tag */ if (node->report_end) { rc = node->report_end(node, client, NULL); } return rc; }