Ejemplo n.º 1
0
/**
 * 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;
		}
	}
Ejemplo n.º 2
0
/**
 * 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);
}
Ejemplo n.º 3
0
/**
 * 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);
}
Ejemplo n.º 4
0
/**
 * 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);
}
Ejemplo n.º 5
0
/**
 * 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;
}
Ejemplo n.º 6
0
/**
 * 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;
}