xref: /freebsd/contrib/pkgconf/libpkgconf/cache.c (revision d15f2551b25f79ddcbe289faa95e655100b952da)
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
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
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
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 *
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
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
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
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