File Coverage

deps/libgit2/src/sortedcache.c
Criterion Covered Total %
statement 77 161 47.8
branch 25 84 29.7
condition n/a
subroutine n/a
pod n/a
total 102 245 41.6


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