2 * Copyright (c) 2019, 2020 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include <sys/queue.h>
28 #include "got_compat.h"
30 #include "got_object.h"
31 #include "got_error.h"
33 #include "got_lib_delta.h"
34 #include "got_lib_inflate.h"
35 #include "got_lib_object.h"
36 #include "got_lib_delta_cache.h"
39 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
42 #define GOT_DELTA_CACHE_MIN_BUCKETS 64
43 #define GOT_DELTA_CACHE_MAX_BUCKETS 2048
44 #define GOT_DELTA_CACHE_MAX_CHAIN 2
45 #define GOT_DELTA_CACHE_MAX_DELTA_SIZE 1024
46 #define GOT_DELTA_CACHE_MAX_FULLTEXT_SIZE 524288
49 struct got_cached_delta
{
57 struct got_delta_cache_head
{
58 struct got_cached_delta entries
[GOT_DELTA_CACHE_MAX_CHAIN
];
62 struct got_delta_cache
{
63 struct got_delta_cache_head
*buckets
;
64 unsigned int nbuckets
;
68 int cache_hit_fulltext
;
72 int cache_toolarge_fulltext
;
73 int cache_maxtoolarge
;
74 int cache_maxtoolarge_fulltext
;
76 #define GOT_DELTA_CACHE_F_NOMEM 0x01
80 const struct got_error
*
81 got_delta_cache_alloc(struct got_delta_cache
**new)
83 const struct got_error
*err
;
84 struct got_delta_cache
*cache
;
88 cache
= calloc(1, sizeof(*cache
));
90 return got_error_from_errno("calloc");
92 cache
->buckets
= calloc(GOT_DELTA_CACHE_MIN_BUCKETS
,
93 sizeof(cache
->buckets
[0]));
94 if (cache
->buckets
== NULL
) {
95 err
= got_error_from_errno("calloc");
99 cache
->nbuckets
= GOT_DELTA_CACHE_MIN_BUCKETS
;
101 arc4random_buf(&cache
->key
, sizeof(cache
->key
));
107 got_delta_cache_free(struct got_delta_cache
*cache
)
109 struct got_cached_delta
*delta
;
112 #ifdef GOT_DELTA_CACHE_DEBUG
113 fprintf(stderr
, "%s: delta cache: %u elements, %d searches, %d hits, "
114 "%d fulltext hits, %d missed, %d evicted, %d too large (max %d), "
115 "%d too large fulltext (max %d)\n",
116 getprogname(), cache
->totelem
, cache
->cache_search
,
117 cache
->cache_hit
, cache
->cache_hit_fulltext
,
118 cache
->cache_miss
, cache
->cache_evict
, cache
->cache_toolarge
,
119 cache
->cache_maxtoolarge
,
120 cache
->cache_toolarge_fulltext
,
121 cache
->cache_maxtoolarge_fulltext
);
123 for (i
= 0; i
< cache
->nbuckets
; i
++) {
124 struct got_delta_cache_head
*head
;
126 head
= &cache
->buckets
[i
];
127 for (j
= 0; j
< head
->nchain
; j
++) {
128 delta
= &head
->entries
[j
];
132 free(cache
->buckets
);
137 delta_cache_hash(struct got_delta_cache
*cache
, off_t delta_offset
)
139 return SipHash24(&cache
->key
, &delta_offset
, sizeof(delta_offset
));
142 #ifndef GOT_NO_DELTA_CACHE
143 static const struct got_error
*
144 delta_cache_resize(struct got_delta_cache
*cache
, unsigned int nbuckets
)
146 struct got_delta_cache_head
*buckets
;
149 buckets
= calloc(nbuckets
, sizeof(buckets
[0]));
150 if (buckets
== NULL
) {
152 return got_error_from_errno("calloc");
153 /* Proceed with our current amount of hash buckets. */
154 cache
->flags
|= GOT_DELTA_CACHE_F_NOMEM
;
158 arc4random_buf(&cache
->key
, sizeof(cache
->key
));
160 for (i
= 0; i
< cache
->nbuckets
; i
++) {
162 for (j
= 0; j
< cache
->buckets
[i
].nchain
; j
++) {
163 struct got_delta_cache_head
*head
;
164 struct got_cached_delta
*delta
;
166 delta
= &cache
->buckets
[i
].entries
[j
];
167 idx
= delta_cache_hash(cache
, delta
->offset
) % nbuckets
;
168 head
= &buckets
[idx
];
169 if (head
->nchain
< nitems(head
->entries
)) {
170 struct got_cached_delta
*new_delta
;
171 new_delta
= &head
->entries
[head
->nchain
];
172 memcpy(new_delta
, delta
, sizeof(*new_delta
));
181 free(cache
->buckets
);
182 cache
->buckets
= buckets
;
183 cache
->nbuckets
= nbuckets
;
187 static const struct got_error
*
188 delta_cache_grow(struct got_delta_cache
*cache
)
190 unsigned int nbuckets
;
192 if ((cache
->flags
& GOT_DELTA_CACHE_F_NOMEM
) ||
193 cache
->nbuckets
== GOT_DELTA_CACHE_MAX_BUCKETS
)
196 if (cache
->nbuckets
>= GOT_DELTA_CACHE_MAX_BUCKETS
/ 2)
197 nbuckets
= GOT_DELTA_CACHE_MAX_BUCKETS
;
199 nbuckets
= cache
->nbuckets
* 2;
201 return delta_cache_resize(cache
, nbuckets
);
205 const struct got_error
*
206 got_delta_cache_add(struct got_delta_cache
*cache
,
207 off_t delta_data_offset
, uint8_t *delta_data
, size_t delta_len
)
209 #ifdef GOT_NO_DELTA_CACHE
210 return got_error(GOT_ERR_NO_SPACE
);
212 const struct got_error
*err
= NULL
;
213 struct got_cached_delta
*delta
;
214 struct got_delta_cache_head
*head
;
217 if (delta_len
> GOT_DELTA_CACHE_MAX_DELTA_SIZE
) {
218 cache
->cache_toolarge
++;
219 if (delta_len
> cache
->cache_maxtoolarge
)
220 cache
->cache_maxtoolarge
= delta_len
;
221 return got_error(GOT_ERR_NO_SPACE
);
224 if (cache
->nbuckets
* 3 < cache
->totelem
* 4) {
225 err
= delta_cache_grow(cache
);
230 idx
= delta_cache_hash(cache
, delta_data_offset
) % cache
->nbuckets
;
231 head
= &cache
->buckets
[idx
];
232 if (head
->nchain
>= nitems(head
->entries
)) {
233 delta
= &head
->entries
[head
->nchain
- 1];
235 free(delta
->fulltext
);
236 memset(delta
, 0, sizeof(*delta
));
239 cache
->cache_evict
++;
242 delta
= &head
->entries
[head
->nchain
];
243 delta
->offset
= delta_data_offset
;
244 delta
->data
= delta_data
;
245 delta
->len
= delta_len
;
246 delta
->fulltext
= NULL
;
247 delta
->fulltext_len
= 0;
255 const struct got_error
*
256 got_delta_cache_add_fulltext(struct got_delta_cache
*cache
,
257 off_t delta_data_offset
, uint8_t *fulltext
, size_t fulltext_len
)
259 #ifdef GOT_NO_DELTA_CACHE
260 return got_error(GOT_ERR_NO_SPACE
);
262 struct got_cached_delta
*delta
;
263 struct got_delta_cache_head
*head
;
267 if (fulltext_len
> GOT_DELTA_CACHE_MAX_FULLTEXT_SIZE
) {
268 cache
->cache_toolarge_fulltext
++;
269 if (fulltext_len
> cache
->cache_maxtoolarge
)
270 cache
->cache_maxtoolarge_fulltext
= fulltext_len
;
271 return got_error(GOT_ERR_NO_SPACE
);
274 idx
= delta_cache_hash(cache
, delta_data_offset
) % cache
->nbuckets
;
275 head
= &cache
->buckets
[idx
];
277 for (i
= 0; i
< head
->nchain
; i
++) {
278 delta
= &head
->entries
[i
];
279 if (delta
->offset
!= delta_data_offset
)
282 struct got_cached_delta tmp
;
284 memcpy(&tmp
, &head
->entries
[0], sizeof(tmp
));
285 memcpy(&head
->entries
[0], &head
->entries
[i
],
286 sizeof(head
->entries
[0]));
287 memcpy(&head
->entries
[i
], &tmp
,
288 sizeof(head
->entries
[i
]));
289 delta
= &head
->entries
[0];
291 delta
->fulltext
= malloc(fulltext_len
);
292 if (delta
->fulltext
== NULL
)
293 return got_error_from_errno("malloc");
294 memcpy(delta
->fulltext
, fulltext
, fulltext_len
);
295 delta
->fulltext_len
= fulltext_len
;
304 got_delta_cache_get(uint8_t **delta_data
, size_t *delta_len
,
305 uint8_t **fulltext
, size_t *fulltext_len
,
306 struct got_delta_cache
*cache
, off_t delta_data_offset
)
309 struct got_delta_cache_head
*head
;
310 struct got_cached_delta
*delta
;
313 idx
= delta_cache_hash(cache
, delta_data_offset
) % cache
->nbuckets
;
314 head
= &cache
->buckets
[idx
];
316 cache
->cache_search
++;
323 for (i
= 0; i
< head
->nchain
; i
++) {
324 delta
= &head
->entries
[i
];
325 if (delta
->offset
!= delta_data_offset
)
329 struct got_cached_delta tmp
;
330 memcpy(&tmp
, &head
->entries
[0], sizeof(tmp
));
331 memcpy(&head
->entries
[0], &head
->entries
[i
],
332 sizeof(head
->entries
[0]));
333 memcpy(&head
->entries
[i
], &tmp
,
334 sizeof(head
->entries
[i
]));
335 delta
= &head
->entries
[0];
337 *delta_data
= delta
->data
;
338 *delta_len
= delta
->len
;
339 if (fulltext
&& fulltext_len
&&
340 delta
->fulltext
&& delta
->fulltext_len
) {
341 *fulltext
= delta
->fulltext
;
342 *fulltext_len
= delta
->fulltext_len
;
343 cache
->cache_hit_fulltext
++;