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