links/sched.c

1183 lines
28 KiB
C

/* sched.c
* Links internal scheduler
* (c) 2002 Mikulas Patocka
* This file is a part of the Links program, released under GPL.
*/
#include "links.h"
static tcount connection_count = 0;
static int active_connections = 0;
tcount netcfg_stamp = 0;
struct list_head queue = {&queue, &queue};
struct h_conn {
list_entry_1st
unsigned char *host;
int conn;
list_entry_last
};
static struct list_head h_conns = {&h_conns, &h_conns};
struct list_head keepalive_connections = {&keepalive_connections, &keepalive_connections};
/* prototypes */
static void send_connection_info(struct connection *c);
static void del_keepalive_socket(struct k_conn *kc);
static void check_keepalive_connections(void);
#ifdef DEBUG
static void check_queue_bugs(void);
#else
#define check_queue_bugs() do { } while (0)
#endif
unsigned long connect_info(int type)
{
int i = 0;
struct connection *ce;
struct list_head *lce;
switch (type) {
case CI_FILES:
return list_size(&queue);
case CI_CONNECTING:
foreach(struct connection, ce, lce, queue) i += ce->state > S_WAIT && ce->state < S_TRANS;
return i;
case CI_TRANSFER:
foreach(struct connection, ce, lce, queue) i += ce->state == S_TRANS;
return i;
case CI_KEEP:
#ifndef DOS
/* this is very slow on DOS */
check_keepalive_connections();
#endif
return list_size(&keepalive_connections);
default:
internal_error("connect_info: bad request");
}
return 0;
}
static int getpri(struct connection *c)
{
int i;
for (i = 0; i < N_PRI; i++) if (c->pri[i]) return i;
internal_error("connection has no owner");
return N_PRI;
}
static int connection_disappeared(struct connection *c, tcount count)
{
struct connection *d;
struct list_head *ld;
foreach(struct connection, d, ld, queue) if (c == d && count == d->count) return 0;
return 1;
}
static struct h_conn *is_host_on_list(struct connection *c)
{
unsigned char *ho;
struct h_conn *h;
struct list_head *lh;
if (!(ho = get_host_name(c->url))) return NULL;
foreach(struct h_conn, h, lh, h_conns) if (!strcmp(cast_const_char h->host, cast_const_char ho)) {
mem_free(ho);
return h;
}
mem_free(ho);
return NULL;
}
static int st_r = 0;
static void stat_timer(void *c_)
{
struct connection *c = (struct connection *)c_;
struct remaining_info *r = &c->prg;
uttime now = get_time();
uttime a = now - r->last_time;
if (a > SPD_DISP_TIME * 100)
a = SPD_DISP_TIME * 100;
if (getpri(c) == PRI_CANCEL && (c->est_length > (longlong)memory_cache_size * MAX_CACHED_OBJECT || c->from > (longlong)memory_cache_size * MAX_CACHED_OBJECT)) register_bottom_half(check_queue, NULL);
if (c->state > S_WAIT) {
r->loaded = c->received;
if ((r->size = c->est_length) < (r->pos = c->from) && r->size != -1)
r->size = c->from;
r->dis_b += a;
while (r->dis_b >= SPD_DISP_TIME * CURRENT_SPD_SEC) {
r->cur_loaded -= r->data_in_secs[0];
memmove(r->data_in_secs, r->data_in_secs + 1, sizeof(off_t) * (CURRENT_SPD_SEC - 1));
r->data_in_secs[CURRENT_SPD_SEC - 1] = 0;
r->dis_b -= SPD_DISP_TIME;
}
r->data_in_secs[CURRENT_SPD_SEC - 1] += r->loaded - r->last_loaded;
r->cur_loaded += r->loaded - r->last_loaded;
r->last_loaded = r->loaded;
r->elapsed += a;
}
r->last_time = now;
r->timer = install_timer(SPD_DISP_TIME, stat_timer, c);
if (!st_r) send_connection_info(c);
}
void setcstate(struct connection *c, int state)
{
struct status *stat;
struct list_head *lstat;
if (state < 0)
if (c->cache)
finish_cache_entry(c->cache);
if (c->state < 0 && state >= 0) c->prev_error = c->state;
if ((c->state = state) == S_TRANS) {
struct remaining_info *r = &c->prg;
if (r->timer == NULL) {
tcount count = c->count;
if (!r->valid) {
memset(r, 0, sizeof(struct remaining_info));
r->valid = 1;
}
r->last_time = get_time();
r->last_loaded = r->loaded;
st_r = 1;
stat_timer(c);
st_r = 0;
if (connection_disappeared(c, count)) return;
}
} else {
struct remaining_info *r = &c->prg;
if (r->timer != NULL) kill_timer(r->timer), r->timer = NULL;
}
foreach(struct status, stat, lstat, c->statuss) {
stat->state = state;
stat->prev_error = c->prev_error;
}
if (state >= 0) send_connection_info(c);
}
static struct k_conn *is_host_on_keepalive_list(struct connection *c)
{
unsigned char *ho;
int po;
void (*ph)(struct connection *);
struct k_conn *h;
struct list_head *lh;
if ((po = get_port(c->url)) == -1) return NULL;
if (!(ph = get_protocol_handle(c->url))) return NULL;
if (!(ho = get_keepalive_id(c->url))) return NULL;
foreach(struct k_conn, h, lh, keepalive_connections)
if (h->protocol == ph && h->port == po && !strcmp(cast_const_char h->host, cast_const_char ho)) {
mem_free(ho);
return h;
}
mem_free(ho);
return NULL;
}
int get_keepalive_socket(struct connection *c, int *protocol_data)
{
struct k_conn *k;
int cc;
if (c->tries > 0 || c->unrestartable) return -1;
if (!(k = is_host_on_keepalive_list(c))) return -1;
cc = k->conn;
if (protocol_data) *protocol_data = k->protocol_data;
#ifdef HAVE_SSL
freeSSL(c->ssl);
c->ssl = k->ssl;
#endif
memcpy(&c->last_lookup_state, &k->last_lookup_state, sizeof(struct lookup_state));
del_from_list(k);
mem_free(k->host);
mem_free(k);
c->sock1 = cc;
if (max_tries == 1) c->tries = -1;
c->keepalive = 1;
return 0;
}
static int abort_all_keepalive_connections(void)
{
int did_something = 0;
while (!list_empty(keepalive_connections)) {
del_keepalive_socket(list_struct(keepalive_connections.next, struct k_conn));
did_something = 1;
}
check_keepalive_connections();
return did_something;
}
static void free_connection_data(struct connection *c)
{
struct h_conn *h;
int rs;
if (c->sock1 != -1) set_handlers(c->sock1, NULL, NULL, NULL);
close_socket(&c->sock2);
if (c->pid) {
EINTRLOOP(rs, kill(c->pid, SIGINT));
EINTRLOOP(rs, kill(c->pid, SIGTERM));
EINTRLOOP(rs, kill(c->pid, SIGKILL));
c->pid = 0;
}
if (!c->running) {
internal_error("connection already suspended");
}
c->running = 0;
if (c->dnsquery) kill_dns_request(&c->dnsquery);
if (c->buffer) {
mem_free(c->buffer);
c->buffer = NULL;
}
if (c->newconn) {
mem_free(c->newconn);
c->newconn = NULL;
}
if (c->info) {
mem_free(c->info);
c->info = NULL;
}
clear_connection_timeout(c);
if (!c->doh) {
if (--active_connections < 0) {
internal_error("active connections underflow");
active_connections = 0;
}
}
if (c->state != S_WAIT && !c->doh) {
if ((h = is_host_on_list(c))) {
if (!--h->conn) {
del_from_list(h);
mem_free(h->host);
mem_free(h);
}
} else internal_error("suspending connection that is not on the list (state %d)", c->state);
}
}
static void send_connection_info(struct connection *c)
{
#if 0
int st = c->state;
tcount count = c->count;
struct status *stat;
struct list_head *lstat;
/*debug("reporting conn info %s", c->url);*/
foreach(struct status, stat, lstat, c->statuss) {
/*debug("status list &%p %p %p, status %p %p %p", &c->statuss, c->statuss.next, c->statuss.prev, lstat, lstat->next, lstat->prev);*/
stat->ce = c->cache;
verify_list_entry(lstat);
lstat = lstat->next;
if (stat->end) stat->end(stat, stat->data);
if (st >= 0 && connection_disappeared(c, count)) return;
verify_list_entry(lstat);
lstat = lstat->prev;
}
/*debug("end report %s", c->url);*/
#else
if (!list_empty(c->statuss)) {
int st = c->state;
tcount count = c->count;
struct list_head *lstat = c->statuss.next;
while (1) {
int e;
struct status *xstat = list_struct(lstat, struct status);
xstat->ce = c->cache;
lstat = lstat->next;
e = lstat == &c->statuss;
if (xstat->end) xstat->end(xstat, xstat->data);
if (e) return;
if (st >= 0 && connection_disappeared(c, count)) return;
}
}
#endif
}
static void del_connection(struct connection *c)
{
struct cache_entry *ce = c->cache;
if (ce) ce->refcount++;
del_from_list(c);
send_connection_info(c);
if (ce) ce->refcount--;
if (c->detached) {
if (ce && !ce->url[0] && !is_entry_used(ce) && !ce->refcount)
delete_cache_entry(ce);
} else {
if (ce)
trim_cache_entry(ce);
}
mem_free(c->url);
if (c->prev_url) mem_free(c->prev_url);
#ifdef HAVE_SSL
freeSSL(c->ssl);
#endif
mem_free(c);
}
void add_keepalive_socket(struct connection *c, uttime timeout, int protocol_data)
{
struct k_conn *k;
int rs;
free_connection_data(c);
if (c->sock1 == -1) {
internal_error("keepalive connection not connected");
goto del;
}
k = mem_alloc(sizeof(struct k_conn));
if (c->netcfg_stamp != netcfg_stamp ||
#ifdef HAVE_SSL
ssl_not_reusable(c->ssl) ||
#endif
(k->port = get_port(c->url)) == -1 ||
!(k->protocol = get_protocol_handle(c->url)) ||
!(k->host = get_keepalive_id(c->url))) {
mem_free(k);
del_connection(c);
goto clos;
}
k->conn = c->sock1;
k->timeout = timeout;
k->add_time = get_absolute_time();
k->protocol_data = protocol_data;
#ifdef HAVE_SSL
k->ssl = c->ssl;
c->ssl = NULL;
#endif
memcpy(&k->last_lookup_state, &c->last_lookup_state, sizeof(struct lookup_state));
add_to_list(keepalive_connections, k);
del:
del_connection(c);
check_queue_bugs();
register_bottom_half(check_queue, NULL);
return;
clos:
EINTRLOOP(rs, close(c->sock1));
check_queue_bugs();
register_bottom_half(check_queue, NULL);
}
static void del_keepalive_socket(struct k_conn *kc)
{
int rs;
del_from_list(kc);
#ifdef HAVE_SSL
freeSSL(kc->ssl);
#endif
EINTRLOOP(rs, close(kc->conn));
mem_free(kc->host);
mem_free(kc);
}
static struct timer *keepalive_timeout = NULL;
static void keepalive_timer(void *x)
{
keepalive_timeout = NULL;
check_keepalive_connections();
}
static void check_keepalive_connections(void)
{
struct k_conn *kc;
struct list_head *lkc;
uttime ct = get_absolute_time();
int p = 0;
if (keepalive_timeout != NULL)
kill_timer(keepalive_timeout), keepalive_timeout = NULL;
foreach(struct k_conn, kc, lkc, keepalive_connections) {
if (can_read(kc->conn) || ct - kc->add_time > kc->timeout) {
lkc = lkc->prev;
del_keepalive_socket(kc);
} else
p++;
}
for (; p > MAX_KEEPALIVE_CONNECTIONS; p--)
if (!list_empty(keepalive_connections))
del_keepalive_socket(list_struct(keepalive_connections.prev, struct k_conn));
else
internal_error("keepalive list empty");
if (!list_empty(keepalive_connections))
keepalive_timeout = install_timer(KEEPALIVE_CHECK_TIME, keepalive_timer, NULL);
}
static void add_to_queue(struct connection *c)
{
struct connection *cc;
struct list_head *lcc;
int pri = getpri(c);
foreach(struct connection, cc, lcc, queue) if (getpri(cc) > pri) break;
add_before_list_entry(lcc, &c->list_entry);
}
static void sort_queue(void)
{
struct connection *c, *n;
struct list_head *lc;
int swp;
do {
swp = 0;
foreach(struct connection, c, lc, queue) if (c->list_entry.next != &queue) {
n = list_struct(c->list_entry.next, struct connection);
if (getpri(n) < getpri(c)) {
del_from_list(c);
add_after_pos(n, c);
swp = 1;
}
}
} while (swp);
}
static void interrupt_connection(struct connection *c)
{
#ifdef HAVE_SSL
freeSSL(c->ssl);
c->ssl = NULL;
#endif
close_socket(&c->sock1);
free_connection_data(c);
}
static void suspend_connection(struct connection *c)
{
interrupt_connection(c);
setcstate(c, S_WAIT);
}
static int try_to_suspend_connection(struct connection *c, unsigned char *ho)
{
int pri = getpri(c);
struct connection *d;
struct list_head *ld;
foreachback(struct connection, d, ld, queue) {
if (getpri(d) <= pri) return -1;
if (d->state == S_WAIT) continue;
if (d->unrestartable == 2 && getpri(d) < PRI_CANCEL) continue;
if (ho) {
unsigned char *h;
if (!(h = get_host_name(d->url))) continue;
if (strcmp(cast_const_char h, cast_const_char ho)) {
mem_free(h);
continue;
}
mem_free(h);
}
suspend_connection(d);
return 0;
}
return -1;
}
int is_noproxy_host(unsigned char *host)
{
if (!proxies.only_proxies) {
unsigned char *np = proxies.no_proxy;
int host_l = (int)strlen(cast_const_char host);
if (*np) while (1) {
int l = (int)strcspn(cast_const_char np, ",");
if (l > host_l)
goto no_match;
if (l < host_l && host[host_l - l - 1] != '.')
goto no_match;
if (casecmp(np, host + (host_l - l), l))
goto no_match;
return 1;
no_match:
if (!np[l])
break;
np += l + 1;
}
}
return 0;
}
int is_noproxy_url(unsigned char *url)
{
unsigned char *host = get_host_name(url);
int r = is_noproxy_host(host);
mem_free(host);
return r;
}
static void run_connection(struct connection *c)
{
struct h_conn *hc;
void (*func)(struct connection *);
if (c->running) {
internal_error("connection already running");
return;
}
memset(&c->last_lookup_state, 0, sizeof(struct lookup_state));
if (is_noproxy_url(remove_proxy_prefix(c->url))) {
c->socks_proxy[0] = 0;
c->dns_append[0] = 0;
} else {
safe_strncpy(c->socks_proxy, proxies.socks_proxy, sizeof c->socks_proxy);
safe_strncpy(c->dns_append, proxies.dns_append, sizeof c->dns_append);
}
if (proxies.only_proxies && !is_proxy_url(c->url) && casecmp(c->url, cast_uchar "data:", 5) && (!*c->socks_proxy || url_bypasses_socks(c->url))) {
setcstate(c, S_NO_PROXY);
del_connection(c);
return;
}
if (!(func = get_protocol_handle(c->url))) {
s_bad_url:
if (is_proxy_url(c->url)) setcstate(c, S_BAD_PROXY);
else setcstate(c, S_BAD_URL);
del_connection(c);
return;
}
if (c->doh && func != http_func && func != https_func && func != proxy_func)
goto s_bad_url;
if (!c->doh) {
if (!(hc = is_host_on_list(c))) {
hc = mem_alloc(sizeof(struct h_conn));
if (!(hc->host = get_host_name(c->url))) {
mem_free(hc);
goto s_bad_url;
}
hc->conn = 0;
add_to_list(h_conns, hc);
}
hc->conn++;
active_connections++;
}
c->keepalive = 0;
c->running = 1;
func(c);
}
static int is_connection_seekable(struct connection *c)
{
unsigned char *protocol = get_protocol_name(c->url);
if (!casestrcmp(protocol, cast_uchar "http") ||
!casestrcmp(protocol, cast_uchar "https") ||
!casestrcmp(protocol, cast_uchar "proxy")) {
unsigned char *d;
mem_free(protocol);
if (!c->cache || !c->cache->head)
return 1;
d = parse_http_header(c->cache->head, cast_uchar "Accept-Ranges", NULL);
if (d) {
mem_free(d);
return 1;
}
return 0;
}
if (!casestrcmp(protocol, cast_uchar "ftp")) {
mem_free(protocol);
return 1;
}
mem_free(protocol);
return 0;
}
int is_connection_restartable(struct connection *c)
{
return !(c->unrestartable >= 2 || (c->tries + 1 >= (max_tries ? max_tries : 1000)));
}
int is_last_try(struct connection *c)
{
int is_restartable;
c->tries++;
is_restartable = is_connection_restartable(c) && c->tries < 10;
c->tries--;
return !is_restartable;
}
void retry_connection(struct connection *c)
{
interrupt_connection(c);
if (!is_connection_restartable(c)) {
del_connection(c);
check_queue_bugs();
register_bottom_half(check_queue, NULL);
} else {
c->tries++;
c->prev_error = c->state;
run_connection(c);
}
}
void abort_connection(struct connection *c)
{
if (c->running) interrupt_connection(c);
del_connection(c);
check_queue_bugs();
register_bottom_half(check_queue, NULL);
}
static int try_connection(struct connection *c)
{
struct h_conn *hc;
if (!c->doh) {
if ((hc = is_host_on_list(c))) {
if (hc->conn >= max_connections_to_host) {
if (try_to_suspend_connection(c, hc->host)) return 0;
else return -1;
}
}
if (active_connections >= max_connections) {
if (try_to_suspend_connection(c, NULL)) return 0;
else return -1;
}
}
run_connection(c);
return 1;
}
#ifdef DEBUG
static void check_queue_bugs(void)
{
struct connection *d;
struct list_head *ld;
int p = 0, ps = 0;
int cc;
again:
cc = 0;
foreach(struct connection, d, ld, queue) {
int q = getpri(d);
cc += d->running && !d->doh;
if (q < p) {
if (!ps) {
internal_error("queue is not sorted");
sort_queue();
ps = 1;
goto again;
} else {
internal_error("queue is not sorted even after sort_queue!");
break;
}
} else p = q;
if (d->state < 0) {
internal_error("interrupted connection on queue (conn %s, state %d)", d->url, d->state);
ld = ld->prev;
abort_connection(d);
}
}
if (cc != active_connections) {
internal_error("bad number of active connections (counted %d, stored %d)", cc, active_connections);
active_connections = cc;
}
}
#endif
void check_queue(void *dummy)
{
struct connection *c;
struct list_head *lc;
again:
check_queue_bugs();
check_keepalive_connections();
foreach(struct connection, c, lc, queue) {
struct connection *d;
struct list_head *ld;
int cp = getpri(c);
foreachfrom(struct connection, d, ld, queue, &c->list_entry) {
if (getpri(d) != cp)
break;
if (!d->state && is_host_on_keepalive_list(d))
if (try_connection(d))
goto again;
}
foreachfrom(struct connection, d, ld, queue, &c->list_entry) {
if (getpri(d) != cp)
break;
if (!d->state)
if (try_connection(d))
goto again;
}
lc = ld->prev;
}
again2:
foreachback(struct connection, c, lc, queue) {
if (getpri(c) < PRI_CANCEL) break;
if (c->state == S_WAIT) {
setcstate(c, S_INTERRUPTED);
del_connection(c);
goto again2;
} else if (c->est_length > (longlong)memory_cache_size * MAX_CACHED_OBJECT || c->from > (longlong)memory_cache_size * MAX_CACHED_OBJECT) {
setcstate(c, S_INTERRUPTED);
abort_connection(c);
goto again2;
}
}
check_queue_bugs();
}
unsigned char *get_proxy_string(unsigned char *url)
{
if (is_noproxy_url(url))
return NULL;
if (*proxies.http_proxy && !casecmp(url, cast_uchar "http://", 7)) return proxies.http_proxy;
if (*proxies.ftp_proxy && !casecmp(url, cast_uchar "ftp://", 6)) return proxies.ftp_proxy;
#ifdef HAVE_SSL
if (*proxies.https_proxy && !casecmp(url, cast_uchar "https://", 8)) return proxies.https_proxy;
#endif
return NULL;
}
unsigned char *get_proxy(unsigned char *url)
{
unsigned char *proxy = get_proxy_string(url);
unsigned char *u;
u = stracpy(cast_uchar "");
if (proxy) {
add_to_strn(&u, cast_uchar "proxy://");
add_to_strn(&u, proxy);
add_to_strn(&u, cast_uchar "/");
}
add_to_strn(&u, url);
return u;
}
int is_proxy_url(unsigned char *url)
{
return !casecmp(url, cast_uchar "proxy://", 8);
}
unsigned char *remove_proxy_prefix(unsigned char *url)
{
unsigned char *x = NULL;
if (is_proxy_url(url))
x = get_url_data(url);
if (!x)
x = url;
return x;
}
int get_allow_flags(unsigned char *url)
{
return !casecmp(url, cast_uchar "smb://", 6) ? ALLOW_SMB :
!casecmp(url, cast_uchar "file://", 7) ? ALLOW_FILE : 0;
}
int disallow_url(unsigned char *url, int allow_flags)
{
if (!casecmp(url, cast_uchar "smb://", 6) && !(allow_flags & ALLOW_SMB) && !smb_options.allow_hyperlinks_to_smb) {
return S_SMB_NOT_ALLOWED;
}
if (!casecmp(url, cast_uchar "file://", 7) && !(allow_flags & ALLOW_FILE)) {
return S_FILE_NOT_ALLOWED;
}
return 0;
}
/* prev_url is a pointer to previous url or NULL */
/* prev_url will NOT be deallocated */
void load_url(unsigned char *url, unsigned char *prev_url, struct status *stat, int pri, int no_cache, int no_compress, int doh, int allow_flags, off_t position)
{
struct cache_entry *e = NULL;
struct connection *c;
struct list_head *lc;
unsigned char *u;
int must_detach = 0;
int err;
if (stat) {
stat->c = NULL;
stat->ce = NULL;
stat->state = S_OUT_OF_MEM;
stat->prev_error = 0;
stat->pri = pri;
}
#ifdef DEBUG
foreach(struct connection, c, lc, queue) {
struct status *st;
struct list_head *lst;
foreach(struct status, st, lst, c->statuss) {
if (st == stat) {
internal_error("status already assigned to '%s'", c->url);
stat->state = S_INTERNAL;
if (stat->end) stat->end(stat, stat->data);
goto ret;
}
}
}
#endif
if (!doh && is_url_blocked(url)) {
if (stat) {
stat->state = S_BLOCKED_URL;
if (stat->end) stat->end(stat, stat->data);
}
goto ret;
}
err = disallow_url(url, allow_flags);
if (err) {
if (stat) {
stat->state = err;
if (stat->end) stat->end(stat, stat->data);
}
goto ret;
}
if (no_cache <= NC_CACHE && !find_in_cache(url, &e)) {
if (e->incomplete) {
e->refcount--;
goto skip_cache;
}
if (!aggressive_cache && no_cache > NC_ALWAYS_CACHE) {
if (e->expire_time && e->expire_time < get_absolute_seconds()) {
if (no_cache < NC_IF_MOD) no_cache = NC_IF_MOD;
e->refcount--;
goto skip_cache;
}
}
if (no_compress) {
unsigned char *enc;
enc = parse_http_header(e->head, cast_uchar "Content-Encoding", NULL);
if (enc) {
mem_free(enc);
e->refcount--;
must_detach = 1;
goto skip_cache;
}
}
if (stat) {
stat->ce = e;
stat->state = S__OK;
if (stat->end) stat->end(stat, stat->data);
}
e->refcount--;
goto ret;
}
skip_cache:
if (is_proxy_url(url)) {
if (stat) {
stat->state = S_BAD_URL;
if (stat->end) stat->end(stat, stat->data);
}
goto ret;
}
u = get_proxy(url);
foreach(struct connection, c, lc, queue) if (!c->detached && !strcmp(cast_const_char c->url, cast_const_char u)) {
if (c->from < position) continue;
if (c->doh != doh) {
must_detach = 1;
break;
}
if (no_compress && !c->no_compress) {
unsigned char *enc;
if ((c->state >= S_WAIT && c->state < S_TRANS) || !c->cache) {
must_detach = 1;
break;
}
enc = parse_http_header(c->cache->head, cast_uchar "Content-Encoding", NULL);
if (enc) {
mem_free(enc);
must_detach = 1;
break;
}
}
mem_free(u);
if (getpri(c) > pri) {
del_from_list(c);
c->pri[pri]++;
add_to_queue(c);
register_bottom_half(check_queue, NULL);
} else c->pri[pri]++;
if (stat) {
stat->prg = &c->prg;
stat->c = c;
stat->ce = c->cache;
add_to_list(c->statuss, stat);
setcstate(c, c->state);
}
check_queue_bugs();
goto ret;
}
c = mem_calloc(sizeof(struct connection));
c->count = connection_count++;
c->url = u;
c->prev_url = stracpy(prev_url);
c->running = 0;
c->prev_error = 0;
if (position || must_detach) {
c->from = position;
} else if (no_cache >= NC_IF_MOD || !e) {
c->from = 0;
} else {
struct fragment *frag;
struct list_head *lfrag;
c->from = 0;
foreach(struct fragment, frag, lfrag, e->frag) {
if (frag->offset != c->from)
break;
c->from += frag->length;
}
}
memset(c->pri, 0, sizeof c->pri);
c->pri[pri] = 1;
c->no_cache = no_cache;
c->sock1 = c->sock2 = -1;
c->dnsquery = NULL;
c->tries = 0;
c->netcfg_stamp = netcfg_stamp;
init_list(c->statuss);
c->info = NULL;
c->buffer = NULL;
c->newconn = NULL;
c->cache = NULL;
c->est_length = -1;
c->unrestartable = 0;
#ifdef HAVE_ANY_COMPRESSION
c->no_compress = http_options.no_compression || no_compress || dmp == D_SOURCE;
#else
c->no_compress = 1;
#endif
c->doh = doh;
c->prg.timer = NULL;
c->timer = NULL;
if (position || must_detach) {
if (new_cache_entry(cast_uchar "", &c->cache)) {
mem_free(c->url);
if (c->prev_url) mem_free(c->prev_url);
mem_free(c);
if (stat) {
stat->state = S_OUT_OF_MEM;
if (stat->end) stat->end(stat, stat->data);
}
goto ret;
}
c->cache->refcount--;
detach_cache_entry(c->cache);
c->detached = 2;
}
if (stat) {
stat->prg = &c->prg;
stat->c = c;
stat->ce = NULL;
add_to_list(c->statuss, stat);
}
add_to_queue(c);
setcstate(c, S_WAIT);
check_queue_bugs();
register_bottom_half(check_queue, NULL);
ret:
return;
}
void change_connection(struct status *oldstat, struct status *newstat, int newpri)
{
struct connection *c;
int oldpri;
oldpri = oldstat->pri;
if (oldstat->state < 0) {
if (newstat) {
struct cache_entry *ce = oldstat->ce;
if (ce) ce->refcount++;
newstat->ce = oldstat->ce;
newstat->state = oldstat->state;
newstat->prev_error = oldstat->prev_error;
if (newstat->end) newstat->end(newstat, newstat->data);
if (ce) ce->refcount--;
}
return;
}
check_queue_bugs();
c = oldstat->c;
if (--c->pri[oldpri] < 0) {
internal_error("priority counter underflow");
c->pri[oldpri] = 0;
}
c->pri[newpri]++;
del_from_list(oldstat);
oldstat->state = S_INTERRUPTED;
if (newstat) {
newstat->prg = &c->prg;
add_to_list(c->statuss, newstat);
newstat->state = c->state;
newstat->prev_error = c->prev_error;
newstat->pri = newpri;
newstat->c = c;
newstat->ce = c->cache;
}
if (c->detached && !newstat) {
setcstate(c, S_INTERRUPTED);
abort_connection(c);
}
sort_queue();
check_queue_bugs();
register_bottom_half(check_queue, NULL);
}
void detach_connection(struct status *stat, off_t pos, int want_to_restart, int dont_check_refcount)
{
struct connection *c;
int i, n_users;
off_t l;
if (stat->state < 0) return;
c = stat->c;
if (c->no_compress && want_to_restart) return;
if (!c->cache || (!dont_check_refcount && c->cache->refcount)) return;
want_to_restart |= pos > c->from && is_connection_seekable(c);
if (c->detached) goto detach_done;
if (c->est_length == -1) l = c->from;
else l = c->est_length;
if (!dont_check_refcount && l < (longlong)memory_cache_size * MAX_CACHED_OBJECT && !want_to_restart) return;
n_users = 0;
for (i = 0; i < PRI_CANCEL; i++) n_users += c->pri[i];
if (!n_users) internal_error("detaching free connection");
if (n_users != 1) return;
shrink_memory(SH_CHECK_QUOTA, 0);
detach_cache_entry(c->cache);
c->detached = 1;
detach_done:
free_entry_to(c->cache, pos);
if (c->detached < 2 && want_to_restart) {
int running = c->running;
c->no_compress = 1;
if (running) interrupt_connection(c);
c->from = pos;
if (running) run_connection(c);
c->detached = 2;
}
}
static uttime get_timeout_value(struct connection *c)
{
uttime t;
if (c->state == S_CONN || c->state == S_CONN_ANOTHER)
t = timeout_multiple_addresses * (c->tries < 1 ? 1 : c->tries + 1);
else if (c->unrestartable)
t = unrestartable_receive_timeout;
else
t = receive_timeout;
t *= 500;
return t;
}
static void connection_timeout(void *c_)
{
struct connection *c = (struct connection *)c_;
c->timer = NULL;
if (c->state == S_CONN || c->state == S_CONN_ANOTHER) {
retry_connect(c, get_error_from_errno(ETIMEDOUT), 0);
return;
}
setcstate(c, S_TIMEOUT);
retry_connection(c);
}
static void connection_timeout_1(void *c_)
{
struct connection *c = (struct connection *)c_;
c->timer = install_timer(get_timeout_value(c), connection_timeout, c);
}
void clear_connection_timeout(struct connection *c)
{
if (c->timer != NULL) kill_timer(c->timer), c->timer = NULL;
}
void set_connection_timeout(struct connection *c)
{
clear_connection_timeout(c);
c->timer = install_timer(get_timeout_value(c), connection_timeout_1, c);
}
void set_connection_timeout_keepal(struct connection *c)
{
if (c->keepalive && !c->unrestartable) {
clear_connection_timeout(c);
c->timer = install_timer(timeout_multiple_addresses * 1000, connection_timeout, c);
return;
}
set_connection_timeout(c);
}
void abort_all_connections(void)
{
while (!list_empty(queue)) {
struct connection *c = list_struct(queue.next, struct connection);
setcstate(c, S_INTERRUPTED);
abort_connection(c);
}
abort_all_keepalive_connections();
}
int abort_background_connections(void)
{
int did_something = 0;
struct connection *c;
struct list_head *lc;
again:
foreach(struct connection, c, lc, queue) {
if (getpri(c) >= PRI_CANCEL) {
setcstate(c, S_INTERRUPTED);
abort_connection(c);
did_something = 1;
goto again;
}
}
return did_something | abort_all_keepalive_connections();
}
int is_entry_used(struct cache_entry *e)
{
struct connection *c;
struct list_head *lc;
foreach(struct connection, c, lc, queue) if (c->cache == e) return 1;
return 0;
}
struct blacklist_entry {
list_entry_1st
int flags;
list_entry_last
unsigned char host[1];
};
static struct list_head blacklist = { &blacklist, &blacklist };
void add_blacklist_entry(unsigned char *host, int flags)
{
struct blacklist_entry *b;
struct list_head *lb;
size_t sl;
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) {
b->flags |= flags;
return;
}
sl = strlen(cast_const_char host);
if (sl > MAXINT - sizeof(struct blacklist_entry)) overalloc();
b = mem_alloc(sizeof(struct blacklist_entry) + sl);
b->flags = flags;
strcpy(cast_char b->host, cast_const_char host);
add_to_list(blacklist, b);
}
void del_blacklist_entry(unsigned char *host, int flags)
{
struct blacklist_entry *b;
struct list_head *lb;
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) {
b->flags &= ~flags;
if (!b->flags) {
del_from_list(b);
mem_free(b);
}
return;
}
}
int get_blacklist_flags(unsigned char *host)
{
struct blacklist_entry *b;
struct list_head *lb;
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) return b->flags;
return 0;
}
void free_blacklist(void)
{
free_list(struct blacklist_entry, blacklist);
}