/* * libdpkg - Debian packaging suite library routines * pkg-hash.c - low level package database routines (hash tables, etc.) * * Copyright © 1995 Ian Jackson * Copyright © 2008-2014 Guillem Jover * Copyright © 2011 Linaro Limited * Copyright © 2011 Raphaël Hertzog * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include /* * This must always be a prime for optimal performance. * * We use a number that is close to the amount of packages currently present * in a Debian suite, so that installed and available packages do not add * tons of collisions. * * The memory usage is «BINS * sizeof(void *)». */ #define BINS 65521 static struct pkgset *bins[BINS]; static int npkg, nset; /** * Return the package set with the given name. * * If the package already exists in the internal database, then it returns * the existing structure. Otherwise it allocates a new one and will return * it. The actual name associated to the package set is a lowercase version * of the name given in parameter. * * A package set (struct pkgset) can be composed of multiple package instances * (struct pkginfo) where each instance is distinguished by its architecture * (as recorded in pkg.installed.arch and pkg.available.arch). * * @param inname Name of the package set. * * @return The package set. */ struct pkgset * pkg_hash_find_set(const char *inname) { struct pkgset **setp, *new_set; char *name = m_strdup(inname), *p; p= name; while (*p) { *p = c_tolower(*p); p++; } setp = bins + (str_fnv_hash(name) % (BINS)); while (*setp && strcasecmp((*setp)->name, name)) setp = &(*setp)->next; if (*setp) { free(name); return *setp; } new_set = nfmalloc(sizeof(*new_set)); pkgset_blank(new_set); new_set->name = nfstrsave(name); new_set->next = NULL; *setp = new_set; nset++; npkg++; free(name); return new_set; } /** * Return the singleton package instance from a package set. * * This means, if none are installed either an instance with native or * all arch or the first if none found, the single installed instance, * or NULL if more than one instance is installed. * * @param set The package set to use. * * @return The singleton package instance. */ struct pkginfo * pkg_hash_get_singleton(struct pkgset *set) { struct pkginfo *pkg; switch (pkgset_installed_instances(set)) { case 0: /* Pick an available candidate. */ for (pkg = &set->pkg; pkg; pkg = pkg->arch_next) { const struct dpkg_arch *arch = pkg->available.arch; if (arch->type == DPKG_ARCH_NATIVE || arch->type == DPKG_ARCH_ALL) return pkg; } /* Or failing that, the first entry. */ return &set->pkg; case 1: for (pkg = &set->pkg; pkg; pkg = pkg->arch_next) { if (pkg->status > PKG_STAT_NOTINSTALLED) return pkg; } internerr("pkgset '%s' should have one installed instance", set->name); default: return NULL; } } /** * Return the singleton package instance with the given name. * * @param name The package name. * * @return The package instance. */ struct pkginfo * pkg_hash_find_singleton(const char *name) { struct pkgset *set; struct pkginfo *pkg; set = pkg_hash_find_set(name); pkg = pkg_hash_get_singleton(set); if (pkg == NULL) ohshit(_("ambiguous package name '%s' with more " "than one installed instance"), set->name); return pkg; } /** * Return the package instance in a set with the given architecture. * * It traverse the various instances to find out whether there's one * matching the given architecture. If found, it returns it. Otherwise it * allocates a new instance and registers it in the package set before * returning it. * * @param set The package set to use. * @param arch The requested architecture. * * @return The package instance. */ struct pkginfo * pkg_hash_get_pkg(struct pkgset *set, const struct dpkg_arch *arch) { struct pkginfo *pkg, **pkgp; if (arch == NULL) internerr("arch argument is NULL"); if (arch->type == DPKG_ARCH_NONE) internerr("arch argument is none"); pkg = &set->pkg; /* If there's a single unused slot, let's use that. */ if (pkg->installed.arch->type == DPKG_ARCH_NONE && pkg->arch_next == NULL) { /* We can only initialize the arch pkgbin members, because those are used * to find instances, anything else will be overwritten at parse time. */ pkg->installed.arch = arch; pkg->available.arch = arch; return pkg; } /* Match the slot with the most appropriate architecture. The installed * architecture always has preference over the available one, as there's * a small time window on cross-grades, where they might differ. */ for (pkgp = &pkg; *pkgp; pkgp = &(*pkgp)->arch_next) { if ((*pkgp)->installed.arch == arch) return *pkgp; } /* Need to create a new instance for the wanted architecture. */ pkg = nfmalloc(sizeof(*pkg)); pkg_blank(pkg); pkg->set = set; pkg->arch_next = NULL; /* We can only initialize the arch pkgbin members, because those are used * to find instances, anything else will be overwritten at parse time. */ pkg->installed.arch = arch; pkg->available.arch = arch; *pkgp = pkg; npkg++; return pkg; } /** * Return the package instance with the given name and architecture. * * @param name The package name. * @param arch The requested architecture. * * @return The package instance. */ struct pkginfo * pkg_hash_find_pkg(const char *name, const struct dpkg_arch *arch) { struct pkgset *set; struct pkginfo *pkg; set = pkg_hash_find_set(name); pkg = pkg_hash_get_pkg(set, arch); return pkg; } /** * Return the number of package sets available in the database. * * @return The number of package sets. */ int pkg_hash_count_set(void) { return nset; } /** * Return the number of package instances available in the database. * * @return The number of package instances. */ int pkg_hash_count_pkg(void) { return npkg; } struct pkg_hash_iter { struct pkginfo *pkg; int nbinn; }; /** * Create a new package iterator. * * It can iterate either over package sets or over package instances. * * @return The iterator. */ struct pkg_hash_iter * pkg_hash_iter_new(void) { struct pkg_hash_iter *iter; iter = m_malloc(sizeof(*iter)); iter->pkg = NULL; iter->nbinn = 0; return iter; } /** * Return the next package set in the database. * * If no further package set is available, it will return NULL. * * @name iter The iterator. * * @return A package set. */ struct pkgset * pkg_hash_iter_next_set(struct pkg_hash_iter *iter) { struct pkgset *set; while (!iter->pkg) { if (iter->nbinn >= BINS) return NULL; if (bins[iter->nbinn]) iter->pkg = &bins[iter->nbinn]->pkg; iter->nbinn++; } set = iter->pkg->set; if (set->next) iter->pkg = &set->next->pkg; else iter->pkg = NULL; return set; } /** * Return the next package instance in the database. * * If no further package instance is available, it will return NULL. Note * that it will return all instances of a given package set in sequential * order. The first instance for a given package set will always correspond * to the native architecture even if that package is not installed or * available. * * @name iter The iterator. * * @return A package instance. */ struct pkginfo * pkg_hash_iter_next_pkg(struct pkg_hash_iter *iter) { struct pkginfo *pkg; while (!iter->pkg) { if (iter->nbinn >= BINS) return NULL; if (bins[iter->nbinn]) iter->pkg = &bins[iter->nbinn]->pkg; iter->nbinn++; } pkg = iter->pkg; if (pkg->arch_next) iter->pkg = pkg->arch_next; else if (pkg->set->next) iter->pkg = &pkg->set->next->pkg; else iter->pkg = NULL; return pkg; } /** * Free the package database iterator. * * @name iter The iterator. */ void pkg_hash_iter_free(struct pkg_hash_iter *iter) { free(iter); } void pkg_hash_reset(void) { dpkg_arch_reset_list(); nffreeall(); nset = 0; npkg = 0; memset(bins, 0, sizeof(bins)); } void pkg_hash_report(FILE *file) { int i, c; struct pkgset *pkg; int *freq; int empty = 0, used = 0, collided = 0; freq = m_malloc(sizeof(int) * nset + 1); for (i = 0; i <= nset; i++) freq[i] = 0; for (i=0; inext); fprintf(file, "pkg-hash: bin %5d has %7d\n", i, c); if (c == 0) empty++; else if (c == 1) used++; else { used++; collided++; } freq[c]++; } for (i = nset; i > 0 && freq[i] == 0; i--); while (i >= 0) { fprintf(file, "pkg-hash: size %7d occurs %5d times\n", i, freq[i]); i--; } fprintf(file, "pkg-hash: bins empty %d\n", empty); fprintf(file, "pkg-hash: bins used %d (collided %d)\n", used, collided); m_output(file, ""); free(freq); }