diff options
Diffstat (limited to '')
-rw-r--r-- | tracking.c | 1468 |
1 files changed, 1468 insertions, 0 deletions
diff --git a/tracking.c b/tracking.c new file mode 100644 index 0000000..6fe5554 --- /dev/null +++ b/tracking.c @@ -0,0 +1,1468 @@ +/* This file is part of "reprepro" + * Copyright (C) 2005,2006,2007,2008,2009,2016 Bernhard R. Link + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <config.h> + +#include <assert.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include "error.h" +#include "names.h" +#include "dirs.h" +#include "names.h" +#include "reference.h" +#include "ignore.h" +#include "configparser.h" +#include "package.h" + +#include "database_p.h" +#include "tracking.h" + +#ifndef NOPARANOIA +#define PARANOIA +#endif + +struct s_tracking { + char *codename; + struct table *table; + enum trackingtype type; + struct trackingoptions options; +}; + +retvalue tracking_done(trackingdb db, struct distribution *distribution) { + retvalue r; + + if (db == NULL) + return RET_OK; + + r = table_close(db->table); + free(db->codename); + free(db); + if (distribution->trackingdb == NULL) { + fprintf(stderr, +"Internal Error: Tracking database was closed, but corresponding entry in the distribution structure %s is missing.\n", + distribution->codename); + } else { + distribution->trackingdb = NULL; + } + return r; +} + +retvalue tracking_initialize(/*@out@*/trackingdb *db, struct distribution *distribution, bool readonly) { + struct s_tracking *t; + retvalue r; + + t = zNEW(struct s_tracking); + if (FAILEDTOALLOC(t)) + return RET_ERROR_OOM; + t->codename = strdup(distribution->codename); + if (FAILEDTOALLOC(t->codename)) { + free(t); + return RET_ERROR_OOM; + } + assert (distribution->tracking != dt_NONE || readonly); + t->type = distribution->tracking; + t->options = distribution->trackingoptions; + r = database_opentracking(t->codename, readonly, &t->table); + if (!RET_IS_OK(r)) { + free(t->codename); + free(t); + return r; + } + *db = t; + distribution->trackingdb = t; + return RET_OK; +} + +static inline enum filetype filetypechar(enum filetype filetype) { + switch (filetype) { + case ft_LOG: + case ft_BUILDINFO: + case ft_CHANGES: + case ft_ALL_BINARY: + case ft_ARCH_BINARY: + case ft_SOURCE: + case ft_XTRA_DATA: + return filetype; + } + assert(false); + return ft_XTRA_DATA; +} + +retvalue trackedpackage_addfilekey(trackingdb tracks, struct trackedpackage *pkg, enum filetype filetype, char *filekey, bool used) { + char *id; + enum filetype ft = filetypechar(filetype); + int i, *newrefcounts; + enum filetype *newfiletypes; + retvalue r; + + if (FAILEDTOALLOC(filekey)) + return RET_ERROR_OOM; + + for (i = 0 ; i < pkg->filekeys.count ; i++) { + if (strcmp(pkg->filekeys.values[i], filekey) == 0) { + if (pkg->filetypes[i] != ft) { + /* if old file has refcount 0, just repair: */ + if (pkg->refcounts[i] <= 0) { + free(filekey); + pkg->filetypes[i] = ft; + if (used) + pkg->refcounts[i] = 1; + return RET_OK; + } + fprintf(stderr, +"Filekey '%s' already registered for '%s_%s' as type '%c' is tried to be reregistered as type '%c'!\n", + filekey, pkg->sourcename, + pkg->sourceversion, + pkg->filetypes[i], ft); + free(filekey); + return RET_ERROR; + } + free(filekey); + if (used) + pkg->refcounts[i]++; + return RET_OK; + } + } + + newrefcounts = realloc(pkg->refcounts, + (pkg->filekeys.count + 1) * sizeof(int)); + if (FAILEDTOALLOC(newrefcounts)) { + free(filekey); + return RET_ERROR_OOM; + } + if (used) + newrefcounts[pkg->filekeys.count]=1; + else + newrefcounts[pkg->filekeys.count]=0; + pkg->refcounts = newrefcounts; + newfiletypes = realloc(pkg->filetypes, + (pkg->filekeys.count + 1) * sizeof(enum filetype)); + if (FAILEDTOALLOC(newfiletypes)) { + free(filekey); + return RET_ERROR_OOM; + } + newfiletypes[pkg->filekeys.count] = filetype; + pkg->filetypes = newfiletypes; + + r = strlist_add(&pkg->filekeys, filekey); + if (RET_WAS_ERROR(r)) + return r; + + id = calc_trackreferee(tracks->codename, + pkg->sourcename, pkg->sourceversion); + if (FAILEDTOALLOC(id)) + return RET_ERROR_OOM; + r = references_increment(filekey, id); + free(id); + return r; +} + +retvalue trackedpackage_adddupfilekeys(trackingdb tracks, struct trackedpackage *pkg, enum filetype filetype, const struct strlist *filekeys, bool used) { + int i; + retvalue result, r; + assert (filekeys != NULL); + + result = RET_OK; + for (i = 0 ; i < filekeys->count ; i++) { + char *filekey = strdup(filekeys->values[i]); + r = trackedpackage_addfilekey(tracks, pkg, filetype, + filekey, used); + RET_UPDATE(result, r); + } + return result; +} + +static inline retvalue trackedpackage_removefilekey(trackingdb tracks, struct trackedpackage *pkg, const char *filekey) { + int i; + + for (i = 0 ; i < pkg->filekeys.count ; i++) { + if (strcmp(pkg->filekeys.values[i], filekey) == 0) { + if (pkg->refcounts[i] > 0) { + pkg->refcounts[i]--; + } else + fprintf(stderr, +"Warning: tracking database of %s has inconsistent refcounts of %s_%s.\n", + tracks->codename, + pkg->sourcename, + pkg->sourceversion); + + return RET_OK; + } + } + fprintf(stderr, +"Warning: tracking database of %s missed files for %s_%s.\n", + tracks->codename, pkg->sourcename, pkg->sourceversion); + return RET_OK; + +} + +retvalue trackedpackage_removefilekeys(trackingdb tracks, struct trackedpackage *pkg, const struct strlist *filekeys) { + int i; + retvalue result, r; + assert (filekeys != NULL); + + result = RET_OK; + for (i = 0 ; i < filekeys->count ; i++) { + const char *filekey = filekeys->values[i]; + r = trackedpackage_removefilekey(tracks, pkg, filekey); + RET_UPDATE(result, r); + } + return result; +} + +void trackedpackage_free(struct trackedpackage *pkg) { + if (pkg != NULL) { + free(pkg->sourcename); + free(pkg->sourceversion); + strlist_done(&pkg->filekeys); + free(pkg->refcounts); + free(pkg->filetypes); + free(pkg); + } +} + +static inline int parsenumber(const char **d, size_t *s) { + int count; + + count = 0; + do { + if (**d < '0' || **d > '7') + return -1; + count = (count*8) + (**d-'0'); + (*d)++; + (*s)--; + if (*s == 0) + return -1; + } while (**d != '\0'); + (*d)++; + (*s)--; + return count; +} + +static retvalue tracking_new(const char *sourcename, const char *version, /*@out@*/struct trackedpackage **pkg) { + struct trackedpackage *p; + assert (pkg != NULL && sourcename != NULL && version != NULL); + + p = zNEW(struct trackedpackage); + if (FAILEDTOALLOC(p)) + return RET_ERROR_OOM; + p->sourcename = strdup(sourcename); + p->sourceversion = strdup(version); + p->flags.isnew = true; + if (FAILEDTOALLOC(p->sourcename) || FAILEDTOALLOC(p->sourceversion)) { + trackedpackage_free(p); + return RET_ERROR_OOM; + } + *pkg = p; + return RET_OK; +} + +static inline retvalue parse_data(const char *name, const char *version, const char *data, size_t datalen, /*@out@*/struct trackedpackage **pkg) { + struct trackedpackage *p; + int i; + + p = zNEW(struct trackedpackage); + if (FAILEDTOALLOC(p)) + return RET_ERROR_OOM; + p->sourcename = strdup(name); + p->sourceversion = strdup(version); + if (FAILEDTOALLOC(p->sourcename) + || FAILEDTOALLOC(p->sourceversion) + /* || FAILEDTOALLOC(p->sourcedir) */) { + trackedpackage_free(p); + return RET_ERROR_OOM; + } + while (datalen > 0 && *data != '\0') { + char *filekey; + const char *separator; + size_t filekeylen; + retvalue r; + + if (((p->filekeys.count)&31) == 0) { + enum filetype *n = realloc(p->filetypes, + (p->filekeys.count+32)*sizeof(enum filetype)); + if (FAILEDTOALLOC(n)) { + trackedpackage_free(p); + return RET_ERROR_OOM; + } + p->filetypes = n; + } + p->filetypes[p->filekeys.count] = *data; + data++; datalen--; + separator = memchr(data, '\0', datalen); + if (separator == NULL) { + fprintf(stderr, +"Internal Error: Corrupt tracking data for %s %s\n", + name, version); + trackedpackage_free(p); + return RET_ERROR; + } + filekeylen = separator - data; + filekey = strndup(data, filekeylen); + if (FAILEDTOALLOC(filekey)) { + trackedpackage_free(p); + return RET_ERROR_OOM; + } + r = strlist_add(&p->filekeys, filekey); + if (RET_WAS_ERROR(r)) { + trackedpackage_free(p); + return r; + } + data += filekeylen + 1; + datalen -= filekeylen + 1; + } + data++; datalen--; + p->refcounts = nzNEW(p->filekeys.count, int); + if (FAILEDTOALLOC(p->refcounts)) { + trackedpackage_free(p); + return RET_ERROR_OOM; + } + for (i = 0 ; i < p->filekeys.count ; i++) { + if ((p->refcounts[i] = parsenumber(&data, &datalen)) < 0) { + fprintf(stderr, +"Internal Error: Corrupt tracking data for %s %s\n", + name, version); + trackedpackage_free(p); + return RET_ERROR; + } + } + if (datalen > 0) { + fprintf(stderr, +"Internal Error: Trailing garbage in tracking data for %s %s\n (%ld bytes)", + name, version, (long)datalen); + trackedpackage_free(p); + return RET_ERROR; + } + p->flags.isnew = false; + p->flags.deleted = false; + *pkg = p; + return RET_OK; +} + +retvalue tracking_get(trackingdb t, const char *sourcename, const char *version, /*@out@*/struct trackedpackage **pkg) { + const char *data; + size_t datalen; + retvalue r; + + assert (pkg != NULL && sourcename != NULL && version != NULL); + + r = table_getpair(t->table, sourcename, version, &data, &datalen); + if (!RET_IS_OK(r)) + return r; + return parse_data(sourcename, version, data, datalen, pkg); +} + +retvalue tracking_getornew(trackingdb tracks, const char *name, const char *version, /*@out@*/struct trackedpackage **pkg) { + retvalue r; + r = tracking_get(tracks, name, version, pkg); + if (r == RET_NOTHING) + r = tracking_new(name, version, pkg); + return r; +} + +static retvalue gen_data(struct trackedpackage *pkg, /*@out@*/char **newdata_p, /*@out@*/size_t *newdatalen_p) { + size_t versionsize = strlen(pkg->sourceversion)+1; + int i; + char *d, *data; + size_t datalen; + + datalen = versionsize + 1; + for (i = 0 ; i < pkg->filekeys.count ; i++) { + size_t l; + l = strlen(pkg->filekeys.values[i]); + if (l > 0) + datalen += l+9; + } + data = malloc(datalen + 1); + if (FAILEDTOALLOC(data)) + return RET_ERROR_OOM; + memcpy(data, pkg->sourceversion, versionsize); + d = data + versionsize; + for (i = 0 ; i < pkg->filekeys.count ; i++) { + size_t l; + l = strlen(pkg->filekeys.values[i]); + if (l > 0) { + *d = pkg->filetypes[i]; + d++; + memcpy(d, pkg->filekeys.values[i], l + 1); + d+=l+1; + } + } + *d ='\0'; d++; + for (i = 0 ; i < pkg->filekeys.count ; i++) { + int j; +#define MAXREFCOUNTOCTETS 7 + char countstring[MAXREFCOUNTOCTETS]; + size_t count = pkg->refcounts[i]; + + countstring[MAXREFCOUNTOCTETS-1] = '\0'; + for (j = MAXREFCOUNTOCTETS-2 ; j >= 0 ; j--) { + countstring[j] = '0' + (count & 7); + count >>= 3; + if (count == 0) + break; + } +#undef MAXREFCOUNTOCTETS + assert (count == 0); + + memcpy(d, countstring+j, 7 - j); + d+=7-j; + datalen -= j; + } + *d ='\0'; + assert ((size_t)(d-data) == datalen); + *newdata_p = data; + *newdatalen_p = datalen; + return RET_OK; +} + +static retvalue tracking_saveatcursor(trackingdb t, struct cursor *cursor, struct trackedpackage *pkg) { + if (pkg->flags.deleted) { + /* delete if delete is requested + * (all unreferencing has to be done before) */ + return cursor_delete(t->table, cursor, + pkg->sourcename, pkg->sourceversion); + } else { + char *newdata; + size_t newdatalen; + retvalue r; + + r = gen_data(pkg, &newdata, &newdatalen); + if (RET_IS_OK(r)) { + r = cursor_replace(t->table, cursor, + newdata, newdatalen); + free(newdata); + } + return r; + } +} + +static retvalue tracking_saveonly(trackingdb t, struct trackedpackage *pkg) { + retvalue r, r2; + char *newdata; + size_t newdatalen; + + assert (pkg != NULL); + + if (!pkg->flags.isnew) { + struct cursor *cursor; + + r = table_newpairedcursor(t->table, + pkg->sourcename, pkg->sourceversion, &cursor, + NULL, NULL); + if (RET_WAS_ERROR(r)) + return r; + if (r == RET_NOTHING) { + fprintf(stderr, +"Internal error: tracking_save with isnew=false called but could not find %s_%s in %s!\n", + pkg->sourcename, pkg->sourceversion, + t->codename); + pkg->flags.isnew = true; + } else { + r = tracking_saveatcursor(t, cursor, pkg); + r2 = cursor_close(t->table, cursor); + RET_ENDUPDATE(r, r2); + return r; + } + } + + if (pkg->flags.deleted) + return RET_OK; + + r = gen_data(pkg, &newdata, &newdatalen); + assert (r != RET_NOTHING); + if (!RET_IS_OK(r)) + return r; + + r = table_addrecord(t->table, pkg->sourcename, newdata, newdatalen, false); + free(newdata); + if (verbose > 18) + fprintf(stderr, "Adding tracked package '%s'_'%s' to '%s'\n", + pkg->sourcename, pkg->sourceversion, + t->codename); + return r; +} + +retvalue tracking_save(trackingdb t, struct trackedpackage *pkg) { + retvalue r = tracking_saveonly(t, pkg); + trackedpackage_free(pkg); + return r; +} + +retvalue tracking_listdistributions(struct strlist *distributions) { + return database_listsubtables("tracking.db", distributions); +} + +retvalue tracking_drop(const char *codename) { + retvalue result, r; + + result = database_dropsubtable("tracking.db", codename); + r = references_remove(codename); + RET_UPDATE(result, r); + + return result; +} + +static retvalue tracking_recreatereferences(trackingdb t) { + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + char *id; + int i; + const char *key, *value, *data; + size_t datalen; + + r = table_newglobalcursor(t->table, true, &cursor); + if (!RET_IS_OK(r)) + return r; + + result = RET_NOTHING; + + while (cursor_nextpair(t->table, cursor, + &key, &value, &data, &datalen)) { + r = parse_data(key, value, data, datalen, &pkg); + if (RET_WAS_ERROR(r)) { + (void)cursor_close(t->table, cursor); + return r; + } + id = calc_trackreferee(t->codename, pkg->sourcename, + pkg->sourceversion); + if (FAILEDTOALLOC(id)) { + trackedpackage_free(pkg); + (void)cursor_close(t->table, cursor); + return RET_ERROR_OOM; + } + for (i = 0 ; i < pkg->filekeys.count ; i++) { + const char *filekey = pkg->filekeys.values[i]; + r = references_increment(filekey, id); + RET_UPDATE(result, r); + } + free(id); + trackedpackage_free(pkg); + } + r = cursor_close(t->table, cursor); + RET_UPDATE(result, r); + return result; +} + +retvalue tracking_rereference(struct distribution *distribution) { + retvalue result, r; + trackingdb tracks; + + result = references_remove(distribution->codename); + if (distribution->tracking == dt_NONE) + return result; + r = tracking_initialize(&tracks, distribution, true); + RET_UPDATE(result, r); + if (!RET_IS_OK(r)) + return result; + r = tracking_recreatereferences(tracks); + RET_UPDATE(result, r); + r = tracking_done(tracks, distribution); + RET_ENDUPDATE(result, r); + return result; +} + +retvalue tracking_remove(trackingdb t, const char *sourcename, const char *version) { + retvalue result, r; + struct cursor *cursor; + const char *data; + size_t datalen; + char *id; + struct trackedpackage *pkg SETBUTNOTUSED(= NULL); + + r = table_newpairedcursor(t->table, sourcename, version, &cursor, + &data, &datalen); + if (!RET_IS_OK(r)) + return r; + + id = calc_trackreferee(t->codename, sourcename, version); + if (FAILEDTOALLOC(id)) { + (void)cursor_close(t->table, cursor); + return RET_ERROR_OOM; + } + + result = parse_data(sourcename, version, data, datalen, &pkg); + if (RET_IS_OK(r)) { + assert (pkg != NULL); + r = references_delete(id, &pkg->filekeys, NULL); + RET_UPDATE(result, r); + trackedpackage_free(pkg); + } else { + RET_UPDATE(result, r); + fprintf(stderr, +"Could not parse data, removing all references blindly...\n"); + r = references_remove(id); + RET_UPDATE(result, r); + } + free(id); + r = cursor_delete(t->table, cursor, sourcename, version); + if (RET_IS_OK(r)) + fprintf(stderr, "Removed %s_%s from %s.\n", + sourcename, version, t->codename); + RET_UPDATE(result, r); + r = cursor_close(t->table, cursor); + RET_ENDUPDATE(result, r); + return result; +} + +static void print(const char *codename, const struct trackedpackage *pkg){ + int i; + + printf("Distribution: %s\n", codename); + printf("Source: %s\n", pkg->sourcename); + printf("Version: %s\n", pkg->sourceversion); + printf("Files:\n"); + for (i = 0 ; i < pkg->filekeys.count ; i++) { + const char *filekey = pkg->filekeys.values[i]; + + printf(" %s %c %d\n", filekey, + pkg->filetypes[i], pkg->refcounts[i]); + } + (void)fputs("\n", stdout); +} + +retvalue tracking_printall(trackingdb t) { + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + const char *key, *value, *data; + size_t datalen; + + r = table_newglobalcursor(t->table, true, &cursor); + if (!RET_IS_OK(r)) + return r; + + result = RET_NOTHING; + + while (cursor_nextpair(t->table, cursor, + &key, &value, &data, &datalen)) { + r = parse_data(key, value, data, datalen, &pkg); + if (RET_IS_OK(r)) { + print(t->codename, pkg); + trackedpackage_free(pkg); + } + RET_UPDATE(result, r); + } + r = cursor_close(t->table, cursor); + RET_ENDUPDATE(result, r); + return result; +} + +retvalue tracking_foreach_ro(struct distribution *d, tracking_foreach_ro_action *action) { + trackingdb t; + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + const char *key, *value, *data; + size_t datalen; + + r = tracking_initialize(&t, d, true); + if (!RET_IS_OK(r)) + return r; + + r = table_newglobalcursor(t->table, true, &cursor); + if (!RET_IS_OK(r)) { + (void)tracking_done(t, d); + return r; + } + + result = RET_NOTHING; + while (cursor_nextpair(t->table, cursor, + &key, &value, &data, &datalen)) { + r = parse_data(key, value, data, datalen, &pkg); + if (RET_IS_OK(r)) { + r = action(d, pkg); + trackedpackage_free(pkg); + } + RET_UPDATE(result, r); + if (RET_WAS_ERROR(r)) + break; + } + r = cursor_close(t->table, cursor); + RET_ENDUPDATE(result, r); + r = tracking_done(t, d); + RET_ENDUPDATE(result, r); + return result; +} + +retvalue tracking_parse(struct distribution *d, struct configiterator *iter) { + enum trackingflags { tf_keep, tf_all, tf_minimal, + tf_includechanges, tf_includebyhand, tf_includelogs, + tf_includebuildinfos, + tf_keepsources, + tf_needsources, tf_embargoalls, + tf_COUNT /* must be last */ + }; + static const struct constant trackingflags[] = { + {"keep", tf_keep}, + {"all", tf_all}, + {"minimal", tf_minimal}, + {"includechanges", tf_includechanges}, + {"includebuildinfos", tf_includebuildinfos}, + {"includelogs", tf_includelogs}, + {"includebyhand", tf_includebyhand}, + {"keepsources", tf_keepsources}, + {"needsources", tf_needsources}, + {"embargoalls", tf_embargoalls}, + {NULL, -1} + }; + bool flags[tf_COUNT]; + retvalue r; + int modecount; + + assert (d->tracking == dt_NONE); + memset(flags, 0, sizeof(flags)); + r = config_getflags(iter, "Tracking", trackingflags, flags, + IGNORABLE(unknownfield), ""); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + modecount = flags[tf_keep]?1:0 + flags[tf_minimal]?1:0 + flags[tf_all]?1:0; + if (modecount > 1) { + fprintf(stderr, +"Error parsing config file %s, line %u:\n" +"Only one of 'keep','all' or 'minimal' can be in one Tracking header.\n", + config_filename(iter), config_line(iter)); + return RET_ERROR; + } + if (modecount < 1) { + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Tracking mode ('keep','all' or 'minimal') expected.\n", + config_filename(iter), config_line(iter), + config_column(iter)); + return RET_ERROR; + } + if (flags[tf_keep]) + d->tracking = dt_KEEP; + else if (flags[tf_minimal]) + d->tracking = dt_MINIMAL; + else + d->tracking = dt_ALL; + + d->trackingoptions.includechanges = flags[tf_includechanges]; + d->trackingoptions.includebyhand = flags[tf_includebyhand]; + d->trackingoptions.includebuildinfos = flags[tf_includebuildinfos]; + d->trackingoptions.includelogs = flags[tf_includelogs]; + d->trackingoptions.keepsources = flags[tf_keepsources]; + d->trackingoptions.needsources = flags[tf_needsources]; + if (flags[tf_needsources]) + fprintf(stderr, +"Warning parsing config file %s, line %u:\n" +"'needsources' ignored as not yet supported.\n", + config_filename(iter), config_line(iter)); + d->trackingoptions.embargoalls = flags[tf_embargoalls]; + if (flags[tf_embargoalls]) + fprintf(stderr, +"Warning parsing config file %s, line %u:\n" +"'embargoall' ignored as not yet supported.\n", + config_filename(iter), config_line(iter)); + return RET_OK; +} + +static retvalue trackingdata_remember(struct trackingdata *td, const char*name, const char*version) { + struct trackingdata_remember *r; + + r = NEW(struct trackingdata_remember); + if (FAILEDTOALLOC(r)) + return RET_ERROR_OOM; + r->name = strdup(name); + r->version = strdup(version); + if (FAILEDTOALLOC(r->name) || FAILEDTOALLOC(r->version)) { + free(r->name); + free(r->version); + free(r); + return RET_ERROR_OOM; + } + r->next = td->remembered; + td->remembered = r; + return RET_OK; +} + +retvalue trackingdata_summon(trackingdb tracks, const char *name, const char *version, struct trackingdata *data) { + struct trackedpackage *pkg; + retvalue r; + + r = tracking_getornew(tracks, name, version, &pkg); + assert (r != RET_NOTHING); + if (RET_IS_OK(r)) { + data->tracks = tracks; + data->pkg = pkg; + data->remembered = NULL; + return r; + } + return r; +} + +retvalue trackingdata_new(trackingdb tracks, struct trackingdata *data) { + + data->tracks = tracks; + data->pkg = NULL; + data->remembered = NULL; + return RET_OK; +} + +retvalue trackingdata_switch(struct trackingdata *data, const char *source, const char *version) { + retvalue r; + + if (data->pkg != NULL) { + if (strcmp(data->pkg->sourcename, source) == 0 && + strcmp(data->pkg->sourceversion, version) == 0) + return RET_OK; + r = tracking_saveonly(data->tracks, data->pkg); + if (RET_WAS_ERROR(r)) + return r; + r = trackingdata_remember(data, data->pkg->sourcename, + data->pkg->sourceversion); + strlist_done(&data->pkg->filekeys); + free(data->pkg->sourcename); + free(data->pkg->sourceversion); + free(data->pkg->refcounts); + free(data->pkg->filetypes); + free(data->pkg); + data->pkg = NULL; + if (RET_WAS_ERROR(r)) + return r; + } + r = tracking_getornew(data->tracks, source, version, &data->pkg); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + return RET_OK; +} + +retvalue trackingdata_insert(struct trackingdata *data, enum filetype filetype, const struct strlist *filekeys, /*@null@*/const struct package *old, /*@null@*/const struct strlist *oldfilekeys) { + retvalue result, r; + struct trackedpackage *pkg; + + assert (data != NULL); + assert(data->pkg != NULL); + result = trackedpackage_adddupfilekeys(data->tracks, data->pkg, + filetype, filekeys, true); + if (RET_WAS_ERROR(result)) { + return result; + } + if (old == NULL || old->source == NULL || old->sourceversion == NULL + || oldfilekeys == NULL) { + return RET_OK; + } + if (strcmp(old->sourceversion, data->pkg->sourceversion) == 0 && + strcmp(old->source, data->pkg->sourcename) == 0) { + /* Unlikely, but it may also be the same source version as + * the package we are currently adding */ + return trackedpackage_removefilekeys(data->tracks, data->pkg, + oldfilekeys); + } + r = tracking_get(data->tracks, old->source, old->sourceversion, &pkg); + if (RET_WAS_ERROR(r)) { + return r; + } + if (r == RET_NOTHING) { + fprintf(stderr, +"Could not find tracking data for %s_%s in %s to remove old files from it.\n", + old->source, old->sourceversion, + data->tracks->codename); + return result; + } + r = trackedpackage_removefilekeys(data->tracks, pkg, oldfilekeys); + RET_UPDATE(result, r); + r = tracking_save(data->tracks, pkg); + RET_UPDATE(result, r); + r = trackingdata_remember(data, old->source, old->sourceversion); + RET_UPDATE(result, r); + + return result; +} + +retvalue trackingdata_remove(struct trackingdata *data, const char* oldsource, const char*oldversion, const struct strlist *oldfilekeys) { + retvalue result, r; + struct trackedpackage *pkg; + + if (verbose >= 15) + fprintf(stderr, "trace: trackingdata_remove(oldsource=%s, oldversion=%s) called.\n", + oldsource, oldversion); + assert(oldsource != NULL && oldversion != NULL && oldfilekeys != NULL); + if (data->pkg != NULL && + strcmp(oldversion, data->pkg->sourceversion) == 0 && + strcmp(oldsource, data->pkg->sourcename) == 0) { + /* Unlikely, but it may also be the same source version as + * the package we are currently adding */ + return trackedpackage_removefilekeys(data->tracks, + data->pkg, oldfilekeys); + } + result = tracking_get(data->tracks, oldsource, oldversion, &pkg); + if (RET_WAS_ERROR(result)) { + return result; + } + if (result == RET_NOTHING) { + fprintf(stderr, +"Could not find tracking data for %s_%s in %s to remove old files from it.\n", + oldsource, oldversion, data->tracks->codename); + return RET_OK; + } + r = trackedpackage_removefilekeys(data->tracks, pkg, oldfilekeys); + RET_UPDATE(result, r); + r = tracking_save(data->tracks, pkg); + RET_UPDATE(result, r); + r = trackingdata_remember(data, oldsource, oldversion); + RET_UPDATE(result, r); + + return result; +} + +void trackingdata_done(struct trackingdata *d) { + trackedpackage_free(d->pkg); + d->pkg = NULL; + d->tracks = NULL; + while (d->remembered != NULL) { + struct trackingdata_remember *h = d->remembered; + d->remembered = h->next; + free(h->name); + free(h->version); + free(h); + } + +} + +static inline retvalue trackedpackage_removeall(trackingdb tracks, struct trackedpackage *pkg) { + retvalue result = RET_OK, r; + char *id; + +// printf("[trackedpackage_removeall %s %s %s]\n", tracks->codename, pkg->sourcename, pkg->sourceversion); + id = calc_trackreferee(tracks->codename, pkg->sourcename, + pkg->sourceversion); + if (FAILEDTOALLOC(id)) + return RET_ERROR_OOM; + + pkg->flags.deleted = true; + r = references_delete(id, &pkg->filekeys, NULL); + RET_UPDATE(result, r); + free(id); + strlist_done(&pkg->filekeys); + strlist_init(&pkg->filekeys); + free(pkg->refcounts); pkg->refcounts = NULL; + return result; +} + +static inline bool tracking_needed(trackingdb tracks, struct trackedpackage *pkg, int ofs) { + if (pkg->refcounts[ofs] > 0) + return true; + // TODO: add checks so that only .changes, .buildinfo and .log files + // belonging to still existing binaries are kept in minimal mode + if (pkg->filetypes[ofs] == ft_LOG && tracks->options.includelogs) + return true; + if (pkg->filetypes[ofs] == ft_BUILDINFO && tracks->options.includebuildinfos) + return true; + if (pkg->filetypes[ofs] == ft_CHANGES && tracks->options.includechanges) + return true; + if (pkg->filetypes[ofs] == ft_XTRA_DATA) + return true; + if (pkg->filetypes[ofs] == ft_SOURCE && tracks->options.keepsources) + return true; + return false; + +} + +static inline retvalue trackedpackage_removeunneeded(trackingdb tracks, struct trackedpackage *pkg) { + retvalue result = RET_OK, r; + char *id = NULL; + int i, j, count; + + assert(tracks->type == dt_MINIMAL); + + count = pkg->filekeys.count; + j = 0; + for (i = 0 ; i < count ; i++) { + if (tracking_needed(tracks, pkg, i)) { + if (j < i) { + pkg->filekeys.values[j] = pkg->filekeys.values[i]; + pkg->refcounts[j] = pkg->refcounts[i]; + pkg->filetypes[j] = pkg->filetypes[i]; + } + j++; + } else { + char *filekey = pkg->filekeys.values[i]; + pkg->filekeys.values[i] = NULL; + if (FAILEDTOALLOC(id)) { + id = calc_trackreferee(tracks->codename, + pkg->sourcename, pkg->sourceversion); + if (id == NULL) + result = RET_ERROR_OOM; + } + if (id != NULL) { +// printf("[trackedpackage_removeunneeded %s %s %s: '%s']\n", tracks->codename, pkg->sourcename, pkg->sourceversion, filekey); + r = references_decrement(filekey, id); + RET_UPDATE(result, r); + } + free(filekey); + } + } + assert (j <= pkg->filekeys.count); + pkg->filekeys.count = j; + free(id); + return result; +} + +static inline retvalue trackedpackage_tidy(trackingdb tracks, struct trackedpackage *pkg) { + int i; + + if (tracks->type == dt_KEEP) + return RET_OK; + /* look if anything clings to this package */ + for (i = 0 ; i < pkg->filekeys.count ; i++) { + if (pkg->refcounts[i] > 0) + break; + } + if (i >= pkg->filekeys.count) + + /* nothing left, remove it all */ + return trackedpackage_removeall(tracks, pkg); + + else if (tracks->type == dt_MINIMAL) + + /* remove all files no longer needed */ + return trackedpackage_removeunneeded(tracks, pkg); + else + return RET_OK; +} + +retvalue trackingdata_finish(trackingdb tracks, struct trackingdata *d) { + retvalue r; + assert (d->tracks == tracks); + if (d->pkg != NULL) { + r = trackedpackage_tidy(tracks, d->pkg); + r = tracking_save(tracks, d->pkg); + } else + r = RET_OK; + d->pkg = NULL; + /* call for all remembered actions... */ + while (d->remembered != NULL) { + struct trackingdata_remember *h = d->remembered; + struct trackedpackage *pkg; + d->remembered = h->next; + r = tracking_get(tracks, h->name, h->version, &pkg); + free(h->name); + free(h->version); + free(h); + if (RET_IS_OK(r)) { + r = trackedpackage_tidy(tracks, pkg); + r = tracking_save(tracks, pkg); + } + } + d->tracks = NULL; + return r; + +} + +retvalue tracking_tidyall(trackingdb t) { + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + const char *key, *value, *data; + size_t datalen; + + r = table_newglobalcursor(t->table, true, &cursor); + if (!RET_IS_OK(r)) + return r; + + result = RET_NOTHING; + + while (cursor_nextpair(t->table, cursor, + &key, &value, &data, &datalen)) { + r = parse_data(key, value, data, datalen, &pkg); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + r = trackedpackage_tidy(t, pkg); + RET_UPDATE(result, r); + r = tracking_saveatcursor(t, cursor, pkg); + RET_UPDATE(result, r); + trackedpackage_free(pkg); + } + r = cursor_close(t->table, cursor); + RET_UPDATE(result, r); + return result; +} + +retvalue tracking_reset(trackingdb t) { + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + const char *key, *value, *data; + char *newdata; + size_t datalen, newdatalen; + int i; + + r = table_newglobalcursor(t->table, true, &cursor); + if (!RET_IS_OK(r)) + return r; + + result = RET_NOTHING; + + while (cursor_nextpair(t->table, cursor, + &key, &value, &data, &datalen)) { + // this would perhaps be more stable if it just replaced + // everything within the string just received... + result = parse_data(key, value, data, datalen, &pkg); + if (RET_WAS_ERROR(result)) + break; + for (i = 0 ; i < pkg->filekeys.count ; i++) { + pkg->refcounts[i] = 0; + } + result = gen_data(pkg, &newdata, &newdatalen); + trackedpackage_free(pkg); + if (RET_IS_OK(result)) + result = cursor_replace(t->table, cursor, + newdata, newdatalen); + free(newdata); + if (RET_WAS_ERROR(result)) + break; + } + r = cursor_close(t->table, cursor); + RET_UPDATE(result, r); + return result; +} + +static retvalue tracking_foreachversion(trackingdb t, struct distribution *distribution, const char *sourcename, retvalue (action)(trackingdb t, struct trackedpackage *, struct distribution *)) { + struct cursor *cursor; + retvalue result, r; + struct trackedpackage *pkg; + const char *value, *data; + size_t datalen; + + r = table_newduplicatepairedcursor(t->table, sourcename, &cursor, + &value, &data, &datalen); + if (!RET_IS_OK(r)) + return r; + + result = RET_NOTHING; + + do { + r = parse_data(sourcename, value, data, datalen, &pkg); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + if (verbose > 10) + printf("Processing track of '%s' version '%s'\n", + pkg->sourcename, pkg->sourceversion); + r = action(t, pkg, distribution); + RET_UPDATE(result, r); + if (RET_WAS_ERROR(r)) { + (void)cursor_close(t->table, cursor); + trackedpackage_free(pkg); + return r; + } + r = trackedpackage_tidy(t, pkg); + RET_ENDUPDATE(result, r); + r = tracking_saveatcursor(t, cursor, pkg); + RET_UPDATE(result, r); + trackedpackage_free(pkg); + } while (cursor_nextpair(t->table, cursor, NULL, + &value, &data, &datalen)); + r = cursor_close(t->table, cursor); + RET_UPDATE(result, r); + return result; +} + + +static retvalue targetremovesourcepackage(trackingdb t, struct trackedpackage *pkg, struct distribution *distribution, struct target *target) { + size_t component_len, arch_len; + retvalue result, r; + int i; + const char *packagetype = atoms_packagetypes[target->packagetype]; + const char *architecture = atoms_architectures[target->architecture]; + const char *component = atoms_components[target->component]; + + if (verbose >= 15) + fprintf(stderr, "trace: targetremovesourcepackage(pkg={sourcename: %s, sourceversion: %s}, distribution.codename=%s, target.identifier=%s) called.\n", + pkg->sourcename, pkg->sourceversion, distribution->codename, target->identifier); + + result = RET_NOTHING; + + component_len = strlen(component); + arch_len = strlen(architecture); + for (i = 0 ; i < pkg->filekeys.count ; i++) { + const char *s, *s2, *basefilename, *filekey = pkg->filekeys.values[i]; + char *packagename, *packageversion; + struct package package; + struct strlist filekeys; + bool savedstaletracking; + + if (pkg->refcounts[i] <= 0) + continue; + if (strncmp(filekey, "pool/", 5) != 0) + continue; + if (strncmp(filekey+5, component, + component_len) != 0) + continue; + if (filekey[5+component_len] != '/') + continue; + /* check this file could actuall be in this target */ + if (pkg->filetypes[i] == ft_ALL_BINARY) { + if (target->packagetype == pt_dsc) + continue; + s = strrchr(filekey, '.'); + if (s == NULL) + continue; + if (strcmp(s+1, packagetype) != 0) + continue; + } else if (pkg->filetypes[i] == ft_SOURCE) { + if (target->packagetype != pt_dsc) + continue; + s = strrchr(filekey, '.'); + if (s == NULL) + continue; + if (strcmp(s+1, "dsc") != 0) + continue; + } else if (pkg->filetypes[i] == ft_ARCH_BINARY) { + if (target->packagetype == pt_dsc) + continue; + s = strrchr(filekey, '_'); + if (s == NULL) + continue; + s++; + if (strncmp(s, architecture, arch_len) != 0 + || s[arch_len] != '.' + || strcmp(s+arch_len+1, packagetype) != 0) + continue; + } else + continue; + /* get this package, check it has the right source and version, + * and if yes, remove... */ + basefilename = strrchr(filekey, '/'); + if (basefilename == NULL) + basefilename = filekey; + else + basefilename++; + s = strchr(basefilename, '_'); + packagename = strndup(basefilename, s - basefilename); + if (FAILEDTOALLOC(packagename)) + return RET_ERROR_OOM; + s2 = strrchr(s, '.'); + packageversion = strndup(s + 1, s2 - s - 1); + if (FAILEDTOALLOC(packageversion)) + return RET_ERROR_OOM; + r = package_get(target, packagename, packageversion, &package); + if (RET_WAS_ERROR(r)) { + free(packagename); + free(packageversion); + return r; + } + if (r == RET_NOTHING) { + if (pkg->filetypes[i] != ft_ALL_BINARY + && verbose >= -1) { + fprintf(stderr, +"Warning: tracking data might be inconsistent:\n" +"cannot find '%s=%s' in '%s', but '%s' should be there.\n", + packagename, packageversion, target->identifier, + filekey); + } + free(packagename); + free(packageversion); + continue; + } + // TODO: ugly + package.pkgname = packagename; + packagename = NULL; + free(packageversion); + + r = package_getsource(&package); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + package_done(&package); + return r; + } + if (strcmp(package.source, pkg->sourcename) != 0) { + if (pkg->filetypes[i] != ft_ALL_BINARY + && verbose >= -1) { + fprintf(stderr, +"Warning: tracking data might be inconsistent:\n" +"'%s' has '%s' of source '%s', but source '%s' contains '%s'.\n", + target->identifier, package.name, + package.source, pkg->sourcename, + filekey); + } + package_done(&package); + continue; + } + if (strcmp(package.sourceversion, pkg->sourceversion) != 0) { + if (pkg->filetypes[i] != ft_ALL_BINARY + && verbose >= -1) { + fprintf(stderr, +"Warning: tracking data might be inconsistent:\n" +"'%s' has '%s' of source version '%s', but version '%s' contains '%s'.\n", + target->identifier, package.name, + package.sourceversion, + pkg->sourceversion, + filekey); + } + package_done(&package); + continue; + } + r = target->getfilekeys(package.control, &filekeys); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + package_done(&package); + return r; + } + + /* we remove the tracking data outself, so this is not + * told to remove the tracking data, so it might mark things + * as stale, which we do not want.. */ + savedstaletracking = target->staletracking; + r = package_remove(&package, distribution->logger, NULL); + target->staletracking = savedstaletracking; + package_done(&package); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + strlist_done(&filekeys); + return r; + } + trackedpackage_removefilekeys(t, pkg, &filekeys); + strlist_done(&filekeys); + result = RET_OK; + } + return result; +} + +/* Try to remove all packages causing refcounts in this tracking record */ +static retvalue removesourcepackage(trackingdb t, struct trackedpackage *pkg, struct distribution *distribution) { + struct target *target; + retvalue result, r; + int i; + + if (verbose >= 15) + fprintf(stderr, "trace: removesourcepackage(pkg={sourcename: %s, sourceversion: %s}, distribution={codename: %s}) called.\n", + pkg->sourcename, pkg->sourceversion, distribution->codename); + + result = RET_NOTHING; + for (target = distribution->targets ; target != NULL ; + target = target->next) { + r = target_initpackagesdb(target, READWRITE); + RET_ENDUPDATE(result, r); + if (RET_IS_OK(r)) { + r = targetremovesourcepackage(t, pkg, + distribution, target); + RET_UPDATE(result, r); + RET_UPDATE(distribution->status, r); + r = target_closepackagesdb(target); + RET_ENDUPDATE(result, r); + RET_ENDUPDATE(distribution->status, r); + if (RET_WAS_ERROR(result)) + return result; + } + } + for (i = 0 ; i < pkg->filekeys.count ; i++) { + const char *filekey = pkg->filekeys.values[i]; + + if (pkg->refcounts[i] <= 0) + continue; + if (pkg->filetypes[i] != ft_ALL_BINARY && + pkg->filetypes[i] != ft_SOURCE && + pkg->filetypes[i] != ft_ARCH_BINARY) + continue; + fprintf(stderr, +"There was an inconsistency in the tracking data of '%s':\n" +"'%s' has refcount > 0, but was nowhere found.\n", + distribution->codename, + filekey); + pkg->refcounts[i] = 0; + } + return result; +} + +retvalue tracking_removepackages(trackingdb t, struct distribution *distribution, const char *sourcename, /*@null@*/const char *version) { + struct trackedpackage *pkg; + retvalue result, r; + + if (verbose >= 15) + fprintf(stderr, "trace: tracking_removepackages(distribution={codename: %s}, sourcename=%s, version=%s) called.\n", + distribution->codename, sourcename, version); + + if (version == NULL) + return tracking_foreachversion(t, distribution, + sourcename, removesourcepackage); + result = tracking_get(t, sourcename, version, &pkg); + if (RET_IS_OK(result)) { + result = removesourcepackage(t, pkg, distribution); + if (RET_IS_OK(result)) { + r = trackedpackage_tidy(t, pkg); + RET_ENDUPDATE(result, r); + r = tracking_save(t, pkg); + RET_ENDUPDATE(result, r); + } else + trackedpackage_free(pkg); + } + return result; +} + +static retvalue package_retrack(struct package *package, void *data) { + trackingdb tracks = data; + + return package->target->doretrack(package->name, + package->control, tracks); +} + +retvalue tracking_retrack(struct distribution *d, bool needsretrack) { + struct target *t; + trackingdb tracks; + retvalue r, rr; + + if (d->tracking == dt_NONE) + return RET_NOTHING; + + for (t = d->targets ; !needsretrack && t != NULL ; t = t->next) { + if (t->staletracking) + needsretrack = true; + } + if (!needsretrack) + return RET_NOTHING; + + if (verbose > 0) + printf("Retracking %s...\n", d->codename); + + r = tracking_initialize(&tracks, d, false); + if (!RET_IS_OK(r)) + return r; + /* first forget that any package is there*/ + r = tracking_reset(tracks); + if (!RET_WAS_ERROR(r)) { + /* add back information about actually used files */ + r = package_foreach(d, + atom_unknown, atom_unknown, atom_unknown, + package_retrack, NULL, tracks); + } + if (RET_IS_OK(r)) { + for (t = d->targets ; t != NULL ; t = t->next) { + t->staletracking = false; + } + } + if (!RET_WAS_ERROR(r)) { + /* now remove everything no longer needed */ + r = tracking_tidyall(tracks); + } + rr = tracking_done(tracks, d); + RET_ENDUPDATE(r, rr); + return r; +} |