]> git.proxmox.com Git - libgit2.git/blame - src/util/sortedcache.c
New upstream version 1.5.0+ds
[libgit2.git] / src / util / sortedcache.c
CommitLineData
eae0bfdc
PP
1/*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
0b7cdc02
RB
8#include "sortedcache.h"
9
0b7cdc02
RB
10int git_sortedcache_new(
11 git_sortedcache **out,
12 size_t item_path_offset,
13 git_sortedcache_free_item_fn free_item,
14 void *free_item_payload,
15 git_vector_cmp item_cmp,
16 const char *path)
17{
18 git_sortedcache *sc;
f1453c59 19 size_t pathlen, alloclen;
0b7cdc02
RB
20
21 pathlen = path ? strlen(path) : 0;
22
ac3d33df
JK
23 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen);
24 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
f1453c59 25 sc = git__calloc(1, alloclen);
ac3d33df 26 GIT_ERROR_CHECK_ALLOC(sc);
0b7cdc02 27
22a2d3d5
UG
28 if (git_pool_init(&sc->pool, 1) < 0 ||
29 git_vector_init(&sc->items, 4, item_cmp) < 0 ||
30 git_strmap_new(&sc->map) < 0)
0b7cdc02
RB
31 goto fail;
32
8d9a85d4 33 if (git_rwlock_init(&sc->lock)) {
ac3d33df 34 git_error_set(GIT_ERROR_OS, "failed to initialize lock");
0b7cdc02
RB
35 goto fail;
36 }
37
8d9a85d4
RB
38 sc->item_path_offset = item_path_offset;
39 sc->free_item = free_item;
0b7cdc02
RB
40 sc->free_item_payload = free_item_payload;
41 GIT_REFCOUNT_INC(sc);
42 if (pathlen)
43 memcpy(sc->path, path, pathlen);
44
45 *out = sc;
46 return 0;
47
48fail:
40ed4990 49 git_strmap_free(sc->map);
0b7cdc02
RB
50 git_vector_free(&sc->items);
51 git_pool_clear(&sc->pool);
52 git__free(sc);
53 return -1;
54}
55
56void git_sortedcache_incref(git_sortedcache *sc)
57{
58 GIT_REFCOUNT_INC(sc);
59}
60
8d9a85d4
RB
61const char *git_sortedcache_path(git_sortedcache *sc)
62{
63 return sc->path;
64}
65
0b7cdc02
RB
66static void sortedcache_clear(git_sortedcache *sc)
67{
68 git_strmap_clear(sc->map);
69
70 if (sc->free_item) {
71 size_t i;
72 void *item;
73
74 git_vector_foreach(&sc->items, i, item) {
75 sc->free_item(sc->free_item_payload, item);
76 }
77 }
78
79 git_vector_clear(&sc->items);
80
81 git_pool_clear(&sc->pool);
82}
83
84static void sortedcache_free(git_sortedcache *sc)
85{
8d9a85d4
RB
86 /* acquire write lock to make sure everyone else is done */
87 if (git_sortedcache_wlock(sc) < 0)
0b7cdc02 88 return;
0b7cdc02
RB
89
90 sortedcache_clear(sc);
0b7cdc02
RB
91 git_vector_free(&sc->items);
92 git_strmap_free(sc->map);
93
8d9a85d4 94 git_sortedcache_wunlock(sc);
0b7cdc02 95
8d9a85d4 96 git_rwlock_free(&sc->lock);
0b7cdc02
RB
97 git__free(sc);
98}
99
100void git_sortedcache_free(git_sortedcache *sc)
101{
102 if (!sc)
103 return;
104 GIT_REFCOUNT_DEC(sc, sortedcache_free);
105}
106
107static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item)
108{
109 git_sortedcache *sc = payload;
110 /* path will already have been copied by upsert */
111 memcpy(tgt_item, src_item, sc->item_path_offset);
112 return 0;
113}
114
115/* copy a sorted cache */
116int git_sortedcache_copy(
117 git_sortedcache **out,
118 git_sortedcache *src,
805755f4 119 bool lock,
0b7cdc02
RB
120 int (*copy_item)(void *payload, void *tgt_item, void *src_item),
121 void *payload)
122{
8d9a85d4 123 int error = 0;
0b7cdc02
RB
124 git_sortedcache *tgt;
125 size_t i;
126 void *src_item, *tgt_item;
127
8d9a85d4 128 /* just use memcpy if no special copy fn is passed in */
0b7cdc02
RB
129 if (!copy_item) {
130 copy_item = sortedcache_copy_item;
131 payload = src;
132 }
133
8d9a85d4 134 if ((error = git_sortedcache_new(
0b7cdc02
RB
135 &tgt, src->item_path_offset,
136 src->free_item, src->free_item_payload,
8d9a85d4
RB
137 src->items._cmp, src->path)) < 0)
138 return error;
0b7cdc02 139
805755f4 140 if (lock && git_sortedcache_rlock(src) < 0) {
0b7cdc02
RB
141 git_sortedcache_free(tgt);
142 return -1;
143 }
144
0b7cdc02 145 git_vector_foreach(&src->items, i, src_item) {
8d9a85d4
RB
146 char *path = ((char *)src_item) + src->item_path_offset;
147
148 if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 ||
149 (error = copy_item(payload, tgt_item, src_item)) < 0)
150 break;
0b7cdc02
RB
151 }
152
805755f4
RB
153 if (lock)
154 git_sortedcache_runlock(src);
8d9a85d4
RB
155 if (error)
156 git_sortedcache_free(tgt);
0b7cdc02 157
8d9a85d4 158 *out = !error ? tgt : NULL;
0b7cdc02 159
8d9a85d4 160 return error;
0b7cdc02
RB
161}
162
8d9a85d4
RB
163/* lock sortedcache while making modifications */
164int git_sortedcache_wlock(git_sortedcache *sc)
0b7cdc02 165{
8d9a85d4 166 GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
0b7cdc02 167
8d9a85d4 168 if (git_rwlock_wrlock(&sc->lock) < 0) {
ac3d33df 169 git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache");
8d9a85d4
RB
170 return -1;
171 }
172 return 0;
0b7cdc02
RB
173}
174
8d9a85d4
RB
175/* unlock sorted cache when done with modifications */
176void git_sortedcache_wunlock(git_sortedcache *sc)
0b7cdc02 177{
8d9a85d4
RB
178 git_vector_sort(&sc->items);
179 git_rwlock_wrunlock(&sc->lock);
0b7cdc02
RB
180}
181
8d9a85d4
RB
182/* lock sortedcache for read */
183int git_sortedcache_rlock(git_sortedcache *sc)
0b7cdc02 184{
8d9a85d4 185 GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
b37359aa 186
8d9a85d4 187 if (git_rwlock_rdlock(&sc->lock) < 0) {
ac3d33df 188 git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache");
0b7cdc02
RB
189 return -1;
190 }
191 return 0;
192}
193
8d9a85d4
RB
194/* unlock sorted cache when done reading */
195void git_sortedcache_runlock(git_sortedcache *sc)
0b7cdc02 196{
8d9a85d4
RB
197 GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
198 git_rwlock_rdunlock(&sc->lock);
0b7cdc02
RB
199}
200
201/* if the file has changed, lock cache and load file contents into buf;
202 * returns <0 on error, >0 if file has not changed
203 */
e579e0f7 204int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf)
0b7cdc02
RB
205{
206 int error, fd;
40ffa07f 207 struct stat st;
0b7cdc02 208
8d9a85d4 209 if ((error = git_sortedcache_wlock(sc)) < 0)
0b7cdc02
RB
210 return error;
211
212 if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
213 goto unlock;
214
40ffa07f
CMN
215 if ((fd = git_futils_open_ro(sc->path)) < 0) {
216 error = fd;
217 goto unlock;
218 }
219
220 if (p_fstat(fd, &st) < 0) {
ac3d33df 221 git_error_set(GIT_ERROR_OS, "failed to stat file");
0b7cdc02 222 error = -1;
24b2182c 223 (void)p_close(fd);
0b7cdc02
RB
224 goto unlock;
225 }
226
40ffa07f 227 if (!git__is_sizet(st.st_size)) {
ac3d33df 228 git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t");
40ffa07f
CMN
229 error = -1;
230 (void)p_close(fd);
0b7cdc02
RB
231 goto unlock;
232 }
233
234 if (buf)
40ffa07f 235 error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
0b7cdc02
RB
236
237 (void)p_close(fd);
238
239 if (error < 0)
240 goto unlock;
241
242 return 1; /* return 1 -> file needs reload and was successfully loaded */
243
244unlock:
8d9a85d4 245 git_sortedcache_wunlock(sc);
0b7cdc02
RB
246 return error;
247}
248
8d9a85d4
RB
249void git_sortedcache_updated(git_sortedcache *sc)
250{
7d490872
RB
251 /* update filestamp to latest value */
252 git_futils_filestamp_check(&sc->stamp, sc->path);
8d9a85d4
RB
253}
254
255/* release all items in sorted cache */
256int git_sortedcache_clear(git_sortedcache *sc, bool wlock)
257{
258 if (wlock && git_sortedcache_wlock(sc) < 0)
259 return -1;
260
261 sortedcache_clear(sc);
262
263 if (wlock)
264 git_sortedcache_wunlock(sc);
265
266 return 0;
267}
268
0b7cdc02 269/* find and/or insert item, returning pointer to item data */
8d9a85d4 270int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key)
0b7cdc02 271{
2b6e1908 272 size_t keylen, itemlen;
22a2d3d5 273 int error = 0;
0b7cdc02 274 char *item_key;
22a2d3d5 275 void *item;
0b7cdc02 276
22a2d3d5 277 if ((item = git_strmap_get(sc->map, key)) != NULL)
0b7cdc02 278 goto done;
0b7cdc02 279
2b6e1908
RB
280 keylen = strlen(key);
281 itemlen = sc->item_path_offset + keylen + 1;
282 itemlen = (itemlen + 7) & ~7;
283
22a2d3d5 284 if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) {
ac3d33df 285 /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */
8d9a85d4
RB
286 error = -1;
287 goto done;
288 }
0b7cdc02
RB
289
290 /* one strange thing is that even if the vector or hash table insert
291 * fail, there is no way to free the pool item so we just abandon it
292 */
293
294 item_key = ((char *)item) + sc->item_path_offset;
295 memcpy(item_key, key, keylen);
296
22a2d3d5 297 if ((error = git_strmap_set(sc->map, item_key, item)) < 0)
0b7cdc02
RB
298 goto done;
299
22a2d3d5
UG
300 if ((error = git_vector_insert(&sc->items, item)) < 0)
301 git_strmap_delete(sc->map, item_key);
0b7cdc02
RB
302
303done:
304 if (out)
305 *out = !error ? item : NULL;
306 return error;
307}
308
309/* lookup item by key */
310void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key)
311{
22a2d3d5 312 return git_strmap_get(sc->map, key);
0b7cdc02
RB
313}
314
315/* find out how many items are in the cache */
316size_t git_sortedcache_entrycount(const git_sortedcache *sc)
317{
318 return git_vector_length(&sc->items);
319}
320
321/* lookup item by index */
8d9a85d4 322void *git_sortedcache_entry(git_sortedcache *sc, size_t pos)
0b7cdc02 323{
8d9a85d4 324 /* make sure the items are sorted so this gets the correct item */
882c7742 325 if (!git_vector_is_sorted(&sc->items))
8d9a85d4
RB
326 git_vector_sort(&sc->items);
327
0b7cdc02
RB
328 return git_vector_get(&sc->items, pos);
329}
a4977169 330
8d9a85d4 331/* helper struct so bsearch callback can know offset + key value for cmp */
a4977169
RB
332struct sortedcache_magic_key {
333 size_t offset;
334 const char *key;
335};
336
337static int sortedcache_magic_cmp(const void *key, const void *value)
338{
339 const struct sortedcache_magic_key *magic = key;
340 const char *value_key = ((const char *)value) + magic->offset;
341 return strcmp(magic->key, value_key);
342}
343
344/* lookup index of item by key */
345int git_sortedcache_lookup_index(
346 size_t *out, git_sortedcache *sc, const char *key)
347{
348 struct sortedcache_magic_key magic;
349
350 magic.offset = sc->item_path_offset;
351 magic.key = key;
352
353 return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic);
354}
355
356/* remove entry from cache */
8d9a85d4 357int git_sortedcache_remove(git_sortedcache *sc, size_t pos)
a4977169 358{
a4977169 359 char *item;
a4977169 360
22a2d3d5
UG
361 /*
362 * Because of pool allocation, this can't actually remove the item,
a4977169
RB
363 * but we can remove it from the items vector and the hash table.
364 */
365
366 if ((item = git_vector_get(&sc->items, pos)) == NULL) {
ac3d33df 367 git_error_set(GIT_ERROR_INVALID, "removing item out of range");
8d9a85d4 368 return GIT_ENOTFOUND;
a4977169
RB
369 }
370
371 (void)git_vector_remove(&sc->items, pos);
372
22a2d3d5 373 git_strmap_delete(sc->map, item + sc->item_path_offset);
a4977169
RB
374
375 if (sc->free_item)
376 sc->free_item(sc->free_item_payload, item);
377
8d9a85d4 378 return 0;
a4977169
RB
379}
380