/** * Record a SHA1 for publishing. */ void publisher_add(const sha1_t *sha1) { struct publisher_entry *pe; struct pubdata *pd; g_assert(sha1 != NULL); if (NULL == db_pubdata) return; /* Shutdowning */ /* * If already known, ignore silently. */ if (hikset_lookup(publisher_sha1, sha1)) return; /* * Create persistent publishing data if none known already. */ pd = get_pubdata(sha1); if (NULL == pd) { struct pubdata new_pd; new_pd.next_enqueue = 0; new_pd.expiration = 0; dbmw_write(db_pubdata, sha1, &new_pd, sizeof new_pd); if (GNET_PROPERTY(publisher_debug) > 2) { g_debug("PUBLISHER allocating new SHA-1 %s", sha1_to_string(sha1)); } } else { if (GNET_PROPERTY(publisher_debug) > 2) { time_delta_t enqueue = delta_time(pd->next_enqueue, tm_time()); time_delta_t expires = delta_time(pd->expiration, tm_time()); g_debug("PUBLISHER existing SHA-1 %s, next enqueue %s%s, %s%s", sha1_to_string(sha1), enqueue > 0 ? "in " : "", enqueue > 0 ? compact_time(enqueue) : "now", pd->expiration ? (expires > 0 ? "expires in " : "expired") : "not published", expires > 0 ? compact_time2(expires) : ""); } } /* * New entry will be processed immediately. */ pe = publisher_entry_alloc(sha1); hikset_insert_key(publisher_sha1, &pe->sha1); publisher_handle(pe); }
/** * Retry publishing after some delay. * * @param pe the entry to publish * @param delay delay in seconds * @param msg if non-NULL, logging message explaining the delay */ static void publisher_retry(struct publisher_entry *pe, int delay, const char *msg) { struct pubdata *pd; publisher_check(pe); g_assert(NULL == pe->publish_ev); g_assert(delay > 0); pd = get_pubdata(pe->sha1); if (pd != NULL) { pd->next_enqueue = time_advance(tm_time(), UNSIGNED(delay)); dbmw_write(db_pubdata, pe->sha1, pd, sizeof *pd); } pe->publish_ev = cq_insert(publish_cq, delay * 1000, handle_entry, pe); pe->last_delayed = tm_time(); if (GNET_PROPERTY(publisher_debug) > 3) { shared_file_t *sf = shared_file_by_sha1(pe->sha1); g_debug("PUBLISHER will retry SHA-1 %s %s\"%s\" in %s: %s", sha1_to_string(pe->sha1), (sf && sf != SHARE_REBUILDING && shared_file_is_partial(sf)) ? "partial " : "", (sf && sf != SHARE_REBUILDING) ? shared_file_name_nfc(sf) : "", compact_time(delay), msg != NULL ? msg : "<no reason>"); shared_file_unref(&sf); } }
static void test_file_hash(unsigned int megs, const char *expect) { int fd; size_t i; char name[32]; uint8_t result[20]; strcpy(name, "hashdata.XXXXXX"); fd = gg_mkstemp(name); if (fd == -1) { fprintf(stderr, "Unable to create temporary file\n"); exit(1); } for (i = 1; i <= megs; i++) { unsigned char j; if (lseek(fd, i * 1048756 - 1, SEEK_SET) == (off_t) -1) { fprintf(stderr, "Unable to seek past end of file\n"); goto fail; } j = i; if (write(fd, &j, sizeof(j)) != sizeof(j)) { fprintf(stderr, "Unable to write past end of file\n"); goto fail; } } if (gg_file_hash_sha1(fd, result) == -1) { fprintf(stderr, "gg_file_hash_sha1() failed for %d megs\n", megs); goto fail; } if (!sha1_compare(result, expect)) { printf("hash failed for %d mesgs, expected %s, got %s\n", megs, expect, sha1_to_string(result)); goto fail; } close(fd); unlink(name); return; fail: close(fd); unlink(name); exit(1); }
/** * Delete pubdata from database. */ static void delete_pubdata(const sha1_t *sha1) { dbmw_delete(db_pubdata, sha1); if (GNET_PROPERTY(publisher_debug) > 2) { shared_file_t *sf = shared_file_by_sha1(sha1); g_debug("PUBLISHER SHA-1 %s %s\"%s\" reclaimed", sha1_to_string(sha1), (sf && sf != SHARE_REBUILDING && shared_file_is_partial(sf)) ? "partial " : "", (sf && sf != SHARE_REBUILDING) ? shared_file_name_nfc(sf) : ""); shared_file_unref(&sf); } }
static void test_login_hash(const char *password, uint32_t seed, const char *expect) { uint8_t result[20]; if (gg_login_hash_sha1_2(password, seed, result) == -1) { fprintf(stderr, "gg_login_hash_sha1_2() failed for \"%s\", 0x%08x\n", password, seed); exit(1); } if (!sha1_compare(result, expect)) { printf("hash failed for \"%s\", 0x%08x, expected %s, got %s\n", password, seed, expect, sha1_to_string(result)); exit(1); } }
/** * Handle a SHA-1 entry, publishing its alt-loc to the DHT if still shared. */ static void publisher_handle(struct publisher_entry *pe) { shared_file_t *sf; bool is_partial = FALSE; int alt_locs; time_delta_t min_uptime; uint32 avg_uptime; publisher_check(pe); g_assert(NULL == pe->publish_ev); sf = shared_file_by_sha1(pe->sha1); /* * Remove SHA1 if no longer shared. */ if (NULL == sf) { fileinfo_t *fi = file_info_by_sha1(pe->sha1); /* * If a partial file has lees than the minimum amount of data for PFSP, * shared_file_by_sha1() will return NULL, hence we need to explicitly * check for existence through file_info_by_sha1() and that the file * still exists. */ if (fi != NULL && file_exists(fi->pathname)) { /* Waiting for more data to be able to share, or PFSP re-enabled */ publisher_retry(pe, PUBLISH_BUSY, "partial file missing"); return; } if (GNET_PROPERTY(publisher_debug)) { g_debug("PUBLISHER SHA-1 %s is no longer shared", sha1_to_string(pe->sha1)); } publisher_entry_free(pe, TRUE); return; } /* * Wait when rebuilding the library. */ if (SHARE_REBUILDING == sf) { publisher_retry(pe, PUBLISH_BUSY, "library being rebuilt"); return; } is_partial = shared_file_is_partial(sf); /* * If the SHA1 is not available, wait. */ if ( !is_partial && (!sha1_hash_available(sf) || !sha1_hash_is_uptodate(sf)) ) { publisher_retry(pe, PUBLISH_BUSY, "SHA-1 of file unknown yet"); goto done; } /* * Look whether this node has a sufficient average uptime. * * We're stricter to publish partial files because we want to favor * publishing of full files in the DHT, and the benefits of publishing * partial entries come only if we're up for a long enough time. * * Since publishing imposes lookup traffic in the DHT, it is not efficient * to have transient nodes publish file sharing information because this * will likely never be useful. */ min_uptime = PUBLISH_TRANSIENT; if (is_partial) min_uptime *= 2; avg_uptime = get_average_servent_uptime(tm_time()); if (avg_uptime < UNSIGNED(min_uptime)) { time_delta_t delay = min_uptime - avg_uptime; delay = MAX(delay, PUBLISH_BUSY); publisher_retry(pe, delay, "minimum average uptime not reached yet"); goto done; } /* * If we are dealing with a file for which we know enough alternate * locations, assume it is popular and do not publish it yet. * * We do not publish the SHA-1 of a partial file for which we know * of at least two alternate locations because the purpose of us publishing * these partial SHA-1s is to attract other PFSP-aware hosts and * recreate a mesh. */ alt_locs = dmesh_count(pe->sha1); is_partial = is_partial && !shared_file_is_finished(sf); if (alt_locs > (is_partial ? PUBLISH_PARTIAL_MAX : PUBLISH_DMESH_MAX)) { if (GNET_PROPERTY(publisher_debug)) { g_debug("PUBLISHER SHA-1 %s %s\"%s\" has %d download mesh " "entr%s, skipped", sha1_to_string(pe->sha1), is_partial ? "partial " : "", shared_file_name_nfc(sf), alt_locs, plural_y(alt_locs)); } publisher_hold(pe, PUBLISH_POPULAR, "popular file"); goto done; } /* * If the DHT is not enabled, postpone processing. */ if (!dht_enabled()) { publisher_hold(pe, PUBLISH_BUSY, "DHT disabled"); goto done; } /* * If this is a partial file for which we have less than the minimum * for PFSP sharing, or if PFSP has been disabled, skip it. */ if (shared_file_is_partial(sf)) { fileinfo_t *fi = shared_file_fileinfo(sf); if ( !file_info_partial_shareable(fi) || fi->done < GNET_PROPERTY(pfsp_minimum_filesize) ) { publisher_hold(pe, PUBLISH_BUSY, "PFSP minima not reached"); goto done; } } /* * Check whether it is time to process the entry, in case we're * restarting quickly after a shutdown. */ if (0 == pe->last_publish) { struct pubdata *pd = get_pubdata(pe->sha1); if (pd != NULL) { time_t now = tm_time(); time_delta_t enqueue = delta_time(pd->next_enqueue, now); time_delta_t expire = delta_time(pd->expiration, now); if (enqueue > 0 && (0 == pd->expiration || expire > 0)) { int delay = MIN(enqueue, PUBLISH_POPULAR); if (pd->expiration != 0) delay = MIN(delay, expire); if (GNET_PROPERTY(publisher_debug) > 1) { g_debug("PUBLISHER SHA-1 %s delayed by %s", sha1_to_string(pe->sha1), compact_time(enqueue)); } publisher_retry(pe, delay, "first-time delay"); goto done; } } } /* * Cancel possible remaining backgrounded publishing. */ if (pe->backgrounded) { pdht_cancel_file(pe->sha1, FALSE); pe->backgrounded = FALSE; } /* * OK, we can publish this alternate location. */ if (pe->last_publish) { if (GNET_PROPERTY(publisher_debug) > 2) { g_debug("PUBLISHER SHA-1 %s re-enqueued %d secs " "after last publish", sha1_to_string(pe->sha1), (int) delta_time(tm_time(), pe->last_publish)); } } pe->last_enqueued = tm_time(); pdht_publish_file(sf, publisher_done, pe); /* FALL THROUGH */ done: shared_file_unref(&sf); }
/** * Publishing callback invoked when asynchronous publication is completed, * or ended with an error. * * @return TRUE if we accept the publishing, FALSE otherwise to get the * publishing layer to continue attempts to failed STORE roots and report * on progress using the same callback. */ static bool publisher_done(void *arg, pdht_error_t code, const pdht_info_t *info) { struct publisher_entry *pe = arg; struct pubdata *pd; int delay = PUBLISH_BUSY; bool expired = FALSE; bool accepted = TRUE; publisher_check(pe); pd = get_pubdata(pe->sha1); /* * Update stats on republishing before value expiration. */ if (PDHT_E_OK == code) { if (pe->last_publish && info->roots > 0) { if (pd != NULL) { if (pd->expiration && delta_time(tm_time(), pd->expiration) > 0) expired = TRUE; } else { time_delta_t elapsed = delta_time(tm_time(), pe->last_publish); if (elapsed > DHT_VALUE_ALOC_EXPIRE) expired = TRUE; } if (expired) gnet_stats_inc_general(GNR_DHT_REPUBLISHED_LATE); } } /* * Compute retry delay. */ switch (code) { case PDHT_E_OK: /* * If we were not able to publish to KDA_K nodes, decrease the * delay before republishing. We use a non-linear decimation of * the republish time, as a function of the number of nodes to which * we could publish. */ delay = publisher_delay(info, DHT_VALUE_ALOC_EXPIRE); accepted = publisher_is_acceptable(info); break; case PDHT_E_POPULAR: /* * Compute the suitable delay: the first time, we use PUBLISH_POPULAR, * and then we double each time until we reach PUBLISH_POPULAR_MAX. * * If we already tried to publish the entry, pe->last_delayed will * be non-zero. */ if (0 != pe->last_delayed) { time_delta_t elapsed = delta_time(tm_time(), pe->last_delayed); if (elapsed < PUBLISH_POPULAR) { delay = PUBLISH_POPULAR; } else if (elapsed >= PUBLISH_POPULAR_MAX / 2) { delay = PUBLISH_POPULAR_MAX; } else { delay = elapsed * 2; } } else { delay = PUBLISH_POPULAR; } break; case PDHT_E_NOT_SHARED: case PDHT_E_LOOKUP_EXPIRED: case PDHT_E_LOOKUP: case PDHT_E_UDP_CLOGGED: case PDHT_E_PUBLISH_EXPIRED: case PDHT_E_PUBLISH_ERROR: case PDHT_E_SHA1: case PDHT_E_PENDING: case PDHT_E_CANCELLED: case PDHT_E_GGEP: case PDHT_E_NONE: delay = PUBLISH_BUSY; break; case PDHT_E_MAX: g_assert_not_reached(); } /* * For a backgrounded entry publishing, we need to adjust the computed * delay with the time that was elapsed */ g_assert(!pe->backgrounded == !(pe->publish_ev != NULL)); if (pe->backgrounded) { time_delta_t elapsed = delta_time(tm_time(), pe->last_delayed); g_assert(pe->last_delayed > 0); cq_cancel(&pe->publish_ev); if (delay > elapsed) { delay -= elapsed; } else { delay = 1; } } /* * Logging. */ if (GNET_PROPERTY(publisher_debug) > 1) { shared_file_t *sf = shared_file_by_sha1(pe->sha1); char retry[80]; char after[80]; const char *late = ""; after[0] = '\0'; if (pe->last_publish) { time_delta_t elapsed = delta_time(tm_time(), pe->last_publish); str_bprintf(after, sizeof after, " after %s", compact_time(elapsed)); if (pd != NULL) { if (expired) late = "late, "; } else { late = "no data, "; } } str_bprintf(retry, sizeof retry, "%s", compact_time(delay)); g_debug("PUBLISHER SHA-1 %s %s%s\"%s\" %spublished to %u node%s%s: %s" " (%stook %s, total %u node%s, proba %.3f%%, retry in %s," " %s bg, path %u) [%s]", sha1_to_string(pe->sha1), pe->backgrounded ? "[bg] " : "", (sf && sf != SHARE_REBUILDING && shared_file_is_partial(sf)) ? "partial " : "", (sf && sf != SHARE_REBUILDING) ? shared_file_name_nfc(sf) : "", pe->last_publish ? "re" : "", info->roots, plural(info->roots), after, pdht_strerror(code), late, compact_time(delta_time(tm_time(), pe->last_enqueued)), info->all_roots, plural(info->all_roots), info->presence * 100.0, retry, info->can_bg ? "can" : "no", info->path_len, accepted ? "OK" : "INCOMPLETE"); shared_file_unref(&sf); } /* * Update last publishing time and remember expiration time. */ if (PDHT_E_OK == code && info->roots > 0) { pe->last_publish = tm_time(); if (pd != NULL) { pd->expiration = time_advance(pe->last_publish, DHT_VALUE_ALOC_EXPIRE); dbmw_write(db_pubdata, pe->sha1, pd, sizeof *pd); } } /* * If entry was deemed popular, we're going to delay its republishing * by a larger amount of time and any data we published already about * it will surely expire. Since this is our decision, we do not want * to be told that republishing, if it occurs again, was done later than * required. Hence call publisher_hold() to mark that we don't care. */ if (PDHT_E_POPULAR == code) publisher_hold(pe, delay, "popular entry"); else publisher_retry(pe, delay, accepted ? "accepted publish" : "published"); pe->backgrounded = !accepted; return accepted; }