Rename pmhandle_t to alpm_handle_t
[pacman-ng.git] / lib / libalpm / pkghash.c
blob9e98fcd8cfb5471ea51771791a5078769f71089a
1 /*
2 * pkghash.c
4 * Copyright (c) 2011 Pacman Development Team <pacman-dev@archlinux.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <errno.h>
22 #include "pkghash.h"
23 #include "util.h"
25 /* List of primes for possible sizes of hash tables.
27 * The maximum table size is the last prime under 1,000,000. That is
28 * more than an order of magnitude greater than the number of packages
29 * in any Linux distribution.
31 static const size_t prime_list[] =
33 11ul, 13ul, 17ul, 19ul, 23ul, 29ul, 31ul, 37ul, 41ul, 43ul, 47ul,
34 53ul, 59ul, 61ul, 67ul, 71ul, 73ul, 79ul, 83ul, 89ul, 97ul, 103ul,
35 109ul, 113ul, 127ul, 137ul, 139ul, 149ul, 157ul, 167ul, 179ul, 193ul,
36 199ul, 211ul, 227ul, 241ul, 257ul, 277ul, 293ul, 313ul, 337ul, 359ul,
37 383ul, 409ul, 439ul, 467ul, 503ul, 541ul, 577ul, 619ul, 661ul, 709ul,
38 761ul, 823ul, 887ul, 953ul, 1031ul, 1109ul, 1193ul, 1289ul, 1381ul,
39 1493ul, 1613ul, 1741ul, 1879ul, 2029ul, 2179ul, 2357ul, 2549ul,
40 2753ul, 2971ul, 3209ul, 3469ul, 3739ul, 4027ul, 4349ul, 4703ul,
41 5087ul, 5503ul, 5953ul, 6427ul, 6949ul, 7517ul, 8123ul, 8783ul,
42 9497ul, 10273ul, 11113ul, 12011ul, 12983ul, 14033ul, 15173ul,
43 16411ul, 17749ul, 19183ul, 20753ul, 22447ul, 24281ul, 26267ul,
44 28411ul, 30727ul, 33223ul, 35933ul, 38873ul, 42043ul, 45481ul,
45 49201ul, 53201ul, 57557ul, 62233ul, 67307ul, 72817ul, 78779ul,
46 85229ul, 92203ul, 99733ul, 107897ul, 116731ul, 126271ul, 136607ul,
47 147793ul, 159871ul, 172933ul, 187091ul, 202409ul, 218971ul, 236897ul,
48 256279ul, 277261ul, 299951ul, 324503ul, 351061ul, 379787ul, 410857ul,
49 444487ul, 480881ul, 520241ul, 562841ul, 608903ul, 658753ul, 712697ul,
50 771049ul, 834181ul, 902483ul, 976369ul
53 /* Allocate a hash table with at least "size" buckets */
54 pmpkghash_t *_alpm_pkghash_create(size_t size)
56 pmpkghash_t *hash = NULL;
57 size_t i, loopsize;
59 CALLOC(hash, 1, sizeof(pmpkghash_t), return NULL);
61 loopsize = sizeof(prime_list) / sizeof(*prime_list);
62 for(i = 0; i < loopsize; i++) {
63 if(prime_list[i] > size) {
64 hash->buckets = prime_list[i];
65 break;
69 if(hash->buckets < size) {
70 errno = ERANGE;
71 free(hash);
72 return NULL;
75 CALLOC(hash->hash_table, hash->buckets, sizeof(alpm_list_t *), \
76 free(hash); return NULL);
78 return hash;
81 static size_t get_hash_position(unsigned long name_hash, pmpkghash_t *hash)
83 size_t position;
85 position = name_hash % hash->buckets;
87 /* collision resolution using open addressing with linear probing */
88 while(hash->hash_table[position] != NULL) {
89 position = (position + 1) % hash->buckets;
92 return position;
95 /* Expand the hash table size to the next increment and rebin the entries */
96 static pmpkghash_t *rehash(pmpkghash_t *oldhash)
98 pmpkghash_t *newhash;
99 size_t newsize, position, i;
101 /* Hash tables will need resized in two cases:
102 * - adding packages to the local database
103 * - poor estimation of the number of packages in sync database
105 * For small hash tables sizes (<500) the increase in size is by a
106 * minimum of a factor of 2 for optimal rehash efficiency. For
107 * larger database sizes, this increase is reduced to avoid excess
108 * memory allocation as both scenarios requiring a rehash should not
109 * require a table size increase that large. */
110 if(oldhash->buckets < 500) {
111 newsize = oldhash->buckets * 2;
112 } else if(oldhash->buckets < 2000) {
113 newsize = oldhash->buckets * 3 / 2;
114 } else if(oldhash->buckets < 5000) {
115 newsize = oldhash->buckets * 4 / 3;
116 } else {
117 newsize = oldhash->buckets + 1;
120 newhash = _alpm_pkghash_create(newsize);
121 if(newhash == NULL) {
122 /* creation of newhash failed, stick with old one... */
123 return oldhash;
126 newhash->list = oldhash->list;
127 oldhash->list = NULL;
129 for(i = 0; i < oldhash->buckets; i++) {
130 if(oldhash->hash_table[i] != NULL) {
131 pmpkg_t *package = oldhash->hash_table[i]->data;
133 position = get_hash_position(package->name_hash, newhash);
135 newhash->hash_table[position] = oldhash->hash_table[i];
136 oldhash->hash_table[i] = NULL;
140 newhash->entries = oldhash->entries;
142 _alpm_pkghash_free(oldhash);
144 return newhash;
147 static pmpkghash_t *pkghash_add_pkg(pmpkghash_t *hash, pmpkg_t *pkg, int sorted)
149 alpm_list_t *ptr;
150 size_t position;
152 if(pkg == NULL || hash == NULL) {
153 return hash;
156 if((hash->entries + 1) / MAX_HASH_LOAD > hash->buckets) {
157 hash = rehash(hash);
160 position = get_hash_position(pkg->name_hash, hash);
162 ptr = calloc(1, sizeof(alpm_list_t));
163 if(ptr == NULL) {
164 return hash;
167 ptr->data = pkg;
168 ptr->next = NULL;
169 ptr->prev = ptr;
171 hash->hash_table[position] = ptr;
172 if(!sorted){
173 hash->list = alpm_list_join(hash->list, ptr);
174 }else{
175 hash->list = alpm_list_mmerge(hash->list, ptr, _alpm_pkg_cmp);
178 hash->entries += 1;
179 return hash;
182 pmpkghash_t *_alpm_pkghash_add(pmpkghash_t *hash, pmpkg_t *pkg)
184 return pkghash_add_pkg(hash, pkg, 0);
187 pmpkghash_t *_alpm_pkghash_add_sorted(pmpkghash_t *hash, pmpkg_t *pkg)
189 return pkghash_add_pkg(hash, pkg, 1);
192 static size_t move_one_entry(pmpkghash_t *hash, size_t start, size_t end)
194 /* Iterate backwards from 'end' to 'start', seeing if any of the items
195 * would hash to 'start'. If we find one, we move it there and break. If
196 * we get all the way back to position and find none that hash to it, we
197 * also end iteration. Iterating backwards helps prevent needless shuffles;
198 * we will never need to move more than one item per function call. The
199 * return value is our current iteration location; if this is equal to
200 * 'start' we can stop this madness. */
201 while(end != start) {
202 alpm_list_t *i = hash->hash_table[end];
203 pmpkg_t *info = i->data;
204 size_t new_position = get_hash_position(info->name_hash, hash);
206 if(new_position == start) {
207 hash->hash_table[start] = i;
208 hash->hash_table[end] = NULL;
209 break;
212 /* the odd math ensures we are always positive, e.g.
213 * e.g. (0 - 1) % 47 == -1
214 * e.g. (47 + 0 - 1) % 47 == 46 */
215 end = (hash->buckets + end - 1) % hash->buckets;
217 return end;
221 * @brief Remove a package from a pkghash.
223 * @param hash the hash to remove the package from
224 * @param pkg the package we are removing
225 * @param data output parameter containing the removed item
227 * @return the resultant hash
229 pmpkghash_t *_alpm_pkghash_remove(pmpkghash_t *hash, pmpkg_t *pkg,
230 pmpkg_t **data)
232 alpm_list_t *i;
233 size_t position;
235 if(data) {
236 *data = NULL;
239 if(pkg == NULL || hash == NULL) {
240 return hash;
243 position = pkg->name_hash % hash->buckets;
244 while((i = hash->hash_table[position]) != NULL) {
245 pmpkg_t *info = i->data;
247 if(info->name_hash == pkg->name_hash &&
248 strcmp(info->name, pkg->name) == 0) {
249 size_t stop, prev;
251 /* remove from list and hash */
252 hash->list = alpm_list_remove_item(hash->list, i);
253 if(data) {
254 *data = info;
256 hash->hash_table[position] = NULL;
257 free(i);
258 hash->entries -= 1;
260 /* Potentially move entries following removed entry to keep open
261 * addressing collision resolution working. We start by finding the
262 * next null bucket to know how far we have to look. */
263 stop = (position + 1) % hash->buckets;
264 while(hash->hash_table[stop] != NULL && stop != position) {
265 stop = (stop + 1) % hash->buckets;
267 stop = (hash->buckets + stop - 1) % hash->buckets;
269 /* We now search backwards from stop to position. If we find an
270 * item that now hashes to position, we will move it, and then try
271 * to plug the new hole we just opened up, until we finally don't
272 * move anything. */
273 while((prev = move_one_entry(hash, position, stop)) != position) {
274 position = prev;
277 return hash;
280 position = (position + 1) % hash->buckets;
283 return hash;
286 void _alpm_pkghash_free(pmpkghash_t *hash)
288 size_t i;
289 if(hash != NULL) {
290 for(i = 0; i < hash->buckets; i++) {
291 free(hash->hash_table[i]);
293 free(hash->hash_table);
295 free(hash);
298 pmpkg_t *_alpm_pkghash_find(pmpkghash_t *hash, const char *name)
300 alpm_list_t *lp;
301 unsigned long name_hash;
302 size_t position;
304 if(name == NULL || hash == NULL) {
305 return NULL;
308 name_hash = _alpm_hash_sdbm(name);
310 position = name_hash % hash->buckets;
312 while((lp = hash->hash_table[position]) != NULL) {
313 pmpkg_t *info = lp->data;
315 if(info->name_hash == name_hash && strcmp(info->name, name) == 0) {
316 return info;
319 position = (position + 1) % hash->buckets;
322 return NULL;
325 /* vim: set ts=2 sw=2 noet: */