#include #include #include #include #include #include #include #include #include "mod_cache.h" #include "mmap_cache.h" #include "http_protocol.h" #include "http_request.h" #include "http_main.h" #include "http_log.h" static int clock_pointer; static ht_entry *hash_table; static ctrl_block *cb_pool; mmap_cache_status mc_stat; static long mmap_cache_size; static inline unsigned long cache_hash_key(char *s, int *len) { /* s must be non-empty string and aligned as long */ /* always returns 0 if len(s)<3 */ unsigned long k; int i; #define STEP sizeof(k) k = 0; /* the result will be here */ for (i = 1; s[i]; i++) /* radix transform doesn't work well ? k = 256*k + s[i]; */ if (i % STEP == 0) k ^= *(long *) (s + i - STEP); /* XOR 'em all! */ *len = i; return k; #undef STEP } static inline int cache_strcmp(char *s1, int l1, char *s2, int l2) { /* returns 0 if strings are equal, non-zero else */ /* apply a simple heuristic */ if (l1 != l2 || s1[l1 - 1] != s2[l1 - 1] || s1[l1 / 2] != s2[l1 / 2]) return 1; return memcmp(s1, s2, l1); } void mmap_cache_init(int mcs) { if (mcs <= 0) return; mmap_cache_size = mcs; if ((hash_table = calloc(mcs, sizeof(ht_entry))) == NULL || (cb_pool = calloc(mcs, sizeof(ctrl_block))) == NULL) { perror("calloc"); exit(1); } fprintf(stderr, "Allocated %d bytes for mmap cache\n", mcs * (sizeof(ht_entry) + sizeof(ctrl_block))); mc_stat.req_cnt = mc_stat.hit_cnt = mc_stat.cached_files = mc_stat.cached_files_size = 0; clock_pointer = 0; } void mmap_cache_exit() { free(hash_table); free(cb_pool); mmap_cache_size = 0; } /* remove control block cb from the hash table's overflow chain */ static inline void detach_cb(ctrl_block * cb) { ctrl_block **cb_prev; ht_entry *hte = cb->hte; for (cb_prev = &(hte->cb); *cb_prev != cb; cb_prev = &((*cb_prev)->next)) continue; *cb_prev = cb->next; /* remove cb from chain */ } /* get a free cb from the pool; or kill somebody using clock */ static ctrl_block *get_free_cb() { ctrl_block *cb; while (cb_pool[clock_pointer].ref_counter-- > 0) clock_pointer = (clock_pointer + 1) % mmap_cache_size; cb = cb_pool + clock_pointer; clock_pointer = (clock_pointer + 1) % mmap_cache_size; if (cb->size == 0) /* free block */ return cb; detach_cb(cb); if (munmap(cb->addr, cb->size) == -1) /* not a fatal error hopefully */ ap_log_error(APLOG_MARK, APLOG_ERR, 0 /*r->server */ , "munmap() failed in mmap_cache"); mc_stat.cached_files--; mc_stat.cached_files_size -= cb->size; return cb; } /* hte is the hash table entry (overflow chain) where to insert the new cb. cb is the pointer to the new control block */ static int cache_request(request_rec * r, int fname_len, ht_entry * hte, ctrl_block ** cbp) { int fd; caddr_t addr; ctrl_block *cb; cb = 0; /* do not need pfopen() here (alarms are blocked) */ fd = open(r->filename, O_RDONLY); if (fd == -1) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "file permissions deny server access: %s", r->filename); return FORBIDDEN; } addr = mmap(0, r->finfo.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (addr == (caddr_t) - 1) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "mmap_cache couldn't mmap: %s", r->filename); /* perhaps the default handler will be luckier */ return DECLINED; } cb = cb_pool->next; mc_stat.cached_files++; mc_stat.cached_files_size += r->finfo.st_size; if (!cb) cb = get_free_cb(cb); cb->fname_len = fname_len; cb->size = r->finfo.st_size; cb->mtime = r->finfo.st_mtime; cb->ref_counter = 0; cb->hte = hte; cb->addr = addr; /* memcpy is faster than strcpy !? */ memcpy(cb->fname, r->filename, fname_len + 1); /* insert cb in the hash table's overflow chain */ cb->next = hte->cb; hte->cb = cb; *cbp = cb; return OK; } int mmap_cache_handle_request(request_rec * r, caddr_t * mmap_addr) { ht_entry *hte; ctrl_block *cb; int error_status = 0; unsigned long hash_value; int fname_len; if (mmap_cache_size <= 0) return DECLINED; mc_stat.req_cnt++; /* fprintf(stderr, "Handling %s\n", r->filename); */ /* if (mc_stat.req_cnt%5000 == 0) fprintf(stderr, "Req:%ld HR:%1.3f Files:%d AvgSz:%ld\n", mc_stat.req_cnt, (double)mc_stat.hit_cnt/mc_stat.req_cnt, mc_stat.cached_files, mc_stat.cached_files_size/mc_stat.cached_files); */ hash_value = cache_hash_key(r->filename, &fname_len); if (fname_len > MAX_FNAME_LENGTH) { /* fprintf(stderr, "too long a file name (%s)by (%d) bytes\n", r->filename, *fname_len-MAX_FNAME_LENGTH); */ return DECLINED; } hte = hash_table + hash_value % mmap_cache_size; /* scan the overflow chain */ cb = hte->cb; while (cb != NULL && cache_strcmp(cb->fname, cb->fname_len, r->filename, fname_len) != 0) cb = cb->next; if (cb != NULL /* found */ && cb->mtime == r->finfo.st_mtime) { /* valid copy */ /* got a hit */ mc_stat.hit_cnt++; if (cb->ref_counter < 100) cb->ref_counter++; r->finfo.st_size = cb->size; } else { /* got either a pure miss or an invalid copy */ if (cb != NULL) /* invalid copy */ cb->ref_counter = -1; /* don't bother to remove the old copy; it will be removed sooner or later anyway */ error_status = cache_request(r, fname_len, hte, &cb); } *mmap_addr = (caddr_t) (cb ? cb->addr : 0); /* fprintf(stderr, "errstatus=%d addr=%p\n", error_status, *mmap_addr); */ return error_status; }