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