Show downloads.c syntax highlighted
/*
* $Id: downloads.c 14748 2007-09-02 23:49:49Z cbiere $
*
* Copyright (c) 2001-2003, Raphael Manfredi
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
/**
* @ingroup core
* @file
*
* Handle downloads.
*
* @author Raphael Manfredi
* @date 2001-2007
*/
#include "common.h"
#include "sockets.h"
#include "downloads.h"
#include "hosts.h"
#include "routing.h"
#include "gmsg.h"
#include "bsched.h"
#include "huge.h"
#include "dmesh.h"
#include "file_object.h"
#include "http.h"
#include "version.h"
#include "ignore.h"
#include "ioheader.h"
#include "verify_sha1.h"
#include "verify_tth.h"
#include "move.h"
#include "settings.h"
#include "nodes.h"
#include "parq.h"
#include "token.h"
#include "hostiles.h"
#include "clock.h"
#include "uploads.h"
#include "ban.h"
#include "guid.h"
#include "pproxy.h"
#include "features.h"
#include "gnet_stats.h"
#include "geo_ip.h"
#include "bh_download.h"
#include "thex_download.h"
#include "tls_cache.h"
#include "udp.h"
#include "rx_inflate.h"
#include "vmsg.h"
#include "if/gnet_property.h"
#include "if/gnet_property_priv.h"
#include "if/bridge/c2ui.h"
#include "lib/adns.h"
#include "lib/array.h"
#include "lib/atoms.h"
#include "lib/base32.h"
#include "lib/dbus_util.h"
#include "lib/endian.h"
#include "lib/file.h"
#include "lib/getdate.h"
#include "lib/getline.h"
#include "lib/glib-missing.h"
#include "lib/hashlist.h"
#include "lib/idtable.h"
#include "lib/palloc.h"
#include "lib/magnet.h"
#include "lib/tigertree.h"
#include "lib/tm.h"
#include "lib/url.h"
#include "lib/utf8.h"
#include "lib/walloc.h"
#include "lib/override.h" /* Must be the last header included */
RCSID("$Id: downloads.c 14748 2007-09-02 23:49:49Z cbiere $")
#if defined(S_IROTH)
#define DOWNLOAD_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) /* 0644 */
#else
#define DOWNLOAD_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP) /* 0640 */
#endif
#define DOWNLOAD_MIN_OVERLAP 64 /**< Minimum overlap for safety */
#define DOWNLOAD_SHORT_DELAY 2 /**< Shortest retry delay */
#define DOWNLOAD_MAX_SINK 16384 /**< Max amount of data to sink */
#define DOWNLOAD_MAX_IGN_DATA 2097152 /**< Max amount of data to ignore */
#define DOWNLOAD_MAX_IGN_TIME 300 /**< Max amount of secs we can ignore */
#define DOWNLOAD_MAX_IGN_REQS 3 /**< How many mismatches per source */
#define DOWNLOAD_SERVER_HOLD 15 /**< Space requests to same server */
#define DOWNLOAD_DNS_LOOKUP 7200 /**< Period of server DNS lookups */
#define DOWNLOAD_STALLED 60 /**< Consider stalled after 60 secs */
#define DOWNLOAD_PING_DELAY 300 /**< Minimum delay for 2 HEAD pings */
#define DOWNLOAD_MAX_HEADER_EOF 5 /**< Max # of EOF in headers we allow */
#define DOWNLOAD_DATA_TIMEOUT 5 /**< Max # of data timeouts we allow */
#define DOWNLOAD_ALT_LOC_SIZE 1024 /**< Max size for alt locs */
#define IO_AVG_RATE 5 /**< Compute global recv rate every 5 secs */
static hash_list_t *sl_downloads; /**< All downloads (queued + unqueued) */
static hash_list_t *sl_unqueued; /**< Unqueued downloads only */
static GSList *sl_removed; /**< Removed downloads only */
static GSList *sl_removed_servers; /**< Removed servers only */
static gchar dl_tmp[4096];
static const gchar DL_OK_EXT[] = ".OK"; /**< Extension to mark OK files */
static const gchar DL_BAD_EXT[] = ".BAD"; /**< "Bad" files (SHA1 mismatch) */
static const gchar DL_UNKN_EXT[] = ".UNKN"; /**< For unchecked files */
static const gchar no_reason[] = "<no reason>"; /**< Don't translate this */
static void download_add_to_list(struct download *d, enum dl_list idx);
static gboolean download_send_push_request(struct download *d);
static gboolean download_read(struct download *d, pmsg_t *mb);
static gboolean download_ignore_data(struct download *d, pmsg_t *mb);
static void download_request(struct download *d, header_t *header, gboolean ok);
static void download_push_ready(struct download *d, getline_t *empty);
static void download_push_remove(struct download *d);
static void download_push(struct download *d, gboolean on_timeout);
static void download_resume_bg_tasks(void);
static void download_incomplete_header(struct download *d);
static gboolean has_blank_guid(const struct download *d);
static void download_verify_sha1(struct download *d);
static void download_verify_tigertree(struct download *d);
static gboolean download_get_server_name(struct download *d, header_t *header);
static gboolean use_push_proxy(struct download *d);
static void download_unavailable(struct download *d,
download_status_t new_status,
const gchar * reason, ...) G_GNUC_PRINTF(3, 4);
static void download_queue_delay(struct download *d, guint32 delay,
const gchar *fmt, ...) G_GNUC_PRINTF(3, 4);
static void download_queue_hold(struct download *d, guint32 hold,
const gchar *fmt, ...) G_GNUC_PRINTF(3, 4);
static void download_reparent(struct download *d, struct dl_server *new_server);
static gboolean download_flush(
struct download *d, gboolean *trimmed, gboolean may_stop);
static gboolean download_dirty = FALSE;
static gboolean download_shutdown = FALSE;
static void download_store(void);
static void download_retrieve(void);
gboolean
download_is_alive(const struct download *d)
{
download_check(d);
switch (d->status) {
case GTA_DL_INVALID:
/* This is the initial status... */
return TRUE;
case GTA_DL_ACTIVE_QUEUED:
case GTA_DL_CONNECTING:
case GTA_DL_HEADERS:
case GTA_DL_IGNORING:
case GTA_DL_PASSIVE_QUEUED:
case GTA_DL_PUSH_SENT:
case GTA_DL_FALLBACK:
case GTA_DL_QUEUED:
case GTA_DL_RECEIVING:
case GTA_DL_REQ_SENDING:
case GTA_DL_REQ_SENT:
case GTA_DL_SINKING:
case GTA_DL_TIMEOUT_WAIT:
return TRUE;
case GTA_DL_ABORTED:
case GTA_DL_COMPLETED:
case GTA_DL_DONE:
case GTA_DL_ERROR:
case GTA_DL_MOVE_WAIT:
case GTA_DL_MOVING:
case GTA_DL_VERIFIED:
case GTA_DL_VERIFYING:
case GTA_DL_VERIFY_WAIT:
case GTA_DL_REMOVED:
return FALSE;
}
g_assert_not_reached();
return FALSE;
}
static void
download_set_status(struct download *d, download_status_t status)
{
gboolean was_alive, is_alive;
download_check(d);
if (status == d->status)
return;
was_alive = download_is_alive(d);
d->status = status;
g_return_if_fail(d->file_info);
is_alive = download_is_alive(d);
if (is_alive != was_alive) {
fileinfo_t *fi = d->file_info;
g_assert(fi->refcount >= fi->lifecount);
fi->lifecount += is_alive ? 1 : -1;
g_assert(fi->refcount >= fi->lifecount);
}
fi_src_status_changed(d);
}
const gchar *
download_pathname(const struct download *d)
{
download_check(d);
file_info_check(d->file_info);
return d->file_info->pathname;
}
const gchar *
download_basename(const struct download *d)
{
return filepath_basename(download_pathname(d));
}
const char *
download_host_info(const struct download *d)
{
static char info[256];
char host[128];
host_addr_port_to_string_buf(download_addr(d), download_port(d),
host, sizeof host);
concat_strings(info, sizeof info,
"<", host, " \'", download_vendor_str(d), "\'>",
(void *) 0);
return info;
}
/*
* Download structures.
*
* This `dl_key' is inserted in the `dl_by_host' hash table were we find a
* `dl_server' structure describing all the downloads for the given host.
*
* All `dl_server' structures are also inserted in the `dl_by_time' struct,
* where hosts are sorted based on their retry time.
*
* The `dl_count_by_name' hash tables is indexed by name, and counts the
* amount of downloads scheduled with that name.
*/
static GHashTable *dl_by_host;
static GHashTable *dl_count_by_name;
#define DHASH_SIZE (1UL << 10) /**< Hash list size, must be a power of 2 */
#define DHASH_MASK (DHASH_SIZE - 1)
#define DL_HASH(x) ((x) & DHASH_MASK)
static struct {
GList *servers[DHASH_SIZE]; /**< Lists of servers, by retry time */
gint change[DHASH_SIZE]; /**< Counts changes to the list */
} dl_by_time;
/**
* To handle download meshes, where we only know the IP/port of the host and
* not its GUID, we need to be able to locate the server. We know that the
* IP will not be a private one.
*
* Therefore, for each (GUID, IP, port) tuple, where IP is NOT private, we
* store the (IP, port) => server association as well. There should be only
* one such entry, ever. If there is more, it means the server changed its
* GUID, which is possible, in which case we simply supersede the old entry.
*/
static GHashTable *dl_by_addr;
/**
* Keys in the `dl_by_addr' table.
*/
struct dl_addr {
host_addr_t addr; /**< IP address of server */
guint16 port; /**< Port of server */
};
static guint dl_establishing = 0; /**< Establishing downloads */
static guint dl_active = 0; /**< Active downloads */
static inline guint
count_running_downloads(void)
{
return dl_establishing + dl_active;
}
static inline guint
server_list_length(const struct dl_server *server, enum dl_list idx)
{
g_assert(dl_server_valid(server));
g_assert((guint) idx < DL_LIST_SZ);
return server->list[idx] ? list_length(server->list[idx]) : 0;
}
static inline guint
count_running_on_server(const struct dl_server *server)
{
return server_list_length(server, DL_LIST_RUNNING);
}
#define MAGIC_TIME 1 /**< For recreation upon startup */
/***
*** RX link callbacks
***/
static G_GNUC_PRINTF(2, 3) void
download_rx_error(gpointer o, const gchar *reason, ...)
{
struct download *d = o;
va_list args;
download_check(d);
va_start(args, reason);
download_stop_v(d, GTA_DL_ERROR, reason, args);
va_end(args);
}
static void
download_rx_got_eof(gpointer o)
{
struct download *d = o;
download_check(d);
download_got_eof(d);
}
/**
* RX data indication callback used to give us some new download traffic in a
* low-level message structure.
*
* @return FALSE if an error occurred.
*/
static gboolean
download_data_ind(rxdrv_t *rx, pmsg_t *mb)
{
struct download *d = rx_owner(rx);
g_assert(DOWNLOAD_IS_ACTIVE(d)); /* No I/O via RX stack otherwise */
return download_read(d, mb);
}
/**
* RX data indication callback used to give us some new download traffic in a
* low-level message structure.
*
* @return FALSE if an error occurred.
*/
static gboolean
download_ignore_data_ind(rxdrv_t *rx, pmsg_t *mb)
{
struct download *d = rx_owner(rx);
g_assert(DOWNLOAD_IS_ACTIVE(d)); /* No I/O via RX stack otherwise */
return download_ignore_data(d, mb);
}
static const struct rx_link_cb download_rx_link_cb = {
NULL, /* add_rx_given */
download_rx_error, /* read_error */
download_rx_got_eof, /* got_eof */
};
static void
download_chunk_rx_done(gpointer o)
{
struct download *d = o;
download_check(d);
download_got_eof(d);
}
static const struct rx_chunk_cb download_rx_chunk_cb = {
download_rx_error, /* chunk_error */
download_chunk_rx_done, /* chunk_end */
};
static const struct rx_inflate_cb download_rx_inflate_cb = {
NULL, /* add_rx_inflated */
download_rx_error, /* inflate_error */
};
/**
* Received data from outside the RX stack.
*/
static void
download_write(struct download *d, gpointer data, size_t len)
{
pdata_t *db;
pmsg_t *mb;
download_check(d);
/*
* Prepare data buffer to feed the RX stack.
*/
db = pdata_allocb_ext(data, len, pdata_free_nop, NULL);
mb = pmsg_alloc(PMSG_P_DATA, db, 0, len);
/*
* The message is given to the RX stack, and it will be freed by
* the last function consuming it.
*/
rx_recv(rx_bottom(d->rx), mb);
}
/**
* The only place to allocate a struct download.
*/
static struct download *
download_alloc(void)
{
static const struct download zero_download;
struct download *d;
d = walloc(sizeof *d);
*d = zero_download;
d->magic = DOWNLOAD_MAGIC;
return d;
}
/**
* The only place to release a struct download. The pointer will
* be nullified to prevent further access of the memory chunk through
* this pointer.
*
* @param d_ptr Pointer to a pointer holding a struct download.
*/
static void
download_free(struct download **d_ptr)
{
struct download *d;
g_assert(d_ptr);
d = *d_ptr;
download_check(d);
d->magic = 0;
wfree(d, sizeof *d);
*d_ptr = NULL;
}
/**
* Hashing of a `dl_key' structure.
*/
static guint
dl_key_hash(gconstpointer key)
{
const struct dl_key *k = key;
guint hash;
hash = guid_hash(k->guid);
hash ^= host_addr_hash(k->addr);
hash ^= (k->port << 16) | k->port;
return hash;
}
/**
* Comparison of `dl_key' structures.
*/
static gint
dl_key_eq(gconstpointer a, gconstpointer b)
{
const struct dl_key *ak = a, *bk = b;
return host_addr_equal(ak->addr, bk->addr) &&
ak->port == bk->port &&
guid_eq(ak->guid, bk->guid);
}
/**
* Hashing of a `dl_addr' structure.
*/
static guint
dl_addr_hash(gconstpointer key)
{
const struct dl_addr *k = key;
guint32 hash;
hash = host_addr_hash(k->addr);
hash ^= (k->port << 16) | k->port;
return (guint) hash;
}
/**
* Comparison of `dl_addr' structures.
*/
static gint
dl_addr_eq(gconstpointer a, gconstpointer b)
{
const struct dl_addr *ak = a, *bk = b;
return host_addr_equal(ak->addr, bk->addr) && ak->port == bk->port;
}
/**
* Compare two `download' structures based on the `retry_after' field.
* The smaller that time, the smaller the structure is.
*/
static gint
dl_retry_cmp(gconstpointer p, gconstpointer q)
{
const struct download *a = p, *b = q;
return CMP(a->retry_after, b->retry_after);
}
/**
* Compare two `dl_server' structures based on the `retry_after' field.
* The smaller that time, the smaller the structure is.
*/
static gint
dl_server_retry_cmp(gconstpointer p, gconstpointer q)
{
const struct dl_server *a = p, *b = q;
return CMP(a->retry_after, b->retry_after);
}
/**
* @returns whether download has a blank (fake) GUID.
*/
static gboolean
has_blank_guid(const struct download *d)
{
const gchar *g = download_guid(d);
guint i;
for (i = 0; i < GUID_RAW_SIZE; i++)
if (g[i])
return FALSE;
return TRUE;
}
gboolean
download_has_blank_guid(const struct download *d)
{
return d->server && has_blank_guid(d);
}
/**
* @returns whether download was faked to reparent a complete orphaned file.
*/
gboolean
is_faked_download(const struct download *d)
{
return !is_host_addr(download_addr(d)) &&
download_port(d) == 0 &&
has_blank_guid(d);
}
/**
* Was downloaded file verified to have a SHA1 matching the advertised one?
*/
static gboolean
has_good_sha1(const struct download *d)
{
fileinfo_t *fi = d->file_info;
return fi->sha1 == NULL || (fi->cha1 && sha1_eq(fi->sha1, fi->cha1));
}
/* ----------------------------------------- */
/**
* Sets proper timeout delay, with exponential back-off and min/max enforcement.
*
* NB: This routine must be invoked before download_stop() is called because
* it uses the d->start_date field which is reset there.
*/
static void
download_update_timeout_delay(struct download *d)
{
download_check(d);
/*
* The d->timeout_delay flag is reset to 0 each time we have a successful
* connection to the server. So when we come here with a non-zero time,
* we make sure we increase the time we're going to spend waiting, until
* we finally get a successful connection.
*/
if (d->timeout_delay == 0)
d->timeout_delay = GNET_PROPERTY(download_retry_timeout_min);
else {
d->timeout_delay *= 2;
if (d->start_date) {
/* We forgive a little while the download is working */
d->timeout_delay -= delta_time(tm_time(), d->start_date) / 10;
}
}
if (d->timeout_delay < GNET_PROPERTY(download_retry_timeout_min))
d->timeout_delay = GNET_PROPERTY(download_retry_timeout_min);
if (d->timeout_delay > GNET_PROPERTY(download_retry_timeout_max))
d->timeout_delay = GNET_PROPERTY(download_retry_timeout_max);
}
/**
* Called to request retry of a download after a timeout, with exponential
* back-off timeout delay, up to a maximum.
*/
static void
download_retry(struct download *d)
{
download_check(d);
/*
* download_stop() sets the time, so all we need to do is set the delay.
*/
download_update_timeout_delay(d);
download_stop(d, GTA_DL_TIMEOUT_WAIT, no_reason);
}
/**
* Return the total progress of a download. The range
* on the return value should be 0..1 but there is no
* guarantee.
*
* @param d The download structure which we are interested
* in knowing the progress of.
*
* @return The total percent completed for this file.
*/
gdouble
download_total_progress(const struct download *d)
{
return filesize_per_10000(download_filesize(d), download_filedone(d))
/ 10000.0;
}
/**
* Return the total progress of a download source. The
* range on the return value should be 0..1 but there is
* no guarantee.
*
* Same as download_total_progress() if source is not receiving.
*
* @param d The download structure which we are interested
* in knowing the progress of.
*
* @return The percent completed for this source, or zero
* if the source is not receiving at the moment.
*/
gdouble
download_source_progress(const struct download *d)
{
if (DOWNLOAD_IS_ACTIVE(d)) {
filesize_t done = d->pos - d->skip + download_buffered(d);
return filesize_per_10000(d->size, done) / 10000.0;
} else {
return 0.0;
}
}
/**
* Initialize downloading data structures.
*/
void
download_init(void)
{
dl_by_host = g_hash_table_new(dl_key_hash, dl_key_eq);
dl_by_addr = g_hash_table_new(dl_addr_hash, dl_addr_eq);
dl_count_by_name = g_hash_table_new(g_str_hash, g_str_equal);
sl_downloads = hash_list_new(NULL, NULL);
sl_unqueued = hash_list_new(NULL, NULL);
}
/**
* Initialize downloading data structures.
*/
void
download_restore_state(void)
{
/*
* The order of the following calls matters.
*/
gcu_download_gui_updates_freeze();
download_freeze_queue();
file_info_retrieve(); /* Get all fileinfos */
/* Pick up orphaned files */
file_info_scandir(GNET_PROPERTY(save_file_path));
download_retrieve(); /* Restore downloads */
file_info_spot_completed_orphans(); /* 100% done orphans => fake dl. */
download_resume_bg_tasks(); /* Reschedule SHA1 and moving */
file_info_store();
download_thaw_queue();
gcu_download_gui_updates_thaw();
}
/* ----------------------------------------- */
/**
* Allocate a set of buffers for data reception.
*/
static void
buffers_alloc(struct download *d)
{
static const struct dl_buffers zero_buffers;
struct dl_buffers *b;
download_check(d);
socket_check(d->socket);
g_assert(d->buffers == NULL);
g_assert(d->status == GTA_DL_RECEIVING);
b = walloc(sizeof *b);
*b = zero_buffers;
b->list = slist_new();
b->amount = GNET_PROPERTY(download_buffer_size);
d->buffers = b;
}
static void
buffers_free_item(gpointer data, gpointer unused_udata)
{
(void) unused_udata;
pmsg_free(data);
}
/**
* Dispose of the buffers used for reading.
*/
static void
buffers_free(struct download *d)
{
struct dl_buffers *b;
download_check(d);
g_assert(d->buffers != NULL);
g_assert(d->buffers->held == 0); /* No pending data */
b = d->buffers;
slist_foreach(b->list, buffers_free_item, NULL);
slist_free(&b->list);
wfree(b, sizeof *b);
d->buffers = NULL;
}
/**
* Reset the I/O vector for reading from the start.
*/
static void
buffers_reset_reading(struct download *d)
{
struct dl_buffers *b;
slist_iter_t *iter;
download_check(d);
socket_check(d->socket);
g_assert(d->buffers != NULL);
g_assert(d->status == GTA_DL_RECEIVING);
g_assert(d->buffers->held == 0);
b = d->buffers;
iter = slist_iter_on_head(b->list);
while (slist_iter_has_item(iter)) {
pmsg_t *mb;
mb = slist_iter_current(iter);
g_assert(mb);
pmsg_free(mb);
slist_iter_remove(iter);
}
slist_iter_free(&iter);
b->mode = DL_BUF_READING;
}
/**
* Reset the I/O vector for writing the whole data held in the buffer.
*/
static struct iovec *
buffers_to_iovec(struct download *d, gint *iov_cnt)
{
struct dl_buffers *b;
struct iovec *iov;
size_t held;
download_check(d);
socket_check(d->socket);
g_assert(iov_cnt);
g_assert(d->buffers != NULL);
g_assert(d->status == GTA_DL_RECEIVING);
b = d->buffers;
g_assert(b->mode == DL_BUF_READING);
g_assert(b->held > 0);
g_assert(b->list);
iov = pmsg_slist_to_iovec(b->list, iov_cnt, &held);
g_assert(iov);
g_assert(*iov_cnt > 0);
g_assert(held == b->held);
b->mode = DL_BUF_WRITING;
return iov;
}
/**
* Discard all read data from buffers.
*/
static inline void
buffers_discard(struct download *d)
{
struct dl_buffers *b;
fileinfo_t *fi;
download_check(d);
g_assert(d->buffers);
b = d->buffers;
fi = d->file_info;
if (fi->buffered >= b->held)
fi->buffered -= b->held;
else
fi->buffered = 0; /* Be fault-tolerant, this is not critical */
b->held = 0;
buffers_reset_reading(d);
}
/**
* Check whether reception buffers are full.
*/
static inline gboolean
buffers_full(const struct download *d)
{
const struct dl_buffers *b;
download_check(d);
g_assert(d->buffers);
b = d->buffers;
return b->held >= GNET_PROPERTY(download_buffer_size);
}
/**
* Check whether we should request flushing of the buffered data.
*/
static inline gboolean
buffers_should_flush(const struct download *d)
{
const struct dl_buffers *b;
download_check(d);
g_assert(d->buffers);
b = d->buffers;
/*
* Check against MAX_IOV_COUNT because if there are more buffers,
* this requires looping with [p]writev() - at least with the
* current download logic - which is inefficient.
*/
return b->held >= b->amount || slist_length(b->list) >= MAX_IOV_COUNT;
}
/**
* Update the buffer structure after having read "amount" more bytes:
* prepare `iovcnt' for the next read and increase the amount of data held.
*/
static void
buffers_add_read(struct download *d, pmsg_t *mb)
{
struct dl_buffers *b;
fileinfo_t *fi;
gint size;
gint available;
pmsg_t *prev_mb;
download_check(d);
socket_check(d->socket);
g_assert(d->buffers != NULL);
g_assert(d->status == GTA_DL_RECEIVING);
b = d->buffers;
fi = d->file_info;
g_assert(b->mode == DL_BUF_READING);
/*
* Check for under-utilization of message buffers, breaking the zero-copy
* policy when the previous buffer has some room and could contain
* the totality of the current buffer.
*
* We don't perform any copy if the amount of data we add is sufficient
* to trigger a disk flush: why copy data we're about to write to disk?
*/
size = pmsg_size(mb);
prev_mb = slist_tail(b->list);
available = prev_mb != NULL ? pmsg_writable_length(prev_mb) : 0;
if (b->held + size < b->amount && size <= available) {
gint written;
g_assert(prev_mb != NULL);
written = pmsg_write(prev_mb, pmsg_start(mb), size);
g_assert(written == size);
pmsg_free(mb);
if (GNET_PROPERTY(download_debug) > 10)
g_message("buffers_add_read(): copied %d bytes "
"into %d-byte long previous #%d (had %d bytes free)",
written, pmsg_size(prev_mb) - written,
slist_length(b->list), available);
} else
slist_append(b->list, mb);
b->held += size; /* Whether copied or not */
/*
* Update read statistics.
*/
fi->buffered += size;
}
/**
* Compare data held in the read buffers with the data chunk supplied.
*
* @return TRUE if data match.
*/
static gboolean
buffers_match(const struct download *d, const gchar *data, size_t len)
{
const struct dl_buffers *b;
slist_iter_t *iter;
download_check(d);
socket_check(d->socket);
g_assert(d->buffers != NULL);
g_assert(d->status == GTA_DL_RECEIVING);
b = d->buffers;
g_assert(len <= b->held);
iter = slist_iter_before_head(b->list);
while (len > 0) {
const pmsg_t *mb;
size_t n;
g_assert(slist_iter_has_next(iter));
mb = slist_iter_next(iter);
g_assert(mb);
n = pmsg_size(mb);
n = MIN(n, len);
if (0 != memcmp(pmsg_read_base(mb), data, n)) {
break;
}
data += n;
len -= n;
}
slist_iter_free(&iter);
return 0 == len;
}
/**
* Strip leading `amount' bytes from the read buffers.
*/
static void
buffers_strip_leading(struct download *d, size_t amount)
{
struct dl_buffers *b;
fileinfo_t *fi;
download_check(d);
g_assert(d->buffers != NULL);
b = d->buffers;
fi = d->file_info;
g_assert(b->mode == DL_BUF_READING);
g_assert(amount <= b->held);
if (b->held <= amount) {
buffers_discard(d);
return;
}
pmsg_slist_discard(b->list, amount);
b->held -= amount;
if (fi->buffered >= amount)
fi->buffered -= amount;
else
fi->buffered = 0; /* Not critical, be fault-tolerant */
}
static void
buffers_check_held(const struct download *d)
{
const struct dl_buffers *b;
size_t held = 0;
slist_iter_t *iter;
download_check(d);
b = d->buffers;
g_assert(b);
g_assert((0 == b->held) ^ (slist_length(b->list) > 0));
iter = slist_iter_before_head(b->list);
while (slist_iter_has_next(iter)) {
const pmsg_t *mb;
size_t size;
mb = slist_iter_next(iter);
g_assert(mb);
size = pmsg_size(mb);
g_assert(size > 0);
g_assert(size <= ((size_t) -1) - held);
held += size;
}
slist_iter_free(&iter);
g_assert(held == b->held);
}
/**
* Strip trailing `amount' bytes from the read buffers.
*/
static void
buffers_strip_trailing(struct download *d, size_t amount)
{
struct dl_buffers *b;
slist_iter_t *iter;
fileinfo_t *fi;
size_t n;
download_check(d);
g_assert(d->buffers != NULL);
b = d->buffers;
fi = d->file_info;
g_assert(b->mode == DL_BUF_READING);
g_assert(amount <= b->held);
if (b->held <= amount) {
buffers_discard(d);
return;
}
n = b->held - amount;
iter = slist_iter_on_head(b->list);
while (slist_iter_has_item(iter)) {
pmsg_t *mb;
size_t size;
mb = slist_iter_current(iter);
g_assert(mb);
if (n > 0) {
size = pmsg_size(mb);
if (size < n) {
n -= size;
} else {
if (size > n) {
pmsg_discard_trailing(mb, size - n);
}
n = 0;
}
slist_iter_next(iter);
} else {
pmsg_free(mb);
slist_iter_remove(iter);
}
}
slist_iter_free(&iter);
b->held -= amount;
if (fi->buffered >= amount)
fi->buffered -= amount;
else
fi->buffered = 0; /* Not critical, be fault-tolerant */
}
/* ----------------------------------------- */
/* ----------------------------------------- */
/**
* Insert server by retry time into the `dl_by_time' structure.
*/
static void
dl_by_time_insert(struct dl_server *server)
{
guint idx = DL_HASH(server->retry_after);
g_assert(dl_server_valid(server));
dl_by_time.change[idx]++;
dl_by_time.servers[idx] = g_list_insert_sorted(dl_by_time.servers[idx],
server, dl_server_retry_cmp);
}
/**
* Remove server from the `dl_by_time' structure.
*/
static void
dl_by_time_remove(struct dl_server *server)
{
guint idx = DL_HASH(server->retry_after);
g_assert(dl_server_valid(server));
dl_by_time.change[idx]++;
dl_by_time.servers[idx] = g_list_remove(dl_by_time.servers[idx], server);
}
/**
* Convert a vector of host to a single-linked list.
*
* @returns new list, with every item cloned.
*/
static GSList *
hostvec_to_slist(const gnet_host_vec_t *vec)
{
GSList *sl = NULL;
gint i;
for (i = gnet_host_vec_count(vec) - 1; i >= 0; i--) {
gnet_host_t *host;
host = walloc(sizeof *host);
*host = gnet_host_vec_get(vec, i);
sl = g_slist_prepend(sl, host);
}
return sl;
}
/**
* Get rid of the list of push proxies held in the server.
*/
static void
free_proxies(struct dl_server *server)
{
g_assert(dl_server_valid(server));
if (server->proxies) {
GSList *sl;
for (sl = server->proxies; sl; sl = g_slist_next(sl)) {
struct gnutella_host *h = sl->data;
wfree(h, sizeof *h);
}
g_slist_free(server->proxies);
server->proxies = NULL;
}
}
/**
* Remove push proxy from server.
*/
static void
remove_proxy(struct dl_server *server, const host_addr_t addr, guint16 port)
{
GSList *sl;
g_assert(dl_server_valid(server));
for (sl = server->proxies; sl; sl = g_slist_next(sl)) {
struct gnutella_host *h = sl->data;
g_assert(h != NULL);
if (
gnet_host_get_port(h) == port &&
host_addr_equal(gnet_host_get_addr(h), addr)
) {
server->proxies = g_slist_remove_link(server->proxies, sl);
g_slist_free_1(sl);
wfree(h, sizeof *h);
return;
}
}
/*
* The following could happen when we reset the list of push-proxies
* for a host after having selected a push-proxy from the old stale list.
*/
if (GNET_PROPERTY(download_debug)) {
g_message("did not find push-proxy %s in server %s",
host_addr_port_to_string(addr, port),
host_addr_to_string(server->key->addr));
}
}
/**
* Allocate new server structure.
*/
static struct dl_server *
allocate_server(const gchar *guid, const host_addr_t addr, guint16 port)
{
struct dl_key *key;
struct dl_server *server;
g_assert(host_addr_initialized(addr));
key = walloc(sizeof *key);
key->addr = addr;
key->port = port;
key->guid = atom_guid_get(guid);
server = walloc0(sizeof *server);
server->magic = DL_SERVER_MAGIC;
server->key = key;
server->retry_after = tm_time();
server->country = gip_country(addr);
server->sha1_counts = g_hash_table_new(sha1_hash, sha1_eq);
g_hash_table_insert(dl_by_host, key, server);
dl_by_time_insert(server);
/*
* If host is reacheable directly, its GUID does not matter much to
* identify the server as the (IP, port) should be unique.
*/
if (host_is_valid(addr, port)) {
struct dl_addr *ipk;
gpointer ipkey;
gpointer x; /* Don't care about freeing values */
gboolean existed;
ipk = walloc(sizeof *ipk);
ipk->addr = addr; /* Struct copy */
ipk->port = port;
existed = g_hash_table_lookup_extended(dl_by_addr, ipk, &ipkey, &x);
/*
* For the rare cases where the key already existed, we "take
* ownership" of the old key by associating our server entry in it.
* We reuse the old key, and free the new one, otherwise we'd
* have a memory leak because noone would free the old key!
*/
if (existed) {
struct dl_addr *da = ipkey;
g_assert(da != ipk);
g_assert(host_addr_initialized(da->addr));
wfree(ipk, sizeof *ipk); /* Keep the old key */
g_hash_table_insert(dl_by_addr, da, server);
} else
g_hash_table_insert(dl_by_addr, ipk, server);
}
return server;
}
static void
server_list_free_all(struct dl_server *server)
{
guint i;
g_assert(dl_server_valid(server));
g_assert(0 == count_running_on_server(server));
for (i = 0; i < DL_LIST_SZ; i++) {
list_free(&server->list[i]);
}
}
/**
* Free server structure.
*/
static void
free_server(struct dl_server *server)
{
struct dl_addr ipk;
g_assert(dl_server_valid(server));
g_assert(server->refcnt == 0);
g_assert(server_list_length(server, DL_LIST_RUNNING) == 0);
g_assert(server_list_length(server, DL_LIST_WAITING) == 0);
g_assert(server_list_length(server, DL_LIST_STOPPED) == 0);
g_assert(server->list[DL_LIST_RUNNING] == NULL);
g_assert(server->list[DL_LIST_WAITING] == NULL);
g_assert(server->list[DL_LIST_STOPPED] == NULL);
dl_by_time_remove(server);
g_hash_table_remove(dl_by_host, server->key);
atom_str_free_null(&server->vendor);
atom_guid_free_null(&server->key->guid);
/*
* We only inserted the server in the `dl_addr' table if it was "reachable".
*/
ipk.addr = server->key->addr;
ipk.port = server->key->port;
{
gpointer ipkey;
gpointer x; /* Don't care about freeing values */
/*
* Only remove server in the `dl_by_addr' table if it is the one
* for which the IP key is recored. Otherwise, what can happen
* is that a server is detached from a download and marked for
* delayed removal. Then a new one with same address is sprung
* to life, and inserted in `dl_by_addr'. If we remove it now,
* we'll free the key of the new server.
*/
if (g_hash_table_lookup_extended(dl_by_addr, &ipk, &ipkey, &x)) {
struct dl_addr *da = ipkey;
g_assert(host_addr_initialized(da->addr));
if (x == server) { /* We own the key */
g_hash_table_remove(dl_by_addr, &ipk);
wfree(da, sizeof *da);
}
}
}
/*
* Get rid of the known push proxies, if any.
*/
free_proxies(server);
atom_str_free_null(&server->hostname);
server_list_free_all(server);
{
guint n = g_hash_table_size(server->sha1_counts);
if (0 != n) {
g_warning("server->sha1_counts (%s) contains still %u items",
host_addr_port_to_string(server->key->addr, server->key->port),
n);
}
}
g_hash_table_destroy(server->sha1_counts);
server->sha1_counts = NULL;
wfree(server->key, sizeof(struct dl_key));
server->magic = 0;
wfree(server, sizeof *server);
}
/**
* Marks server for delayed removal (via asynchronous timer).
*/
static void
server_delay_delete(struct dl_server *server)
{
g_assert(dl_server_valid(server));
g_assert(!(server->attrs & DLS_A_REMOVED));
server->attrs |= DLS_A_REMOVED; /* Insert once in list */
sl_removed_servers = g_slist_prepend(sl_removed_servers, server);
}
/**
* Resurrect server pending deletion.
*/
static void
server_undelete(struct dl_server *server)
{
g_assert(dl_server_valid(server));
g_assert(server->attrs & DLS_A_REMOVED);
server->attrs &= ~DLS_A_REMOVED; /* Clear flag */
sl_removed_servers = g_slist_remove(sl_removed_servers, server);
}
/**
* Fetch server entry identified by IP:port first, then GUID+IP:port.
*
* @returns server, allocated if needed when allocate is TRUE.
*/
static struct dl_server *
get_server(
const gchar *guid, const host_addr_t addr, guint16 port, gboolean allocate)
{
struct dl_addr ikey;
struct dl_key key;
struct dl_server *server;
g_assert(guid);
g_assert(host_addr_initialized(addr));
ikey.addr = addr;
ikey.port = port;
/*
* A server can have its freeing "delayed". If we are asked for a
* server that has been deleted, we need to "undelete" it.
*/
server = g_hash_table_lookup(dl_by_addr, &ikey);
if (server) {
if (server->attrs & DLS_A_REMOVED)
server_undelete(server);
goto done;
}
key.guid = deconstify_gchar(guid);
key.addr = addr;
key.port = port;
server = g_hash_table_lookup(dl_by_host, &key);
g_assert(server == NULL || dl_server_valid(server));
if (server && (server->attrs & DLS_A_REMOVED))
server_undelete(server);
/*
* Allocate new server if it does not exist already.
*/
if (NULL == server) {
if (!allocate)
return NULL;
server = allocate_server(guid, addr, port);
/* FALL THROUGH */
}
done:
g_assert(dl_server_valid(server));
return server;
}
/**
* The server address changed.
*/
static void
change_server_addr(struct dl_server *server, const host_addr_t new_addr)
{
struct dl_key *key = server->key;
struct dl_server *duplicate;
g_assert(dl_server_valid(server));
g_assert(!host_addr_equal(key->addr, new_addr));
g_assert(host_addr_initialized(new_addr));
g_hash_table_remove(dl_by_host, key);
/*
* We only inserted the server in the `dl_addr' table if it was "reachable".
*/
if (host_is_valid(key->addr, key->port)) {
struct dl_addr ipk;
gpointer ipkey;
gpointer x; /* Don't care about freeing values */
ipk.addr = key->addr;
ipk.port = key->port;
if (g_hash_table_lookup_extended(dl_by_addr, &ipk, &ipkey, &x)) {
struct dl_addr *da = ipkey;
g_assert(host_addr_initialized(da->addr));
if (x == server) { /* We "own" the key -- see free_server() */
g_hash_table_remove(dl_by_addr, da);
wfree(da, sizeof *da);
}
}
}
/*
* Get rid of the known push proxies, if any.
*/
free_proxies(server);
if (GNET_PROPERTY(download_debug)) {
gchar buf[128];
g_strlcpy(buf, host_addr_to_string(new_addr), sizeof buf);
g_message("server <%s> at %s:%u changed its IP from %s to %s",
server->vendor == NULL ? "UNKNOWN" : server->vendor,
server->hostname == NULL ? "NONAME" : server->hostname,
key->port, host_addr_to_string(key->addr), buf);
}
/*
* Perform the IP change.
*/
key->addr = new_addr;
server->country = gip_country(new_addr);
/*
* Look for a duplicate. It's quite possible that we saw some IP
* address 1.2.3.4 and 5.6.7.8 without knowing that they both were
* for the foo.example.com host. And now we learn that the name
* foo.example.com which we thought was 5.6.7.8 is at 1.2.3.4...
*/
duplicate = get_server(key->guid, new_addr, key->port, FALSE);
if (duplicate != NULL) {
g_assert(host_addr_equal(duplicate->key->addr, key->addr));
g_assert(duplicate->key->port == key->port);
g_assert(duplicate != server);
if (GNET_PROPERTY(download_debug)) {
g_message(
"new IP %s for server <%s> at %s:%u was used by <%s> at %s:%u",
host_addr_to_string(new_addr),
server->vendor == NULL ? "UNKNOWN" : server->vendor,
server->hostname == NULL ? "NONAME" : server->hostname,
key->port,
duplicate->vendor == NULL ? "UNKNOWN" : duplicate->vendor,
duplicate->hostname == NULL ? "NONAME" : duplicate->hostname,
duplicate->key->port);
}
/*
* If there was no GUID known for `server', copy the one
* from `duplicate'.
*/
if (
guid_eq(key->guid, blank_guid) &&
!guid_eq(duplicate->key->guid, blank_guid)
) {
atom_guid_change(&key->guid, duplicate->key->guid);
} else if (
!guid_eq(key->guid, duplicate->key->guid) &&
!guid_eq(duplicate->key->guid, blank_guid)
) {
if (GNET_PROPERTY(download_debug)) g_warning(
"found two distinct GUID for <%s> at %s:%u, keeping %s",
server->vendor == NULL ? "UNKNOWN" : server->vendor,
server->hostname == NULL ? "NONAME" : server->hostname,
key->port, guid_hex_str(key->guid));
}
/*
* All the downloads attached to the `duplicate' server need to be
* reparented to `server' instead.
*/
{
struct download *next;
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
download_check(d);
next = hash_list_next(sl_downloads, next);
if (d->status == GTA_DL_REMOVED)
continue;
if (d->server == duplicate)
download_reparent(d, server);
}
}
}
/*
* We can now blindly insert `server' in the hash. If there was a
* conflicting entry, all its downloads have been reparented and that
* server will be freed later, asynchronously.
*/
g_assert(server->key == key);
g_hash_table_insert(dl_by_host, key, server);
if (host_is_valid(key->addr, key->port)) {
struct dl_addr *ipk;
gpointer ipkey;
gpointer x; /* Don't care about freeing values */
gboolean existed;
ipk = walloc(sizeof *ipk);
ipk->addr = new_addr;
ipk->port = key->port;
existed = g_hash_table_lookup_extended(dl_by_addr, ipk, &ipkey, &x);
/*
* For the rare cases where the key already existed, we "take
* ownership" of the old key by associating our server entry in it.
* We reuse the old key, and free the new one, otherwise we'd
* have a memory leak because noone would free the old key!
*/
if (existed) {
struct dl_addr *da = ipkey;
g_assert(host_addr_initialized(da->addr));
g_assert(da != ipk);
wfree(ipk, sizeof *ipk); /* Keep the old key around */
g_hash_table_insert(dl_by_addr, da, server);
} else
g_hash_table_insert(dl_by_addr, ipk, server);
}
}
/**
* Set/change the server's hostname.
*/
static void
set_server_hostname(struct dl_server *server, const gchar *hostname)
{
g_assert(dl_server_valid(server));
atom_str_change(&server->hostname, hostname);
}
/**
* Check whether we can safely ignore Push indication for this server,
* identified by its GUID, IP and port.
*/
gboolean
download_server_nopush(const gchar *guid, const host_addr_t addr, guint16 port)
{
struct dl_server *server = get_server(guid, addr, port, FALSE);
if (server == NULL)
return FALSE;
g_assert(dl_server_valid(server));
/*
* Rreturns true if we already made a direct connection to this server.
*/
return server->attrs & DLS_A_PUSH_IGN;
}
/**
* How many downloads with same filename are running (active or establishing)?
*/
static guint
count_running_downloads_with_name(const char *name)
{
return GPOINTER_TO_UINT(g_hash_table_lookup(dl_count_by_name, name));
}
/**
* Add one to the amount of downloads running and bearing the filename.
*/
static void
downloads_with_name_inc(const gchar *name)
{
guint val;
val = GPOINTER_TO_UINT(g_hash_table_lookup(dl_count_by_name, name));
g_hash_table_insert(dl_count_by_name, deconstify_gchar(name),
GUINT_TO_POINTER(val + 1));
}
/**
* Remove one from the amount of downloads running and bearing the filename.
*/
static void
downloads_with_name_dec(const gchar *name)
{
guint val;
val = GPOINTER_TO_UINT(g_hash_table_lookup(dl_count_by_name, name));
g_return_if_fail(val > 0); /* Cannot decrement something not present */
if (val > 1)
gm_hash_table_insert_const(dl_count_by_name,
name, GUINT_TO_POINTER(val - 1));
else
g_hash_table_remove(dl_count_by_name, name);
}
static inline const struct sha1 *
download_get_sha1(const struct download *d)
{
download_check(d);
if (d->sha1) {
/* These are atoms */
if (d->file_info && d->file_info->sha1) {
g_assert(d->sha1 == d->file_info->sha1);
}
return d->sha1;
} else if (d->file_info) {
return d->file_info->sha1;
} else {
return NULL;
}
}
static inline void
server_sha1_count_inc(struct dl_server *server, struct download *d)
{
const struct sha1 *sha1;
download_check(d);
g_assert(server == d->server);
sha1 = download_get_sha1(d);
if (sha1) {
gpointer value;
guint n;
value = g_hash_table_lookup(server->sha1_counts, sha1);
n = GPOINTER_TO_UINT(value);
g_assert(n < (guint) -1);
n++;
value = GUINT_TO_POINTER(n);
gm_hash_table_insert_const(server->sha1_counts, sha1, value);
}
}
static inline void
server_sha1_count_dec(struct dl_server *server, struct download *d)
{
const struct sha1 *sha1;
download_check(d);
g_assert(server == d->server);
sha1 = download_get_sha1(d);
if (sha1) {
gpointer value;
guint n;
value = g_hash_table_lookup(server->sha1_counts, sha1);
n = GPOINTER_TO_UINT(value);
/* g_assert(n > 0); */
/* XXX -- counter is sometimes off -- RAM, 2006-08-29 */
if (n == 0) {
g_warning("BUG: no SHA1 %s for server %s, ignoring decrement",
sha1_base32(sha1),
host_addr_port_to_string(server->key->addr, server->key->port));
return;
}
n--;
if (n > 0) {
value = GUINT_TO_POINTER(n);
gm_hash_table_insert_const(server->sha1_counts, sha1, value);
} else {
g_hash_table_remove(server->sha1_counts, sha1);
}
}
}
static gboolean
download_eq(gconstpointer p, gconstpointer q)
{
const struct download *a = p, *b = q;
const struct sha1 *a_sha1, *b_sha1;
if (a == b)
return TRUE;
a_sha1 = download_get_sha1(a);
b_sha1 = download_get_sha1(b);
if (a_sha1 || b_sha1) {
return a_sha1 == b_sha1; /* These are atoms! */
} else if (
a->file_size == b->file_size &&
(
a->file_name == b->file_name ||
0 == strcmp(a->file_name, b->file_name)
)
) {
return TRUE;
}
return FALSE;
}
static struct download *
server_list_lookup(const struct dl_server *server, enum dl_list idx,
const struct sha1 *sha1, const gchar *file, filesize_t size)
{
struct download *d = NULL;
g_assert(dl_server_valid(server));
g_assert((guint) idx < DL_LIST_SZ);
if (server->list[idx]) {
static const struct download zero_key;
struct download key = zero_key;
gpointer orig_key;
key.magic = DOWNLOAD_MAGIC;
key.sha1 = sha1 ? atom_sha1_get(sha1) : NULL;
key.file_name = deconstify_gpointer(file);
key.file_size = size;
if (list_contains(server->list[idx], &key, download_eq, &orig_key)) {
d = orig_key;
download_check(d);
}
atom_sha1_free_null(&key.sha1);
}
return d;
}
static list_t *
server_list_by_index(struct dl_server *server, enum dl_list idx)
{
g_assert(dl_server_valid(server));
g_assert((guint) idx < DL_LIST_SZ);
if (!server->list[idx]) {
server->list[idx] = list_new();
}
return server->list[idx];
}
static void
server_list_insert_download_sorted(struct dl_server *server, enum dl_list idx,
struct download *d)
{
g_assert(dl_server_valid(server));
download_check(d);
server_sha1_count_inc(server, d);
list_insert_sorted(server_list_by_index(server, idx), d, dl_retry_cmp);
}
static void
server_list_append_download(struct dl_server *server, enum dl_list idx,
struct download *d)
{
g_assert(dl_server_valid(server));
download_check(d);
server_sha1_count_inc(server, d);
list_append(server_list_by_index(server, idx), d);
}
static void
server_list_prepend_download(struct dl_server *server, enum dl_list idx,
struct download *d)
{
g_assert(dl_server_valid(server));
download_check(d);
server_sha1_count_inc(server, d);
list_prepend(server_list_by_index(server, idx), d);
}
static struct download *
server_list_head(struct dl_server *server, enum dl_list idx)
{
g_assert(dl_server_valid(server));
return server_list_length(server, idx) > 0
? list_head(server_list_by_index(server, idx))
: NULL;
}
static void
server_list_remove_download(struct dl_server *server, enum dl_list idx,
struct download *d)
{
g_assert(dl_server_valid(server));
g_assert((guint) idx < DL_LIST_SZ);
g_assert(server->list[idx]);
download_check(d);
server_sha1_count_dec(server, d);
list_remove(server->list[idx], d);
if (0 == server_list_length(server, idx)) {
list_free(&server->list[idx]);
}
}
/**
* Check whether we already have an identical (same file, same SHA1, same host)
* running or queued download.
*
* @returns found active download, or NULL if we have no such download yet.
*/
static struct download *
has_same_download(
const gchar *file, const struct sha1 *sha1, filesize_t size,
const gchar *guid, const host_addr_t addr, guint16 port)
{
static const enum dl_list listnum[] = { DL_LIST_WAITING, DL_LIST_RUNNING };
struct dl_server *server = get_server(guid, addr, port, FALSE);
struct download *d;
guint i;
if (server == NULL)
return NULL;
g_assert(dl_server_valid(server));
if (sha1 && NULL == g_hash_table_lookup(server->sha1_counts, sha1)) {
return NULL;
}
/*
* Note that we scan the WAITING downloads first, and then only
* the RUNNING ones. This is because that routine can now be called
* from download_convert_to_urires(), where the download is actually
* running!
*/
for (i = 0; i < G_N_ELEMENTS(listnum); i++) {
d = server_list_lookup(server, i, sha1, file, size);
if (d) {
download_check(d);
g_assert(!DOWNLOAD_IS_STOPPED(d));
return d;
}
}
return NULL;
}
static gboolean
download_has_enough_active_sources(struct download *d)
{
guint n;
/*
* Disabled: this is broken logic. Indeed, near the end, when only a
* few small holes remain, most of the source don't get scheduled, and
* the few partial ones that do get a slot may not have the chunks we
* need, resulting in an endless catch-22.
* --RAM, 2007-05-17
*/
#if 0
if (d->file_info->use_swarming) {
filesize_t m = download_filesize(d) - download_filedone(d);
/*
* Don't use more than one source per 16 kB because the HTTP
* overhead becomes significant for small chunks.
*/
m /= 16000;
m = MAX(m, 1);
n = MIN(m, GNET_PROPERTY(max_simultaneous_downloads_per_file));
} else {
n = 1;
}
#else
n = GNET_PROPERTY(max_simultaneous_downloads_per_file);
#endif
return count_running_downloads_with_name(download_basename(d)) >= n;
}
/**
* Mark a download as being actively queued.
*/
void
download_actively_queued(struct download *d, gboolean queued)
{
download_check(d);
if (queued) {
download_set_status(d, GTA_DL_ACTIVE_QUEUED);
if (d->flags & DL_F_ACTIVE_QUEUED) /* Already accounted for */
return;
d->flags |= DL_F_ACTIVE_QUEUED;
d->file_info->active_queued++;
g_assert(GNET_PROPERTY(dl_aqueued_count) < INT_MAX);
gnet_prop_incr_guint32(PROP_DL_AQUEUED_COUNT);
} else {
if (!(d->flags & DL_F_ACTIVE_QUEUED)) /* Already accounted for */
return;
g_assert(GNET_PROPERTY(dl_aqueued_count) > 0);
gnet_prop_decr_guint32(PROP_DL_AQUEUED_COUNT);
d->flags &= ~DL_F_ACTIVE_QUEUED;
g_assert(d->file_info->active_queued > 0);
d->file_info->active_queued--;
}
file_info_changed(d->file_info);
}
/**
* Mark download as being passively queued.
*/
static void
download_passively_queued(struct download *d, gboolean queued)
{
download_check(d);
if (queued) {
if (d->flags & DL_F_PASSIVE_QUEUED) /* Already accounted for */
return;
d->flags |= DL_F_PASSIVE_QUEUED;
d->file_info->passive_queued++;
g_assert(GNET_PROPERTY(dl_pqueued_count) < INT_MAX);
gnet_prop_incr_guint32(PROP_DL_PQUEUED_COUNT);
} else {
if (!(d->flags & DL_F_PASSIVE_QUEUED)) /* Already accounted for */
return;
g_assert(GNET_PROPERTY(dl_pqueued_count) > 0);
gnet_prop_decr_guint32(PROP_DL_PQUEUED_COUNT);
d->flags &= ~DL_F_PASSIVE_QUEUED;
g_assert(d->file_info->passive_queued > 0);
d->file_info->passive_queued--;
}
file_info_changed(d->file_info);
}
/**
* @returns whether the download file exists in the temporary directory.
*/
gboolean
download_file_exists(const struct download *d)
{
struct stat sb;
download_check(d);
return -1 != stat(download_pathname(d), &sb) && S_ISREG(sb.st_mode);
}
/**
* Remove temporary download file.
*
* Optionally reset the fileinfo if unlinking is successful and `reset' is
* TRUE. The purpose of resetting on unlink is to prevent the fileinfo
* from being discarded at the next relaunch (we discard non-reset fileinfos
* when the file is missing).
*/
void
download_remove_file(struct download *d, gboolean reset)
{
fileinfo_t *fi;
struct download *next;
download_check(d);
if (download_shutdown)
return;
fi = d->file_info;
file_info_unlink(fi);
if (reset)
file_info_reset(fi);
/*
* Requeue all the active downloads that were referencing that file.
*/
next = hash_list_head(sl_downloads);
while (next) {
struct download *dl = next;
download_check(dl);
next = hash_list_next(sl_downloads, next);
if (dl->status == GTA_DL_REMOVED)
continue;
if (dl->file_info != fi)
continue;
/*
* An actively queued download is counted as running, but for our
* purposes here, it does not matter: we're not in the process of
* requesting the file. Likewise for other special states that are
* counted as running but are harmless here.
* --RAM, 17/05/2003
*/
switch (dl->status) {
case GTA_DL_ACTIVE_QUEUED:
case GTA_DL_PUSH_SENT:
case GTA_DL_FALLBACK:
case GTA_DL_SINKING: /* Will only make one request afterwards */
case GTA_DL_CONNECTING:
continue;
default:
break; /* go on */
}
if (DOWNLOAD_IS_RUNNING(dl)) {
download_stop(dl, GTA_DL_TIMEOUT_WAIT, no_reason);
download_queue(dl, _("Requeued due to file removal"));
}
}
}
/**
* Change all the fileinfo of downloads from `old_fi' to `new_fi'.
*
* All running downloads are requeued immediately, since a change means
* the underlying file we're writing to can change.
*/
void
download_info_change_all(fileinfo_t *old_fi, fileinfo_t *new_fi)
{
struct download *next;
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
gboolean is_running;
download_check(d);
next = hash_list_next(sl_downloads, next);
if (d->status == GTA_DL_REMOVED)
continue;
if (d->file_info != old_fi)
continue;
is_running = DOWNLOAD_IS_RUNNING(d);
/*
* The following states are marked as being running, but the
* fileinfo structure has not yet been used to request anything,
* so we don't need to stop.
*/
switch (d->status) {
case GTA_DL_ACTIVE_QUEUED:
case GTA_DL_PUSH_SENT:
case GTA_DL_FALLBACK:
case GTA_DL_SINKING:
case GTA_DL_CONNECTING:
is_running = FALSE;
break;
default:
break;
}
if (is_running) {
download_stop(d, GTA_DL_TIMEOUT_WAIT, no_reason);
}
g_assert(old_fi->refcount > 0);
file_info_remove_source(old_fi, d, FALSE); /* Keep it around */
file_info_add_source(new_fi, d);
d->flags &= ~DL_F_SUSPENDED;
if (new_fi->flags & FI_F_SUSPEND)
d->flags |= DL_F_SUSPENDED;
if (is_running)
download_queue(d, _("Requeued by file info change"));
}
}
/**
* Remove all downloads to a given peer from the download queue
* and abort all connections to peer in the active download list.
*
* When `unavailable' is TRUE, the downloads are marked unavailable,
* so that they can be cleared up differently by the GUI .
*
* @return the number of removed downloads.
*/
gint
download_remove_all_from_peer(const gchar *guid,
const host_addr_t addr, guint16 port, gboolean unavailable)
{
struct dl_server *server[2];
gint n = 0;
enum dl_list listnum[] = { DL_LIST_RUNNING, DL_LIST_WAITING };
GSList *to_remove = NULL;
GSList *sl;
gint i;
guint j;
/*
* There can be two distinct server entries for a given IP:port.
* One with the GUID, and one with a blank GUID. The latter is
* used when we enqueue entries from the download mesh: we don't
* have the GUID handy at that point.
*
* NB: It is conceivable that a server could change GUID between two
* sessions, and therefore we may miss to remove downloads from the
* same IP:port. Apart from looping throughout the whole queue,
* there is nothing we can do.
* --RAM, 15/10/2002.
*/
server[0] = get_server(guid, addr, port, FALSE);
server[1] = get_server(blank_guid, addr, port, FALSE);
if (server[1] == server[0])
server[1] = NULL;
for (i = 0; i < 2; i++) {
if (server[i] == NULL)
continue;
for (j = 0; j < G_N_ELEMENTS(listnum); j++) {
enum dl_list idx = listnum[j];
list_iter_t *iter;
iter = list_iter_before_head(server[i]->list[idx]);
while (list_iter_has_next(iter)) {
struct download *d;
d = list_iter_next(iter);
download_check(d);
g_assert(d->status != GTA_DL_REMOVED);
n++;
to_remove = g_slist_prepend(to_remove, d);
}
list_iter_free(&iter);
}
}
/*
* We "forget" instead of "aborting" all requested downloads: we do
* not want to delete the file on the disk if they selected "delete on
* abort".
* Do NOT mark the fileinfo as "discard".
*/
for (sl = to_remove; sl != NULL; sl = g_slist_next(sl)) {
struct download *d = sl->data;
download_forget(d, unavailable);
}
g_slist_free(to_remove);
return n;
}
/**
* Remove all THEX downloads for a given sha1.
*/
static void
download_remove_all_thex(const struct sha1 *sha1)
{
struct download *next;
g_return_if_fail(sha1 != NULL);
/*
* Abort THEX downloads aimed at the given sha1.
*/
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
download_check(d);
next = hash_list_next(sl_downloads, next);
if (d->thex) {
const struct sha1 *d_sha1 = thex_download_get_sha1(d->thex);
if (sha1_eq(sha1, d_sha1)) {
download_forget(d, FALSE);
}
}
}
}
/**
* Change the socket RX buffer size for all the currently connected
* downloads.
*/
void
download_set_socket_rx_size(unsigned rx_size)
{
struct download *next;
/* This is called from settings_init() before download_init() */
if (NULL == sl_downloads)
return;
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
download_check(d);
next = hash_list_next(sl_downloads, next);
if (d->socket != NULL)
socket_recv_buf(d->socket, rx_size, TRUE);
}
}
static void
download_set_sha1(struct download *d, const struct sha1 *sha1)
{
download_check(d);
if (DL_LIST_INVALID != d->list_idx) {
g_assert(d->server);
server_sha1_count_dec(d->server, d);
}
atom_sha1_change(&d->sha1, sha1);
if (DL_LIST_INVALID != d->list_idx) {
server_sha1_count_inc(d->server, d);
}
}
/*
* Downloads management
*/
static void
download_add_to_list(struct download *d, enum dl_list idx)
{
struct dl_server *server;
download_check(d);
server = d->server;
g_assert(dl_server_valid(server));
g_assert(idx != DL_LIST_INVALID);
g_assert(d->list_idx == DL_LIST_INVALID); /* Not in any list */
d->list_idx = idx;
/*
* The DL_LIST_WAITING list is sorted by increasing retry after.
*/
if (idx == DL_LIST_WAITING) {
server_list_insert_download_sorted(server, idx, d);
} else {
server_list_prepend_download(server, idx, d);
}
}
/**
* Move download from its current list to the `idx' one.
*/
static void
download_move_to_list(struct download *d, enum dl_list idx)
{
struct dl_server *server = d->server;
enum dl_list old_idx = d->list_idx;
download_check(d);
server = d->server;
old_idx = d->list_idx;
g_assert(dl_server_valid(server));
g_assert(d->list_idx != DL_LIST_INVALID); /* In some list */
g_assert(d->list_idx != idx); /* Not in the target list */
/*
* Global counters update.
*/
if (old_idx == DL_LIST_RUNNING) {
if (DOWNLOAD_IS_ACTIVE(d))
dl_active--;
else {
g_assert(DOWNLOAD_IS_ESTABLISHING(d));
g_assert(dl_establishing > 0);
dl_establishing--;
}
downloads_with_name_dec(download_basename(d));
} else if (idx == DL_LIST_RUNNING) {
dl_establishing++;
downloads_with_name_inc(download_basename(d));
}
g_assert(dl_active <= INT_MAX && dl_establishing <= INT_MAX);
/*
* Local counter and list update.
* The DL_LIST_WAITING list is sorted by increasing retry after.
*/
g_assert(server_list_length(server, old_idx) > 0);
server_list_remove_download(server, old_idx, d);
if (idx == DL_LIST_WAITING) {
server_list_insert_download_sorted(server, idx, d);
} else {
server_list_append_download(server, idx, d);
}
d->list_idx = idx;
}
/**
* Change the `retry_after' field of the host where this download runs.
* If a non-zero `hold' is specified, make sure nothing will be scheduled
* from this server before the next `hold' seconds.
*/
static void
download_server_retry_after(struct dl_server *server, time_t now, gint hold)
{
struct download *d;
time_t after;
g_assert(dl_server_valid(server));
g_assert(server_list_length(server, DL_LIST_WAITING) > 0);
/*
* Always consider the earliest time in the future for all the downloads
* enqueued in the server when updating its `retry_after' field.
*
* Indeed, we may have several downloads queued with PARQ, and each
* download bears its own retry_after time. But we need to know the
* earliest time at which we should start browsing through the downloads
* for a given server.
* --RAM, 16/07/2003
*/
d = server_list_head(server, DL_LIST_WAITING);
download_check(d);
after = d->retry_after;
/*
* We impose a minimum of DOWNLOAD_SERVER_HOLD seconds between retries.
* If we have some entries passively queued, well, we have some grace time
* before the entry expires. And even if it expires, we won't lose the
* slot. People having 100 entries passively queued on the same host with
* low retry rates will have problems, but if they requested too often,
* they would get banned anyway. Let the system regulate itself via chaos.
* --RAM, 17/07/2003
*/
if (delta_time(after, now) < DOWNLOAD_SERVER_HOLD)
after = time_advance(now, DOWNLOAD_SERVER_HOLD);
/*
* If server was given a "hold" period (e.g. requests to it were
* timeouting) then put it on hold now and reset the holding period.
*/
if (hold != 0)
after = MAX(after, time_advance(now, hold));
if (server->retry_after != after) {
dl_by_time_remove(server);
server->retry_after = after;
dl_by_time_insert(server);
}
}
/**
* Reclaim download's server if it is no longer holding anything.
* If `delayed' is true, we're performing a batch free of downloads.
*/
static void
download_reclaim_server(struct download *d, gboolean delayed)
{
struct dl_server *server;
download_check(d);
g_assert(dl_server_valid(d->server));
g_assert(d->list_idx == DL_LIST_INVALID);
server = d->server;
d->server = NULL;
server->refcnt--;
/*
* We cannot reclaim the server structure immediately if `delayed' is set,
* because we can be removing physically several downloads that all
* pointed to the same server, and which have all been removed from it.
* Therefore, the server structure appears empty but is still referenced.
*
* Because we split the detaching of the download from the server and
* the actual reclaiming, the lists can be empty but still the server
* can have downloads referencing it, so we don't physically free it
* until all of them have been detached,
*/
if (
server_list_length(server, DL_LIST_RUNNING) == 0 &&
server_list_length(server, DL_LIST_WAITING) == 0 &&
server_list_length(server, DL_LIST_STOPPED) == 0
) {
if (delayed) {
if (!(server->attrs & DLS_A_REMOVED))
server_delay_delete(server);
} else if (server->refcnt == 0)
free_server(server);
}
}
/**
* Remove download from server.
* Reclaim server if this was the last download held and `reclaim' is true.
*/
static void
download_remove_from_server(struct download *d, gboolean reclaim)
{
struct dl_server *server;
enum dl_list idx;
download_check(d);
g_assert(dl_server_valid(d->server));
g_assert(d->list_idx != DL_LIST_INVALID);
idx = d->list_idx;
server = d->server;
d->list_idx = DL_LIST_INVALID;
g_assert(server_list_length(server, idx) > 0);
server_list_remove_download(server, idx, d);
if (reclaim)
download_reclaim_server(d, FALSE);
}
/**
* Move download from a server to another one.
*/
static void
download_reparent(struct download *d, struct dl_server *new_server)
{
enum dl_list list_idx;
download_check(d);
g_assert(dl_server_valid(d->server));
list_idx = d->list_idx; /* Save index, before removal from server */
download_remove_from_server(d, FALSE); /* Server reclaimed later */
download_reclaim_server(d, TRUE); /* Delays free if empty */
d->server = new_server;
d->server->refcnt++;
d->always_push = d->always_push && !has_blank_guid(d);
/*
* Insert download in new server, in the same list.
*/
d->list_idx = DL_LIST_INVALID; /* Pre-cond. for download_add_to_list() */
download_add_to_list(d, list_idx);
}
/**
* Move download from a server to another when the IP:port changed due
* to a Location: redirection for instance, or because of a QUEUE callback.
*/
void
download_redirect_to_server(struct download *d,
const host_addr_t addr, guint16 port)
{
struct dl_server *server;
gchar old_guid[GUID_RAW_SIZE];
enum dl_list list_idx;
download_check(d);
g_assert(dl_server_valid(d->server));
/*
* If neither the IP nor the port changed, do nothing.
*/
server = d->server;
if (host_addr_equal(server->key->addr, addr) && server->key->port == port)
return;
/*
* We have no way to know the GUID of the new IP:port server, so we
* reuse the old one. We must save it before removing the download
* from the old server.
*/
list_idx = d->list_idx; /* Save index, before removal from server */
memcpy(old_guid, download_guid(d), GUID_RAW_SIZE);
download_remove_from_server(d, TRUE);
/*
* Associate to server.
*/
server = get_server(old_guid, addr, port, TRUE);
d->server = server;
d->server->refcnt++;
d->always_push = d->always_push && !has_blank_guid(d);
/*
* Insert download in new server, in the same list.
*/
/* Pre-condition for download_add_to_list() */
d->list_idx = DL_LIST_INVALID;
download_add_to_list(d, list_idx);
}
/**
* Vectorized version common to download_stop() and download_unavailable().
*/
void
download_stop_v(struct download *d, download_status_t new_status,
const gchar *reason, va_list ap)
{
gboolean store_queue = FALSE; /* Shall we call download_store()? */
enum dl_list list_target;
download_check(d);
file_info_check(d->file_info);
g_assert(!DOWNLOAD_IS_QUEUED(d));
g_assert(!DOWNLOAD_IS_STOPPED(d));
g_assert(d->status != new_status);
if (DOWNLOAD_IS_ACTIVE(d)) {
g_assert(d->file_info->recvcount > 0);
g_assert(d->file_info->recvcount <= d->file_info->refcount);
g_assert(d->file_info->recvcount <= d->file_info->lifecount);
/*
* If there is unflushed downloaded data, try to flush it now.
*/
if (d->buffers != NULL) {
if (d->buffers->held > 0) {
download_flush(d, NULL, FALSE);
if (d->buffers->held > 0) {
buffers_discard(d);
}
}
buffers_free(d);
}
d->file_info->recvcount--;
d->file_info->dirty_status = TRUE;
}
g_assert(d->buffers == NULL);
switch (new_status) {
case GTA_DL_COMPLETED:
case GTA_DL_ABORTED:
list_target = DL_LIST_STOPPED;
store_queue = TRUE;
break;
case GTA_DL_ERROR:
list_target = DL_LIST_STOPPED;
break;
case GTA_DL_TIMEOUT_WAIT:
list_target = DL_LIST_WAITING;
break;
default:
g_error("unexpected new status %u !", (guint) new_status);
return;
}
switch (new_status) {
case GTA_DL_COMPLETED:
{
/*
* Update average download speed, computing a fast EMA on the
* last 3 terms. Average is initialized with the actual download
* rate the first time we compute it.
*/
time_delta_t t = delta_time(d->last_update, d->start_date);
struct dl_server *server = d->server;
g_assert(server != NULL);
if (t > 0) {
filesize_t amount = d->range_end - d->skip + d->overlap_size;
guint avg = amount / t;
if (server->speed_avg == 0)
server->speed_avg = avg; /* First time */
else
server->speed_avg += (avg >> 1) - (server->speed_avg >> 1);
}
}
d->data_timeouts = 0; /* Got a full chunk all right */
/* FALL THROUGH */
case GTA_DL_ABORTED:
case GTA_DL_ERROR:
break;
default:
break;
}
/*
* Do not reset the start_date field when the dowmload is completed.
* The GUI is going to use this field to compute the average download
* speed. And it does not matter now for this request.
*/
if (new_status != GTA_DL_COMPLETED)
d->start_date = 0; /* Download no longer running */
if (reason && no_reason != reason) {
gm_vsnprintf(d->error_str, sizeof(d->error_str), reason, ap);
d->remove_msg = d->error_str;
} else
d->remove_msg = NULL;
/*
* Disable RX stacks to stop reception and clean up I/O structures.
*/
if (d->browse) {
browse_host_dl_close(d->browse);
d->bio = NULL; /* Was a copy via browse_host_io_source() */
}
if (d->thex) {
const struct sha1 *sha1;
fileinfo_t *fi;
sha1 = thex_download_get_sha1(d->thex);
fi = file_info_by_sha1(sha1);
if (fi) {
fi->flags &= ~FI_F_FETCH_TTH;
}
thex_download_close(d->thex);
d->bio = NULL; /* Was a copy via thex_download_io_source() */
}
if (d->rx) {
rx_free(d->rx);
d->bio = NULL; /* Was a copy via rx_bio_source() */
d->rx = NULL;
}
if (d->bio) {
bsched_source_remove(d->bio);
d->bio = NULL;
}
socket_free_null(&d->socket); /* Close socket */
file_object_release(&d->out_file); /* Close output file */
if (d->io_opaque) { /* I/O data */
io_free(d->io_opaque);
g_assert(d->io_opaque == NULL);
}
if (d->req) {
http_buffer_free(d->req);
d->req = NULL;
}
if (d->cproxy) {
cproxy_free(d->cproxy);
d->cproxy = NULL;
}
/* Don't clear ranges if simply queuing, or if completed */
if (d->ranges) {
switch (new_status) {
case GTA_DL_ERROR:
case GTA_DL_ABORTED:
http_range_free(d->ranges);
d->ranges = NULL;
break;
default:
break;
}
}
if (new_status == GTA_DL_COMPLETED) {
browse_host_dl_free(&d->browse);
thex_download_free(&d->thex);
}
if (d->list_idx != list_target)
download_move_to_list(d, list_target);
/* Register the new status, and update the GUI if needed */
download_set_status(d, new_status);
d->last_update = tm_time();
if (d->status != GTA_DL_TIMEOUT_WAIT) {
d->retries = 0; /* If they retry, go over whole cycle again */
}
if (store_queue) {
download_dirty = TRUE; /* Refresh list, in case we crash */
}
if (DOWNLOAD_IS_STOPPED(d) && DOWNLOAD_IS_IN_PUSH_MODE(d)) {
download_push_remove(d);
}
file_info_clear_download(d, FALSE);
file_info_changed(d->file_info);
d->flags &= ~DL_F_CHUNK_CHOSEN;
download_actively_queued(d, FALSE);
gnet_prop_set_guint32_val(PROP_DL_RUNNING_COUNT, count_running_downloads());
gnet_prop_set_guint32_val(PROP_DL_ACTIVE_COUNT, dl_active);
}
/**
* Stop an active download, close its socket and its data file descriptor.
*/
void
download_stop(struct download *d,
download_status_t new_status, const gchar * reason, ...)
{
va_list args;
download_check(d);
d->unavailable = FALSE;
va_start(args, reason);
download_stop_v(d, new_status, reason, args);
va_end(args);
}
/**
* Like download_stop(), but flag the download as "unavailable".
*/
static void
download_unavailable(struct download *d, download_status_t new_status,
const gchar * reason, ...)
{
va_list args;
download_check(d);
d->unavailable = TRUE;
va_start(args, reason);
download_stop_v(d, new_status, reason, args);
va_end(args);
}
static void
download_queue_set_status(struct download *d, const gchar *fmt, va_list ap)
{
if (fmt) {
size_t len;
gchar event[80], resched[80], pfs[40];
time_t rescheduled;
len = gm_vsnprintf(d->error_str, sizeof d->error_str, fmt, ap);
/* d->remove_msg updated below */
/*
* Rescheduling time is the largest of `retry_after' (absolute) and
* `timeout_delay' secs after `last_update'.
* See download_pickup_queued() for details on how this is handled.
* --RAM, 2007-05-06
*/
rescheduled = d->last_update + d->timeout_delay;
rescheduled = MAX(rescheduled, d->retry_after);
/* Append times of event/reschedule */
time_locale_to_string_buf(tm_time(), event, sizeof event);
time_locale_to_string_buf(rescheduled, resched, sizeof resched);
/* Append PFS indication */
pfs[0] = '\0';
if (d->ranges != NULL)
gm_snprintf(pfs, sizeof pfs, " <PFS %4.02f%%>",
d->ranges_size * 100.0 / d->file_info->size);
gm_snprintf(&d->error_str[len], sizeof d->error_str - len,
_(" at %s - rescheduled for %s%s"),
lazy_locale_to_ui_string(event),
lazy_locale_to_ui_string2(resched), pfs);
}
}
/**
* The vectorized (message-wise) version of download_queue().
*/
static void
download_queue_v(struct download *d, const gchar *fmt, va_list ap)
{
download_check(d);
file_info_check(d->file_info);
g_assert(!DOWNLOAD_IS_QUEUED(d));
g_assert(d->file_info->refcount > 0);
g_assert(d->file_info->lifecount <= d->file_info->refcount);
g_assert(d->sha1 == NULL || d->file_info->sha1 == d->sha1);
download_queue_set_status(d, fmt, ap);
if (DOWNLOAD_IS_RUNNING(d)) {
download_retry(d);
} else {
file_info_clear_download(d, TRUE); /* Also done by download_stop() */
}
/*
* Since download stop can change "d->remove_msg", update it now.
*/
d->remove_msg = fmt ? d->error_str: NULL;
download_set_status(d, d->parq_dl ? GTA_DL_PASSIVE_QUEUED : GTA_DL_QUEUED);
g_assert(d->socket == NULL);
if (d->list_idx != DL_LIST_WAITING) /* Timeout wait is in "waiting" */
download_move_to_list(d, DL_LIST_WAITING);
hash_list_remove(sl_unqueued, d);
gnet_prop_incr_guint32(PROP_DL_QUEUE_COUNT);
if (d->flags & DL_F_REPLIED) {
gnet_prop_incr_guint32(PROP_DL_QALIVE_COUNT);
}
}
/**
* Put download into queue.
*/
void
download_queue(struct download *d, const gchar *fmt, ...)
{
va_list args;
download_check(d);
va_start(args, fmt);
download_queue_v(d, fmt, args);
va_end(args);
}
/**
* Freeze the scheduling queue. Multiple freezing requires
* multiple thawing.
*/
void
download_freeze_queue(void)
{
g_return_if_fail(GNET_PROPERTY(download_queue_frozen) < (guint32)-1);
gnet_prop_incr_guint32(PROP_DOWNLOAD_QUEUE_FROZEN);
}
/**
* Thaw the scheduling queue. Multiple freezing requires
* multiple thawing.
*/
void
download_thaw_queue(void)
{
g_return_if_fail(GNET_PROPERTY(download_queue_frozen) > 0);
gnet_prop_decr_guint32(PROP_DOWNLOAD_QUEUE_FROZEN);
}
/**
* Test whether download queue is frozen.
*/
gboolean
download_queue_is_frozen(void)
{
return GNET_PROPERTY(download_queue_frozen) > 0;
}
/**
* Common vectorized code for download_queue_delay() and download_queue_hold().
*/
static void
download_queue_hold_delay_v(struct download *d,
gint delay, time_t hold,
const gchar *fmt, va_list ap)
{
time_t now = tm_time();
download_check(d);
/*
* Must update `retry_after' before enqueuing, since the "waiting" list
* is sorted by increasing retry_after for a given server.
*/
d->last_update = now;
d->retry_after = time_advance(now, delay);
download_queue_v(d, fmt, ap);
download_server_retry_after(d->server, now, hold);
}
/**
* Put download back to queue, but don't reconsider it for starting
* before the next `delay' seconds. -- RAM, 03/09/2001
*/
static void
download_queue_delay(struct download *d, guint32 delay, const gchar *fmt, ...)
{
va_list args;
download_check(d);
va_start(args, fmt);
download_queue_hold_delay_v(d, (time_t) delay, 0, fmt, args);
va_end(args);
}
/**
* Same as download_queue_delay(), but make sure we don't consider
* scheduling any currently queued download to this server before
* the holding delay.
*/
static void
download_queue_hold(struct download *d, guint32 hold, const gchar *fmt, ...)
{
va_list args;
download_check(d);
va_start(args, fmt);
download_queue_hold_delay_v(d, (time_t) hold, (time_t) hold, fmt, args);
va_end(args);
}
/**
* Record that we sent a push request for this download.
*/
static void
download_push_insert(struct download *d)
{
download_check(d);
g_assert(!d->push);
d->push = TRUE;
}
/**
* Forget that we sent a push request for this download.
*/
static void
download_push_remove(struct download *d)
{
download_check(d);
g_assert(d->push);
d->push = FALSE;
}
/**
* Send a HEAD Ping vendor message to node to get alternate sources via
* UDP since we're not going to issue an HTTP request right now.
*/
static void
download_send_head_ping(struct download *d)
{
time_t now = tm_time();
time_delta_t delay;
download_check(d);
file_info_check(d->file_info);
g_assert(d->server);
if (download_queue_is_frozen())
return;
if ((DL_F_THEX | DL_F_BROWSE) & d->flags)
return;
if (NULL == d->file_info->sha1 || !d->file_info->use_swarming)
return;
if (
!udp_active() ||
GNET_PROPERTY(is_firewalled) ||
GNET_PROPERTY(is_udp_firewalled)
)
return;
/*
* Increase the ping delay quadratically with the number of alive sources.
*/
delay = d->file_info->lifecount / 256;
delay = MIN(delay, 512);
delay *= delay;
delay = MAX(DOWNLOAD_PING_DELAY, delay);
if (delta_time(now, d->head_ping_sent) < delay)
return;
if (d->always_push) {
GSList *sl;
/*
* Requires a PUSH: send the HEAD Ping to all the HTTP proxies.
*/
g_assert(!has_blank_guid(d));
for (sl = d->server->proxies; sl; sl = g_slist_next(sl)) {
gnet_host_t *host = sl->data;
vmsg_send_head_ping(d->file_info->sha1,
gnet_host_get_addr(host), gnet_host_get_port(host),
download_guid(d));
d->head_ping_sent = now;
}
} else {
/*
* Not firewalled, just send direct message to the server.
*/
vmsg_send_head_ping(d->file_info->sha1,
download_addr(d), download_port(d), NULL);
d->head_ping_sent = now;
}
}
/**
* Send a HEAD Ping to all the downloads in the list.
*/
static void
download_list_send_head_ping(list_t *list)
{
list_iter_t *iter;
if (
!udp_active() ||
GNET_PROPERTY(is_firewalled) ||
GNET_PROPERTY(is_udp_firewalled)
)
return;
iter = list_iter_before_head(list);
while (list_iter_has_next(iter)) {
struct download *d;
d = list_iter_next(iter);
download_send_head_ping(d);
}
list_iter_free(&iter);
}
/**
* Invalidate improper fileinfo for the download, and get new one.
*
* This usually happens when we discover the SHA1 of the file on the remote
* server, and see that it does not match the one for the associated file on
* disk, as described in `file_info'.
*/
static void
download_info_reget(struct download *d)
{
fileinfo_t *fi;
gboolean file_size_known;
download_check(d);
fi = d->file_info;
g_assert(fi);
g_assert(fi->lifecount > 0);
g_assert(fi->lifecount <= fi->refcount);
if (fi->flags & FI_F_TRANSIENT)
return;
downloads_with_name_dec(download_basename(d)); /* File name can change! */
file_info_clear_download(d, TRUE); /* `d' might be running */
file_size_known = fi->file_size_known; /* This should not change */
file_info_remove_source(fi, d, FALSE); /* Keep it around for others */
fi = file_info_get(d->file_name, GNET_PROPERTY(save_file_path),
d->file_size, d->sha1, file_size_known);
g_return_if_fail(fi);
file_info_add_source(fi, d);
d->flags &= ~(DL_F_SUSPENDED | DL_F_PAUSED);
if (fi->flags & FI_F_SUSPEND)
d->flags |= DL_F_SUSPENDED;
if (fi->flags & FI_F_PAUSED)
d->flags |= DL_F_PAUSED;
downloads_with_name_inc(download_basename(d));
}
/**
* Mark all downloads that point to the file_info struct as "suspended" if
* `suspend' is TRUE, or clear that mark if FALSE.
*/
static void
queue_suspend_downloads_with_file(fileinfo_t *fi, gboolean suspend)
{
struct download *next;
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
download_check(d);
next = hash_list_next(sl_downloads, next);
switch (d->status) {
case GTA_DL_REMOVED:
case GTA_DL_COMPLETED:
case GTA_DL_VERIFY_WAIT:
case GTA_DL_VERIFYING:
case GTA_DL_VERIFIED:
case GTA_DL_MOVE_WAIT:
case GTA_DL_MOVING:
continue;
case GTA_DL_DONE: /* We want to be able to "un-suspend" */
break;
default:
break;
}
if (d->file_info != fi)
continue;
if (suspend) {
if (DOWNLOAD_IS_RUNNING(d))
download_queue(d, _("Suspended (SHA1 checking)"));
d->flags |= DL_F_SUSPENDED; /* Can no longer be scheduled */
} else
d->flags &= ~DL_F_SUSPENDED;
}
if (suspend)
fi->flags |= FI_F_SUSPEND;
else
fi->flags &= ~FI_F_SUSPEND;
}
/**
* Freeing a download cannot be done simply, because it might happen when
* we are traversing the `sl_downloads' or `sl_unqueued' lists.
*
* Therefore download_free() marks the download as "removed" and frees some
* of the memory used, but does not reclaim the download structure yet, nor
* does it remove it from the lists.
*
* The "freed" download is marked GTA_DL_REMOVED and is put into the
* `sl_removed' list where it will be reclaimed later on via
* download_free_removed().
*/
gboolean
download_remove(struct download *d)
{
download_check(d);
g_assert(d->status != GTA_DL_REMOVED); /* Not already freed */
if (!download_shutdown) {
/*
* Make sure download is not used by a background task
* -- JA 25/10/2003
*/
if (d->status == GTA_DL_VERIFY_WAIT || d->status == GTA_DL_VERIFYING)
return FALSE;
}
if (DOWNLOAD_IS_QUEUED(d)) {
g_assert(GNET_PROPERTY(dl_queue_count) > 0);
gnet_prop_decr_guint32(PROP_DL_QUEUE_COUNT);
if (d->flags & DL_F_REPLIED) {
g_assert(GNET_PROPERTY(dl_qalive_count) > 0);
gnet_prop_decr_guint32(PROP_DL_QALIVE_COUNT);
}
}
/*
* Abort running download (which will decrement the lifecount), otherwise
* make sure we decrement it here (e.g. if the download was queued).
*/
if (DOWNLOAD_IS_RUNNING(d)) {
download_stop(d, GTA_DL_ABORTED, no_reason);
}
g_assert(d->io_opaque == NULL);
g_assert(d->buffers == NULL);
if (d->browse) {
g_assert(d->flags & DL_F_BROWSE);
browse_host_dl_free(&d->browse);
}
if (d->thex) {
g_assert(d->flags & DL_F_THEX);
thex_download_free(&d->thex);
}
if (d->push)
download_push_remove(d);
download_set_sha1(d, NULL);
if (d->ranges) {
http_range_free(d->ranges);
d->ranges = NULL;
}
if (d->req) {
http_buffer_free(d->req);
d->req = NULL;
}
/*
* Let parq remove and free its allocated memory
* -- JA, 18/4/2003
*/
parq_dl_remove(d);
download_remove_from_server(d, FALSE);
download_set_status(d, GTA_DL_REMOVED);
atom_str_free_null(&d->file_name);
atom_str_free_null(&d->escaped_name);
atom_str_free_null(&d->uri);
file_info_remove_source(d->file_info, d, FALSE); /* Keep fileinfo around */
d->file_info = NULL;
download_check(d);
sl_removed = g_slist_prepend(sl_removed, d);
/* download structure will be freed in download_free_removed() */
return TRUE;
}
/**
* Removes all downloads that point to the file_info struct.
* If `skip' is non-NULL, that download is skipped.
*/
static void
queue_remove_downloads_with_file(fileinfo_t *fi, struct download *skip)
{
struct download *next;
next = hash_list_head(sl_downloads);
while (next) {
struct download *d = next;
download_check(d);
next = hash_list_next(sl_downloads, next);
switch (d->status) {
case GTA_DL_REMOVED:
case GTA_DL_COMPLETED:
case GTA_DL_VERIFY_WAIT:
case GTA_DL_VERIFYING:
case GTA_DL_VERIFIED:
case GTA_DL_MOVE_WAIT:
case GTA_DL_MOVING:
case GTA_DL_DONE:
continue;
default:
break;
}
if (d->file_info != fi || d == skip)
continue;
download_remove(d);
}
}
/**
* Check whether download should be ignored, and stop it immediately if it is.
*
* @returns whether download was stopped (i.e. if it must be ignored).
*/
static gboolean
download_ignore_requested(struct download *d)
{
enum ignore_val reason = IGNORE_FALSE;
fileinfo_t *fi;
download_check(d);
fi = d->file_info;
/*
* Reject if we're trying to download from ourselves (could happen
* if someone echoes back our own alt-locs to us with PFSP).
*/
if (!(SOCK_F_FORCE & d->cflags)) {
if (is_my_address_and_port(download_addr(d), download_port(d))) {
reason = IGNORE_OURSELVES;
} else if (hostiles_check(download_addr(d))) {
reason = IGNORE_HOSTILE;
}
}
if (reason == IGNORE_FALSE)
reason = ignore_is_requested(download_basename(d), fi->size, fi->sha1);
if (reason != IGNORE_FALSE) {
const gchar *s_reason;
s_reason = ignore_reason_to_string(reason);
g_assert(s_reason);
download_stop(d, GTA_DL_ERROR, _("Ignoring requested (%s)"), s_reason);
/*
* If we're ignoring this file, make sure we don't keep any
* track of it on disk: dispose of the fileinfo when the last
* reference will be removed, remove all known downloads from the
* queue and delete the file (if not complete, or it could be in
* the process of being moved).
*/
switch (reason) {
case IGNORE_HOSTILE:
case IGNORE_OURSELVES:
break;
case IGNORE_SHA1:
case IGNORE_SPAM:
case IGNORE_LIBRARY:
case IGNORE_NAMESIZE:
file_info_set_discard(d->file_info, TRUE);
queue_remove_downloads_with_file(fi, d);
if (!FILE_INFO_COMPLETE(fi)) {
download_remove_file(d, FALSE);
}
break;
case IGNORE_FALSE:
g_assert_not_reached();
}
return TRUE;
}
return FALSE;
}
/**
* Remove download from queue.
* It is put in a state where it can be stopped if necessary.
*/
static void
download_unqueue(struct download *d)
{
download_check(d);
g_assert(DOWNLOAD_IS_QUEUED(d));
g_assert(GNET_PROPERTY(dl_queue_count) > 0);
hash_list_prepend(sl_unqueued, d);
gnet_prop_decr_guint32(PROP_DL_QUEUE_COUNT);
if (d->flags & DL_F_REPLIED) {
g_assert(GNET_PROPERTY(dl_qalive_count) > 0);
gnet_prop_decr_guint32(PROP_DL_QALIVE_COUNT);
}
download_set_status(d, GTA_DL_CONNECTING);/* Allow download to be stopped */
}
/**
* Setup the download structure with proper range offset, and check that the
* download is not otherwise completed.
*
* @returns TRUE if we may continue with the download, FALSE if it has been
* stopped due to a problem.
*/
gboolean
download_start_prepare_running(struct download *d)
{
fileinfo_t *fi;
/*
* Do NOT use g_return_val_if_fail(blah, FALSE) in this routine.
* It MUST be g_assert() because before returning FALSE, some
* cleanup would be necessary to move back the download to the queue.
*
* Also if some of the assertions here are false, there is an important
* bug we need to tackle.
*/
download_check(d);
file_info_check(d->file_info);
fi = d->file_info;
g_assert(!DOWNLOAD_IS_QUEUED(d));
g_assert(d->list_idx == DL_LIST_RUNNING);
g_assert(fi->lifecount > 0);
/* Most common state if we succeed */
download_set_status(d, GTA_DL_CONNECTING);
/*
* If we were asked to ignore this download, abort now.
*/
if (download_ignore_requested(d))
return FALSE;
/*
* Even though we should not schedule a "suspended" download, we could
* be asked via a user-event to start such a download.
*/
if (d->flags & DL_F_SUSPENDED) {
download_queue(d, _("Suspended (SHA1 checking)"));
return FALSE;
}
if (d->flags & DL_F_PAUSED) {
download_queue(d, _("Paused"));
return FALSE;
}
/*
* If the file already exists, and has less than `download_overlap_range'
* bytes, we restart the download from scratch. Otherwise, we request
* that amount before the resuming point.
* Later on, in download_write_data(), and as soon as we have read more
* than `download_overlap_range' bytes, we'll check for a match.
* --RAM, 12/01/2002
*/
d->skip = 0; /* We're setting it here only if not swarming */
d->keep_alive = FALSE; /* Until proven otherwise by server's reply */
d->got_giv = FALSE; /* Don't know yet, assume no GIV */
if (d->socket == NULL)
d->served_reqs = 0; /* No request served yet, since not connected */
d->flags &= ~DL_F_OVERLAPPED; /* Clear overlapping indication */
d->flags &= ~DL_F_SHRUNK_REPLY; /* Clear server shrinking indication */
/*
* If this file is swarming, the overlapping size and skipping offset
* will be determined before making the requst, in download_pick_chunk().
* --RAM, 22/08/2002.
*/
if (!fi->use_swarming) {
if (fi->done > GNET_PROPERTY(download_overlap_range)) {
d->skip = fi->done; /* Not swarming => file has no holes */
}
d->pos = d->skip;
d->overlap_size = (d->skip == 0 || d->size <= d->pos)
? 0
: GNET_PROPERTY(download_overlap_range);
g_assert(d->overlap_size == 0 || d->skip > d->overlap_size);
}
d->last_update = tm_time();
/*
* Is there anything to get at all?
*/
if (FILE_INFO_COMPLETE(fi)) {
download_stop(d, GTA_DL_ERROR, _("Nothing more to get"));
download_verify_sha1(d);
return FALSE;
}
return TRUE;
}
/**
* Make download a "running" one (in running list, unqueued), then call
* download_start_prepare_running().
*
* @returns TRUE if we may continue with the download, FALSE if it has been
* stopped due to a problem.
*/
gboolean
download_start_prepare(struct download *d)
{
download_check(d);
g_assert(d->list_idx != DL_LIST_RUNNING); /* Not already running */
/*
* Updata global accounting data.
*/
download_move_to_list(d, DL_LIST_RUNNING);
/*
* If the download is in the queue, we remove it from there.
*/
if (DOWNLOAD_IS_QUEUED(d))
download_unqueue(d);
/*
* Reset flags that must be cleared only once per session, i.e. when
* we start issuing requests for a queued download, or after we cloned
* a completed download.
*
* Since download_start_prepare_running() is called from download_request(),
* we must reset DL_F_SUNK_DATA here, since we want to sink only ONCE
* per session.
*/
d->flags &= ~DL_F_SUNK_DATA; /* Restarting, nothing sunk yet */
/*
* download_start_prepare_running() promises that it will dispose of
* the download properly if it returns FALSE (e.g. moving it back to
* the waiting list).
*/
return download_start_prepare_running(d);
}
/**
* Called for swarming downloads when we are connected to the remote server,
* but before making the request, to pick up a chunk for downloading.
*
* @returns TRUE if we can continue with the download, FALSE if it has
* been stopped.
*/
static gboolean
download_pick_chunk(struct download *d)
{
enum dl_chunk_status status;
filesize_t from, to;
download_check(d);
g_assert(d->file_info->use_swarming);
d->overlap_size = 0;
d->last_update = tm_time();
status = file_info_find_hole(d, &from, &to);
switch (status) {
case DL_CHUNK_EMPTY:
d->skip = d->pos = from;
d->size = to - from;
if (
from > GNET_PROPERTY(download_overlap_range) &&
file_info_chunk_status(d->file_info,
from - GNET_PROPERTY(download_overlap_range),
from) == DL_CHUNK_DONE
)
d->overlap_size = GNET_PROPERTY(download_overlap_range);
break;
case DL_CHUNK_BUSY:
download_queue_delay(d, 10, _("Waiting for a free chunk"));
return FALSE;
case DL_CHUNK_DONE:
download_stop(d, GTA_DL_ERROR, _("No more gaps to fill"));
queue_remove_downloads_with_file(d->file_info, d);
return FALSE;
}
g_assert(d->overlap_size == 0 || d->skip > d->overlap_size);
return TRUE;
}
/**
* Pickup a range we don't have yet from the available ranges.
*
* @returns TRUE if we selected a chunk, FALSE if we can't select a chunk
* (e.g. we have everything the remote server makes available).
*/
static gboolean
download_pick_available(struct download *d)
{
filesize_t from, to;
download_check(d);
g_assert(d->ranges != NULL);
d->overlap_size = 0;
d->last_update = tm_time();
if (!file_info_find_available_hole(d, d->ranges, &from, &to)) {
if (GNET_PROPERTY(download_debug) > 3)
g_message("PFSP no interesting chunks from %s for \"%s\", "
"available was: %s",
host_addr_port_to_string(download_addr(d), download_port(d)),
download_basename(d), http_range_to_string(d->ranges));
return FALSE;
}
/*
* We found a chunk that the remote end has and which we miss.
*/
d->skip = d->pos = from;
d->size = to - from;
/*
* Maybe we can do some overlapping check if the remote server has
* some data before that chunk and we also have the corresponding
* range.
*/
if (
from > GNET_PROPERTY(download_overlap_range) &&
file_info_chunk_status(d->file_info,
from - GNET_PROPERTY(download_overlap_range),
from) == DL_CHUNK_DONE &&
http_range_contains(d->ranges,
from - GNET_PROPERTY(download_overlap_range),
from - 1)
)
d->overlap_size = GNET_PROPERTY(download_overlap_range);
if (GNET_PROPERTY(download_debug) > 3)
g_message("PFSP selected %s-%s (overlap=%u) "
"from %s for \"%s\", available was: %s",
uint64_to_string(from), uint64_to_string2(to - 1), d->overlap_size,
host_addr_port_to_string(download_addr(d), download_port(d)),
download_basename(d), http_range_to_string(d->ranges));
return TRUE;
}
/**
* Indicates that this download source is not good enough for us: it is either
* non-connectible, does not allow resuming, etc... Remove it from the mesh.
*/
static void
download_bad_source(struct download *d)
{
download_check(d);
download_passively_queued(d, FALSE);
if (!d->always_push && d->sha1 && !d->uri)
dmesh_remove(d->sha1, download_addr(d), download_port(d),
d->record_index, d->file_name);
}
/**
* Establish asynchronous connection to remote server.
*
* @returns connecting socket.
*/
static struct gnutella_socket *
download_connect(struct download *d)
{
struct dl_server *server;
guint16 port;
download_check(d);
server = d->server;
g_assert(dl_server_valid(server));
port = download_port(d);
d->flags &= ~DL_F_DNS_LOOKUP;
/*
* If there is a fully qualified domain name, look it up for possible
* change if either sufficient time passed since last lookup, or if the
* DLS_A_DNS_LOOKUP attribute was set because of a connection failure.
*/
if (
(server->attrs & DLS_A_DNS_LOOKUP) ||
(server->hostname != NULL &&
delta_time(tm_time(), server->dns_lookup) > DOWNLOAD_DNS_LOOKUP)
) {
g_assert(server->hostname != NULL);
d->flags |= DL_F_DNS_LOOKUP;
server->attrs &= ~DLS_A_DNS_LOOKUP;
server->dns_lookup = tm_time();
return socket_connect_by_name(
server->hostname, port, SOCK_TYPE_DOWNLOAD, d->cflags);
} else
return socket_connect(download_addr(d), port, SOCK_TYPE_DOWNLOAD,
d->cflags);
}
/**
* (Re)start a stopped or queued download.
*/
static void
download_start(struct download *d, gboolean check_allowed)
{
download_check(d);
file_info_check(d->file_info);
g_return_if_fail(!FILE_INFO_FINISHED(d->file_info));
g_return_if_fail(!DOWNLOAD_IS_MOVING(d));
g_return_if_fail(!DOWNLOAD_IS_RUNNING(d));
g_return_if_fail(!DOWNLOAD_IS_VERIFYING(d));
g_return_if_fail(
GTA_DL_INVALID == d->status ||
DOWNLOAD_IS_QUEUED(d) ||
DOWNLOAD_IS_WAITING(d));
g_return_if_fail(d->list_idx != DL_LIST_RUNNING); /* Waiting or stopped */
g_return_if_fail(d->file_info->refcount > 0);
g_return_if_fail(d->file_info->lifecount > 0);
g_return_if_fail(d->file_info->lifecount <= d->file_info->refcount);
g_return_if_fail(d->sha1 == NULL || d->file_info->sha1 == d->sha1);
if (download_queue_is_frozen()) {
if (!DOWNLOAD_IS_QUEUED(d)) {
download_queue(d, _("Download queue is frozen"));
}
return;
}
/*
* If caller did not check whether we were allowed to start downloading
* this file, do it now. --RAM, 03/09/2001
*/
if (check_allowed && (
download_has_enough_active_sources(d) ||
count_running_downloads() >= GNET_PROPERTY(max_downloads) ||
count_running_on_server(d->server) >= GNET_PROPERTY(max_host_downloads)
)
) {
if (!DOWNLOAD_IS_QUEUED(d)) {
download_send_head_ping(d);
download_queue(d, _("No download slot (start)"));
}
return;
}
if (!download_start_prepare(d))
return;
g_assert(d->list_idx == DL_LIST_RUNNING); /* Moved to "running" list */
g_assert(d->file_info->refcount > 0); /* Still alive */
g_assert(d->file_info->lifecount > 0);
g_assert(d->file_info->lifecount <= d->file_info->refcount);
if (
d->push &&
(GNET_PROPERTY(is_firewalled) || !GNET_PROPERTY(send_pushes))
) {
download_push_remove(d);
}
/*
* If server is known to be reachable without pushes, reset the flag.
*/
if (d->always_push && (d->server->attrs & DLS_A_PUSH_IGN)) {
if (d->push)
download_push_remove(d);
d->always_push = FALSE;
}
if (
!DOWNLOAD_IS_IN_PUSH_MODE(d) &&
host_is_valid(download_addr(d), download_port(d))
) {
/* Direct download */
download_set_status(d, GTA_DL_CONNECTING);
d->socket = download_connect(d);
if (!d->socket) {
/*
* If we ran out of file descriptors, requeue this download.
* We don't want to lose the source. We can't be sure, but
* if we see a banned_count of 0 and file_descriptor_runout set,
* then the lack of connection is probably due to a lack of
* descriptors.
* --RAM, 2004-06-21
*/
if (
GNET_PROPERTY(file_descriptor_runout) &&
GNET_PROPERTY(banned_count) == 0
) {
download_queue_delay(d,
GNET_PROPERTY(download_retry_busy_delay),
_("Connection failed (Out of file descriptors?)"));
return;
}
/*
* If DNS lookup was attempted, and we fail immediately, it
* means either the address returned by the DNS was invalid or
* there was no successful (synchronous) resolution for this
* host.
*/
if (d->flags & DL_F_DNS_LOOKUP) {
atom_str_free_null(&d->server->hostname);
fi_src_info_changed(d);
}
download_unavailable(d, GTA_DL_ERROR, _("Connection failed"));
return;
}
d->socket->resource.download = d;
d->socket->pos = 0;
} else { /* We have to send a push request */
download_set_status(d, GTA_DL_PUSH_SENT);
g_assert(d->socket == NULL);
download_push(d, FALSE);
}
gnet_prop_set_guint32_val(PROP_DL_RUNNING_COUNT, count_running_downloads());
gnet_prop_set_guint32_val(PROP_DL_ACTIVE_COUNT, dl_active);
}
void
download_request_start(struct download *d)
{
download_check(d);
g_return_if_fail(d->file_info);
file_info_check(d->file_info);
d->flags &= ~DL_F_PAUSED;
file_info_resume(d->file_info);
download_start(d, TRUE);
}
/**
* Pause a download.
*/
static void
download_pause(struct download *d)
{
download_check(d);
file_info_check(d->file_info);
if (FILE_INFO_FINISHED(d->file_info))
return;
if (DOWNLOAD_IS_VERIFYING(d)) /* Can't requeue: it's done */
return;
file_info_pause(d->file_info);
d->flags |= DL_F_PAUSED;
fi_src_status_changed(d);
if (!DOWNLOAD_IS_QUEUED(d)) {
download_queue(d, _("Paused"));
}
}
void
download_request_pause(struct download *d)
{
download_check(d);
g_return_if_fail(d->file_info);
file_info_check(d->file_info);
if (!FILE_INFO_FINISHED(d->file_info)) {
download_pause(d);
}
}
/**
* Pick up new downloads from the queue as needed.
*/
static void
download_pickup_queued(void)
{
time_t now = tm_time();
guint i;
/*
* To select downloads, we iterate over the sorted `dl_by_time' list and
* look for something we could schedule.
*
* Note that we jump from one host to the other, even if we have multiple
* things to schedule on the same host: It's better to spread load among
* all hosts first.
*/
for (i = 0; i < DHASH_SIZE; i++) {
GList *l;
gint last_change;
if (count_running_downloads() >= GNET_PROPERTY(max_downloads))
break;
if (!bws_can_connect(SOCK_TYPE_DOWNLOAD))
break;
retry:
l = dl_by_time.servers[i];
last_change = dl_by_time.change[i];
for (/* NOTHING */; NULL != l; l = g_list_next(l)) {
struct dl_server *server = l->data;
list_iter_t *iter;
struct download *d;
guint n;
g_assert(dl_server_valid(server));
if (count_running_downloads() >= GNET_PROPERTY(max_downloads))
break;
/*
* List is sorted, so as soon as we go beyond the current time,
* we can stop.
*/
if (delta_time(now, server->retry_after) < 0)
break;
if (server_list_length(server, DL_LIST_WAITING) == 0)
continue;
if (
count_running_on_server(server)
>= GNET_PROPERTY(max_host_downloads)
) {
download_list_send_head_ping(server->list[DL_LIST_WAITING]);
continue;
}
/*
* OK, pick the download at the start of the waiting list, but
* do not remove it yet. This will be done by download_start().
*/
g_assert(server->list[DL_LIST_WAITING]); /* Since count != 0 */
n = 0;
d = NULL;
iter = list_iter_before_head(server->list[DL_LIST_WAITING]);
while (list_iter_has_next(iter)) {
struct download *cur;
cur = list_iter_next(iter);
download_check(cur);
if (cur->flags & (DL_F_SUSPENDED | DL_F_PAUSED))
continue;
if (download_has_enough_active_sources(cur)) {
download_send_head_ping(cur);
continue;
}
if (
delta_time(now, cur->last_update) <=
(time_delta_t) cur->timeout_delay
) {
download_send_head_ping(cur);
continue;
}
/* Note that we skip over paused and suspended downloads */
if (delta_time(now, cur->retry_after) < 0)
break; /* List is sorted */
if (d) {
if ((NULL != d->thex) == (NULL != cur->thex)) {
/*
* Pick the download with the most progress. Otherwise
* we easily end up with dozens of partials from the
* the server.
*/
if (
download_total_progress(d)
>= download_total_progress(cur)
) {
download_send_head_ping(cur);
continue;
}
}
/* Give priority to THEX downloads */
if (d->thex && NULL == cur->thex) {
download_send_head_ping(cur);
continue;
}
}
if (d)
download_send_head_ping(d);
d = cur;
/*
* If there are a lot of downloads queued at a single server we
* might spend a lot of time scanning the queue of a download
* to pick. Thus limit the amount of items we're going to take
* into account.
*/
if (n++ > 100)
break;
}
list_iter_free(&iter);
if (d) {
download_start(d, FALSE);
}
/*
* It's possible that download_start() ended-up changing the
* dl_by_time list we're iterating over. That's why all changes
* to that list update the dl_by_time_change variable, which we
* snapshot upon entry into the loop.
* --RAM, 24/08/2002.
*/
if (last_change != dl_by_time.change[i])
goto retry;
}
}
}
static void
download_push(struct download *d, gboolean on_timeout)
{
gboolean ignore_push = FALSE;
download_check(d);
if (
(d->flags & DL_F_PUSH_IGN) ||
(d->server->attrs & DLS_A_PUSH_IGN) ||
has_blank_guid(d)
)
ignore_push = TRUE;
if (
ignore_push ||
GNET_PROPERTY(is_firewalled) ||
!GNET_PROPERTY(send_pushes)
) {
if (d->push)
download_push_remove(d);
goto attempt_retry;
}
if (!d->push)
download_push_insert(d);
g_assert(d->push);
if (download_send_push_request(d)) {
/*
* The first time we come here, we simply record we did send UDP
* pushes and return. Next time, we'll continue below.
* The rational here is that UDP is a faster way to propagate PUSH
* requests, but we have to fallback in case it does not work.
* --RAM, 2007-05-06
*/
if (!(d->flags & DL_F_UDP_PUSH)) {
d->flags |= DL_F_UDP_PUSH;
return;
}
/* FALL THROUGH */
}
/*
* Contact push proxies via TCP, if we have any.
*/
if (use_push_proxy(d))
return;
/*
* Nothing is working, we may be out of reach. Try to ignore the PUSH
* flag if the address is deemed to be reacheable...
*/
if (!d->always_push) {
download_push_remove(d);
goto attempt_retry;
} else {
/*
* If the address is not a private IP, it is possible that the
* servent set the "Push" flag incorrectly.
* -- RAM, 18/08/2002.
*/
if (!host_is_valid(download_addr(d), download_port(d))) {
download_unavailable(d, GTA_DL_ERROR, _("Push route lost"));
download_remove_all_from_peer(
download_guid(d), download_addr(d), download_port(d), TRUE);
} else {
/*
* Later on, if we manage to connect to the server, we'll
* make sure to mark it so that we ignore pushes to it, and
* we will clear the `always_push' indication.
* (see download_send_request() for more information)
*/
download_push_remove(d);
if (GNET_PROPERTY(download_debug) > 2)
g_message("PUSH trying to ignore them for %s",
host_addr_port_to_string(download_addr(d),
download_port(d)));
d->flags |= DL_F_PUSH_IGN;
download_queue(d, _("Ignoring Push flag"));
}
}
return;
attempt_retry:
/*
* If we're aborting a download flagged with "Push ignore" due to a
* timeout reason, chances are great that this host is indeed firewalled!
* Tell them so. -- RAM, 18/08/2002.
*/
if (
d->always_push && /* Normally requires a push */
(d->flags & DL_F_PUSH_IGN) && /* Started to ignore pushes */
!(d->server->attrs & DLS_A_PUSH_IGN) /* But never connected yet */
) {
d->retries++;
if (on_timeout || d->retries > 5) {
/*
* Looks like we won't be able to ever reach this host.
* Abort the download, and remove all the ones for the same host.
*/
download_unavailable(d, GTA_DL_ERROR,
_("Can't reach host (Push or Direct)"));
download_remove_all_from_peer(
download_guid(d), download_addr(d), download_port(d), TRUE);
} else
download_queue_hold(d, GNET_PROPERTY(download_retry_refused_delay),
NG_("No direct connection yet (%u retry)",
"No direct connection yet (%u retries)", d->retries),
d->retries);
} else if (d->retries < GNET_PROPERTY(download_max_retries)) {
d->retries++;
if (on_timeout)
download_queue_hold(d, GNET_PROPERTY(download_retry_timeout_delay),
NG_("Timeout (%u retry)",
"Timeout (%u retries)", d->retries), d->retries);
else
download_queue_hold(d, GNET_PROPERTY(download_retry_refused_delay),
NG_("Connection refused (%u retry)",
"Connection refused (%u retries)", d->retries),
d->retries);
} else {
/*
* Looks like this host is down. Abort the download, and remove all
* the ones queued for the same host.
*/
download_unavailable(d, GTA_DL_ERROR,
NG_("Timeout (%u retry)",
"Timeout (%u retries)", d->retries), d->retries);
download_remove_all_from_peer(
download_guid(d), download_addr(d), download_port(d), TRUE);
}
/*
* Remove this source from mesh, since we don't seem to be able to
* connect to it properly.
*/
download_bad_source(d);
}
/**
* Direct download failed, let's try it with a push request.
*/
void
download_fallback_to_push(struct download *d,
gboolean on_timeout, gboolean user_request)
{
g_return_if_fail(d);
download_check(d);
if (DOWNLOAD_IS_QUEUED(d)) {
if (!d->push) {
download_push_insert(d);
}
return;
}
/* If we're receiving data or already sent push, we're wrong
* here. Most likely it was unnecessarily requested by the user.
*/
if (DOWNLOAD_IS_ACTIVE(d) || DOWNLOAD_IS_EXPECTING_GIV(d))
return;
if (DOWNLOAD_IS_STOPPED(d))
return;
if (!d->socket) {
g_warning("download_fallback_to_push(): no socket for '%s'",
download_basename(d));
} else {
/*
* If a DNS lookup error occurred, discard the hostname we have.
* Due to the async nature of the DNS lookups, we must check for
* a non-NULL hostname, in case we already detected it earlier for
* this server, in another connection attempt.
*
* XXX we should allow for DNS failure and mark the hostname bad
* XXX for a while only, then re-attempt periodically, instead of
* XXX simply discarding it.
*/
if (socket_bad_hostname(d->socket) && d->server->hostname != NULL) {
g_warning("hostname \"%s\" for %s could not resolve, discarding",
d->server->hostname,
host_addr_port_to_string(download_addr(d), download_port(d)));
atom_str_free_null(&d->server->hostname);
fi_src_info_changed(d);
}
/*
* If we could not connect to the host, but we have a hostname and
* we did not perform a DNS lookup this time, request one for the
* next attempt.
*/
if (d->server->hostname != NULL && !(d->flags & DL_F_DNS_LOOKUP))
d->server->attrs |= DLS_A_DNS_LOOKUP;
socket_free_null(&d->socket);
}
file_object_release(&d->out_file);
download_set_status(d, user_request ? GTA_DL_PUSH_SENT : GTA_DL_FALLBACK);
d->last_update = tm_time(); /* Reset timeout if we send the push */
download_push(d, on_timeout);
fi_src_status_changed(d);
}
static const gchar *
download_escape_name(const gchar *name)
{
const gchar *atom;
gchar *escaped;
escaped = url_escape_cntrl(name);
atom = atom_str_get(escaped);
if (name != escaped) {
G_FREE_NULL(escaped);
}
return atom;
}
static guint32
get_index_from_uri(const gchar *uri)
{
guint32 idx = 0;
if (uri) {
const gchar *endptr;
endptr = is_strprefix(uri, "/get/");
if (endptr) {
gint error;
/*
* Only accept URIs of this form with a non-empty filename:
*
* "/get/<32-bit integer>/<filename>"
*/
idx = parse_uint32(endptr, &endptr, 10, &error);
if (
error ||
'/' != endptr[0] ||
'\0' == endptr[1] ||
NULL != strchr(&endptr[1], '/')
) {
idx = 0;
}
}
}
return idx;
}
/*
* Downloads creation and destruction
*/
/**
* Create a new download.
*
* @returns created download structure, or NULL if none.
*/
static struct download *
create_download(
const gchar *file_name,
const gchar *uri,
filesize_t size,
const host_addr_t addr,
guint16 port,
const gchar *guid,
const gchar *hostname,
const struct sha1 *sha1,
const struct tth *tth,
time_t stamp,
fileinfo_t *file_info,
const gnet_host_vec_t *proxies,
guint32 cflags,
const gchar *parq_id,
gboolean use_mesh)
{
struct dl_server *server;
struct download *d;
const gchar *reason;
guint32 record_index;
fileinfo_t *fi;
g_assert(host_addr_initialized(addr));
if (file_info) {
g_return_val_if_fail(!sha1 || sha1_eq(file_info->sha1, sha1), NULL);
if (file_info->tth) {
g_return_val_if_fail(!tth || tth_eq(file_info->tth, tth), NULL);
}
}
#if 0 /* This is helpful when you have a transparent proxy running */
/* XXX make that configurable from the GUI --RAM, 2005-08-15 */
/*
* Never try to download from ports 80 or 443.
*/
if ((port == 80) || (port == 443)) {
return NULL;
}
#endif
/*
* Reject if we're trying to download from ourselves (could happen
* if someone echoes back our own alt-locs to us with PFSP).
*/
if (0 != port && is_my_address_and_port(addr, port)) {
if (GNET_PROPERTY(download_debug)) {
g_warning("create_download(): ignoring download from own address");
}
return NULL;
}
{
const gchar *orig_name;
gchar *s;
orig_name = file_name;
s = gm_sanitize_filename(orig_name, FALSE, FALSE);
/* An empty filename would create a corrupt download entry */
file_name = atom_str_get('\0' != s[0] ? s : "noname");
if (orig_name != s) {
G_FREE_NULL(s);
}
}
/*
* Create server if none exists already.
*/
if (NULL == guid) {
guid = blank_guid;
}
server = get_server(guid, addr, port, TRUE);
g_assert(dl_server_valid(server));
/*
* If some push proxies are given, and provided the `stamp' argument
* is recent enough, drop the existing list and replace it with the
* one coming from the query hit.
*/
if (proxies != NULL && delta_time(stamp, server->proxies_stamp) > 0) {
free_proxies(server);
server->proxies = hostvec_to_slist(proxies);
server->proxies_stamp = stamp;
}
/*
* Refuse to queue the same download twice. --RAM, 04/11/2001
*/
d = has_same_download(file_name, sha1, size, guid, addr, port);
if (d) {
download_check(d);
atom_str_free_null(&file_name);
return NULL;
}
fi = file_info == NULL
? file_info_get(file_name, GNET_PROPERTY(save_file_path),
size, sha1, 0 != size)
: file_info;
if (NULL == fi || (FI_F_SEEDING & fi->flags)) {
atom_str_free_null(&file_name);
return NULL;
}
if (tth) {
if (NULL == fi->tth) {
file_info_got_tth(fi, tth);
} else if (!tth_eq(tth, fi->tth)) {
atom_str_free_null(&file_name);
return NULL;
}
}
/*
* Initialize download.
*/
d = download_alloc();
d->last_update = tm_time();
d->server = server;
d->server->refcnt++;
/*
* If we know that this server can be directly connected to, ignore
* the push flag. --RAM, 18/08/2002.
*/
if ((d->server->attrs & DLS_A_PUSH_IGN) || has_blank_guid(d)) {
cflags &= ~SOCK_F_PUSH;
}
d->cflags = cflags;
d->always_push = 0 != (SOCK_F_PUSH & d->cflags);
d->list_idx = DL_LIST_INVALID;
d->file_name = file_name;
d->escaped_name = download_escape_name(d->file_name);
d->uri = uri ? atom_str_get(uri) : NULL;
d->file_size = size;
/*
* Note: size and skip will be filled by download_pick_chunk() later
* if we use swarming.
*/
d->size = size; /* Will be changed if range requested */
d->record_stamp = stamp;
download_set_sha1(d, sha1);
if (d->always_push) {
download_push_insert(d);
} else {
d->push = FALSE;
}
download_add_to_list(d, DL_LIST_WAITING);
/*
* If fileinfo is marked with FI_F_SUSPEND, it means we are in the process
* of verifying the SHA1 of the download. If it matches with the SHA1
* we got initially, we'll remove the downloads, otherwise we will
* restart it.
*
* That's why we still accept downloads for that fileinfo, but do not
* schedule them: we wait for the outcome of the SHA1 verification process.
*/
if (fi->flags & FI_F_SUSPEND)
d->flags |= DL_F_SUSPENDED;
if (fi->flags & FI_F_PAUSED)
d->flags |= DL_F_PAUSED;
if (stamp == MAGIC_TIME) /* Download recreated at startup */
file_info_add_source(fi, d); /* Preserve original "ntime" */
else
file_info_add_new_source(fi, d);
/*
* NOTE: These are explicitely prepended to avoid inconsistencies if
* we just happen to iterate forwards over these lists.
*/
hash_list_prepend(sl_downloads, d);
hash_list_prepend(sl_unqueued, d);
download_dirty = TRUE; /* Refresh list, in case we crash */
/*
* Record server's hostname if non-NULL and not empty.
*/
if (hostname != NULL && *hostname != '\0')
set_server_hostname(d->server, hostname);
/*
* Insert in download mesh if it does not require a push and has a SHA1.
*/
record_index = get_index_from_uri(d->uri);
if (!d->always_push && d->sha1 && (NULL == d->uri || 0 != record_index))
dmesh_add(d->sha1, addr, port, record_index, d->file_name, stamp);
/*
* When we know our SHA1, if we don't have a SHA1 in the `fi' and we
* looked for it, it means that they didn't have "strict_sha1_matching"
* at some point in time.
*
* If we have a SHA1, it must match.
*/
if (d->sha1 != NULL && fi->sha1 == NULL) {
gboolean success = file_info_got_sha1(fi, d->sha1);
if (success) {
g_message("forced SHA1 %s after %s byte%s "
"downloaded for %s",
sha1_base32(d->sha1), uint64_to_string(fi->done),
fi->done == 1 ? "" : "s",
download_basename(d));
if (DOWNLOAD_IS_QUEUED(d)) { /* file_info_got_sha1() can queue */
return d;
}
} else {
download_info_reget(d);
download_queue(d, _("Dup SHA1 during creation"));
return d;
}
}
g_assert(d->sha1 == NULL || d->file_info->sha1 == d->sha1);
if (d->flags & DL_F_SUSPENDED) {
reason = _("Suspended (SHA1 checking)");
} else if (d->flags & DL_F_PAUSED) {
reason = _("Paused");
} else if (count_running_downloads() >= GNET_PROPERTY(max_downloads)) {
reason = _("Max. number of downloads reached");
} else if (
count_running_on_server(d->server)
>= GNET_PROPERTY(max_host_downloads)
) {
reason = _("Max. number of downloads for this host reached");
} else if (download_has_enough_active_sources(d)) {
reason = _("Has already enough active sources");
} else {
reason = _("download_start() failed");
download_start(d, FALSE); /* Start the download immediately */
}
if (GTA_DL_INVALID == d->status) {
/* Ensure it has a time for status display */
d->retry_after = time_advance(tm_time(), (random_raw() % 4) + 1);
download_queue(d, "%s", reason);
}
/*
* Record PARQ id if present, so we may answer QUEUE callbacks.
*/
if (parq_id && !d->parq_dl) {
d->parq_dl = parq_dl_create(d);
parq_dl_add_id(d, parq_id);
}
if (use_mesh && sha1 && size)
dmesh_multiple_downloads(sha1, size, d->file_info);
return d;
}
/**
* Automatic download request.
*/
void
download_auto_new(const gchar *file_name,
filesize_t size,
const host_addr_t addr,
guint16 port,
const gchar *guid,
const gchar *hostname,
const struct sha1 *sha1,
const struct tth *tth,
time_t stamp,
fileinfo_t *fi,
gnet_host_vec_t *proxies,
guint32 flags)
{
const char *reason;
enum ignore_val ign_reason;
/*
* Make sure host is reacheable, especially if we come from the GUI,
* which cannot access the bogus IP database.
*/
if (0 == (SOCK_F_PUSH & flags) && !host_is_valid(addr, port)) {
/* We cannot send a PUSH without a valid GUID */
if (NULL == guid || guid_eq(guid, blank_guid))
return;
flags |= SOCK_F_PUSH;
}
/*
* Make sure we're not prevented from downloading that file.
*/
ign_reason = ignore_is_requested(
fi ? filepath_basename(fi->pathname) : file_name,
fi ? fi->size : size,
fi ? fi->sha1 : sha1);
if (IGNORE_FALSE != ign_reason) {
reason = ignore_reason_to_string(ign_reason);
if (!reason) {
g_error("ignore_is_requested() returned unexpected %u",
(guint) ign_reason);
}
goto abort_download;
}
/*
* Create download.
*/
create_download(file_name,
NULL, /* URI */
size,
addr,
port,
guid,
hostname,
sha1,
tth,
stamp,
fi,
proxies,
flags,
NULL, /* PARQ ID */
FALSE /* Don't use download mesh */
);
return;
abort_download:
if (GNET_PROPERTY(download_debug) > 4)
g_message("ignoring auto download for \"%s\": %s", file_name, reason);
return;
}
/**
* Clone download, resetting most dynamically allocated structures in the
* original since they are shallow-copied to the new download.
*
* (This routine is used because each different download from the same host
* will become a line in the GUI, and the GUI stores download structures in
* ts row data, expecting a one-to-one mapping between a download and the GUI).
*/
static struct download *
download_clone(struct download *d)
{
struct download *cd;
fileinfo_t *fi;
download_check(d);
g_assert(!(d->flags & (DL_F_ACTIVE_QUEUED|DL_F_PASSIVE_QUEUED)));
g_assert(d->buffers != NULL);
g_assert(d->buffers->held == 0); /* All data flushed */
fi = d->file_info;
cd = download_alloc();
*cd = *d; /* Struct copy */
cd->file_info = NULL; /* has not been added to fi sources list */
cd->src_handle_valid = FALSE;
file_info_add_source(fi, cd); /* add cloned source */
g_assert(d->io_opaque == NULL); /* If cloned, we were receiving! */
cd->rx = NULL;
cd->bio = NULL; /* Recreated on each transfer */
cd->out_file = NULL; /* File re-opened each time */
cd->socket->resource.download = cd; /* Takes ownership of socket */
cd->list_idx = DL_LIST_INVALID;
cd->sha1 = d->sha1 ? atom_sha1_get(d->sha1) : NULL;
cd->file_name = atom_str_get(d->file_name);
cd->escaped_name = atom_str_get(d->escaped_name);
cd->uri = d->uri ? atom_str_get(d->uri) : NULL;
cd->push = FALSE;
download_set_status(cd, GTA_DL_CONNECTING);
cd->server->refcnt++;
download_add_to_list(cd, DL_LIST_WAITING); /* Will add SHA1 to server */
download_set_sha1(d, NULL);
/*
* NOTE: These are explicitely prepended to avoid inconsistencies if
* we just happen to iterate forwards over these lists.
*/
hash_list_prepend(sl_downloads, cd);
hash_list_prepend(sl_unqueued, cd);
if (d->push) {
download_push_remove(d);
download_push_insert(cd);
}
if (d->parq_dl)
parq_dl_reparent_id(d, cd);
if (d->cproxy != NULL)
cproxy_reparent(d, cd);
g_assert(d->parq_dl == NULL); /* Cleared by parq_dl_reparent_id() */
/*
* The following copied data are cleared in the child.
*/
cd->buffers = NULL; /* Allocated at each new request */
/*
* The following have been copied and appropriated by the cloned download.
* They are reset so that a download_free() on the original will not
* free them.
*/
d->socket = NULL;
d->ranges = NULL;
return cd;
}
/**
* Search has detected index change in queued download.
*/
void
download_index_changed(const host_addr_t addr, guint16 port, const gchar *guid,
guint32 from, guint32 to)
{
struct dl_server *server = get_server(guid, addr, port, FALSE);
guint nfound = 0;
GSList *to_stop = NULL;
GSList *sl;
guint n;
enum dl_list listnum[] = { DL_LIST_RUNNING, DL_LIST_WAITING };
if (!server)
return;
g_assert(dl_server_valid(server));
for (n = 0; n < G_N_ELEMENTS(listnum); n++) {
list_iter_t *iter;
iter = list_iter_before_head(server->list[n]);
while (list_iter_has_next(iter)) {
struct download *d;
d = list_iter_next(iter);
download_check(d);
if (d->record_index != from)
continue;
d->record_index = to;
nfound++;
switch (d->status) {
case GTA_DL_REQ_SENT:
case GTA_DL_HEADERS:
case GTA_DL_PUSH_SENT:
/*
* We've sent a request with possibly the wrong index.
* We can't know for sure, but it's safer to stop it, and
* restart it in a while. Sure, we might lose the download
* slot, but we might as well have gotten a wrong file.
*
* NB: this can't happen when the remote peer is gtk-gnutella
* since we check the matching between the index and the file
* name, but some peers might not bother.
*/
g_message("stopping request for \"%s\": index changed",
download_basename(d));
to_stop = g_slist_prepend(to_stop, d);
break;
case GTA_DL_RECEIVING:
/*
* Ouch. Pray and hope that the change occurred after we
* requested the file. There's nothing we can do now.
*/
g_message("index of \"%s\" changed during reception",
download_basename(d));
break;
default:
/*
* Queued or other state not needing special notice
*/
if (GNET_PROPERTY(download_debug) > 3) {
g_message("noted index change "
"from %u to %u at %s for \"%s\"",
from, to, guid_hex_str(guid), download_basename(d));
}
break;
}
}
list_iter_free(&iter);
}
for (sl = to_stop; sl; sl = g_slist_next(sl)) {
struct download *d = sl->data;
download_check(d);
download_queue_delay(d, GNET_PROPERTY(download_retry_stopped_delay),
_("Stopped (Index changed)"));
}
g_slist_free(to_stop);
to_stop = NULL;
/*
* This is a sanity check: we should not have any duplicate request
* in our download list.
*/
if (nfound > 1) {