/** * @return NULL on error, a newly allocated string via halloc() otherwise. */ static char * uhc_get_next(void) { struct uhc *uhc; char *host; time_t now; size_t n; g_return_val_if_fail(uhc_list, NULL); now = tm_time(); n = hash_list_count(uhc_list); if (0 == n) return NULL; /* * Wait UHC_RETRY_AFTER secs before contacting the UHC again. * Can't be too long because the UDP reply may get lost if the * requesting host already has a saturated b/w. * If we come here, it's because we're lacking hosts for establishing * a Gnutella connection, after we exhausted our caches. */ while (n-- != 0) { uhc = hash_list_head(uhc_list); g_assert(uhc != NULL); /* We computed count on entry */ if (delta_time(now, uhc->stamp) >= UHC_RETRY_AFTER) goto found; hash_list_moveto_tail(uhc_list, uhc); } return NULL; found: uhc->stamp = now; host = h_strdup(uhc->host); if (uhc->used < UHC_MAX_ATTEMPTS) { uhc->used++; hash_list_moveto_tail(uhc_list, uhc); } else { hash_list_remove(uhc_list, uhc); uhc_free(&uhc); } return host; }
/** * Write value to the database file, possibly caching it and deferring write. * * Any registered value cleanup callback will be invoked right after the value * is written to disk (for immediated writes) or removed from the cache (for * deferred writes). * * @param dw the DBM wrapper * @param key the key (constant-width, determined at open time) * @param value the start of the value in memory * @param length length of the value */ void dbmw_write(dbmw_t *dw, gconstpointer key, gpointer value, size_t length) { struct cached *entry; dbmw_check(dw); g_assert(key); g_assert(length <= dw->value_size); g_assert(length || value == NULL); g_assert(length == 0 || value); dw->w_access++; entry = map_lookup(dw->values, key); if (entry) { if (entry->dirty) dw->w_hits++; else if (entry->absent) dw->count_needs_sync = TRUE; /* Key exists now */ fill_entry(dw, entry, value, length); hash_list_moveto_tail(dw->keys, key); } else if (dw->max_cached > 1) { entry = allocate_entry(dw, key, NULL); fill_entry(dw, entry, value, length); dw->count_needs_sync = TRUE; /* Does not know whether key exists */ } else { write_immediately(dw, key, value, length); } }
/** * Delete key from database. */ void dbmw_delete(dbmw_t *dw, gconstpointer key) { struct cached *entry; dbmw_check(dw); g_assert(key); dw->w_access++; entry = map_lookup(dw->values, key); if (entry) { if (entry->dirty) dw->w_hits++; if (!entry->absent) { dw->count_needs_sync = TRUE; /* Deferred delete */ fill_entry(dw, entry, NULL, 0); entry->absent = TRUE; } hash_list_moveto_tail(dw->keys, key); } else { dw->ioerr = FALSE; dbmap_remove(dw->dm, key); if (dbmap_has_ioerr(dw->dm)) { dw->ioerr = TRUE; dw->error = errno; g_warning("DBMW \"%s\" I/O error whilst deleting key: %s", dw->name, dbmap_strerror(dw->dm)); } /* * If the maximum value length of the DB is 0, then it is used as a * "search table" only, meaning there will be no read to get values, * only existence checks. * * Therefore, it makes sense to cache that the key is no longer valid. * Otherwise, possibly pushing a value out of the cache to record * a deletion is not worth it. */ if (0 == dw->value_size) { WALLOC0(entry); entry->absent = TRUE; (void) allocate_entry(dw, key, entry); } } }
/** * Write value to the database file, possibly caching it and deferring write. * * Any registered value cleanup callback will be invoked right after the value * is written to disk (for immediated writes) or removed from the cache (for * deferred writes). * * @param dw the DBM wrapper * @param key the key (constant-width, determined at open time) * @param value the start of the value in memory * @param length length of the value */ void dbmw_write(dbmw_t *dw, const void *key, void *value, size_t length) { struct cached *entry; dbmw_check(dw); g_assert(key); g_assert(length <= dw->value_size); g_assert(length || value == NULL); g_assert(length == 0 || value); dw->w_access++; entry = map_lookup(dw->values, key); if (entry) { if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_CACHING | DBG_DSF_UPDATE)) { dbg_ds_log(dw->dbg, dw, "%s: %s key=%s%s", G_STRFUNC, entry->dirty ? "dirty" : "clean", dbg_ds_keystr(dw->dbg, key, (size_t) -1), entry->absent ? " (was absent)" : ""); } if (entry->dirty) dw->w_hits++; if (entry->absent) dw->cached++; /* Key exists now, in unflushed status */ fill_entry(dw, entry, value, length); hash_list_moveto_tail(dw->keys, key); } else if (dw->max_cached > 1) { if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_CACHING | DBG_DSF_UPDATE)) { dbg_ds_log(dw->dbg, dw, "%s: deferring key=%s", G_STRFUNC, dbg_ds_keystr(dw->dbg, key, (size_t) -1)); } entry = allocate_entry(dw, key, NULL); fill_entry(dw, entry, value, length); dw->count_needs_sync = TRUE; /* Does not know whether key exists */ } else { if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_CACHING | DBG_DSF_UPDATE)) { dbg_ds_log(dw->dbg, dw, "%s: writing key=%s", G_STRFUNC, dbg_ds_keystr(dw->dbg, key, (size_t) -1)); } write_immediately(dw, key, value, length); } }
/** * Callback for adns_resolve(), invoked when the resolution is complete. */ static void uhc_host_resolved(const host_addr_t *addrs, size_t n, void *uu_udata) { (void) uu_udata; g_assert(addrs); /* * If resolution failed, try again if possible. */ if (0 == n) { if (GNET_PROPERTY(bootstrap_debug)) g_warning("could not resolve UDP host cache \"%s\"", uhc_ctx.host); uhc_try_next(); return; } if (n > 1) { size_t i; host_addr_t *hav; /* Current UHC was moved to tail by uhc_get_next() */ struct uhc *uhc = hash_list_tail(uhc_list); /* * UHC resolved to multiple endpoints. Could be roundrobbin or * IPv4 and IPv6 addresss. Adding them as seperate entries: if the * IPv6 is unreachable we have an opportunity to skip it. * -- JA 24/7/2011 * * Shuffle the address array before appending them to the UHC list. * --RAM, 2015-10-01 */ hav = HCOPY_ARRAY(addrs, n); SHUFFLE_ARRAY_N(hav, n); for (i = 0; i < n; i++) { const char *host = host_addr_port_to_string(hav[i], uhc_ctx.port); g_debug("BOOT UDP host cache \"%s\" resolved to %s (#%zu)", uhc_ctx.host, host, i + 1); uhc_list_append(host); } hash_list_remove(uhc_list, uhc); /* Replaced by IP address list */ uhc_free(&uhc); /* * We're going to continue and process the first address (in our * shuffled array). Make sure it is put at the end of the list * and marked as being used, mimicing what uhc_get_next() would do. * --RAM, 2015-10-01 */ { struct uhc key; key.host = host_addr_port_to_string(hav[0], uhc_ctx.port); uhc = hash_list_lookup(uhc_list, &key); g_assert(uhc != NULL); /* We added the entry above! */ uhc->stamp = tm_time(); uhc->used++; hash_list_moveto_tail(uhc_list, uhc); } uhc_ctx.addr = hav[0]; /* Struct copy */ HFREE_NULL(hav); } else { uhc_ctx.addr = addrs[0]; } if (GNET_PROPERTY(bootstrap_debug)) g_debug("BOOT UDP host cache \"%s\" resolved to %s", uhc_ctx.host, host_addr_to_string(uhc_ctx.addr)); /* * Now send the ping. */ uhc_send_ping(); }
/** * Delete key from database. */ void dbmw_delete(dbmw_t *dw, const void *key) { struct cached *entry; dbmw_check(dw); g_assert(key); dw->w_access++; entry = map_lookup(dw->values, key); if (entry) { if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_CACHING | DBG_DSF_DELETE)) { dbg_ds_log(dw->dbg, dw, "%s: %s key=%s%s", G_STRFUNC, entry->dirty ? "dirty" : "clean", dbg_ds_keystr(dw->dbg, key, (size_t) -1), entry->absent ? " (was absent)" : ""); } if (entry->dirty) dw->w_hits++; if (!entry->absent) { /* * Entry was present but is now deleted. * * If it was clean, then it was flushed to the database and we now * know that there is one less entry in the database than there is * physically present in the map. * * If it was dirty, then we do not know whether it exists in the * database or not, and therefore we cannot adjust the amount * of cached entries down. */ if (entry->dirty) dw->count_needs_sync = TRUE; /* Deferred delete */ else dw->cached--; /* One less entry in database */ fill_entry(dw, entry, NULL, 0); entry->absent = TRUE; } hash_list_moveto_tail(dw->keys, key); } else { if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_DELETE)) { dbg_ds_log(dw->dbg, dw, "%s: removing key=%s", G_STRFUNC, dbg_ds_keystr(dw->dbg, key, (size_t) -1)); } dw->ioerr = FALSE; dbmap_remove(dw->dm, key); if (dbmap_has_ioerr(dw->dm)) { dw->ioerr = TRUE; dw->error = errno; s_warning("DBMW \"%s\" I/O error whilst deleting key: %s", dw->name, dbmap_strerror(dw->dm)); } /* * If the maximum value length of the DB is 0, then it is used as a * "search table" only, meaning there will be no read to get values, * only existence checks. * * Therefore, it makes sense to cache that the key is no longer valid. * Otherwise, possibly pushing a value out of the cache to record * a deletion is not worth it. */ if (0 == dw->value_size) { WALLOC0(entry); entry->absent = TRUE; (void) allocate_entry(dw, key, entry); if (dbg_ds_debugging(dw->dbg, 2, DBG_DSF_CACHING)) { dbg_ds_log(dw->dbg, dw, "%s: cached absent key=%s", G_STRFUNC, dbg_ds_keystr(dw->dbg, key, (size_t) -1)); } } } }