552 lines
16 KiB
C
552 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");
|
|
}
|