/** * Copies user_callback and user_data from the query buffer to the * reply buffer. This function won't fail. However, if gethostbyname() * fails ``reply->addr'' will be set to zero. */ static void adns_gethostbyname(const struct adns_request *req, struct adns_response *ans) { g_assert(NULL != req); g_assert(NULL != ans); ans->common = req->common; if (req->common.reverse) { const struct adns_reverse_query *query = &req->query.reverse; struct adns_reverse_reply *reply = &ans->reply.reverse; const char *host; if (common_dbg > 1) { s_debug("%s: reverse-resolving \"%s\" ...", G_STRFUNC, host_addr_to_string(query->addr)); } reply->addr = query->addr; host = host_addr_to_name(query->addr); clamp_strcpy(reply->hostname, sizeof reply->hostname, host ? host : ""); } else { const struct adns_query *query = &req->query.by_addr; struct adns_reply *reply = &ans->reply.by_addr; pslist_t *sl_addr, *sl; size_t i = 0; if (common_dbg > 1) { s_debug("%s: resolving \"%s\" ...", G_STRFUNC, query->hostname); } clamp_strcpy(reply->hostname, sizeof reply->hostname, query->hostname); sl_addr = name_to_host_addr(query->hostname, query->net); PSLIST_FOREACH(sl_addr, sl) { host_addr_t *addr = sl->data; g_assert(addr); if (i >= G_N_ELEMENTS(reply->addrs)) { break; } reply->addrs[i++] = *addr; } host_addr_free_list(&sl_addr); if (i < G_N_ELEMENTS(reply->addrs)) { reply->addrs[i] = zero_host_addr; } }
/** * Destroy the DBM wrapper, optionally closing the underlying DB map. */ void dbmw_destroy(dbmw_t *dw, bool close_map) { dbmw_check(dw); if (common_stats) { s_debug("DBMW destroying \"%s\" with %s back-end " "(read cache hits = %.2f%% on %s request%s, " "write cache hits = %.2f%% on %s request%s)", dw->name, dbmw_map_type(dw) == DBMAP_SDBM ? "sdbm" : "map", dw->r_hits * 100.0 / MAX(1, dw->r_access), uint64_to_string(dw->r_access), plural(dw->r_access), dw->w_hits * 100.0 / MAX(1, dw->w_access), uint64_to_string2(dw->w_access), plural(dw->w_access)); } if (dbg_ds_debugging(dw->dbg, 1, DBG_DSF_DESTROY)) { dbg_ds_log(dw->dbg, dw, "%s: with %s back-end " "(read cache hits = %.2f%% on %s request%s, " "write cache hits = %.2f%% on %s request%s)", G_STRFUNC, dbmw_map_type(dw) == DBMAP_SDBM ? "sdbm" : "map", dw->r_hits * 100.0 / MAX(1, dw->r_access), uint64_to_string(dw->r_access), plural(dw->r_access), dw->w_hits * 100.0 / MAX(1, dw->w_access), uint64_to_string2(dw->w_access), plural(dw->w_access)); } /* * If we close the map and we're volatile, there's no need to flush * the cache as the data is going to be gone soon anyway. */ if (!close_map || !dw->is_volatile) { dbmw_sync(dw, DBMW_SYNC_CACHE); } dbmw_clear_cache(dw); hash_list_free(&dw->keys); map_destroy(dw->values); if (dw->mb) pmsg_free(dw->mb); bstr_free(&dw->bs); if (close_map) dbmap_destroy(dw->dm); WFREE_TYPE_NULL(dw->dbmap_dbg); dw->magic = 0; WFREE(dw); }
/** * The heartbeat of our callout queue. * * Called to notify us about the elapsed "time" so that we can expire timeouts * and maintain our notion of "current time". * * NB: The time maintained by the callout queue is "virtual". It's the * elapased delay given by regular calls to cq_clock() that define its unit. * For gtk-gnutella, the time unit is the millisecond. */ static void cq_clock(cqueue_t *cq, int elapsed) { int bucket; int last_bucket, old_last_bucket; struct chash *ch, *old_current; cevent_t *ev; cq_time_t now; int processed = 0; cqueue_check(cq); g_assert(elapsed >= 0); g_assert(mutex_is_owned(&cq->cq_lock)); /* * Recursive calls are possible: in the middle of an event, we could * trigger something that will call cq_dispatch() manually for instance. * * Therefore, we save the cq_current and cq_last_bucket fields upon * entry and restore them at the end as appropriate. If cq_current is * NULL initially, it means we were not in the middle of any recursion * so we won't have to restore cq_last_bucket. * * Note that we enforce recursive calls to cq_clock() to be on the * same thread due to the use of a mutex. However, each initial run of * cq_clock() could happen on a different thread each time. */ old_current = cq->cq_current; old_last_bucket = cq->cq_last_bucket; cq->cq_ticks++; cq->cq_time += elapsed; now = cq->cq_time; bucket = cq->cq_last_bucket; /* Bucket we traversed last time */ ch = &cq->cq_hash[bucket]; last_bucket = EV_HASH(now); /* Last bucket to traverse now */ /* * If `elapsed' has overflowed the hash size, then we'll need to look at * all the buckets in the table (wrap around). */ if (EV_OVER(elapsed)) last_bucket = bucket; /* * Since the hashed time is a not a strictly monotonic function of time, * we have to rescan the last bucket, in case the earliest event have * expired now, before moving forward. */ cq->cq_current = ch; while ((ev = ch->ch_head) && ev->ce_time <= now) { cq_expire(ev); processed++; } /* * If we don't have to move forward (elapsed is too small), we're done. */ if (cq->cq_last_bucket == last_bucket && !EV_OVER(elapsed)) goto done; cq->cq_last_bucket = last_bucket; do { ch++; if (++bucket >= HASH_SIZE) { bucket = 0; ch = cq->cq_hash; } /* * Since each bucket is sorted, we can stop our walkthrough as * soon as we reach an event scheduled after `now'. */ cq->cq_current = ch; while ((ev = ch->ch_head) && ev->ce_time <= now) { cq_expire(ev); processed++; } } while (bucket != last_bucket); done: cq->cq_current = old_current; if G_UNLIKELY(old_current != NULL) cq->cq_last_bucket = old_last_bucket; /* Was in recursive call */ if (cq_debug() > 5) { s_debug("CQ: %squeue \"%s\" %striggered %d event%s (%d item%s)", cq->cq_magic == CSUBQUEUE_MAGIC ? "sub" : "", cq->cq_name, NULL == old_current ? "" : "recursively", processed, 1 == processed ? "" : "s", cq->cq_items, 1 == cq->cq_items ? "" : "s"); } mutex_unlock(&cq->cq_lock); /* * Run idle callbacks if nothing was processed. * * Note that we released the mutex before running idle callbacks, to let * concurrent threads register callout events. */ if (0 == processed) cq_run_idle(cq); }
/** * Check which of qsort(), xqsort(), xsort() or smsort() is best for sorting * aligned arrays with a native item size of OPSIZ. At identical performance * level, we prefer our own sorting algorithms instead of libc's qsort() for * memory allocation purposes. * * @param items amount of items to use in the sorted array * @param idx index of the virtual routine to update * @param verbose whether to be verbose * @param which either "large" or "small", for logging */ static void vsort_init_items(size_t items, unsigned idx, int verbose, const char *which) { struct vsort_testing tests[] = { { vsort_qsort, qsort, 0.0, 0, "qsort" }, { vsort_xqsort, xqsort, 0.0, 2, "xqsort" }, { vsort_xsort, xsort, 0.0, 1, "xsort" }, { vsort_tqsort, tqsort, 0.0, 1, "tqsort" }, { vsort_smsort, smsort, 0.0, 1, "smsort" }, /* Only for almost sorted */ }; size_t len = items * OPSIZ; struct vsort_timing vt; size_t loops, highest_loops; unsigned i; g_assert(uint_is_non_negative(idx)); g_assert(idx < N_ITEMS(vsort_table)); vt.data = vmm_alloc(len); vt.copy = vmm_alloc(len); vt.items = items; vt.isize = OPSIZ; vt.len = len; random_bytes(vt.data, len); highest_loops = loops = vsort_loops(items); /* The -1 below is to avoid benchmarking smsort() for the general case */ retry_random: for (i = 0; i < N_ITEMS(tests) - 1; i++) { tests[i].v_elapsed = vsort_timeit(tests[i].v_timer, &vt, &loops); if (verbose > 1) { s_debug("%s() took %.4f secs for %s array (%zu loops)", tests[i].v_name, tests[i].v_elapsed * loops, which, loops); } if (loops != highest_loops) { highest_loops = loops; /* Redo all the tests if the number of timing loops changes */ if (i != 0) goto retry_random; } } /* * When dealing with a large amount of items, redo the tests twice with * another set of random bytes to make sure we're not hitting a special * ordering case. */ if (items >= VSORT_ITEMS) { unsigned j; for (j = 0; j < 2; j++) { random_bytes(vt.data, len); for (i = 0; i < N_ITEMS(tests) - 1; i++) { tests[i].v_elapsed += vsort_timeit(tests[i].v_timer, &vt, &loops); if (verbose > 1) { s_debug("%s() spent %.6f secs total for %s array", tests[i].v_name, tests[i].v_elapsed, which); } if (loops != highest_loops) { highest_loops = loops; /* Redo all the tests if the number of loops changes */ s_info("%s(): restarting %s array tests with %zu loops", G_STRFUNC, which, loops); goto retry_random; } } } } xqsort(tests, N_ITEMS(tests) - 1, sizeof tests[0], vsort_testing_cmp); vsort_table[idx].v_sort = vsort_routine(tests[0].v_routine, items); if (verbose) { s_info("vsort() will use %s() for %s arrays", vsort_routine_name(tests[0].v_name, items), which); } /* * Now sort the data, then randomly perturb them by swapping a few items * so that the array is almost sorted. */ xqsort(vt.data, vt.items, vt.isize, vsort_long_cmp); vsort_perturb_sorted_array(vt.data, vt.items, vt.isize); retry_sorted: for (i = 0; i < N_ITEMS(tests); i++) { tests[i].v_elapsed = vsort_timeit(tests[i].v_timer, &vt, &loops); if (verbose > 1) { s_debug("%s() on almost-sorted took %.4f secs " "for %s array (%zu loops)", tests[i].v_name, tests[i].v_elapsed * loops, which, loops); } if (loops != highest_loops) { highest_loops = loops; /* Redo all the tests if the number of timing loops changes */ if (i != 0) goto retry_sorted; } } xqsort(tests, N_ITEMS(tests), sizeof tests[0], vsort_testing_cmp); vsort_table[idx].v_sort_almost = vsort_routine(tests[0].v_routine, items); if (verbose) { s_info("vsort_almost() will use %s() for %s arrays", vsort_routine_name(tests[0].v_name, items), which); } vmm_free(vt.data, len); vmm_free(vt.copy, len); }
/** * Open configuration file, renaming it as ".orig" when `renaming' is TRUE. * If configuration file cannot be found, try opening the ".orig" variant * if already present and `renaming' is TRUE. * If not found, try with successive alternatives, if supplied. * * @attention * NB: the supplied `fv' argument is a vector of `fvcnt' elements. Items * with a NULL `dir' field are ignored, but fv[0].dir cannot be NULL. * * @param what is what is being opened, for logging purposes. * @param fv is a vector of files to try to open, in sequence * @param fvcnt is the size of the vector * @param renaming indicates whether the opened file should be renamed .orig. * @param chosen is filled with the index of the chosen path in the vector, * unless NULL is given. * * @return opened FILE, or NULL if we were unable to open any. `chosen' is * only filled if the file is opened. */ static FILE * open_read( const char *what, const file_path_t *fv, int fvcnt, bool renaming, int *chosen) { FILE *in; char *path; char *path_orig; const char *instead = empty_str; int idx = 0; g_assert(fv != NULL); g_assert(fvcnt >= 1); g_assert(fv->dir != NULL); path = make_pathname(fv->dir, fv->name); if (!is_absolute_path(path)) { HFREE_NULL(path); return NULL; } path_orig = h_strdup_printf("%s.%s", path, orig_ext); in = fopen(path, "r"); if (in != NULL) { if (renaming) { if (is_running_on_mingw()) { /* Windows can't rename an opened file */ fclose(in); in = NULL; } if (-1 == rename(path, path_orig)) { s_warning("[%s] could not rename \"%s\" as \"%s\": %m", what, path, path_orig); if (is_running_on_mingw()) in = fopen(path, "r"); } else { if (is_running_on_mingw()) in = fopen(path_orig, "r"); } if (is_running_on_mingw() && NULL == in) { s_warning("[%s] cannot reopen \"%s\": %m", what, path); goto open_failed; } } goto out; } /* FALL THROUGH */ open_failed: if (ENOENT == errno) { if (common_dbg > 0) { s_debug("[%s] cannot load non-existent \"%s\"", what, path); } } else { instead = instead_str; /* Regular file was present */ s_warning("[%s] failed to retrieve from \"%s\": %m", what, path); } if (fvcnt > 1 && common_dbg > 0) s_debug("[%s] trying to load from alternate locations...", what); /* * Maybe we crashed after having retrieved the file in a previous run * but before being able to write it again correctly? Try to open the * ".orig" file instead. */ g_assert(in == NULL); if (renaming) in = fopen(path_orig, "r"); /* The ".orig", in case of a crash */ if (in != NULL) { instead = instead_str; HFREE_NULL(path); path = path_orig; path_orig = NULL; } /* * Try with alternatives, if supplied. */ if (in == NULL && fvcnt > 1) { const file_path_t *xfv; int xfvcnt; instead = instead_str; for (xfv = fv + 1, xfvcnt = fvcnt - 1; xfvcnt; xfv++, xfvcnt--) { HFREE_NULL(path); if (NULL == xfv->dir) /* In alternatives, dir may be NULL */ continue; path = make_pathname(xfv->dir, xfv->name); idx++; if (NULL != path && NULL != (in = fopen(path, "r"))) break; if (path != NULL && common_dbg > 0) { s_debug("[%s] cannot load non-existent \"%s\" either", what, path); } } } if (common_dbg > 0) { if (in) { s_debug("[%s] retrieving from \"%s\"%s", what, path, instead); } else if (instead == instead_str) { s_debug("[%s] unable to retrieve: tried %d alternate location%s", what, fvcnt, plural(fvcnt)); } else { s_debug("[%s] unable to retrieve: no alternate locations known", what); } } out: HFREE_NULL(path); HFREE_NULL(path_orig); if (in != NULL && chosen != NULL) *chosen = idx; return in; }
/** * Create a new DBM wrapper over already created DB map. * * If value_data_size is 0, the length for value_size is used. * * @param dm The database (already opened) * @param name Database name, for logs * @param value_size Maximum value size, in bytes (structure) * @param value_data_size Maximum value size, in bytes (serialized form) * @param pack Serialization routine for values * @param unpack Deserialization routine for values * @param valfree Free routine for value (or NULL if none needed) * @param cache_size Amount of items to cache (0 = no cache, 1 = default) * @param hash_func Key hash function * @param eq_func Key equality test function * * If serialization and deserialization routines are NULL pointers, data * will be stored and retrieved as-is. In that case, they must be both * NULL. */ dbmw_t * dbmw_create(dbmap_t *dm, const char *name, size_t value_size, size_t value_data_size, dbmw_serialize_t pack, dbmw_deserialize_t unpack, dbmw_free_t valfree, size_t cache_size, hash_fn_t hash_func, eq_fn_t eq_func) { dbmw_t *dw; g_assert(pack == NULL || value_size); g_assert((pack != NULL) == (unpack != NULL)); g_assert(valfree == NULL || unpack != NULL); g_assert(dm); WALLOC0(dw); dw->magic = DBMW_MAGIC; dw->dm = dm; dw->name = name; dw->key_size = dbmap_key_size(dm); dw->key_len = dbmap_key_length(dm); dw->value_size = value_size; dw->value_data_size = 0 == value_data_size ? value_size : value_data_size; /* Make sure we do not violate the SDBM constraint */ g_assert(sdbm_is_storable(dw->key_size, dw->value_data_size)); /* * There must be a serialization routine if the serialized length is not * the same as the structure length. */ g_assert(dw->value_size == dw->value_data_size || pack != NULL); /* * For a small amount of items, a PATRICIA tree is more efficient * than a hash table although it uses more memory. */ if ( NULL == dw->key_len && dw->key_size * 8 <= PATRICIA_MAXBITS && cache_size <= DBMW_CACHE ) { dw->values = map_create_patricia(dw->key_size * 8); } else { dw->values = map_create_hash(hash_func, eq_func); } dw->keys = hash_list_new(hash_func, eq_func); dw->pack = pack; dw->unpack = unpack; dw->valfree = valfree; /* * If a serialization routine is provided, we'll also have a need for * deserialization. Allocate the message in/out streams. * * We're allocating one more byte than necessary to be able to check * whether serialization stays within the imposed boundaries. */ if (dw->pack) { dw->bs = bstr_create(); dw->mb = pmsg_new(PMSG_P_DATA, NULL, dw->value_data_size + 1); } /* * If cache_size is zero, we won't cache anything but the latest * value requested, in deserialized form. If modified, it will be * written back immediately. * * If cache_size is one, use the default (DBMW_CACHE). * * Any other value is used as-is. */ if (0 == cache_size) dw->max_cached = 1; /* No cache, only keep latest around */ else if (cache_size == 1) dw->max_cached = DBMW_CACHE; else dw->max_cached = cache_size; if (common_dbg) s_debug("DBMW created \"%s\" with %s back-end " "(max cached = %zu, key=%zu bytes, value=%zu bytes, " "%zu max serialized)", dw->name, dbmw_map_type(dw) == DBMAP_SDBM ? "sdbm" : "map", dw->max_cached, dw->key_size, dw->value_size, dw->value_data_size); return dw; }