links/cache.c

553 lines
16 KiB
C

/* cache.c
* (c) 2002 Mikulas Patocka
* This file is a part of the Links program, released under GPL.
*/
#include "links.h"
#if defined(HAVE_SEARCH_H) && defined(HAVE_TDELETE) && defined(HAVE_TFIND) && defined(HAVE_TSEARCH)
#define USE_TREE
#endif
static struct list_head cache = {&cache, &cache};
static my_uintptr_t cache_size = 0;
static tcount cache_count = 1;
#ifdef USE_TREE
static void *cache_root = NULL;
static int ce_compare(const void *p1, const void *p2)
{
const unsigned char *u1 = (const unsigned char *)p1;
const unsigned char *u2 = (const unsigned char *)p2;
if (u1 == u2) return 0;
return strcmp(cast_const_char u1, cast_const_char u2);
}
static void cache_add_to_tree(struct cache_entry *e)
{
void **p;
if (!e->url[0]) return;
#if !defined(__GLIBC__)
if (!cache_root) {
/*
* Some implementations misbehave when deleting the last
* element. They leak memory or return NULL from tdelete.
* To guard against misbehavior, we insert one static element
* and never delete it.
* Glibc doesn't have this bug.
*/
static unsigned char empty = 0;
retry_static:
p = tsearch(&empty, &cache_root, ce_compare);
if (!p) {
out_of_memory(0, cast_uchar "tsearch static", 0);
goto retry_static;
}
if ((unsigned char *)*p != &empty) internal_error("cache_add_to_tree: static entry not added: %p, %p", *p, &empty);
}
#endif
retry:
p = tsearch(e->url, &cache_root, ce_compare);
if (!p) {
out_of_memory(0, cast_uchar "tsearch", 0);
goto retry;
}
if ((unsigned char *)*p != e->url) internal_error("cache_add_to_tree: url '%s' is already present", e->url);
}
static void cache_delete_from_tree(struct cache_entry *e)
{
void *p;
if (!e->url[0]) return;
p = tdelete(e->url, &cache_root, ce_compare);
if (!p) internal_error("cache_delete_from_tree: url '%s' not found", e->url);
}
static struct cache_entry *cache_search_tree(unsigned char *url)
{
void **p;
p = tfind(url, (void *)&cache_root, ce_compare);
if (!p) return NULL;
return get_struct(*p, struct cache_entry, url);
}
#else
static void cache_add_to_tree(struct cache_entry *e) { }
static void cache_delete_from_tree(struct cache_entry *e) { }
static struct cache_entry *cache_search_tree(unsigned char *url)
{
struct cache_entry *e;
struct list_head *le;
foreach(struct cache_entry, e, le, cache)
if (!strcmp(cast_const_char e->url, cast_const_char url))
return e;
return NULL;
}
#endif
my_uintptr_t cache_info(int type)
{
my_uintptr_t i = 0;
struct cache_entry *ce;
struct list_head *lce;
switch (type) {
case CI_BYTES:
return cache_size;
case CI_FILES:
return (my_uintptr_t)list_size(&cache);
case CI_LOCKED:
foreach(struct cache_entry, ce, lce, cache) i += !!ce->refcount;
return i;
case CI_LOADING:
foreach(struct cache_entry, ce, lce, cache) i += is_entry_used(ce);
return i;
default:
internal_error("cache_info: bad request");
}
return 0;
}
my_uintptr_t decompress_info(int type)
{
my_uintptr_t i = 0;
struct cache_entry *ce;
struct list_head *lce;
switch (type) {
case CI_BYTES:
return decompressed_cache_size;
case CI_FILES:
foreach(struct cache_entry, ce, lce, cache) i += !!ce->decompressed;
return i;
case CI_LOCKED:
foreach(struct cache_entry, ce, lce, cache) i += ce->decompressed && ce->refcount;
return i;
default:
internal_error("compress_info: bad request");
}
return 0;
}
int find_in_cache(unsigned char *url, struct cache_entry **f)
{
struct cache_entry *e;
url = remove_proxy_prefix(url);
e = cache_search_tree(url);
if (e) {
e->refcount++;
del_from_list(e);
add_to_list(cache, e);
*f = e;
return 0;
}
return -1;
}
static int get_cache_entry(unsigned char *url, struct cache_entry **f)
{
if (!find_in_cache(url, f)) return 0;
return new_cache_entry(url, f);
}
int get_connection_cache_entry(struct connection *c)
{
struct cache_entry *e;
if (get_cache_entry(c->url, &c->cache))
return -1;
e = c->cache;
if (e->ip_address) mem_free(e->ip_address), e->ip_address = NULL;
if (!*c->socks_proxy && !is_proxy_url(c->url) && c->last_lookup_state.addr.n) {
unsigned char *a;
unsigned char *s = init_str();
int l = 0;
a = print_address(&c->last_lookup_state.addr.a[c->last_lookup_state.addr_index]);
if (a)
add_to_str(&s, &l, a);
if (c->last_lookup_state.addr.n > 1) {
int i, d = 0;
if (l) add_chr_to_str(&s, &l, ' ');
add_chr_to_str(&s, &l, '(');
for (i = 0; i < c->last_lookup_state.addr.n; i++) {
if (i == c->last_lookup_state.addr_index)
continue;
a = print_address(&c->last_lookup_state.addr.a[i]);
if (a) {
if (d)
add_to_str(&s, &l, cast_uchar ", ");
add_to_str(&s, &l, a);
d = 1;
}
}
add_chr_to_str(&s, &l, ')');
}
e->ip_address = s;
}
return 0;
}
int new_cache_entry(unsigned char *url, struct cache_entry **f)
{
struct cache_entry *e;
shrink_memory(SH_CHECK_QUOTA, 0);
url = remove_proxy_prefix(url);
e = mem_calloc_mayfail(sizeof(struct cache_entry) + strlen(cast_const_char url));
if (!e)
return -1;
strcpy(cast_char e->url, cast_const_char url);
e->length = 0;
e->incomplete = 1;
e->data_size = 0;
e->http_code = -1;
init_list(e->frag);
e->count = cache_count++;
e->count2 = cache_count++;
e->refcount = 1;
e->decompressed = NULL;
e->decompressed_len = 0;
cache_add_to_tree(e);
add_to_list(cache, e);
*f = e;
return 0;
}
void detach_cache_entry(struct cache_entry *e)
{
cache_delete_from_tree(e);
e->url[0] = 0;
}
static void mem_free_fragment(struct fragment *f)
{
mem_free(f);
}
#define sf(x) e->data_size += (x), cache_size += (my_uintptr_t)(x)
#define C_ALIGN(x) ((((x) + (sizeof(struct fragment) + alloc_overhead - 1)) | (page_size - 1)) - (sizeof(struct fragment) + alloc_overhead - 1))
int add_fragment(struct cache_entry *e, off_t offset, const unsigned char *data, off_t length)
{
struct fragment *f;
struct list_head *lf;
struct fragment *nf;
int trunc = 0;
icc_volatile off_t ca;
if (!length) return 0;
free_decompressed_data(e);
e->incomplete = 1;
if ((off_t)(0UL + offset + length) < 0 || (off_t)(0UL + offset + length) < offset) return S_LARGE_FILE;
if ((off_t)(0UL + offset + (off_t)C_ALIGN(length)) < 0 || (off_t)(0UL + offset + (off_t)C_ALIGN(length)) < offset) return S_LARGE_FILE;
if (e->length < offset + length) e->length = offset + length;
e->count = cache_count++;
if (list_empty(e->frag)) e->count2 = cache_count++;
else {
f = list_struct(e->frag.prev, struct fragment);
if (f->offset + f->length != offset) e->count2 = cache_count++;
else {
lf = &f->list_entry;
goto have_f;
}
}
foreach(struct fragment, f, lf, e->frag) {
have_f:
if (f->offset > offset) break;
if (f->offset <= offset && f->offset + f->length >= offset) {
if (offset+length > f->offset + f->length) {
if (memcmp(f->data + offset - f->offset, data, (size_t)(f->offset + f->length - offset))) trunc = 1;
if (offset - f->offset + length <= f->real_length) {
sf((offset + length) - (f->offset + f->length));
f->length = offset - f->offset + length;
} else {
sf(-(f->offset + f->length - offset));
f->length = offset - f->offset;
lf = f->list_entry.next;
break;
}
} else {
if (memcmp(f->data + offset-f->offset, data, (size_t)length)) trunc = 1;
}
memcpy(f->data+offset - f->offset, data, (size_t)length);
goto ch_o;
}
}
/* Intel C 9 has a bug and miscompiles this statement (< 0 test is true) */
/*if (C_ALIGN(length) > MAXINT - sizeof(struct fragment) || C_ALIGN(length) < 0) overalloc();*/
ca = C_ALIGN(length);
if (ca > MAXINT - (int)sizeof(struct fragment) || ca < 0) return S_LARGE_FILE;
nf = mem_alloc_mayfail(sizeof(struct fragment) + (size_t)ca);
if (!nf) return S_OUT_OF_MEM;
sf(length);
nf->offset = offset;
nf->length = length;
nf->real_length = C_ALIGN(length);
memcpy(nf->data, data, (size_t)length);
add_before_list_entry(lf, &nf->list_entry);
f = nf;
ch_o:
while (f->list_entry.next != &e->frag && f->offset + f->length > list_struct(f->list_entry.next, struct fragment)->offset) {
struct fragment *next = list_struct(f->list_entry.next, struct fragment);
if (f->offset + f->length < next->offset + next->length) {
f = mem_realloc(f, sizeof(struct fragment) + (size_t)(next->offset - f->offset + next->length));
fix_list_after_realloc(f);
if (memcmp(f->data + next->offset - f->offset, next->data, (size_t)(f->offset + f->length - next->offset))) trunc = 1;
memcpy(f->data + f->length, next->data + f->offset + f->length - next->offset, (size_t)(next->offset + next->length - f->offset - f->length));
sf(next->offset + next->length - f->offset - f->length);
f->length = f->real_length = next->offset + next->length - f->offset;
} else {
if (memcmp(f->data + next->offset - f->offset, next->data, (size_t)next->length)) trunc = 1;
}
del_from_list(next);
sf(-next->length);
mem_free_fragment(next);
}
if (trunc) truncate_entry(e, offset + length, 0);
if (e->length > e->max_length) {
e->max_length = e->length;
return 1;
}
return 0;
}
int defrag_entry(struct cache_entry *e)
{
struct fragment *f, *n;
struct list_head *g, *h;
off_t l;
if (list_empty(e->frag)) {
return 0;
}
f = list_struct(e->frag.next, struct fragment);
if (f->offset) {
return 0;
}
for (g = f->list_entry.next;
g != &e->frag &&
list_struct(g, struct fragment)->offset <= list_struct(g->prev, struct fragment)->offset + list_struct(g->prev, struct fragment)->length; g = g->next)
if (list_struct(g, struct fragment)->offset < list_struct(g->prev, struct fragment)->offset + list_struct(g->prev, struct fragment)->length) {
internal_error("fragments overlay");
return S_INTERNAL;
}
if (g == f->list_entry.next) {
if (f->length != f->real_length) {
f = mem_realloc_mayfail(f, sizeof(struct fragment) + (size_t)f->length);
if (f) {
f->real_length = f->length;
fix_list_after_realloc(f);
}
}
return 0;
}
for (l = 0, h = &f->list_entry; h != g; h = h->next) {
if ((off_t)(0UL + l + list_struct(h, struct fragment)->length) < 0 || (off_t)(0UL + l + list_struct(h, struct fragment)->length) < l) return S_LARGE_FILE;
l += list_struct(h, struct fragment)->length;
}
if (l > MAXINT - (int)sizeof(struct fragment)) return S_LARGE_FILE;
n = mem_alloc_mayfail(sizeof(struct fragment) + (size_t)l);
if (!n) return S_OUT_OF_MEM;
n->offset = 0;
n->length = l;
n->real_length = l;
for (l = 0, h = &f->list_entry; h != g; h = h->next) {
struct fragment *hf = list_struct(h, struct fragment);
memcpy(n->data + l, hf->data, (size_t)hf->length);
l += hf->length;
h = h->prev;
del_from_list(hf);
mem_free_fragment(hf);
}
add_to_list(e->frag, n);
return 0;
}
void truncate_entry(struct cache_entry *e, off_t off, int final)
{
int modified = final == 2;
struct fragment *f, *g;
struct list_head *lf;
if (e->length > off) e->length = off, e->incomplete = 1;
foreach(struct fragment, f, lf, e->frag) {
if (f->offset >= off) {
modified = 1;
sf(-f->length);
lf = lf->prev;
del_from_list(f);
mem_free_fragment(f);
continue;
}
if (f->offset + f->length > off) {
modified = 1;
sf(-(f->offset + f->length - off));
f->length = off - f->offset;
if (final) {
g = mem_realloc_mayfail(f, sizeof(struct fragment) + (size_t)f->length);
if (g) {
f = g;
fix_list_after_realloc(f);
f->real_length = f->length;
lf = &f->list_entry;
}
}
}
}
if (modified) {
free_decompressed_data(e);
e->count = cache_count++;
e->count2 = cache_count++;
}
}
void free_entry_to(struct cache_entry *e, off_t off)
{
struct fragment *f;
struct list_head *lf;
e->incomplete = 1;
free_decompressed_data(e);
foreach(struct fragment, f, lf, e->frag) {
if (f->offset + f->length <= off) {
sf(-f->length);
lf = lf->prev;
del_from_list(f);
mem_free_fragment(f);
} else if (f->offset < off) {
sf(f->offset - off);
memmove(f->data, f->data + (off - f->offset), (size_t)(f->length -= off - f->offset));
f->offset = off;
} else break;
}
}
void delete_entry_content(struct cache_entry *e)
{
truncate_entry(e, 0, 2);
if (e->last_modified) {
mem_free(e->last_modified);
e->last_modified = NULL;
}
}
void trim_cache_entry(struct cache_entry *e)
{
struct fragment *f, *nf;
struct list_head *lf;
foreach(struct fragment, f, lf, e->frag) {
if (f->length != f->real_length) {
nf = mem_realloc_mayfail(f, sizeof(struct fragment) + (size_t)f->length);
if (nf) {
f = nf;
fix_list_after_realloc(f);
f->real_length = f->length;
lf = &f->list_entry;
}
}
}
}
void delete_cache_entry(struct cache_entry *e)
{
if (e->refcount) internal_error("deleting locked cache entry");
#ifdef DEBUG
if (is_entry_used(e)) internal_error("deleting loading cache entry");
#endif
cache_delete_from_tree(e);
delete_entry_content(e);
del_from_list(e);
if (e->head) mem_free(e->head);
if (e->redirect) mem_free(e->redirect);
if (e->ip_address) mem_free(e->ip_address);
#ifdef HAVE_SSL
if (e->ssl_info) mem_free(e->ssl_info);
if (e->ssl_authority) mem_free(e->ssl_authority);
#endif
mem_free(e);
}
void finish_cache_entry(struct cache_entry *e)
{
e->count = cache_count++;
}
static int shrink_file_cache(int u)
{
int r = 0;
struct cache_entry *e, *f;
struct list_head *le, *lf;
my_uintptr_t ncs = cache_size;
my_uintptr_t ccs = 0, ccs2 = 0;
if (u == SH_CHECK_QUOTA && cache_size + decompressed_cache_size <= (my_uintptr_t)memory_cache_size) goto ret;
foreachback(struct cache_entry, e, le, cache) {
if (e->refcount || is_entry_used(e)) {
if (ncs < (my_uintptr_t)e->data_size) {
internal_error("cache_size underflow: %lu, %lu", (unsigned long)ncs, (unsigned long)e->data_size);
}
ncs -= (my_uintptr_t)e->data_size;
} else if (u == SH_FREE_SOMETHING) {
if (e->decompressed_len) free_decompressed_data(e);
else delete_cache_entry(e);
r |= ST_SOMETHING_FREED;
goto ret;
}
if (!e->refcount && e->decompressed_len && cache_size + decompressed_cache_size > (my_uintptr_t)memory_cache_size) {
free_decompressed_data(e);
r |= ST_SOMETHING_FREED;
}
ccs += (my_uintptr_t)e->data_size;
ccs2 += e->decompressed_len;
}
if (ccs != cache_size) internal_error("cache size badly computed: %lu != %lu", (unsigned long)cache_size, (unsigned long)ccs), cache_size = ccs;
if (ccs2 != decompressed_cache_size) internal_error("decompressed cache size badly computed: %lu != %lu", (unsigned long)decompressed_cache_size, (unsigned long)ccs2), decompressed_cache_size = ccs2;
if (u == SH_CHECK_QUOTA && ncs <= (my_uintptr_t)memory_cache_size) goto ret;
foreachback(struct cache_entry, e, le, cache) {
if (u == SH_CHECK_QUOTA && (longlong)ncs <= (longlong)memory_cache_size * MEMORY_CACHE_GC_PERCENT) goto g;
if (e->refcount || is_entry_used(e)) {
e->tgc = 0;
continue;
}
e->tgc = 1;
if (ncs < (my_uintptr_t)e->data_size) {
internal_error("cache_size underflow: %lu, %lu", (unsigned long)ncs, (unsigned long)e->data_size);
}
ncs -= (my_uintptr_t)e->data_size;
}
if (ncs) internal_error("cache_size(%lu) is larger than size of all objects(%lu)", (unsigned long)cache_size, (unsigned long)(cache_size - ncs));
g:
if (le->next == &cache) goto ret;
le = le->next;
if (u == SH_CHECK_QUOTA) {
foreachfrom(struct cache_entry, f, lf, cache, le) {
if (f->data_size && (longlong)ncs + f->data_size <= (longlong)memory_cache_size * MEMORY_CACHE_GC_PERCENT) {
ncs += (my_uintptr_t)f->data_size;
f->tgc = 0;
}
}
}
foreachfrom(struct cache_entry, f, lf, cache, le) {
if (f->tgc) {
lf = lf->prev;
delete_cache_entry(f);
r |= ST_SOMETHING_FREED;
}
}
ret:
return r | (list_empty(cache) ? ST_CACHE_EMPTY : 0);
}
void init_cache(void)
{
register_cache_upcall(shrink_file_cache, 0, cast_uchar "file");
}