Blob


1 /*
2 * Copyright (c) 2019, 2020 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/queue.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sha1.h>
22 #include <stdio.h>
23 #include <zlib.h>
24 #include <limits.h>
25 #include <time.h>
27 #include "got_object.h"
28 #include "got_error.h"
30 #include "got_lib_delta.h"
31 #include "got_lib_inflate.h"
32 #include "got_lib_object.h"
33 #include "got_lib_delta_cache.h"
35 #ifndef nitems
36 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
37 #endif
39 struct got_delta_cache_element {
40 TAILQ_ENTRY(got_delta_cache_element) entry;
41 off_t delta_data_offset;
42 uint8_t *delta_data;
43 size_t delta_len;
44 };
46 TAILQ_HEAD(got_delta_cache_head, got_delta_cache_element);
48 struct got_delta_cache {
49 struct got_delta_cache_head entries;
50 int nelem;
51 int maxelem;
52 size_t maxelemsize;
53 int cache_search;
54 int cache_hit;
55 int cache_miss;
56 int cache_evict;
57 int cache_toolarge;
58 };
60 struct got_delta_cache *
61 got_delta_cache_alloc(int maxelem, size_t maxelemsize)
62 {
63 struct got_delta_cache *cache;
65 cache = calloc(1, sizeof(*cache));
66 if (cache == NULL)
67 return NULL;
69 TAILQ_INIT(&cache->entries);
70 cache->maxelem = maxelem;
71 cache->maxelemsize = maxelemsize;
72 return cache;
73 }
75 void
76 got_delta_cache_free(struct got_delta_cache *cache)
77 {
78 struct got_delta_cache_element *entry;
80 #ifdef GOT_OBJ_CACHE_DEBUG
81 fprintf(stderr, "%s: delta cache: %d elements, %d searches, %d hits, "
82 "%d missed, %d evicted, %d too large\n", getprogname(), cache->nelem,
83 cache->cache_search, cache->cache_hit, cache->cache_miss,
84 cache->cache_evict, cache->cache_toolarge);
85 #endif
86 while (!TAILQ_EMPTY(&cache->entries)) {
87 entry = TAILQ_FIRST(&cache->entries);
88 TAILQ_REMOVE(&cache->entries, entry, entry);
89 free(entry->delta_data);
90 free(entry);
91 }
92 free(cache);
93 }
95 #ifndef GOT_NO_OBJ_CACHE
96 static void
97 remove_least_used_element(struct got_delta_cache *cache)
98 {
99 struct got_delta_cache_element *entry;
101 if (cache->nelem == 0)
102 return;
104 entry = TAILQ_LAST(&cache->entries, got_delta_cache_head);
105 TAILQ_REMOVE(&cache->entries, entry, entry);
106 free(entry->delta_data);
107 free(entry);
108 cache->nelem--;
109 cache->cache_evict++;
111 #endif
113 const struct got_error *
114 got_delta_cache_add(struct got_delta_cache *cache,
115 off_t delta_data_offset, uint8_t *delta_data, size_t delta_len)
117 #ifdef GOT_NO_OBJ_CACHE
118 return got_error(GOT_ERR_NO_SPACE);
119 #else
120 struct got_delta_cache_element *entry;
122 if (delta_len > cache->maxelemsize) {
123 cache->cache_toolarge++;
124 return got_error(GOT_ERR_NO_SPACE);
127 if (cache->nelem >= cache->maxelem)
128 remove_least_used_element(cache);
130 entry = calloc(1, sizeof(*entry));
131 if (entry == NULL)
132 return got_error_from_errno("calloc");
134 entry->delta_data_offset = delta_data_offset;
135 entry->delta_data = delta_data;
136 entry->delta_len = delta_len;
138 TAILQ_INSERT_HEAD(&cache->entries, entry, entry);
139 cache->nelem++;
140 return NULL;
141 #endif
144 void
145 got_delta_cache_get(uint8_t **delta_data, size_t *delta_len,
146 struct got_delta_cache *cache, off_t delta_data_offset)
148 struct got_delta_cache_element *entry;
150 cache->cache_search++;
151 *delta_data = NULL;
152 *delta_len = 0;
153 TAILQ_FOREACH(entry, &cache->entries, entry) {
154 if (entry->delta_data_offset != delta_data_offset)
155 continue;
156 cache->cache_hit++;
157 if (entry != TAILQ_FIRST(&cache->entries)) {
158 TAILQ_REMOVE(&cache->entries, entry, entry);
159 TAILQ_INSERT_HEAD(&cache->entries, entry, entry);
161 *delta_data = entry->delta_data;
162 *delta_len = entry->delta_len;
163 return;
166 cache->cache_miss++;