1 /*
2 * cache.c
3 * package object cache
4 *
5 * SPDX-License-Identifier: pkgconf
6 *
7 * Copyright (c) 2013 pkgconf authors (see AUTHORS).
8 *
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * This software is provided 'as is' and without any warranty, express or
14 * implied. In no event shall the authors be liable for any damages arising
15 * from the use of this software.
16 */
17
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20
21 #include <assert.h>
22
23 /*
24 * !doc
25 *
26 * libpkgconf `cache` module
27 * =========================
28 *
29 * The libpkgconf `cache` module manages a package/module object cache, allowing it to
30 * avoid loading duplicate copies of a package/module.
31 *
32 * A cache is tied to a specific pkgconf client object, so package objects should not
33 * be shared across threads.
34 */
35
36 static int
cache_member_cmp(const void * a,const void * b)37 cache_member_cmp(const void *a, const void *b)
38 {
39 const char *key = a;
40 const pkgconf_pkg_t *pkg = *(void **) b;
41
42 return strcmp(key, pkg->id);
43 }
44
45 static int
cache_member_sort_cmp(const void * a,const void * b)46 cache_member_sort_cmp(const void *a, const void *b)
47 {
48 const pkgconf_pkg_t *pkgA = *(void **) a;
49 const pkgconf_pkg_t *pkgB = *(void **) b;
50
51 if (pkgA == NULL)
52 return 1;
53
54 if (pkgB == NULL)
55 return -1;
56
57 return strcmp(pkgA->id, pkgB->id);
58 }
59
60 static void
cache_dump(const pkgconf_client_t * client)61 cache_dump(const pkgconf_client_t *client)
62 {
63 size_t i;
64
65 PKGCONF_TRACE(client, "dumping package cache contents");
66
67 for (i = 0; i < client->cache_count; i++)
68 {
69 const pkgconf_pkg_t *pkg = client->cache_table[i];
70
71 PKGCONF_TRACE(client, SIZE_FMT_SPECIFIER": %p(%s)",
72 i, pkg, pkg == NULL ? "NULL" : pkg->id);
73 }
74 }
75
76 /*
77 * !doc
78 *
79 * .. c:function:: pkgconf_pkg_t *pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id)
80 *
81 * Looks up a package in the cache given an `id` atom,
82 * such as ``gtk+-3.0`` and returns the already loaded version
83 * if present.
84 *
85 * :param pkgconf_client_t* client: The client object to access.
86 * :param char* id: The package atom to look up in the client object's cache.
87 * :return: A package object if present, else ``NULL``.
88 * :rtype: pkgconf_pkg_t *
89 */
90 pkgconf_pkg_t *
pkgconf_cache_lookup(pkgconf_client_t * client,const char * id)91 pkgconf_cache_lookup(pkgconf_client_t *client, const char *id)
92 {
93 if (client->cache_table == NULL)
94 return NULL;
95
96 pkgconf_pkg_t **pkg;
97
98 pkg = bsearch(id, client->cache_table,
99 client->cache_count, sizeof (void *),
100 cache_member_cmp);
101
102 if (pkg != NULL)
103 {
104 PKGCONF_TRACE(client, "found: %s @%p", id, *pkg);
105 return pkgconf_pkg_ref(client, *pkg);
106 }
107
108 PKGCONF_TRACE(client, "miss: %s", id);
109 return NULL;
110 }
111
112 /*
113 * !doc
114 *
115 * .. c:function:: void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
116 *
117 * Adds an entry for the package to the package cache.
118 * The cache entry must be removed if the package is freed.
119 *
120 * :param pkgconf_client_t* client: The client object to modify.
121 * :param pkgconf_pkg_t* pkg: The package object to add to the client object's cache.
122 * :return: nothing
123 */
124 void
pkgconf_cache_add(pkgconf_client_t * client,pkgconf_pkg_t * pkg)125 pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
126 {
127 if (pkg == NULL)
128 return;
129
130 pkgconf_pkg_ref(client, pkg);
131
132 pkgconf_pkg_t **new_table;
133
134 /* mark package as cached */
135 pkg->flags |= PKGCONF_PKG_PROPF_CACHED;
136
137 ++client->cache_count;
138 new_table = pkgconf_reallocarray(client->cache_table,
139 client->cache_count, sizeof (void *));
140
141 /* if we are out of memory, roll back adding to cache and bail */
142 if (new_table == NULL)
143 {
144 --client->cache_count;
145 pkg->flags &= ~PKGCONF_PKG_PROPF_CACHED;
146 pkgconf_pkg_unref(client, pkg);
147 return;
148 }
149
150 client->cache_table = new_table;
151 client->cache_table[client->cache_count - 1] = pkg;
152
153 qsort(client->cache_table, client->cache_count,
154 sizeof(void *), cache_member_sort_cmp);
155
156 PKGCONF_TRACE(client, "added @%p to cache", pkg);
157 }
158
159 /*
160 * !doc
161 *
162 * .. c:function:: void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
163 *
164 * Deletes a package from the client object's package cache.
165 *
166 * :param pkgconf_client_t* client: The client object to modify.
167 * :param pkgconf_pkg_t* pkg: The package object to remove from the client object's cache.
168 * :return: nothing
169 */
170 void
pkgconf_cache_remove(pkgconf_client_t * client,pkgconf_pkg_t * pkg)171 pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
172 {
173 if (client->cache_table == NULL)
174 return;
175
176 if (pkg == NULL)
177 return;
178
179 if (!(pkg->flags & PKGCONF_PKG_PROPF_CACHED))
180 return;
181
182 PKGCONF_TRACE(client, "removed @%p from cache", pkg);
183
184 pkgconf_pkg_t **slot;
185
186 slot = bsearch(pkg->id, client->cache_table,
187 client->cache_count, sizeof (void *),
188 cache_member_cmp);
189
190 if (slot == NULL)
191 return;
192
193 (*slot)->flags &= ~PKGCONF_PKG_PROPF_CACHED;
194 pkgconf_pkg_unref(client, *slot);
195 *slot = NULL;
196
197 qsort(client->cache_table, client->cache_count,
198 sizeof(void *), cache_member_sort_cmp);
199
200 if (client->cache_table[client->cache_count - 1] != NULL)
201 {
202 PKGCONF_TRACE(client, "end of cache table refers to %p, not NULL",
203 client->cache_table[client->cache_count - 1]);
204 cache_dump(client);
205 abort();
206 }
207
208 client->cache_count--;
209 if (client->cache_count > 0)
210 {
211 pkgconf_pkg_t **new_table = pkgconf_reallocarray(client->cache_table,
212 client->cache_count, sizeof(void *));
213
214 if (new_table != NULL)
215 client->cache_table = new_table;
216 }
217 else
218 {
219 free(client->cache_table);
220 client->cache_table = NULL;
221 }
222 }
223
224 /*
225 * !doc
226 *
227 * .. c:function:: void pkgconf_cache_free(pkgconf_client_t *client)
228 *
229 * Releases all resources related to a client object's package cache.
230 * This function should only be called to clear a client object's package cache,
231 * as it may release any package in the cache.
232 *
233 * :param pkgconf_client_t* client: The client object to modify.
234 */
235 void
pkgconf_cache_free(pkgconf_client_t * client)236 pkgconf_cache_free(pkgconf_client_t *client)
237 {
238 if (client->cache_table == NULL)
239 return;
240
241 while (client->cache_count > 0)
242 pkgconf_cache_remove(client, client->cache_table[0]);
243
244 free(client->cache_table);
245 client->cache_table = NULL;
246 client->cache_count = 0;
247
248 PKGCONF_TRACE(client, "cleared package cache");
249 }
250