jcs's openbsd hax
openbsd
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
34static int
35cache_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
43static int
44cache_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
58static void
59cache_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 */
88pkgconf_pkg_t *
89pkgconf_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 */
122void
123pkgconf_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 */
155void
156pkgconf_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 */
217void
218pkgconf_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}