diff options
Diffstat (limited to '')
-rw-r--r-- | remoterepository.c | 2103 |
1 files changed, 2103 insertions, 0 deletions
diff --git a/remoterepository.c b/remoterepository.c new file mode 100644 index 0000000..ec1d726 --- /dev/null +++ b/remoterepository.c @@ -0,0 +1,2103 @@ +/* This file is part of "reprepro" + * Copyright (C) 2003,2004,2005,2007,2008,2009,2012 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> + +#include "globals.h" +#include "error.h" +#include "ignore.h" +#include "filecntl.h" +#include "checksums.h" +#include "mprintf.h" +#include "dirs.h" +#include "chunks.h" +#include "names.h" +#include "aptmethod.h" +#include "signature.h" +#include "readtextfile.h" +#include "uncompression.h" +#include "diffindex.h" +#include "rredpatch.h" +#include "remoterepository.h" + +/* This is code to handle lists from remote repositories. + Those are stored in the lists/ (or --listdir) directory + and needs some maintaince: + + - cleaning (unneeded) lists from that directory + - deciding what to download from a remote repository + (needs knowledge what is there and what is there) + - in the future: implement ed to use remote .diffs +*/ + +struct remote_repository { + struct remote_repository *next, *prev; + + /* repository is determined by pattern name currently. + * That might change if there is some safe way to combine + * some. (note that method options might make equally looking + * repositories different ones, so that is hard to decide). + * + * This is possible as pattern is not modifyable in options + * or method by the using distribution. + */ + const char *name; + const char *method; + const char *fallback; + const struct strlist *config; + + struct aptmethod *download; + + struct remote_distribution *distributions; +}; +static struct remote_repository *repositories = NULL; + +struct remote_distribution { + struct remote_distribution *next; + + /* repository and suite uniquely identify it, + as the only thing the distribution can change is the suite. + Currently most of the other fields would also fit in the + remote_repository structure, but I plan to add new patters + allowing this by distribution... + */ + struct remote_repository *repository; + char *suite; + + /* flat repository */ + bool flat; bool flatnonflatwarned; + char *suite_base_dir; + + /* if true, do not download or check Release file */ + bool ignorerelease; + /* hashes to ignore */ + bool ignorehashes[cs_hashCOUNT]; + + /* linked list of key descriptions to check against, each must match */ + struct signature_requirement *verify; + + /* local copy of InRelease, Release and Release.gpg file, + * only set if available */ + char *inreleasefile; + char *releasefile; + char *releasegpgfile; + const char *usedreleasefile; + + /* filenames and checksums from the Release file */ + struct checksumsarray remotefiles; + + /* the index files we need */ + struct remote_index *indices; + + /* InRelease failed or requested not to be used */ + bool noinrelease; +}; + +struct remote_index { + /* next index in remote distribution */ + struct remote_index *next; + + struct remote_distribution *from; + + /* what to download? .gz better than .bz2? and so on */ + struct encoding_preferences downloadas; + + /* remote filename as to be found in Release file*/ + char *filename_in_release; + + /* the name without suffix in the lists/ dir */ + char *cachefilename; + /* the basename of the above */ + const char *cachebasename; + + /* index in checksums for the different types, -1 = not avail */ + int ofs[c_COUNT], diff_ofs; + + /* index in requested download methods so we can continue later */ + int lasttriedencoding; + /* the compression to be tried currently */ + enum compression compression; + + /* the old uncompressed file, so that it is only deleted + * when needed, to avoid losing it for a patch run */ + /*@dependant@*/struct cachedlistfile *olduncompressed; + struct checksums *oldchecksums; + + /* if using pdiffs, the content of the Packages.diff/Index: */ + struct diffindex *diffindex; + /* the last patch queued to be applied */ + char *patchfilename; + /*@dependant@*/const struct diffindex_patch *selectedpatch; + bool deletecompressedpatch; + + bool queued; + bool needed; + bool got; +}; + +#define MAXPARTS 5 +struct cachedlistfile { + struct cachedlistfile *next; + const char *basefilename; + unsigned int partcount; + const char *parts[MAXPARTS]; + /* might be used by some rule */ + bool needed, deleted; + char fullfilename[]; +}; + + +static void remote_index_free(/*@only@*/struct remote_index *i) { + if (i == NULL) + return; + free(i->cachefilename); + free(i->patchfilename); + free(i->filename_in_release); + diffindex_free(i->diffindex); + checksums_free(i->oldchecksums); + free(i); +} + +static void remote_distribution_free(/*@only@*/struct remote_distribution *d) { + if (d == NULL) + return; + free(d->suite); + signature_requirements_free(d->verify); + free(d->inreleasefile); + free(d->releasefile); + free(d->releasegpgfile); + free(d->suite_base_dir); + checksumsarray_done(&d->remotefiles); + while (d->indices != NULL) { + struct remote_index *h = d->indices; + d->indices = h->next; + remote_index_free(h); + } + free(d); +} + +void remote_repository_free(struct remote_repository *remote) { + if (remote == NULL) + return; + while (remote->distributions != NULL) { + struct remote_distribution *h = remote->distributions; + remote->distributions = h->next; + remote_distribution_free(h); + } + if (remote->next != NULL) + remote->next->prev = remote->prev; + if (remote->prev != NULL) + remote->prev->next = remote->next; + free(remote); + return; +} + +void cachedlistfile_freelist(struct cachedlistfile *c) { + while (c != NULL) { + struct cachedlistfile *n = c->next; + free(c); + c = n; + } +} + +void cachedlistfile_deleteunneeded(const struct cachedlistfile *c) { + for (; c != NULL ; c = c->next) { + if (c->needed) + continue; + if (verbose >= 0) + printf("deleting %s\n", c->fullfilename); + deletefile(c->fullfilename); + } +} + +static /*@null@*/ struct cachedlistfile *cachedlistfile_new(const char *basefilename, size_t len, size_t listdirlen) { + struct cachedlistfile *c; + size_t l; + char *p; + char ch; + + c = malloc(sizeof(struct cachedlistfile) + listdirlen + 2*len + 3); + if (FAILEDTOALLOC(c)) + return NULL; + c->next = NULL; + c->needed = false; + c->deleted = false; + p = c->fullfilename; + assert ((size_t)(p - (char*)c) <= sizeof(struct cachedlistfile)); + memcpy(p, global.listdir, listdirlen); + p += listdirlen; + *(p++) = '/'; + assert ((size_t)(p - c->fullfilename) == listdirlen + 1); + c->basefilename = p; + memcpy(p, basefilename, len); p += len; + *(p++) = '\0'; + assert ((size_t)(p - c->fullfilename) == listdirlen + len + 2); + + c->parts[0] = p; + c->partcount = 1; + l = len; + while (l-- > 0 && (ch = *(basefilename++)) != '\0') { + if (ch == '_') { + *(p++) = '\0'; + if (c->partcount < MAXPARTS) + c->parts[c->partcount] = p; + c->partcount++; + } else if (ch == '%') { + char first, second; + + if (len <= 1) { + c->partcount = 0; + return c; + } + first = *(basefilename++); + second = *(basefilename++); + if (first >= '0' && first <= '9') + *p = (first - '0') << 4; + else if (first >= 'a' && first <= 'f') + *p = (first - 'a' + 10) << 4; + else { + c->partcount = 0; + return c; + } + if (second >= '0' && second <= '9') + *p |= (second - '0'); + else if (second >= 'a' && second <= 'f') + *p |= (second - 'a' + 10); + else { + c->partcount = 0; + return c; + } + p++; + } else + *(p++) = ch; + } + *(p++) = '\0'; + assert ((size_t)(p - c->fullfilename) <= listdirlen + 2*len + 3); + return c; +} + +retvalue cachedlists_scandir(/*@out@*/struct cachedlistfile **cachedfiles_p) { + struct cachedlistfile *cachedfiles = NULL, **next_p; + struct dirent *r; + size_t listdirlen = strlen(global.listdir); + DIR *dir; + + // TODO: check if it is always created before... + dir = opendir(global.listdir); + if (dir == NULL) { + int e = errno; + fprintf(stderr, +"Error %d opening directory '%s': %s!\n", + e, global.listdir, strerror(e)); + return RET_ERRNO(e); + } + next_p = &cachedfiles; + while (true) { + size_t namelen; + int e; + + errno = 0; + r = readdir(dir); + if (r == NULL) { + e = errno; + if (e == 0) + break; + /* this should not happen... */ + e = errno; + fprintf(stderr, "Error %d reading dir '%s': %s!\n", + e, global.listdir, strerror(e)); + (void)closedir(dir); + cachedlistfile_freelist(cachedfiles); + return RET_ERRNO(e); + } + namelen = _D_EXACT_NAMLEN(r); + if (namelen == 1 && r->d_name[0] == '.') + continue; + if (namelen == 2 && r->d_name[0] == '.' && r->d_name[1] == '.') + continue; + *next_p = cachedlistfile_new(r->d_name, namelen, listdirlen); + if (FAILEDTOALLOC(*next_p)) { + (void)closedir(dir); + cachedlistfile_freelist(cachedfiles); + return RET_ERROR_OOM; + } + next_p = &(*next_p)->next; + } + if (closedir(dir) != 0) { + int e = errno; + fprintf(stderr, "Error %d closing directory '%s': %s!\n", + e, global.listdir, strerror(e)); + cachedlistfile_freelist(cachedfiles); + return RET_ERRNO(e); + } + *cachedfiles_p = cachedfiles; + return RET_OK; +} + +static retvalue cachedlistfile_delete(struct cachedlistfile *old) { + int e; + if (old->deleted) + return RET_OK; + e = deletefile(old->fullfilename); + if (e != 0) + return RET_ERRNO(e); + old->deleted = true; + return RET_OK; +} + +struct remote_repository *remote_repository_prepare(const char *name, const char *method, const char *fallback, const struct strlist *config) { + struct remote_repository *n; + + /* calling code ensures no two with the same name are created, + * so just create it... */ + + n = zNEW(struct remote_repository); + if (FAILEDTOALLOC(n)) + return NULL; + n->name = name; + n->method = method; + n->fallback = fallback; + n->config = config; + + n->next = repositories; + if (n->next != NULL) + n->next->prev = n; + repositories = n; + + return n; +} + +/* This escaping is quite harsh, but so nothing bad can happen... */ +static inline size_t escapedlen(const char *p) { + size_t l = 0; + if (*p == '-') { + l = 3; + p++; + } + while (*p != '\0') { + if ((*p < 'A' || *p > 'Z') && (*p < 'a' || *p > 'z') && + (*p < '0' || *p > '9') && *p != '-') + l +=3; + else + l++; + p++; + } + return l; +} + +static inline char *escapedcopy(char *dest, const char *orig) { + static char hex[16] = "0123456789ABCDEF"; + if (*orig == '-') { + orig++; + *dest = '%'; dest++; + *dest = '2'; dest++; + *dest = 'D'; dest++; + } + while (*orig != '\0') { + if ((*orig < 'A' || *orig > 'Z') + && (*orig < 'a' || *orig > 'z') + && (*orig < '0' || *orig > '9') + && *orig != '-') { + *dest = '%'; dest++; + *dest = hex[(*orig >> 4)& 0xF ]; dest++; + *dest = hex[*orig & 0xF ]; dest++; + } else { + *dest = *orig; + dest++; + } + orig++; + } + return dest; +} + +char *genlistsfilename(const char *type, unsigned int count, ...) { + const char *fields[count]; + unsigned int i; + size_t listdir_len, type_len, len; + char *result, *p; + va_list ap; + + len = 0; + va_start(ap, count); + for (i = 0 ; i < count ; i++) { + fields[i] = va_arg(ap, const char*); + assert (fields[i] != NULL); + len += escapedlen(fields[i]) + 1; + } + /* check sentinel */ + assert (va_arg(ap, const char*) == NULL); + va_end(ap); + listdir_len = strlen(global.listdir); + if (type != NULL) + type_len = strlen(type); + else + type_len = 0; + + result = malloc(listdir_len + type_len + len + 2); + if (FAILEDTOALLOC(result)) + return NULL; + memcpy(result, global.listdir, listdir_len); + p = result + listdir_len; + *(p++) = '/'; + for (i = 0 ; i < count ; i++) { + p = escapedcopy(p, fields[i]); + *(p++) = '_'; + } + assert ((size_t)(p - result) == listdir_len + len + 1); + if (type != NULL) + memcpy(p, type, type_len + 1); + else + *(--p) = '\0'; + return result; +} + +void cachedlistfile_need(struct cachedlistfile *list, const char *type, unsigned int count, ...) { + struct cachedlistfile *file; + const char *fields[count]; + unsigned int i; + va_list ap; + + va_start(ap, count); + for (i = 0 ; i < count ; i++) { + fields[i] = va_arg(ap, const char*); + assert (fields[i] != NULL); + } + /* check sentinel */ + assert (va_arg(ap, const char*) == NULL); + va_end(ap); + + for (file = list ; file != NULL ; file = file->next) { + if (file->partcount != count + 1) + continue; + i = 0; + while (i < count && strcmp(file->parts[i], fields[i]) == 0) + i++; + if (i < count) + continue; + if (strcmp(type, file->parts[i]) != 0) + continue; + file->needed = true; + } +} + +retvalue remote_distribution_prepare(struct remote_repository *repository, const char *suite, bool ignorerelease, bool getinrelease, const char *verifyrelease, bool flat, bool *ignorehashes, struct remote_distribution **out_p) { + struct remote_distribution *n, **last; + enum checksumtype cs; + + for (last = &repository->distributions ; (n = *last) != NULL + ; last = &n->next) { + if (strcmp(n->suite, suite) != 0) + continue; + if (n->flat != flat) { + if (verbose >= 0 && !n->flatnonflatwarned && + !IGNORABLE(flatandnonflat)) + fprintf(stderr, +"Warning: From the same remote repository '%s', distribution '%s'\n" +"is requested both flat and non-flat. While this is possible\n" +"(having %s/dists/%s and %s/%s), it is unlikely.\n" +"To no longer see this message, use --ignore=flatandnonflat.\n", + repository->method, suite, + repository->method, suite, + repository->method, suite); + n->flatnonflatwarned = true; + continue; + } + break; + } + + if (*last != NULL) { + n = *last; + assert (n->flat == flat); + + if ((n->ignorerelease && !ignorerelease) || + (!n->ignorerelease && ignorerelease)) { + // TODO a hint which two are at fault would be nice, + // but how to get the information... + if (verbose >= 0) + fprintf(stderr, +"Warning: I was told to both ignore Release files for Suite '%s'\n" +"from remote repository '%s' and to not ignore it. Going to not ignore!\n", + suite, repository->name); + n->ignorerelease = false; + } + if ((n->noinrelease && getinrelease) || + (!n->noinrelease && !getinrelease)) { + if (verbose >= 0) + fprintf(stderr, +"Warning: Conflicting GetInRelease values for Suite '%s'\n" +"from remote repository '%s'. Resolving to get InRelease files!\n", + suite, repository->name); + n->noinrelease = false; + } + for (cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++) { + if ((n->ignorehashes[cs] && !ignorehashes[cs]) || + (!n->ignorehashes[cs] && ignorehashes[cs])) { + // TODO dito + if (verbose >= 0) + fprintf(stderr, +"Warning: I was told to both ignore '%s' for Suite '%s'\n" +"from remote repository '%s' and to not ignore it. Going to not ignore!\n", + suite, + release_checksum_names[cs], + repository->name); + n->ignorehashes[cs] = false; + } + } + if (verifyrelease != NULL) { + retvalue r; + + r = signature_requirement_add(&n->verify, + verifyrelease); + if (RET_WAS_ERROR(r)) + return r; + } + *out_p = n; + return RET_OK; + } + + n = zNEW(struct remote_distribution); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + n->repository = repository; + n->suite = strdup(suite); + n->ignorerelease = ignorerelease; + n->noinrelease = !getinrelease; + if (verifyrelease != NULL) { + retvalue r; + + r = signature_requirement_add(&n->verify, verifyrelease); + if (RET_WAS_ERROR(r)) { + remote_distribution_free(n); + return r; + } + } + memcpy(n->ignorehashes, ignorehashes, sizeof(bool [cs_hashCOUNT])); + n->flat = flat; + if (flat) + n->suite_base_dir = strdup(suite); + else + n->suite_base_dir = calc_dirconcat("dists", suite); + if (FAILEDTOALLOC(n->suite) || + FAILEDTOALLOC(n->suite_base_dir)) { + remote_distribution_free(n); + return RET_ERROR_OOM; + } + /* ignorerelease can be unset later, so always calculate the filename */ + if (flat) + n->inreleasefile = genlistsfilename("InRelease", 3, + repository->name, suite, "flat", + ENDOFARGUMENTS); + else + n->inreleasefile = genlistsfilename("InRelease", 2, + repository->name, suite, ENDOFARGUMENTS); + if (FAILEDTOALLOC(n->inreleasefile)) { + remote_distribution_free(n); + return RET_ERROR_OOM; + } + if (flat) + n->releasefile = genlistsfilename("Release", 3, + repository->name, suite, "flat", + ENDOFARGUMENTS); + else + n->releasefile = genlistsfilename("Release", 2, + repository->name, suite, ENDOFARGUMENTS); + if (FAILEDTOALLOC(n->releasefile)) { + remote_distribution_free(n); + return RET_ERROR_OOM; + } + n->releasegpgfile = calc_addsuffix(n->releasefile, "gpg"); + if (FAILEDTOALLOC(n->releasefile)) { + remote_distribution_free(n); + return RET_ERROR_OOM; + } + *last = n; + *out_p = n; + return RET_OK; +} + +static retvalue copytoplace(const char *gotfilename, const char *wantedfilename, const char *method, struct checksums **checksums_p) { + retvalue r; + struct checksums *checksums = NULL; + + /* if the file is somewhere else, copy it: */ + if (strcmp(gotfilename, wantedfilename) != 0) { + /* never link index files, but copy them */ + if (verbose > 1) + fprintf(stderr, +"Copy file '%s' to '%s'...\n", gotfilename, wantedfilename); + r = checksums_copyfile(wantedfilename, gotfilename, false, + &checksums); + if (r == RET_ERROR_EXIST) { + fprintf(stderr, +"Unexpected error: '%s' exists while it should not!\n", + wantedfilename); + } + if (r == RET_NOTHING) { + fprintf(stderr, +"Cannot open '%s', obtained from '%s' method.\n", + gotfilename, method); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) { + return r; + } + } + if (checksums_p == NULL) + checksums_free(checksums); + else + *checksums_p = checksums; + return RET_OK; +} + +static retvalue enqueue_old_release_files(struct remote_distribution *d); + +/* handle a downloaded Release or Release.gpg file: + * no checksums to test, nothing to trigger, as they have to be all + * read at once to decide what is new and what actually needs downloading */ +static retvalue release_callback(enum queue_action action, void *privdata, void *privdata2, UNUSED(const char *uri), const char *gotfilename, const char *wantedfilename, UNUSED(/*@null@*/const struct checksums *checksums), const char *methodname) { + struct remote_distribution *d = privdata; + retvalue r; + + /* if the InRelease file cannot be got, + * try Release (and Release.gpg if checking) instead */ + if (action == qa_error && privdata2 == d->inreleasefile) { + assert (!d->noinrelease); + + return enqueue_old_release_files(d); + } + + if (action != qa_got) + return RET_ERROR; + + r = copytoplace(gotfilename, wantedfilename, methodname, NULL); + if (RET_WAS_ERROR(r)) + return r; + return r; +} + +static retvalue enqueue_old_release_files(struct remote_distribution *d) { + retvalue r; + + d->noinrelease = true; + r = aptmethod_enqueueindex(d->repository->download, + d->suite_base_dir, "Release", "", + d->releasefile, "", + release_callback, d, NULL); + if (RET_WAS_ERROR(r)) + return r; + if (d->verify != NULL) { + r = aptmethod_enqueueindex(d->repository->download, + d->suite_base_dir, "Release", ".gpg", + d->releasegpgfile, "", + release_callback, d, NULL); + if (RET_WAS_ERROR(r)) + return r; + } + return RET_OK; +} + +static retvalue remote_distribution_enqueuemetalists(struct remote_distribution *d) { + struct remote_repository *repository = d->repository; + + assert (repository->download != NULL); + + if (d->ignorerelease) + return RET_NOTHING; + + (void)unlink(d->inreleasefile); + (void)unlink(d->releasefile); + if (d->verify != NULL) { + (void)unlink(d->releasegpgfile); + } + + if (d->noinrelease) + return enqueue_old_release_files(d); + else + return aptmethod_enqueueindex(repository->download, + d->suite_base_dir, "InRelease", "", d->inreleasefile, + "", release_callback, d, d->inreleasefile); +} + +retvalue remote_startup(struct aptmethodrun *run) { + struct remote_repository *rr; + retvalue r; + + if (interrupted()) + return RET_ERROR_INTERRUPTED; + + for (rr = repositories ; rr != NULL ; rr = rr->next) { + assert (rr->download == NULL); + + r = aptmethod_newmethod(run, + rr->method, rr->fallback, + rr->config, &rr->download); + if (RET_WAS_ERROR(r)) + return r; + } + return RET_OK; +} + +static void find_index(const struct strlist *files, struct remote_index *ri) { + const char *filename = ri->filename_in_release; + size_t len = strlen(filename); + int i; + enum compression c; + + for (i = 0 ; i < files->count ; i++) { + const char *value = files->values[i]; + + if (strncmp(value, filename, len) != 0) + continue; + + value += len; + + if (*value == '\0') { + ri->ofs[c_none] = i; + continue; + } + if (*value != '.') + continue; + if (strcmp(value, ".diff/Index") == 0) { + ri->diff_ofs = i; + continue; + } + + for (c = 0 ; c < c_COUNT ; c++) + if (strcmp(value, uncompression_suffix[c]) == 0) { + ri->ofs[c] = i; + break; + } + } +} + +/* get a strlist with the md5sums of a Release-file */ +static inline retvalue release_getchecksums(const char *releasefile, const char *chunk, const bool ignorehash[cs_hashCOUNT], struct checksumsarray *out) { + retvalue r; + struct strlist files[cs_hashCOUNT]; + enum checksumtype cs; + bool foundanything = false; + + for (cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++) { + if (ignorehash[cs]) { + strlist_init(&files[cs]); + continue; + } + assert (release_checksum_names[cs] != NULL); + r = chunk_getextralinelist(chunk, release_checksum_names[cs], + &files[cs]); + if (RET_WAS_ERROR(r)) { + while (cs-- > cs_md5sum) { + strlist_done(&files[cs]); + } + return r; + } else if (r == RET_NOTHING) + strlist_init(&files[cs]); + else + foundanything = true; + } + + if (!foundanything) { + fprintf(stderr, "Missing checksums in Release file '%s'!\n", + releasefile); + return RET_ERROR; + } + + r = checksumsarray_parse(out, files, releasefile); + for (cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++) { + strlist_done(&files[cs]); + } + return r; +} + +static retvalue process_remoterelease(struct remote_distribution *rd) { + struct remote_repository *rr = rd->repository; + struct remote_index *ri; + retvalue r; + char *releasedata; + size_t releaselen; + + if (!rd->noinrelease) { + r = signature_check_inline(rd->verify, + rd->inreleasefile, &releasedata); + assert (r != RET_NOTHING); + if (r == RET_NOTHING) + r = RET_ERROR_BADSIG; + if (r == RET_ERROR_BADSIG) { + fprintf(stderr, +"Error: Not enough signatures found for remote repository %s (%s %s)!\n", + rr->name, rr->method, rd->suite); + r = RET_ERROR_BADSIG; + } + if (RET_WAS_ERROR(r)) + return r; + rd->usedreleasefile = rd->inreleasefile; + } else { + r = readtextfile(rd->releasefile, rd->releasefile, + &releasedata, &releaselen); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + rd->usedreleasefile = rd->releasefile; + + if (rd->verify != NULL) { + r = signature_check(rd->verify, + rd->releasegpgfile, rd->releasefile, + releasedata, releaselen); + assert (r != RET_NOTHING); + if (r == RET_NOTHING) + r = RET_ERROR_BADSIG; + if (r == RET_ERROR_BADSIG) { + fprintf(stderr, +"Error: Not enough signatures found for remote repository %s (%s %s)!\n", + rr->name, rr->method, rd->suite); + r = RET_ERROR_BADSIG; + } + if (RET_WAS_ERROR(r)) { + free(releasedata); + return r; + } + } + } + r = release_getchecksums(rd->usedreleasefile, releasedata, + rd->ignorehashes, &rd->remotefiles); + free(releasedata); + if (RET_WAS_ERROR(r)) + return r; + + /* Check for our files in there */ + for (ri = rd->indices ; ri != NULL ; ri = ri->next) { + find_index(&rd->remotefiles.names, ri); + } + // TODO: move checking if not exists at all to here? + return RET_OK; +} + +retvalue remote_preparemetalists(struct aptmethodrun *run, bool nodownload) { + struct remote_repository *rr; + struct remote_distribution *rd; + retvalue r; + + if (!nodownload) { + for (rr = repositories ; rr != NULL ; rr = rr->next) { + for (rd = rr->distributions ; rd != NULL ; + rd = rd->next) { + r = remote_distribution_enqueuemetalists(rd); + if (RET_WAS_ERROR(r)) + return r; + } + } + r = aptmethod_download(run); + if (RET_WAS_ERROR(r)) + return r; + } + for (rr = repositories ; rr != NULL ; rr = rr->next) { + for (rd = rr->distributions ; rd != NULL ; rd = rd->next) { + if (!rd->ignorerelease) { + if (nodownload) + if (!isregularfile(rd->inreleasefile)) + rd->noinrelease = true; + r = process_remoterelease(rd); + if (RET_WAS_ERROR(r)) + return r; + } + } + } + return RET_OK; +} + +bool remote_index_isnew(/*@null@*/const struct remote_index *ri, struct donefile *done) { + const char *basefilename; + struct checksums *checksums; + bool hashes_missing, improves; + + /* files without uncompressed checksum cannot be tested */ + if (ri->ofs[c_none] < 0) + return true; + /* if not there or the wrong files comes next, then something + * has changed and we better reload everything */ + if (!donefile_nextindex(done, &basefilename, &checksums)) + return true; + if (strcmp(basefilename, ri->cachebasename) != 0) { + checksums_free(checksums); + return true; + } + /* otherwise check if the file checksums match */ + if (!checksums_check(checksums, + ri->from->remotefiles.checksums[ri->ofs[c_none]], + &hashes_missing)) { + checksums_free(checksums); + return true; + } + if (hashes_missing) { + /* if Release has checksums we do not yet know about, + * process it to make sure those match as well */ + checksums_free(checksums); + return true; + } + if (!checksums_check(ri->from->remotefiles.checksums[ri->ofs[c_none]], + checksums, &improves)) { + /* this should not happen, but ... */ + checksums_free(checksums); + return true; + } + if (improves) { + /* assume this is our file and add the other hashes so they + * will show up in the file again the next time. + * This is a bit unelegant in mixing stuff, but otherwise this + * will cause redownloading when remote adds more hashes. + * The only downside of mixing can reject files that have the + * same recorded hashes as a previously processed files. + * But that is quite inlikely unless on attack, so getting some + * hint in that case cannot harm.*/ + (void)checksums_combine(&ri->from->remotefiles.checksums[ + ri->ofs[c_none]], checksums, NULL); + } + checksums_free(checksums); + return false; +} + +static inline void remote_index_oldfiles(struct remote_index *ri, /*@null@*/struct cachedlistfile *oldfiles, /*@out@*/struct cachedlistfile *old[c_COUNT]) { + struct cachedlistfile *o; + size_t l; + enum compression c; + + for (c = 0 ; c < c_COUNT ; c++) + old[c] = NULL; + + l = strlen(ri->cachebasename); + for (o = oldfiles ; o != NULL ; o = o->next) { + if (o->deleted) + continue; + if (strncmp(o->basefilename, ri->cachebasename, l) != 0) + continue; + for (c = 0 ; c < c_COUNT ; c++) + if (strcmp(o->basefilename + l, + uncompression_suffix[c]) == 0) { + old[c] = o; + o->needed = true; + break; + } + if (strcmp(o->basefilename + l, ".diffindex") == 0) + (void)cachedlistfile_delete(o); + if (strncmp(o->basefilename + l, ".diff-", 6) == 0) + (void)cachedlistfile_delete(o); + } +} + +static inline void remote_index_delete_oldfiles(struct remote_index *ri, /*@null@*/struct cachedlistfile *oldfiles) { + struct cachedlistfile *o; + size_t l; + + l = strlen(ri->cachebasename); + for (o = oldfiles ; o != NULL ; o = o->next) { + if (o->deleted) + continue; + if (strncmp(o->basefilename, ri->cachebasename, l) != 0) + continue; + (void)cachedlistfile_delete(o); + } +} + +static queue_callback index_callback; +static queue_callback diff_callback; + +static retvalue queue_next_without_release(struct remote_distribution *rd, struct remote_index *ri) { + const struct encoding_preferences *downloadas; + static const struct encoding_preferences defaultdownloadas = { + .count = 5, + .requested = { + { .diff = false, .force = false, .compression = c_gzip }, + { .diff = false, .force = false, .compression = c_bzip2 }, + { .diff = false, .force = false, .compression = c_none }, + { .diff = false, .force = false, .compression = c_lzma }, + { .diff = false, .force = false, .compression = c_xz } + } + }; + int e; + + if (ri->downloadas.count == 0) + downloadas = &defaultdownloadas; + else + downloadas = &ri->downloadas; + + for (e = ri->lasttriedencoding + 1 ; e < downloadas->count ; e++) { + enum compression c = downloadas->requested[e].compression; + + if (downloadas->requested[e].diff) + continue; + if (uncompression_supported(c)) { + ri->lasttriedencoding = e; + ri->compression = c; + return aptmethod_enqueueindex(rd->repository->download, + rd->suite_base_dir, + ri->filename_in_release, + uncompression_suffix[c], + ri->cachefilename, + uncompression_suffix[c], + index_callback, ri, NULL); + } + } + if (ri->lasttriedencoding < 0) + fprintf(stderr, +"ERROR: no supported compressions in DownloadListsAs for '%s' by '%s'!\n", + rd->suite, rd->repository->method); + ri->lasttriedencoding = e; + return RET_ERROR; +} + +static inline retvalue find_requested_encoding(struct remote_index *ri, const char *releasefile) { + int e; + enum compression c, stopat, + /* the most-preferred requested but unsupported */ + unsupported = c_COUNT, + /* the best unrequested but supported */ + unrequested = c_COUNT; + + if (ri->downloadas.count > 0) { + bool found = false; + for (e = ri->lasttriedencoding + 1 ; + e < ri->downloadas.count ; + e++) { + struct compression_preference req; + + req = ri->downloadas.requested[e]; + + if (req.diff) { + if (ri->olduncompressed == NULL) + continue; + assert (ri->ofs[c_none] >= 0); + if (!req.force && ri->diff_ofs < 0) + continue; + ri->compression = c_COUNT; + ri->lasttriedencoding = e; + return RET_OK; + } + if (ri->ofs[req.compression] < 0 && + (!req.force || ri->ofs[c_none] < 0)) + continue; + if (uncompression_supported(req.compression)) { + ri->compression = req.compression; + ri->lasttriedencoding = e; + return RET_OK; + } else if (unsupported == c_COUNT) + unsupported = req.compression; + } + if (ri->lasttriedencoding > -1) { + /* we already tried something, and nothing else + * is available, so give up */ + ri->lasttriedencoding = e; + return RET_ERROR; + } + + /* nothing that is both requested by the user and supported + * and listed in the Release file found, check what is there + * to get a meaningful error message */ + + for (c = 0 ; c < c_COUNT ; c++) { + if (ri->ofs[c] < 0) + continue; + found = true; + if (uncompression_supported(c)) + unrequested = c; + } + + if (!found) { + // TODO: might be nice to check for not-yet-even + // known about compressions and say they are not + // yet know yet instead then here... + fprintf(stderr, +"Could not find '%s' within '%s'\n", + ri->filename_in_release, releasefile); + return RET_ERROR_WRONG_MD5; + } + + if (unsupported != c_COUNT && unrequested != c_COUNT) { + fprintf(stderr, +"Error: '%s' only lists unusable or unrequested compressions of '%s'.\n" +"Try e.g the '%s' option (or check what it is set to) to make more useable.\n" +"Or change your DownloadListsAs to request e.g. '%s'.\n", + releasefile, ri->filename_in_release, + uncompression_option[unsupported], + uncompression_config[unrequested]); + return RET_ERROR; + } + if (unsupported != c_COUNT) { + fprintf(stderr, +"Error: '%s' only lists unusable compressions of '%s'.\n" +"Try e.g the '%s' option (or check what it is set to) to make more useable.\n", + releasefile, ri->filename_in_release, + uncompression_option[unsupported]); + return RET_ERROR; + } + if (unrequested != c_COUNT) { + fprintf(stderr, +"Error: '%s' only lists unrequested compressions of '%s'.\n" +"Try changing your DownloadListsAs to request e.g. '%s'.\n", + releasefile, ri->filename_in_release, + uncompression_config[unrequested]); + return RET_ERROR; + } + fprintf(stderr, +"Error: '%s' lists no requested and usable compressions of '%s'.\n", + releasefile, ri->filename_in_release); + return RET_ERROR; + } + /* When nothing specified, use the newest compression. + * This might make it slow on older computers (and perhaps + * on relatively new ones, too), but usually bandwidth costs + * and your time not. + * And you can always configure it to prefer a faster one... + */ + + /* ri->lasttriedencoding -1 means nothing tried, + * 0 means Package.diff was tried, + * 1 means nothing c_COUNT - 1 was already tried, + * 2 means nothing c_COUNT - 2 was already tried, + * and so on...*/ + + if (ri->lasttriedencoding < 0) { + if (ri->olduncompressed != NULL && ri->diff_ofs >= 0) { + ri->compression = c_COUNT; + ri->lasttriedencoding = 0; + return RET_OK; + } + stopat = c_COUNT; + } else + stopat = c_COUNT - ri->lasttriedencoding; + + ri->compression = c_COUNT; + for (c = 0 ; c < stopat ; c++) { + if (ri->ofs[c] < 0) + continue; + if (uncompression_supported(c)) + ri->compression = c; + else + unsupported = c; + } + if (ri->compression == c_COUNT) { + if (ri->lasttriedencoding > -1) { + /* not the first try, no error message needed */ + ri->lasttriedencoding = c_COUNT; + return RET_ERROR; + } + if (unsupported != c_COUNT) { + fprintf(stderr, +"Error: '%s' only lists unusable compressions of '%s'.\n" +"Try e.g the '%s' option (or check what it is set to) to enable more.\n", + releasefile, ri->filename_in_release, + uncompression_option[unsupported]); + return RET_ERROR; + } + fprintf(stderr, +"Could not find '%s' within '%s'\n", + ri->filename_in_release, releasefile); + return RET_ERROR_WRONG_MD5; + + } + ri->lasttriedencoding = c_COUNT - ri->compression; + return RET_OK; +} + +static inline retvalue remove_old_uncompressed(struct remote_index *ri) { + retvalue r; + + if (ri->olduncompressed != NULL) { + r = cachedlistfile_delete(ri->olduncompressed); + ri->olduncompressed = NULL; + return r; + } else + return RET_NOTHING; +} + +static retvalue queue_next_encoding(struct remote_distribution *rd, struct remote_index *ri); + +// TODO: check if this still makes sense. +// (might be left over to support switching from older versions +// of reprepro that also put compressed files there) +static inline retvalue reuse_old_compressed_index(struct remote_distribution *rd, struct remote_index *ri, enum compression c, const char *oldfullfilename) { + retvalue r; + + r = uncompress_file(oldfullfilename, ri->cachefilename, c); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + if (ri->ofs[c_none] >= 0) { + r = checksums_test(ri->cachefilename, + rd->remotefiles.checksums[ri->ofs[c_none]], + &rd->remotefiles.checksums[ri->ofs[c_none]]); + if (r == RET_ERROR_WRONG_MD5) { + fprintf(stderr, +"Error: File '%s' looked correct according to '%s',\n" +"but after unpacking '%s' looks wrong.\n" +"Something is seriously broken!\n", + oldfullfilename, rd->usedreleasefile, + ri->cachefilename); + } + if (r == RET_NOTHING) { + fprintf(stderr, +"File '%s' mysteriously vanished!\n", ri->cachefilename); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + } + /* already there, nothing to do to get it... */ + ri->queued = true; + ri->got = true; + return RET_OK; +} + +static inline retvalue queueindex(struct remote_distribution *rd, struct remote_index *ri, bool nodownload, /*@null@*/struct cachedlistfile *oldfiles) { + enum compression c; + retvalue r; + struct cachedlistfile *old[c_COUNT]; + + if (rd->ignorerelease) { + ri->queued = true; + if (nodownload) { + ri->got = true; + return RET_OK; + } + + /* as there is no way to know which are current, + * just delete everything */ + remote_index_delete_oldfiles(ri, oldfiles); + + return queue_next_without_release(rd, ri); + } + + /* check if this file is still available from an earlier download */ + remote_index_oldfiles(ri, oldfiles, old); + ri->olduncompressed = NULL; + ri->oldchecksums = NULL; + if (ri->ofs[c_none] < 0 && old[c_none] != NULL) { + /* if we know not what it should be, + * we canot use the old... */ + r = cachedlistfile_delete(old[c_none]); + if (RET_WAS_ERROR(r)) + return r; + old[c_none] = NULL; + } else if (old[c_none] != NULL) { + bool improves; + int uo = ri->ofs[c_none]; + struct checksums **wanted_p = &rd->remotefiles.checksums[uo]; + + r = checksums_read(old[c_none]->fullfilename, + &ri->oldchecksums); + if (r == RET_NOTHING) { + fprintf(stderr, "File '%s' mysteriously vanished!\n", + old[c_none]->fullfilename); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + if (checksums_check(*wanted_p, ri->oldchecksums, &improves)) { + /* already there, nothing to do to get it... */ + ri->queued = true; + ri->got = true; + if (improves) + r = checksums_combine(wanted_p, + ri->oldchecksums, NULL); + else + r = RET_OK; + checksums_free(ri->oldchecksums); + ri->oldchecksums = NULL; + return r; + } + ri->olduncompressed = old[c_none]; + old[c_none] = NULL; + } + + assert (old[c_none] == NULL); + + /* make sure everything old is deleted or check if it can be used */ + for (c = 0 ; c < c_COUNT ; c++) { + if (old[c] == NULL) + continue; + if (c != c_none && ri->ofs[c] >= 0) { + /* check if it can be used */ + r = checksums_test(old[c]->fullfilename, + rd->remotefiles.checksums[ri->ofs[c]], + &rd->remotefiles.checksums[ri->ofs[c]]); + if (r == RET_ERROR_WRONG_MD5) + r = RET_NOTHING; + if (RET_WAS_ERROR(r)) + return r; + if (RET_IS_OK(r)) { + r = remove_old_uncompressed(ri); + if (RET_WAS_ERROR(r)) + return r; + assert (old[c_none] == NULL); + return reuse_old_compressed_index(rd, ri, c, + old[c]->fullfilename); + } + } + r = cachedlistfile_delete(old[c]); + if (RET_WAS_ERROR(r)) + return r; + old[c] = NULL; + } + + /* nothing found, we'll have to download: */ + + if (nodownload) { + if (ri->olduncompressed != NULL) + fprintf(stderr, +"Error: '%s' does not match Release file, try without --nolistsdownload to download new one!\n", + ri->cachefilename); + else + fprintf(stderr, +"Error: Missing '%s', try without --nolistsdownload to download it!\n", + ri->cachefilename); + return RET_ERROR_MISSING; + } + + return queue_next_encoding(rd, ri); +} + +static retvalue queue_next_encoding(struct remote_distribution *rd, struct remote_index *ri) { + struct remote_repository *rr = rd->repository; + retvalue r; + + if (rd->ignorerelease) + return queue_next_without_release(rd, ri); + + r = find_requested_encoding(ri, rd->usedreleasefile); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + + assert (ri->compression <= c_COUNT); + + /* check if downloading a .diff/Index (aka .pdiff) is requested */ + if (ri->compression == c_COUNT) { + assert (ri->olduncompressed != NULL); + assert (ri->oldchecksums != NULL); + + ri->queued = true; + return aptmethod_enqueueindex(rr->download, rd->suite_base_dir, + ri->filename_in_release, ".diff/Index", + ri->cachefilename, ".diffindex", + diff_callback, ri, NULL); + } + + assert (ri->compression < c_COUNT); + assert (uncompression_supported(ri->compression)); + + if (ri->compression == c_none) { + r = remove_old_uncompressed(ri); + if (RET_WAS_ERROR(r)) + return r; + } +/* as those checksums might be overwritten with completed data, + * this assumes that the uncompressed checksums for one index is never + * the compressed checksum for another... */ + + ri->queued = true; + return aptmethod_enqueueindex(rr->download, rd->suite_base_dir, + ri->filename_in_release, + uncompression_suffix[ri->compression], + ri->cachefilename, + uncompression_suffix[ri->compression], + index_callback, ri, NULL); +} + + +static retvalue remote_distribution_enqueuelists(struct remote_distribution *rd, bool nodownload, struct cachedlistfile *oldfiles) { + struct remote_index *ri; + retvalue r; + + /* check what to get for the requested indicies */ + for (ri = rd->indices ; ri != NULL ; ri = ri->next) { + if (ri->queued) + continue; + if (!ri->needed) { + /* if we do not know anything about it, + * it cannot have got marked as old + * or otherwise as unneeded */ + assert (!rd->ignorerelease); + continue; + } + r = queueindex(rd, ri, nodownload, oldfiles); + if (RET_WAS_ERROR(r)) + return r; + } + return RET_OK; +} + +retvalue remote_preparelists(struct aptmethodrun *run, bool nodownload) { + struct remote_repository *rr; + struct remote_distribution *rd; + retvalue r; + struct cachedlistfile *oldfiles; + + r = cachedlists_scandir(&oldfiles); + if (RET_WAS_ERROR(r)) + return r; + if (r == RET_NOTHING) + oldfiles = NULL; + + for (rr = repositories ; rr != NULL ; rr = rr->next) { + for (rd = rr->distributions ; rd != NULL + ; rd = rd->next) { + r = remote_distribution_enqueuelists(rd, + nodownload, oldfiles); + if (RET_WAS_ERROR(r)) { + cachedlistfile_freelist(oldfiles); + return r; + } + } + } + r = aptmethod_download(run); + if (RET_WAS_ERROR(r)) { + cachedlistfile_freelist(oldfiles); + return r; + } + + cachedlistfile_freelist(oldfiles); + return RET_OK; +} + +static struct remote_index *addindex(struct remote_distribution *rd, /*@only@*/char *cachefilename, /*@only@*/char *filename, /*@null@*/const struct encoding_preferences *downloadas) { + struct remote_index *ri, **last; + enum compression c; + const char *cachebasename; + + if (FAILEDTOALLOC(cachefilename) || FAILEDTOALLOC(filename)) + return NULL; + + cachebasename = dirs_basename(cachefilename); + last = &rd->indices; + while (*last != NULL && strcmp((*last)->cachebasename, cachebasename) != 0) + last = &(*last)->next; + if (*last != NULL) { + ri = *last; + // TODO: perhaps try to calculate some form of intersections + // instead of just using the shorter one... + if (downloadas != NULL && + (ri->downloadas.count == 0 + || ri->downloadas.count > downloadas->count)) + ri->downloadas = *downloadas; + free(cachefilename); free(filename); + return ri; + } + + ri = zNEW(struct remote_index); + if (FAILEDTOALLOC(ri)) { + free(cachefilename); free(filename); + return NULL; + } + + *last = ri; + ri->from = rd; + ri->cachefilename = cachefilename; + ri->cachebasename = cachebasename; + ri->filename_in_release = filename; + if (downloadas != NULL) + ri->downloadas = *downloadas; + for (c = 0 ; c < c_COUNT ; c++) + ri->ofs[c] = -1; + ri->diff_ofs = -1; + ri->lasttriedencoding = -1; + return ri; +} + +struct remote_index *remote_index(struct remote_distribution *rd, const char *architecture, const char *component, packagetype_t packagetype, /*@null@*/const struct encoding_preferences *downloadas) { + char *cachefilename, *filename_in_release; + + assert (!rd->flat); + if (packagetype == pt_deb) { + filename_in_release = mprintf( +"%s/binary-%s/Packages", + component, architecture); + cachefilename = genlistsfilename("Packages", 4, + rd->repository->name, rd->suite, + component, architecture, ENDOFARGUMENTS); + } else if (packagetype == pt_udeb) { + filename_in_release = mprintf( +"%s/debian-installer/binary-%s/Packages", + component, architecture); + cachefilename = genlistsfilename("uPackages", 4, + rd->repository->name, rd->suite, + component, architecture, ENDOFARGUMENTS); + } else if (packagetype == pt_dsc) { + filename_in_release = mprintf( +"%s/source/Sources", + component); + cachefilename = genlistsfilename("Sources", 3, + rd->repository->name, rd->suite, + component, ENDOFARGUMENTS); + } else { + assert ("Unexpected package type" == NULL); + } + return addindex(rd, cachefilename, filename_in_release, downloadas); +} + +void cachedlistfile_need_index(struct cachedlistfile *list, const char *repository, const char *suite, const char *architecture, const char *component, packagetype_t packagetype) { + if (packagetype == pt_deb) { + cachedlistfile_need(list, "Packages", 4, + repository, suite, + component, architecture, ENDOFARGUMENTS); + } else if (packagetype == pt_udeb) { + cachedlistfile_need(list, "uPackages", 4, + repository, suite, + component, architecture, ENDOFARGUMENTS); + } else if (packagetype == pt_dsc) { + cachedlistfile_need(list, "Sources", 3, + repository, suite, + component, ENDOFARGUMENTS); + } +} + +struct remote_index *remote_flat_index(struct remote_distribution *rd, packagetype_t packagetype, /*@null@*/const struct encoding_preferences *downloadas) { + char *cachefilename, *filename_in_release; + + assert (rd->flat); + if (packagetype == pt_deb) { + filename_in_release = strdup("Packages"); + cachefilename = genlistsfilename("Packages", 2, + rd->repository->name, rd->suite, + ENDOFARGUMENTS); + } else if (packagetype == pt_dsc) { + filename_in_release = strdup("Sources"); + cachefilename = genlistsfilename("Sources", 2, + rd->repository->name, rd->suite, + ENDOFARGUMENTS); + } else { + assert ("Unexpected package type" == NULL); + } + return addindex(rd, cachefilename, filename_in_release, downloadas); +} + +void cachedlistfile_need_flat_index(struct cachedlistfile *list, const char *repository, const char *suite, packagetype_t packagetype) { + if (packagetype == pt_deb) { + cachedlistfile_need(list, "Packages", 2, + repository, suite, ENDOFARGUMENTS); + } else if (packagetype == pt_dsc) { + cachedlistfile_need(list, "Sources", 1, + repository, suite, ENDOFARGUMENTS); + } +} + +const char *remote_index_file(const struct remote_index *ri) { + assert (ri->needed && ri->queued && ri->got); + return ri->cachefilename; +} +const char *remote_index_basefile(const struct remote_index *ri) { + assert (ri->needed && ri->queued); + return ri->cachebasename; +} + +struct aptmethod *remote_aptmethod(const struct remote_distribution *rd) { + return rd->repository->download; +} + +void remote_index_markdone(const struct remote_index *ri, struct markdonefile *done) { + if (ri->ofs[c_none] < 0) + return; + markdone_index(done, ri->cachebasename, + ri->from->remotefiles.checksums[ri->ofs[c_none]]); +} +void remote_index_needed(struct remote_index *ri) { + ri->needed = true; +} + +static retvalue indexfile_mark_got(struct remote_distribution *rd, struct remote_index *ri, /*@null@*/const struct checksums *gotchecksums) { + struct checksums **checksums_p; + + if (!rd->ignorerelease && ri->ofs[c_none] >= 0) { + checksums_p = &rd->remotefiles.checksums[ri->ofs[c_none]]; + bool matches, improves; + + // TODO: this no longer calculates all the checksums if + // the Release does not contain more and the apt method + // returned not all (but all that are in Release). + // This will then cause the done file not containing all + // checksums. (but if the Release not contain them, this + // does not harm, does it?) + + if (gotchecksums != NULL) { + matches = checksums_check(*checksums_p, gotchecksums, + &improves); + /* that should have been tested earlier */ + assert (matches); + if (! matches) + return RET_ERROR_WRONG_MD5; + if (improves) { + retvalue r; + + r = checksums_combine(checksums_p, + gotchecksums, NULL); + if (RET_WAS_ERROR(r)) + return r; + } + } + } + ri->got = true; + return RET_OK; +} + +static retvalue indexfile_unpacked(void *privdata, const char *compressed, bool failed) { + struct remote_index *ri = privdata; + struct remote_distribution *rd = ri->from; + retvalue r; + struct checksums *readchecksums = NULL; + + if (failed) { + // TODO: check if alternative can be used... + return RET_ERROR; + } + + /* file got uncompressed, check if it has the correct checksum */ + + /* even with a Release file, an old-style one might + * not list the checksums for the uncompressed indices */ + if (!rd->ignorerelease && ri->ofs[c_none] >= 0) { + int ofs = ri->ofs[c_none]; + const struct checksums *wantedchecksums = + rd->remotefiles.checksums[ofs]; + bool matches, missing = false; + + r = checksums_read(ri->cachefilename, &readchecksums); + if (r == RET_NOTHING) { + fprintf(stderr, +"Cannot open '%s', though it should just have been unpacked from '%s'!\n", + ri->cachefilename, + compressed); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + missing = false; + matches = checksums_check(readchecksums, + wantedchecksums, &missing); + assert (!missing); + if (!matches) { + fprintf(stderr, +"Wrong checksum of uncompressed content of '%s':\n", compressed); + checksums_printdifferences(stderr, + wantedchecksums, + readchecksums); + checksums_free(readchecksums); + return RET_ERROR_WRONG_MD5; + } + /* if the compressed file was downloaded or copied, delete it. + * This is only done if we know the uncompressed checksum, so + * that less downloading is needed (though as apt no longer + * supports such archieves, they are unlikely anyway). */ + + if (strncmp(ri->cachefilename, compressed, + strlen(ri->cachefilename)) == 0) { + (void)unlink(compressed); + } + } + r = indexfile_mark_got(rd, ri, readchecksums); + checksums_free(readchecksums); + if (RET_WAS_ERROR(r)) + return r; + return RET_OK; +} + +/* *checksums_p must be either NULL or gotchecksums list all known checksums */ +static inline retvalue check_checksums(const char *methodname, const char *uri, const char *gotfilename, const struct checksums *wantedchecksums, /*@null@*/const struct checksums *gotchecksums, struct checksums **checksums_p) { + bool matches, missing = false; + struct checksums *readchecksums = NULL; + retvalue r; + + if (gotchecksums == NULL) { + matches = true; + missing = true; + } else + matches = checksums_check(gotchecksums, + wantedchecksums, &missing); + /* if the apt method did not generate all checksums + * we want to check, we'll have to do so: */ + if (matches && missing) { + /* we assume that everything we know how to + * extract from a Release file is something + * we know how to calculate out of a file */ + assert (checksums_p == NULL || *checksums_p == NULL); + r = checksums_read(gotfilename, &readchecksums); + if (r == RET_NOTHING) { + fprintf(stderr, +"Cannot open '%s', though apt-method '%s' claims it is there!\n", + gotfilename, methodname); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + gotchecksums = readchecksums; + missing = false; + matches = checksums_check(gotchecksums, + wantedchecksums, &missing); + assert (!missing); + } + if (!matches) { + fprintf(stderr, "Wrong checksum during receive of '%s':\n", + uri); + checksums_printdifferences(stderr, + wantedchecksums, + gotchecksums); + checksums_free(readchecksums); + return RET_ERROR_WRONG_MD5; + } + if (checksums_p == NULL) + checksums_free(readchecksums); + else if (readchecksums != NULL) + *checksums_p = readchecksums; + return RET_OK; +} + +static retvalue index_callback(enum queue_action action, void *privdata, UNUSED(void *privdata2), const char *uri, const char *gotfilename, const char *wantedfilename, /*@null@*/const struct checksums *gotchecksums, const char *methodname) { + struct remote_index *ri = privdata; + struct remote_distribution *rd = ri->from; + struct checksums *readchecksums = NULL; + retvalue r; + + if (action == qa_error) + return queue_next_encoding(rd, ri); + if (action != qa_got) + return RET_ERROR; + + if (ri->compression == c_none) { + assert (strcmp(wantedfilename, ri->cachefilename) == 0); + r = copytoplace(gotfilename, wantedfilename, methodname, + &readchecksums); + if (RET_WAS_ERROR(r)) + return r; + gotfilename = wantedfilename; + if (readchecksums != NULL) + gotchecksums = readchecksums; + } + + if (!rd->ignorerelease && ri->ofs[ri->compression] >= 0) { + int ofs = ri->ofs[ri->compression]; + const struct checksums *wantedchecksums = + rd->remotefiles.checksums[ofs]; + + r = check_checksums(methodname, uri, gotfilename, + wantedchecksums, gotchecksums, &readchecksums); + if (RET_WAS_ERROR(r)) { + checksums_free(readchecksums); + return r; + } + if (readchecksums != NULL) + gotchecksums = readchecksums; + } + + if (ri->compression == c_none) { + assert (strcmp(gotfilename, wantedfilename) == 0); + r = indexfile_mark_got(rd, ri, gotchecksums); + checksums_free(readchecksums); + if (RET_WAS_ERROR(r)) + return r; + return RET_OK; + } else { + checksums_free(readchecksums); + r = remove_old_uncompressed(ri); + if (RET_WAS_ERROR(r)) + return r; + r = uncompress_queue_file(gotfilename, ri->cachefilename, + ri->compression, + indexfile_unpacked, privdata); + if (RET_WAS_ERROR(r)) + return r; + return RET_OK; + } +} + +static queue_callback diff_got_callback; + +static retvalue queue_next_diff(struct remote_index *ri) { + struct remote_distribution *rd = ri->from; + struct remote_repository *rr = rd->repository; + int i; + retvalue r; + + for (i = 0 ; i < ri->diffindex->patchcount ; i++) { + bool improves; + struct diffindex_patch *p = &ri->diffindex->patches[i]; + char *patchsuffix, *c; + + if (p->done || p->frompackages == NULL) + continue; + + if (!checksums_check(ri->oldchecksums, p->frompackages, + &improves)) + continue; + /* p->frompackages should only have sha1 and oldchecksums + * should definitely list a sha1 hash */ + assert (!improves); + + p->done = true; + + free(ri->patchfilename); + ri->patchfilename = mprintf("%s.diff-%s", ri->cachefilename, + p->name); + if (FAILEDTOALLOC(ri->patchfilename)) + return RET_ERROR_OOM; + c = ri->patchfilename + strlen(ri->cachefilename); + while (*c != '\0') { + if ((*c < '0' || *c > '9') + && (*c < 'A' || *c > 'Z') + && (*c < 'a' || *c > 'z') + && *c != '.' && *c != '-') + *c = '_'; + c++; + } + ri->selectedpatch = p; + patchsuffix = mprintf(".diff/%s.gz", p->name); + if (FAILEDTOALLOC(patchsuffix)) + return RET_ERROR_OOM; + + /* found a matching patch, tell the downloader we want it */ + r = aptmethod_enqueueindex(rr->download, rd->suite_base_dir, + ri->filename_in_release, + patchsuffix, + ri->patchfilename, ".gz", + diff_got_callback, ri, p); + free(patchsuffix); + return r; + } + /* no patch matches, try next possibility... */ + fprintf(stderr, "Error: available '%s' not listed in '%s.diffindex'.\n", + ri->cachefilename, ri->cachefilename); + return queue_next_encoding(rd, ri); +} + +static retvalue diff_uncompressed(void *privdata, const char *compressed, bool failed) { + struct remote_index *ri = privdata; + struct remote_distribution *rd = ri->from; + const struct diffindex_patch *p = ri->selectedpatch; + char *tempfilename; + struct rred_patch *rp; + FILE *f; + int i; + retvalue r; + bool dummy; + + if (ri->deletecompressedpatch) + (void)unlink(compressed); + if (failed) + return RET_ERROR; + + r = checksums_test(ri->patchfilename, p->checksums, NULL); + if (r == RET_NOTHING) { + fprintf(stderr, "Mysteriously vanished file '%s'!\n", + ri->patchfilename); + r = RET_ERROR_MISSING; + } + if (r == RET_ERROR_WRONG_MD5) + fprintf(stderr, "Corrupted package diff '%s'!\n", + ri->patchfilename); + if (RET_WAS_ERROR(r)) + return r; + + r = patch_load(ri->patchfilename, + checksums_getfilesize(p->checksums), &rp); + ASSERT_NOT_NOTHING(r); + if (RET_WAS_ERROR(r)) + return r; + + tempfilename = calc_addsuffix(ri->cachefilename, "tmp"); + if (FAILEDTOALLOC(tempfilename)) { + patch_free(rp); + return RET_ERROR_OOM; + } + (void)unlink(tempfilename); + i = rename(ri->cachefilename, tempfilename); + if (i != 0) { + int e = errno; + fprintf(stderr, "Error %d moving '%s' to '%s': %s\n", + e, ri->cachefilename, tempfilename, + strerror(e)); + free(tempfilename); + patch_free(rp); + return RET_ERRNO(e); + } + f = fopen(ri->cachefilename, "w"); + if (f == NULL) { + int e = errno; + fprintf(stderr, "Error %d creating '%s': %s\n", + e, ri->cachefilename, strerror(e)); + (void)unlink(tempfilename); + ri->olduncompressed->deleted = true; + ri->olduncompressed = NULL; + free(tempfilename); + patch_free(rp); + return RET_ERRNO(e); + } + r = patch_file(f, tempfilename, patch_getconstmodifications(rp)); + (void)unlink(tempfilename); + (void)unlink(ri->patchfilename); + free(ri->patchfilename); + ri->patchfilename = NULL; + free(tempfilename); + patch_free(rp); + if (RET_WAS_ERROR(r)) { + (void)fclose(f); + remove_old_uncompressed(ri); + // TODO: fall back to downloading at once? + return r; + } + i = ferror(f); + if (i != 0) { + int e = errno; + (void)fclose(f); + fprintf(stderr, "Error %d writing to '%s': %s\n", + e, ri->cachefilename, strerror(e)); + remove_old_uncompressed(ri); + return RET_ERRNO(e); + } + i = fclose(f); + if (i != 0) { + int e = errno; + fprintf(stderr, "Error %d writing to '%s': %s\n", + e, ri->cachefilename, strerror(e)); + remove_old_uncompressed(ri); + return RET_ERRNO(e); + } + checksums_free(ri->oldchecksums); + ri->oldchecksums = NULL; + r = checksums_read(ri->cachefilename, &ri->oldchecksums); + if (r == RET_NOTHING) { + fprintf(stderr, "Myteriously vanished file '%s'!\n", + ri->cachefilename); + r = RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + if (checksums_check(ri->oldchecksums, + rd->remotefiles.checksums[ri->ofs[c_none]], + &dummy)) { + ri->olduncompressed->deleted = true; + ri->olduncompressed = NULL; + /* we have a winner */ + return indexfile_mark_got(rd, ri, ri->oldchecksums); + } + /* let's see what patch we need next */ + return queue_next_diff(ri); +} + +static retvalue diff_got_callback(enum queue_action action, void *privdata, UNUSED(void *privdata2), UNUSED(const char *uri), const char *gotfilename, const char *wantedfilename, UNUSED(/*@null@*/const struct checksums *gotchecksums), UNUSED(const char *methodname)) { + struct remote_index *ri = privdata; + retvalue r; + + if (action == qa_error) + return queue_next_encoding(ri->from, ri); + if (action != qa_got) + return RET_ERROR; + + ri->deletecompressedpatch = strcmp(gotfilename, wantedfilename) == 0; + r = uncompress_queue_file(gotfilename, ri->patchfilename, + c_gzip, diff_uncompressed, ri); + if (RET_WAS_ERROR(r)) + (void)unlink(gotfilename); + return r; +} + +static retvalue diff_callback(enum queue_action action, void *privdata, UNUSED(void *privdata2), const char *uri, const char *gotfilename, const char *wantedfilename, /*@null@*/const struct checksums *gotchecksums, const char *methodname) { + struct remote_index *ri = privdata; + struct remote_distribution *rd = ri->from; + struct checksums *readchecksums = NULL; + int ofs; + retvalue r; + + if (action == qa_error) + return queue_next_encoding(rd, ri); + if (action != qa_got) + return RET_ERROR; + + r = copytoplace(gotfilename, wantedfilename, methodname, + &readchecksums); + if (RET_WAS_ERROR(r)) + return r; + if (readchecksums != NULL) + gotchecksums = readchecksums; + + ofs = ri->diff_ofs; + if (ofs >= 0) { + const struct checksums *wantedchecksums = + rd->remotefiles.checksums[ofs]; + bool matches, missing = false; + + if (gotchecksums == NULL) { + matches = true; + missing = true; + } else + matches = checksums_check(gotchecksums, + wantedchecksums, &missing); + /* if the apt method did not generate all checksums + * we want to check, we'll have to do so: */ + if (matches && missing) { + /* we assume that everything we know how to + * extract from a Release file is something + * we know how to calculate out of a file */ + assert (readchecksums == NULL); + r = checksums_read(gotfilename, &readchecksums); + if (r == RET_NOTHING) { + fprintf(stderr, +"Cannot open '%s', though apt-method '%s' claims it is there!\n", + gotfilename, methodname); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + gotchecksums = readchecksums; + missing = false; + matches = checksums_check(gotchecksums, + wantedchecksums, &missing); + assert (!missing); + } + if (!matches) { + fprintf(stderr, +"Wrong checksum during receive of '%s':\n", uri); + checksums_printdifferences(stderr, + wantedchecksums, gotchecksums); + checksums_free(readchecksums); + return RET_ERROR_WRONG_MD5; + } + } + checksums_free(readchecksums); + r = diffindex_read(wantedfilename, &ri->diffindex); + ASSERT_NOT_NOTHING(r); + if (RET_WAS_ERROR(r)) + return queue_next_encoding(rd, ri); + if (ri->ofs[c_none] >= 0) { + bool dummy; + if (!checksums_check(rd->remotefiles.checksums[ + ri->ofs[c_none]], + ri->diffindex->destination, &dummy)) { + fprintf(stderr, +"'%s' does not match file requested in '%s'. Aborting diff processing...\n", + gotfilename, rd->usedreleasefile); + /* as this is claimed to be a common error + * (outdated .diff/Index file), proceed with + * other requested way to retrieve index file */ + return queue_next_encoding(rd, ri); + } + } + return queue_next_diff(ri); +} |