diff options
Diffstat (limited to '')
-rw-r--r-- | release.c | 1900 |
1 files changed, 1900 insertions, 0 deletions
diff --git a/release.c b/release.c new file mode 100644 index 0000000..473c5e6 --- /dev/null +++ b/release.c @@ -0,0 +1,1900 @@ +/* This file is part of "reprepro" + * Copyright (C) 2003,2004,2005,2007,2009,2012,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 <errno.h> +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <time.h> +#include <zlib.h> +#ifdef HAVE_LIBBZ2 +#include <bzlib.h> +#endif +#ifdef HAVE_LIBLZMA +#include <lzma.h> +#endif +#define CHECKSUMS_CONTEXT visible +#include "error.h" +#include "ignore.h" +#include "mprintf.h" +#include "strlist.h" +#include "filecntl.h" +#include "chunks.h" +#include "checksums.h" +#include "dirs.h" +#include "names.h" +#include "signature.h" +#include "distribution.h" +#include "outhook.h" +#include "release.h" + +#define INPUT_BUFFER_SIZE 1024 +#define GZBUFSIZE 40960 +#define BZBUFSIZE 40960 +// TODO: what is the correct value here: +#define XZBUFSIZE 40960 + +struct release { + /* The base-directory of the distribution we are exporting */ + char *dirofdist; + /* anything new yet added */ + bool new; + /* NULL if no snapshot */ + /*@null@*/char *snapshotname; + /* specific overrides for fakeprefixes or snapshots: */ + /*@null@*/char *fakesuite; + /*@null@*/char *fakecodename; + /*@null@*/const char *fakecomponentprefix; + size_t fakecomponentprefixlen; + /* the files yet for the list */ + struct release_entry { + struct release_entry *next; + char *relativefilename; + struct checksums *checksums; + char *fullfinalfilename; + char *fulltemporaryfilename; + char *symlinktarget; + /* name chks NULL NULL NULL: add old filename or virtual file + * name chks file file NULL: rename new file and publish + * name NULL file file NULL: rename new file + * name NULL file NULL NULL: delete if done + * name NULL file NULL file: create symlink */ + } *files; + /* the Release file in preperation + * (only valid between _prepare and _finish) */ + struct signedfile *signedfile; + /* the cache database for old files */ + struct table *cachedb; +}; + +static void release_freeentry(struct release_entry *e) { + free(e->relativefilename); + checksums_free(e->checksums); + free(e->fullfinalfilename); + if (!global.keeptemporaries && e->fulltemporaryfilename != NULL) + (void)unlink(e->fulltemporaryfilename); + free(e->fulltemporaryfilename); + free(e->symlinktarget); + free(e); +} + +void release_free(struct release *release) { + struct release_entry *e; + + free(release->snapshotname); + free(release->dirofdist); + free(release->fakesuite); + free(release->fakecodename); + while ((e = release->files) != NULL) { + release->files = e->next; + release_freeentry(e); + } + if (release->signedfile != NULL) + signedfile_free(release->signedfile); + if (release->cachedb != NULL) { + table_close(release->cachedb); + } + free(release); +} + +const char *release_dirofdist(struct release *release) { + return release->dirofdist; +} + +static retvalue newreleaseentry(struct release *release, /*@only@*/ char *relativefilename, + /*@only@*/ struct checksums *checksums, + /*@only@*/ /*@null@*/ char *fullfinalfilename, + /*@only@*/ /*@null@*/ char *fulltemporaryfilename, + /*@only@*/ /*@null@*/ char *symlinktarget) { + struct release_entry *n, *p; + + /* everything has a relative name */ + assert (relativefilename != NULL); + /* it's either something to do or to publish */ + assert (fullfinalfilename != NULL || checksums != NULL); + /* if there is something temporary, it has a final place */ + assert (fulltemporaryfilename == NULL || fullfinalfilename != NULL); + /* a symlink cannot be published (Yet?) */ + assert (symlinktarget == NULL || checksums == NULL); + /* cannot place a file and a symlink */ + assert (symlinktarget == NULL || fulltemporaryfilename == NULL); + /* something to publish cannot be a file deletion */ + assert (checksums == NULL + || fullfinalfilename == NULL + || fulltemporaryfilename != NULL + || symlinktarget != NULL); + n = NEW(struct release_entry); + if (FAILEDTOALLOC(n)) { + checksums_free(checksums); + free(fullfinalfilename); + free(fulltemporaryfilename); + free(symlinktarget); + return RET_ERROR_OOM; + } + n->next = NULL; + n->relativefilename = relativefilename; + n->checksums = checksums; + n->fullfinalfilename = fullfinalfilename; + n->fulltemporaryfilename = fulltemporaryfilename; + n->symlinktarget = symlinktarget; + if (release->files == NULL) + release->files = n; + else { + p = release->files; + while (p->next != NULL) + p = p->next; + p->next = n; + } + return RET_OK; +} + +retvalue release_init(struct release **release, const char *codename, const char *suite, const char *fakecomponentprefix) { + struct release *n; + size_t len, suitelen, codenamelen; + retvalue r; + + if (verbose >= 15) + fprintf(stderr, "trace: release_init(codename=%s, suite=%s, fakecomponentprefix=%s) called.\n", + codename, suite, fakecomponentprefix); + + n = zNEW(struct release); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + n->dirofdist = calc_dirconcat(global.distdir, codename); + if (FAILEDTOALLOC(n->dirofdist)) { + free(n); + return RET_ERROR_OOM; + } + if (fakecomponentprefix != NULL) { + len = strlen(fakecomponentprefix); + codenamelen = strlen(codename); + + n->fakecomponentprefix = fakecomponentprefix; + n->fakecomponentprefixlen = len; + if (codenamelen > len && + codename[codenamelen - len - 1] == '/' && + memcmp(codename + (codenamelen - len), + fakecomponentprefix, len) == 0) { + n->fakecodename = strndup(codename, + codenamelen - len - 1); + if (FAILEDTOALLOC(n->fakecodename)) { + free(n->dirofdist); + free(n); + return RET_ERROR_OOM; + } + } + if (suite != NULL && (suitelen = strlen(suite)) > len && + suite[suitelen - len - 1] == '/' && + memcmp(suite + (suitelen - len), + fakecomponentprefix, len) == 0) { + n->fakesuite = strndup(suite, + suitelen - len - 1); + if (FAILEDTOALLOC(n->fakesuite)) { + free(n->fakecodename); + free(n->dirofdist); + free(n); + return RET_ERROR_OOM; + } + } + } + r = database_openreleasecache(codename, &n->cachedb); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + n->cachedb = NULL; + free(n->fakecodename); + free(n->fakesuite); + free(n->dirofdist); + free(n); + return r; + } + *release = n; + return RET_OK; +} + +retvalue release_initsnapshot(const char *codename, const char *name, struct release **release) { + struct release *n; + + n = zNEW(struct release); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + n->dirofdist = calc_snapshotbasedir(codename, name); + if (FAILEDTOALLOC(n->dirofdist)) { + free(n); + return RET_ERROR_OOM; + } + /* apt only removes the last /... part but we create two, + * so stop it generating warnings by faking a suite */ + n->fakesuite = mprintf("%s/snapshots/%s", codename, name); + if (FAILEDTOALLOC(n->fakesuite)) { + free(n->dirofdist); + free(n); + return RET_ERROR_OOM; + } + n->fakecodename = NULL; + n->fakecomponentprefix = NULL; + n->fakecomponentprefixlen = 0; + n->cachedb = NULL; + n->snapshotname = strdup(name); + if (n->snapshotname == NULL) { + free(n->fakesuite); + free(n->dirofdist); + free(n); + return RET_ERROR_OOM; + } + *release = n; + return RET_OK; +} + +retvalue release_adddel(struct release *release, /*@only@*/char *reltmpfile) { + char *filename; + + filename = calc_dirconcat(release->dirofdist, reltmpfile); + if (FAILEDTOALLOC(filename)) { + free(reltmpfile); + return RET_ERROR_OOM; + } + return newreleaseentry(release, reltmpfile, NULL, filename, NULL, NULL); +} + +retvalue release_addnew(struct release *release, /*@only@*/char *reltmpfile, /*@only@*/char *relfilename) { + retvalue r; + char *filename, *finalfilename; + struct checksums *checksums; + + filename = calc_dirconcat(release->dirofdist, reltmpfile); + if (FAILEDTOALLOC(filename)) { + free(reltmpfile); + free(relfilename); + return RET_ERROR_OOM; + } + free(reltmpfile); + r = checksums_read(filename, &checksums); + if (!RET_IS_OK(r)) { + free(relfilename); + free(filename); + return r; + } + finalfilename = calc_dirconcat(release->dirofdist, relfilename); + if (FAILEDTOALLOC(finalfilename)) { + free(relfilename); + free(filename); + checksums_free(checksums); + return RET_ERROR_OOM; + } + release->new = true; + return newreleaseentry(release, relfilename, + checksums, finalfilename, filename, NULL); +} + +retvalue release_addsilentnew(struct release *release, /*@only@*/char *reltmpfile, /*@only@*/char *relfilename) { + char *filename, *finalfilename; + + filename = calc_dirconcat(release->dirofdist, reltmpfile); + if (FAILEDTOALLOC(filename)) { + free(reltmpfile); + free(relfilename); + return RET_ERROR_OOM; + } + free(reltmpfile); + finalfilename = calc_dirconcat(release->dirofdist, relfilename); + if (FAILEDTOALLOC(finalfilename)) { + free(relfilename); + free(filename); + return RET_ERROR_OOM; + } + release->new = true; + return newreleaseentry(release, relfilename, + NULL, finalfilename, filename, NULL); +} + +retvalue release_addold(struct release *release, /*@only@*/char *relfilename) { + retvalue r; + char *filename; + struct checksums *checksums; + + filename = calc_dirconcat(release->dirofdist, relfilename); + if (FAILEDTOALLOC(filename)) { + free(filename); + return RET_ERROR_OOM; + } + r = checksums_read(filename, &checksums); + free(filename); + if (!RET_IS_OK(r)) { + free(relfilename); + return r; + } + return newreleaseentry(release, relfilename, + checksums, NULL, NULL, NULL); +} + +static retvalue release_addsymlink(struct release *release, /*@only@*/char *relfilename, /*@only@*/ char *symlinktarget) { + char *fullfilename; + + fullfilename = calc_dirconcat(release->dirofdist, relfilename); + if (FAILEDTOALLOC(fullfilename)) { + free(symlinktarget); + free(relfilename); + return RET_ERROR_OOM; + } + release->new = true; + return newreleaseentry(release, relfilename, NULL, + fullfilename, NULL, symlinktarget); +} + +static char *calc_compressedname(const char *name, enum indexcompression ic) { + switch (ic) { + case ic_uncompressed: + return strdup(name); + case ic_gzip: + return calc_addsuffix(name, "gz"); +#ifdef HAVE_LIBBZ2 + case ic_bzip2: + return calc_addsuffix(name, "bz2"); +#endif +#ifdef HAVE_LIBLZMA + case ic_xz: + return calc_addsuffix(name, "xz"); +#endif + default: + assert ("Huh?" == NULL); + return NULL; + } +} + +static retvalue release_usecached(struct release *release, + const char *relfilename, + compressionset compressions) { + retvalue result, r; + enum indexcompression ic; + char *filename[ic_count]; + struct checksums *checksums[ic_count]; + + memset(filename, 0, sizeof(filename)); + memset(checksums, 0, sizeof(checksums)); + result = RET_OK; + + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + if (ic != ic_uncompressed && + (compressions & IC_FLAG(ic)) == 0) + continue; + filename[ic] = calc_compressedname(relfilename, ic); + if (FAILEDTOALLOC(filename[ic])) { + result = RET_ERROR_OOM; + break; + } + } + if (RET_IS_OK(result)) { + /* first look if the there are actual files, in case + * the cache still lists them but they got lost */ + + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + char *fullfilename; + + if ((compressions & IC_FLAG(ic)) == 0) + continue; + assert (filename[ic] != NULL); + fullfilename = calc_dirconcat(release->dirofdist, + filename[ic]); + if (FAILEDTOALLOC(fullfilename)) { + result = RET_ERROR_OOM; + break; + } + if (!isregularfile(fullfilename)) { + free(fullfilename); + result = RET_NOTHING; + break; + } + free(fullfilename); + } + } + if (RET_IS_OK(result) && release->cachedb == NULL) + result = RET_NOTHING; + if (!RET_IS_OK(result)) { + for (ic = ic_uncompressed ; ic < ic_count ; ic++) + free(filename[ic]); + return result; + } + + /* now that the files are there look into the cache + * what checksums they have. */ + + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + char *combinedchecksum; + + if (filename[ic] == NULL) + continue; + r = table_getrecord(release->cachedb, false, filename[ic], + &combinedchecksum, NULL); + if (!RET_IS_OK(r)) { + result = r; + break; + } + r = checksums_parse(&checksums[ic], combinedchecksum); + // TODO: handle malformed checksums better? + free(combinedchecksum); + if (!RET_IS_OK(r)) { + result = r; + break; + } + } + /* some files might not yet have some type of checksum available, + * so calculate them (checking the other checksums match...): */ + if (RET_IS_OK(result)) { + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + char *fullfilename; + if (filename[ic] == NULL) + continue; + fullfilename = calc_dirconcat(release->dirofdist, + filename[ic]); + if (FAILEDTOALLOC(fullfilename)) + r = RET_ERROR_OOM; + else + r = checksums_complete(&checksums[ic], + fullfilename); + if (r == RET_ERROR_WRONG_MD5) { + fprintf(stderr, +"WARNING: '%s' is different from recorded checksums.\n" +"(This was only caught because some new checksum type was not yet available.)\n" +"Triggering recreation of that file.\n", fullfilename); + r = RET_NOTHING; + } + free(fullfilename); + if (!RET_IS_OK(r)) { + result = r; + break; + } + } + } + if (!RET_IS_OK(result)) { + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + if (filename[ic] == NULL) + continue; + free(filename[ic]); + checksums_free(checksums[ic]); + } + return result; + } + /* everything found, commit it: */ + result = RET_OK; + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + if (filename[ic] == NULL) + continue; + r = newreleaseentry(release, filename[ic], + checksums[ic], + NULL, NULL, NULL); + RET_UPDATE(result, r); + } + return result; +} + + +struct filetorelease { + retvalue state; + struct openfile { + int fd; + struct checksumscontext context; + char *relativefilename; + char *fullfinalfilename; + char *fulltemporaryfilename; + char *symlinkas; + } f[ic_count]; + /* input buffer, to checksum/compress data at once */ + unsigned char *buffer; size_t waiting_bytes; + /* output buffer for gzip compression */ + unsigned char *gzoutputbuffer; size_t gz_waiting_bytes; + z_stream gzstream; +#ifdef HAVE_LIBBZ2 + /* output buffer for bzip2 compression */ + char *bzoutputbuffer; size_t bz_waiting_bytes; + bz_stream bzstream; +#endif +#ifdef HAVE_LIBLZMA + /* output buffer for bzip2 compression */ + unsigned char *xzoutputbuffer; size_t xz_waiting_bytes; + lzma_stream xzstream; +#endif +}; + +void release_abortfile(struct filetorelease *file) { + enum indexcompression i; + + for (i = ic_uncompressed ; i < ic_count ; i++) { + if (file->f[i].fd >= 0) { + (void)close(file->f[i].fd); + if (file->f[i].fulltemporaryfilename != NULL) + (void)unlink(file->f[i].fulltemporaryfilename); + } + free(file->f[i].relativefilename); + free(file->f[i].fullfinalfilename); + free(file->f[i].fulltemporaryfilename); + free(file->f[i].symlinkas); + } + free(file->buffer); + free(file->gzoutputbuffer); + if (file->gzstream.next_out != NULL) { + (void)deflateEnd(&file->gzstream); + } +#ifdef HAVE_LIBBZ2 + free(file->bzoutputbuffer); + if (file->bzstream.next_out != NULL) { + (void)BZ2_bzCompressEnd(&file->bzstream); + } +#endif +#ifdef HAVE_LIBLZMA + if (file->xzoutputbuffer != NULL) { + free(file->xzoutputbuffer); + lzma_end(&file->xzstream); + } +#endif +} + +bool release_oldexists(struct filetorelease *file) { + enum indexcompression ic; + bool hadanything = false; + + for (ic = ic_uncompressed ; ic < ic_count ; ic++) { + char *f = file->f[ic].fullfinalfilename; + + if (f != NULL) { + if (isregularfile(f)) + hadanything = true; + else + return false; + } + } + return hadanything; +} + +static retvalue openfile(const char *dirofdist, struct openfile *f) { + + f->fullfinalfilename = calc_dirconcat(dirofdist, f->relativefilename); + if (FAILEDTOALLOC(f->fullfinalfilename)) + return RET_ERROR_OOM; + f->fulltemporaryfilename = calc_addsuffix(f->fullfinalfilename, "new"); + if (FAILEDTOALLOC(f->fulltemporaryfilename)) + return RET_ERROR_OOM; + (void)unlink(f->fulltemporaryfilename); + f->fd = open(f->fulltemporaryfilename, + O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY, 0666); + if (f->fd < 0) { + int e = errno; + fprintf(stderr, "Error %d opening file %s for writing: %s\n", + e, f->fulltemporaryfilename, strerror(e)); + return RET_ERRNO(e); + } + return RET_OK; +} + +static retvalue writetofile(struct openfile *file, const unsigned char *data, size_t len) { + + checksumscontext_update(&file->context, data, len); + + if (file->fd < 0) + return RET_NOTHING; + + while (len > 0) { + ssize_t written = write(file->fd, data, len); + if (written >= 0) { + len -= written; + data += written; + } else { + int e = errno; + if (e == EAGAIN || e == EINTR) + continue; + fprintf(stderr, "Error %d writing to %s: %s\n", + e, file->fullfinalfilename, + strerror(e)); + return RET_ERRNO(e); + } + } + return RET_OK; +} + +static retvalue initgzcompression(struct filetorelease *f) { + int zret; + + if ((zlibCompileFlags() & (1<<17)) !=0) { + fprintf(stderr, "libz compiled without .gz supporting code\n"); + return RET_ERROR; + } + f->gzoutputbuffer = malloc(GZBUFSIZE); + if (FAILEDTOALLOC(f->gzoutputbuffer)) + return RET_ERROR_OOM; + f->gzstream.next_in = NULL; + f->gzstream.avail_in = 0; + f->gzstream.next_out = f->gzoutputbuffer; + f->gzstream.avail_out = GZBUFSIZE; + f->gzstream.zalloc = NULL; + f->gzstream.zfree = NULL; + f->gzstream.opaque = NULL; + zret = deflateInit2(&f->gzstream, + /* Level: 0-9 or Z_DEFAULT_COMPRESSION: */ + Z_DEFAULT_COMPRESSION, + /* only possibility yet: */ + Z_DEFLATED, + /* +16 to generate gzip header */ + 16 + MAX_WBITS, + /* how much memory to use 1-9 */ + 8, + /* default or Z_FILTERED or Z_HUFFMAN_ONLY or Z_RLE */ + Z_DEFAULT_STRATEGY + ); + f->gz_waiting_bytes = GZBUFSIZE - f->gzstream.avail_out; + if (zret == Z_MEM_ERROR) + return RET_ERROR_OOM; + if (zret != Z_OK) { + if (f->gzstream.msg == NULL) { + fprintf(stderr, "Error from zlib's deflateInit2: " + "unknown(%d)\n", zret); + } else { + fprintf(stderr, "Error from zlib's deflateInit2: %s\n", + f->gzstream.msg); + } + return RET_ERROR; + } + return RET_OK; +} + +#ifdef HAVE_LIBBZ2 + +static retvalue initbzcompression(struct filetorelease *f) { + int bzret; + + f->bzoutputbuffer = malloc(BZBUFSIZE); + if (FAILEDTOALLOC(f->bzoutputbuffer)) + return RET_ERROR_OOM; + f->bzstream.next_in = NULL; + f->bzstream.avail_in = 0; + f->bzstream.next_out = f->bzoutputbuffer; + f->bzstream.avail_out = BZBUFSIZE; + f->bzstream.bzalloc = NULL; + f->bzstream.bzfree = NULL; + f->bzstream.opaque = NULL; + bzret = BZ2_bzCompressInit(&f->bzstream, + /* blocksize (1-9) */ + 9, + /* verbosity */ + 0, + /* workFaktor (1-250, 0 = default(30)) */ + 0 + ); + if (bzret == BZ_MEM_ERROR) + return RET_ERROR_OOM; + if (bzret != BZ_OK) { + fprintf(stderr, "Error from libbz2's bzCompressInit: " + "%d\n", bzret); + return RET_ERROR; + } + return RET_OK; +} +#endif + +#ifdef HAVE_LIBLZMA + +static retvalue initxzcompression(struct filetorelease *f) { + lzma_ret lret; + + f->xzoutputbuffer = malloc(XZBUFSIZE); + if (FAILEDTOALLOC(f->xzoutputbuffer)) + return RET_ERROR_OOM; + memset(&f->xzstream, 0, sizeof(f->xzstream)); + lret = lzma_easy_encoder(&f->xzstream, 9, LZMA_CHECK_CRC64); + if (lret == LZMA_MEM_ERROR) + return RET_ERROR_OOM; + if (lret != LZMA_OK) { + fprintf(stderr, "Error from liblzma's lzma_easy_encoder: " + "%d\n", lret); + return RET_ERROR; + } + return RET_OK; +} +#endif + + +static const char * const ics[ic_count] = { "", ".gz" +#ifdef HAVE_LIBBZ2 + , ".bz2" +#endif +#ifdef HAVE_LIBLZMA + , ".xz" +#endif +}; + +static inline retvalue setfilename(struct filetorelease *n, const char *relfilename, /*@null@*/const char *symlinkas, enum indexcompression ic) { + n->f[ic].relativefilename = mprintf("%s%s", relfilename, ics[ic]); + if (FAILEDTOALLOC(n->f[ic].relativefilename)) + return RET_ERROR_OOM; + if (symlinkas == NULL) + return RET_OK; + /* symlink creation fails horrible if the symlink is not in the base + * directory */ + assert (strchr(symlinkas, '/') == NULL); + n->f[ic].symlinkas = mprintf("%s%s", symlinkas, ics[ic]); + if (FAILEDTOALLOC(n->f[ic].symlinkas)) + return RET_ERROR_OOM; + return RET_OK; +} + +static inline void warnfilename(struct release *release, const char *relfilename, enum indexcompression ic) { + char *fullfilename; + + if (IGNORABLE(oldfile)) + return; + + fullfilename = mprintf("%s/%s%s", release->dirofdist, + relfilename, ics[ic]); + if (FAILEDTOALLOC(fullfilename)) + return; + if (isanyfile(fullfilename)) { + fprintf(stderr, "Possibly left over file '%s'.\n", + fullfilename); + if (!ignored[IGN_oldfile]) { + fputs("You might want to delete it or use --ignore=oldfile to no longer get this message.\n", stderr); + ignored[IGN_oldfile] = true; + } + } + free(fullfilename); +} + +static retvalue startfile(struct release *release, const char *filename, /*@null@*/const char *symlinkas, compressionset compressions, bool usecache, struct filetorelease **file) { + struct filetorelease *n; + enum indexcompression i; + + if (usecache) { + retvalue r = release_usecached(release, filename, compressions); + if (r != RET_NOTHING) { + if (RET_IS_OK(r)) + return RET_NOTHING; + return r; + } + } + + n = zNEW(struct filetorelease); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + n->buffer = malloc(INPUT_BUFFER_SIZE); + if (FAILEDTOALLOC(n->buffer)) { + release_abortfile(n); + return RET_ERROR_OOM; + } + for (i = ic_uncompressed ; i < ic_count ; i ++) { + n->f[i].fd = -1; + } + if ((compressions & IC_FLAG(ic_uncompressed)) != 0) { + retvalue r; + + r = setfilename(n, filename, symlinkas, ic_uncompressed); + if (!RET_WAS_ERROR(r)) + r = openfile(release->dirofdist, &n->f[ic_uncompressed]); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + } else { + /* the uncompressed file always shows up in Release */ + n->f[ic_uncompressed].relativefilename = strdup(filename); + if (FAILEDTOALLOC(n->f[ic_uncompressed].relativefilename)) { + release_abortfile(n); + return RET_ERROR_OOM; + } + } + + if ((compressions & IC_FLAG(ic_gzip)) != 0) { + retvalue r; + + r = setfilename(n, filename, symlinkas, ic_gzip); + if (!RET_WAS_ERROR(r)) + r = openfile(release->dirofdist, &n->f[ic_gzip]); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + checksumscontext_init(&n->f[ic_gzip].context); + r = initgzcompression(n); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + } +#ifdef HAVE_LIBBZ2 + if ((compressions & IC_FLAG(ic_bzip2)) != 0) { + retvalue r; + r = setfilename(n, filename, symlinkas, ic_bzip2); + if (!RET_WAS_ERROR(r)) + r = openfile(release->dirofdist, &n->f[ic_bzip2]); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + checksumscontext_init(&n->f[ic_bzip2].context); + r = initbzcompression(n); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + } +#endif +#ifdef HAVE_LIBLZMA + if ((compressions & IC_FLAG(ic_xz)) != 0) { + retvalue r; + r = setfilename(n, filename, symlinkas, ic_xz); + if (!RET_WAS_ERROR(r)) + r = openfile(release->dirofdist, &n->f[ic_xz]); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + checksumscontext_init(&n->f[ic_xz].context); + r = initxzcompression(n); + if (RET_WAS_ERROR(r)) { + release_abortfile(n); + return r; + } + } +#endif + checksumscontext_init(&n->f[ic_uncompressed].context); + *file = n; + return RET_OK; +} + +retvalue release_startfile(struct release *release, const char *filename, compressionset compressions, bool usecache, struct filetorelease **file) { + return startfile(release, filename, NULL, compressions, usecache, file); +} + +retvalue release_startlinkedfile(struct release *release, const char *filename, const char *symlinkas, compressionset compressions, bool usecache, struct filetorelease **file) { + return startfile(release, filename, symlinkas, compressions, usecache, file); +} + +void release_warnoldfileorlink(struct release *release, const char *filename, compressionset compressions) { + enum indexcompression i; + + for (i = ic_uncompressed ; i < ic_count ; i ++) + if ((compressions & IC_FLAG(i)) != 0) + warnfilename(release, filename, i); +} + +static inline char *calc_relative_path(const char *target, const char *linkname) { + size_t t_len, l_len, common_len, len; + const char *t, *l; + int depth; + char *n, *p; + + t_len = strlen(target); + l_len = strlen(linkname); + + t = target; l = linkname; common_len = 0; + while (*t == *l && *t != '\0') { + if (*t == '/') + common_len = (t - target) + 1; + t++; + l++; + } + depth = 0; + while (*l != '\0') { + if (*l++ == '/') + depth++; + } + assert (common_len <= t_len && common_len <= l_len && + memcmp(target, linkname, common_len) == 0); + len = 3 * depth + t_len - common_len; + + n = malloc(len + 1); + if (FAILEDTOALLOC(n)) + return NULL; + p = n; + while (depth > 0) { + memcpy(p, "../", 3); + p += 3; + } + memcpy(p, target + common_len, 1 + t_len - common_len); + p += t_len - common_len; + assert ((size_t)(p-n) == len); + return n; +} + +static retvalue releasefile(struct release *release, struct openfile *f) { + struct checksums *checksums; + retvalue r; + + if (f->relativefilename == NULL) { + assert (f->fullfinalfilename == NULL); + assert (f->fulltemporaryfilename == NULL); + return RET_NOTHING; + } + assert((f->fullfinalfilename == NULL + && f->fulltemporaryfilename == NULL) + || (f->fullfinalfilename != NULL + && f->fulltemporaryfilename != NULL)); + + r = checksums_from_context(&checksums, &f->context); + if (RET_WAS_ERROR(r)) + return r; + if (f->symlinkas) { + char *symlinktarget = calc_relative_path(f->relativefilename, + f->symlinkas); + if (FAILEDTOALLOC(symlinktarget)) + return RET_ERROR_OOM; + r = release_addsymlink(release, f->symlinkas, + symlinktarget); + f->symlinkas = NULL; + if (RET_WAS_ERROR(r)) + return r; + } + + r = newreleaseentry(release, f->relativefilename, checksums, + f->fullfinalfilename, + f->fulltemporaryfilename, + NULL); + f->relativefilename = NULL; + f->fullfinalfilename = NULL; + f->fulltemporaryfilename = NULL; + return r; +} + +static retvalue writegz(struct filetorelease *f) { + int zret; + + assert (f->f[ic_gzip].fd >= 0); + + f->gzstream.next_in = f->buffer; + f->gzstream.avail_in = INPUT_BUFFER_SIZE; + + do { + f->gzstream.next_out = f->gzoutputbuffer + f->gz_waiting_bytes; + f->gzstream.avail_out = GZBUFSIZE - f->gz_waiting_bytes; + + zret = deflate(&f->gzstream, Z_NO_FLUSH); + f->gz_waiting_bytes = GZBUFSIZE - f->gzstream.avail_out; + + if ((zret == Z_OK && f->gz_waiting_bytes >= GZBUFSIZE / 2) + || zret == Z_BUF_ERROR) { + retvalue r; + /* there should be anything to write, otherwise + * better break to avoid an infinite loop */ + if (f->gz_waiting_bytes == 0) + break; + r = writetofile(&f->f[ic_gzip], + f->gzoutputbuffer, f->gz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->gz_waiting_bytes = 0; + } + /* as we start with some data to process, Z_BUF_ERROR + * should only happen when no output is possible, as that + * gets possible again it should finally produce more output + * and return Z_OK and always terminate. Hopefully... */ + } while (zret == Z_BUF_ERROR + || (zret == Z_OK && f->gzstream.avail_in != 0)); + + f->gzstream.next_in = NULL; + f->gzstream.avail_in = 0; + + if (zret != Z_OK) { + if (f->gzstream.msg == NULL) { + fprintf(stderr, "Error from zlib's deflate: " + "unknown(%d)\n", zret); + } else { + fprintf(stderr, "Error from zlib's deflate: %s\n", + f->gzstream.msg); + } + return RET_ERROR; + } + return RET_OK; +} + +static retvalue finishgz(struct filetorelease *f) { + int zret; + + assert (f->f[ic_gzip].fd >= 0); + + f->gzstream.next_in = f->buffer; + f->gzstream.avail_in = f->waiting_bytes; + + do { + f->gzstream.next_out = f->gzoutputbuffer + f->gz_waiting_bytes; + f->gzstream.avail_out = GZBUFSIZE - f->gz_waiting_bytes; + + zret = deflate(&f->gzstream, Z_FINISH); + f->gz_waiting_bytes = GZBUFSIZE - f->gzstream.avail_out; + + if (zret == Z_OK || zret == Z_STREAM_END + || zret == Z_BUF_ERROR) { + retvalue r; + if (f->gz_waiting_bytes == 0) { + if (zret != Z_STREAM_END) { + fprintf(stderr, +"Unexpected buffer error after deflate (%d)\n", zret); + return RET_ERROR; + } + break; + } + r = writetofile(&f->f[ic_gzip], + f->gzoutputbuffer, f->gz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->gz_waiting_bytes = 0; + } + /* see above */ + } while (zret == Z_BUF_ERROR || zret == Z_OK); + + if (zret != Z_STREAM_END) { + if (f->gzstream.msg == NULL) { + fprintf(stderr, "Error from zlib's deflate: " + "unknown(%d)\n", zret); + } else { + fprintf(stderr, "Error from zlib's deflate: %s\n", + f->gzstream.msg); + } + return RET_ERROR; + } + + zret = deflateEnd(&f->gzstream); + /* to avoid deflateEnd called again */ + f->gzstream.next_out = NULL; + if (zret != Z_OK) { + if (f->gzstream.msg == NULL) { + fprintf(stderr, "Error from zlib's deflateEnd: " + "unknown(%d)\n", zret); + } else { + fprintf(stderr, "Error from zlib's deflateEnd: %s\n", + f->gzstream.msg); + } + return RET_ERROR; + } + + + return RET_OK; +} + +#ifdef HAVE_LIBBZ2 + +static retvalue writebz(struct filetorelease *f) { + int bzret; + + assert (f->f[ic_bzip2].fd >= 0); + + f->bzstream.next_in = (char*)f->buffer; + f->bzstream.avail_in = INPUT_BUFFER_SIZE; + + do { + f->bzstream.next_out = f->bzoutputbuffer + f->bz_waiting_bytes; + f->bzstream.avail_out = BZBUFSIZE - f->bz_waiting_bytes; + + bzret = BZ2_bzCompress(&f->bzstream, BZ_RUN); + f->bz_waiting_bytes = BZBUFSIZE - f->bzstream.avail_out; + + if (bzret == BZ_RUN_OK && + f->bz_waiting_bytes >= BZBUFSIZE / 2) { + retvalue r; + assert (f->bz_waiting_bytes > 0); + r = writetofile(&f->f[ic_bzip2], + (const unsigned char *)f->bzoutputbuffer, + f->bz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->bz_waiting_bytes = 0; + } + } while (bzret == BZ_RUN_OK && f->bzstream.avail_in != 0); + + f->bzstream.next_in = NULL; + f->bzstream.avail_in = 0; + + if (bzret != BZ_RUN_OK) { + fprintf(stderr, "Error from libbz2's bzCompress: " + "%d\n", bzret); + return RET_ERROR; + } + return RET_OK; +} + +static retvalue finishbz(struct filetorelease *f) { + int bzret; + + assert (f->f[ic_bzip2].fd >= 0); + + f->bzstream.next_in = (char*)f->buffer; + f->bzstream.avail_in = f->waiting_bytes; + + do { + f->bzstream.next_out = f->bzoutputbuffer + f->bz_waiting_bytes; + f->bzstream.avail_out = BZBUFSIZE - f->bz_waiting_bytes; + + bzret = BZ2_bzCompress(&f->bzstream, BZ_FINISH); + f->bz_waiting_bytes = BZBUFSIZE - f->bzstream.avail_out; + + /* BZ_RUN_OK most likely is not possible here, but BZ_FINISH_OK + * is returned when it cannot be finished in one step. + * but better safe then sorry... */ + if ((bzret == BZ_RUN_OK || bzret == BZ_FINISH_OK + || bzret == BZ_STREAM_END) + && f->bz_waiting_bytes > 0) { + retvalue r; + r = writetofile(&f->f[ic_bzip2], + (const unsigned char*)f->bzoutputbuffer, + f->bz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->bz_waiting_bytes = 0; + } + } while (bzret == BZ_RUN_OK || bzret == BZ_FINISH_OK); + + if (bzret != BZ_STREAM_END) { + fprintf(stderr, "Error from bzlib's bzCompress: " + "%d\n", bzret); + return RET_ERROR; + } + + bzret = BZ2_bzCompressEnd(&f->bzstream); + /* to avoid bzCompressEnd called again */ + f->bzstream.next_out = NULL; + if (bzret != BZ_OK) { + fprintf(stderr, "Error from libbz2's bzCompressEnd: " + "%d\n", bzret); + return RET_ERROR; + } + + return RET_OK; +} +#endif + +#ifdef HAVE_LIBLZMA + +static retvalue writexz(struct filetorelease *f) { + lzma_ret xzret; + + assert (f->f[ic_xz].fd >= 0); + + f->xzstream.next_in = f->buffer; + f->xzstream.avail_in = INPUT_BUFFER_SIZE; + + do { + f->xzstream.next_out = f->xzoutputbuffer + f->xz_waiting_bytes; + f->xzstream.avail_out = XZBUFSIZE - f->xz_waiting_bytes; + + xzret = lzma_code(&f->xzstream, LZMA_RUN); + f->xz_waiting_bytes = XZBUFSIZE - f->xzstream.avail_out; + + if (xzret == LZMA_OK && + f->xz_waiting_bytes >= XZBUFSIZE / 2) { + retvalue r; + assert (f->xz_waiting_bytes > 0); + r = writetofile(&f->f[ic_xz], + (const unsigned char *)f->xzoutputbuffer, + f->xz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->xz_waiting_bytes = 0; + } + } while (xzret == LZMA_OK && f->xzstream.avail_in != 0); + + f->xzstream.next_in = NULL; + f->xzstream.avail_in = 0; + + if (xzret != LZMA_OK) { + fprintf(stderr, "Error from liblzma's lzma_code: " + "%d\n", xzret); + return RET_ERROR; + } + return RET_OK; +} + +static retvalue finishxz(struct filetorelease *f) { + lzma_ret xzret; + + assert (f->f[ic_xz].fd >= 0); + + f->xzstream.next_in = f->buffer; + f->xzstream.avail_in = f->waiting_bytes; + + do { + f->xzstream.next_out = f->xzoutputbuffer + f->xz_waiting_bytes; + f->xzstream.avail_out = XZBUFSIZE - f->xz_waiting_bytes; + + xzret = lzma_code(&f->xzstream, LZMA_FINISH); + f->xz_waiting_bytes = XZBUFSIZE - f->xzstream.avail_out; + + if ((xzret == LZMA_OK || xzret == LZMA_STREAM_END) + && f->xz_waiting_bytes > 0) { + retvalue r; + r = writetofile(&f->f[ic_xz], + (const unsigned char*)f->xzoutputbuffer, + f->xz_waiting_bytes); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + f->xz_waiting_bytes = 0; + } + } while (xzret == LZMA_OK); + + if (xzret != LZMA_STREAM_END) { + fprintf(stderr, "Error from liblzma's lzma_code: " + "%d\n", xzret); + return RET_ERROR; + } + assert (f->xz_waiting_bytes == 0); + + lzma_end(&f->xzstream); + free(f->xzoutputbuffer); + f->xzoutputbuffer = NULL; + + return RET_OK; +} +#endif + +retvalue release_finishfile(struct release *release, struct filetorelease *file) { + retvalue result, r; + enum indexcompression i; + + if (RET_WAS_ERROR(file->state)) { + r = file->state; + release_abortfile(file); + return r; + } + + r = writetofile(&file->f[ic_uncompressed], + file->buffer, file->waiting_bytes); + if (RET_WAS_ERROR(r)) { + release_abortfile(file); + return r; + } + if (file->f[ic_uncompressed].fd >= 0) { + if (close(file->f[ic_uncompressed].fd) != 0) { + int e = errno; + file->f[ic_uncompressed].fd = -1; + release_abortfile(file); + return RET_ERRNO(e); + } + file->f[ic_uncompressed].fd = -1; + } + if (file->f[ic_gzip].fd >= 0) { + r = finishgz(file); + if (RET_WAS_ERROR(r)) { + release_abortfile(file); + return r; + } + if (close(file->f[ic_gzip].fd) != 0) { + int e = errno; + file->f[ic_gzip].fd = -1; + release_abortfile(file); + return RET_ERRNO(e); + } + file->f[ic_gzip].fd = -1; + } +#ifdef HAVE_LIBBZ2 + if (file->f[ic_bzip2].fd >= 0) { + r = finishbz(file); + if (RET_WAS_ERROR(r)) { + release_abortfile(file); + return r; + } + if (close(file->f[ic_bzip2].fd) != 0) { + int e = errno; + file->f[ic_bzip2].fd = -1; + release_abortfile(file); + return RET_ERRNO(e); + } + file->f[ic_bzip2].fd = -1; + } +#endif +#ifdef HAVE_LIBLZMA + if (file->f[ic_xz].fd >= 0) { + r = finishxz(file); + if (RET_WAS_ERROR(r)) { + release_abortfile(file); + return r; + } + if (close(file->f[ic_xz].fd) != 0) { + int e = errno; + file->f[ic_xz].fd = -1; + release_abortfile(file); + return RET_ERRNO(e); + } + file->f[ic_xz].fd = -1; + } +#endif + release->new = true; + result = RET_OK; + + for (i = ic_uncompressed ; i < ic_count ; i++) { + r = releasefile(release, &file->f[i]); + if (RET_WAS_ERROR(r)) { + release_abortfile(file); + return r; + } + RET_UPDATE(result, r); + } + free(file->buffer); + free(file->gzoutputbuffer); +#ifdef HAVE_LIBBZ2 + free(file->bzoutputbuffer); +#endif +#ifdef HAVE_LIBLZMA + assert(file->xzoutputbuffer == NULL); +#endif + free(file); + return result; +} + +static retvalue release_processbuffer(struct filetorelease *file) { + retvalue result, r; + + result = RET_OK; + assert (file->waiting_bytes == INPUT_BUFFER_SIZE); + + /* always call this - even if there is no uncompressed file + * to generate - so that checksums are calculated */ + r = writetofile(&file->f[ic_uncompressed], + file->buffer, INPUT_BUFFER_SIZE); + RET_UPDATE(result, r); + + if (file->f[ic_gzip].relativefilename != NULL) { + r = writegz(file); + RET_UPDATE(result, r); + } + RET_UPDATE(file->state, result); +#ifdef HAVE_LIBBZ2 + if (file->f[ic_bzip2].relativefilename != NULL) { + r = writebz(file); + RET_UPDATE(result, r); + } + RET_UPDATE(file->state, result); +#endif +#ifdef HAVE_LIBLZMA + if (file->f[ic_xz].relativefilename != NULL) { + r = writexz(file); + RET_UPDATE(result, r); + } + RET_UPDATE(file->state, result); +#endif + return result; +} + +retvalue release_writedata(struct filetorelease *file, const char *data, size_t len) { + retvalue result, r; + size_t free_bytes; + + result = RET_OK; + /* move stuff into buffer, so stuff is not processed byte by byte */ + free_bytes = INPUT_BUFFER_SIZE - file->waiting_bytes; + if (len < free_bytes) { + memcpy(file->buffer + file->waiting_bytes, data, len); + file->waiting_bytes += len; + assert (file->waiting_bytes < INPUT_BUFFER_SIZE); + return RET_OK; + } + memcpy(file->buffer + file->waiting_bytes, data, free_bytes); + len -= free_bytes; + data += free_bytes; + file->waiting_bytes += free_bytes; + r = release_processbuffer(file); + RET_UPDATE(result, r); + while (len >= INPUT_BUFFER_SIZE) { + /* should not hopefully not happen, as all this copying + * is quite slow... */ + memcpy(file->buffer, data, INPUT_BUFFER_SIZE); + len -= INPUT_BUFFER_SIZE; + data += INPUT_BUFFER_SIZE; + r = release_processbuffer(file); + RET_UPDATE(result, r); + } + memcpy(file->buffer, data, len); + file->waiting_bytes = len; + assert (file->waiting_bytes < INPUT_BUFFER_SIZE); + return result; +} + +/* Generate a "Release"-file for arbitrary directory */ +retvalue release_directorydescription(struct release *release, const struct distribution *distribution, const struct target *target, const char *releasename, bool onlyifneeded) { + retvalue r; + struct filetorelease *f; + char *relfilename; + + relfilename = calc_dirconcat(target->relativedirectory, releasename); + if (FAILEDTOALLOC(relfilename)) + return RET_ERROR_OOM; + r = startfile(release, relfilename, NULL, + IC_FLAG(ic_uncompressed), onlyifneeded, &f); + free(relfilename); + if (RET_WAS_ERROR(r) || r == RET_NOTHING) + return r; + +#define release_writeheader(name, data) \ + if (data != NULL) { \ + (void)release_writestring(f, name ": "); \ + (void)release_writestring(f, data); \ + (void)release_writestring(f, "\n"); \ + } + + release_writeheader("Archive", distribution->suite); + release_writeheader("Version", distribution->version); + release_writeheader("Component", + atoms_components[target->component]); + release_writeheader("Origin", distribution->origin); + release_writeheader("Label", distribution->label); + release_writeheader("Architecture", + atoms_architectures[target->architecture]); + release_writeheader("NotAutomatic", distribution->notautomatic); + release_writeheader("ButAutomaticUpgrades", + distribution->butautomaticupgrades); + release_writeheader("Description", distribution->description); +#undef release_writeheader + r = release_finishfile(release, f); + return r; +} + +static retvalue storechecksums(struct release *release) { + struct release_entry *file; + retvalue result, r; + const char *combinedchecksum; + /* size including trailing '\0' character: */ + size_t len; + + result = RET_OK; + + for (file = release->files ; file != NULL ; file = file->next) { + + assert (file->relativefilename != NULL); + + r = table_deleterecord(release->cachedb, + file->relativefilename, true); + if (RET_WAS_ERROR(r)) + return r; + + if (file->checksums == NULL) + continue; + + r = checksums_getcombined(file->checksums, &combinedchecksum, &len); + RET_UPDATE(result, r); + if (!RET_IS_OK(r)) + continue; + + r = table_adduniqsizedrecord(release->cachedb, + file->relativefilename, combinedchecksum, len+1, + false, false); + RET_UPDATE(result, r); + } + return result; +} + +static inline bool componentneedsfake(const char *cn, const struct release *release) { + if (release->fakecomponentprefix == NULL) + return false; + if (strncmp(cn, release->fakecomponentprefix, + release->fakecomponentprefixlen) != 0) + return true; + return cn[release->fakecomponentprefixlen] != '/'; +} + + +static struct release_entry *newspecialreleaseentry(struct release *release, const char *relativefilename) { + struct release_entry *n, *p; + + assert (relativefilename != NULL); + n = zNEW(struct release_entry); + if (FAILEDTOALLOC(n)) + return NULL; + n->relativefilename = strdup(relativefilename); + n->fullfinalfilename = calc_dirconcat(release->dirofdist, + relativefilename); + if (!FAILEDTOALLOC(n->fullfinalfilename)) + n->fulltemporaryfilename = mprintf("%s.new", + n->fullfinalfilename); + if (FAILEDTOALLOC(n->relativefilename) + || FAILEDTOALLOC(n->fullfinalfilename) + || FAILEDTOALLOC(n->fulltemporaryfilename)) { + release_freeentry(n); + return NULL; + } + if (release->files == NULL) + release->files = n; + else { + p = release->files; + while (p->next != NULL) + p = p->next; + p->next = n; + } + return n; +} +static void omitunusedspecialreleaseentry(struct release *release, struct release_entry *e) { + struct release_entry **p; + + if (e->fulltemporaryfilename != NULL) + /* new file available, nothing to omit */ + return; + if (isregularfile(e->fullfinalfilename)) + /* this will be deleted, everything fine */ + return; + p = &release->files; + while (*p != NULL && *p != e) + p = &(*p)->next; + if (*p != e) { + assert (*p == e); + return; + } + *p = e->next; + release_freeentry(e); +} + +/* Generate a main "Release" file for a distribution */ +retvalue release_prepare(struct release *release, struct distribution *distribution, bool onlyifneeded) { + size_t s; + retvalue r; + char buffer[100], untilbuffer[100]; + time_t t; + struct tm *gmt; + struct release_entry *file; + enum checksumtype cs; + int i; + static const char * const release_checksum_headers[cs_hashCOUNT] = + { "MD5Sum:\n", "SHA1:\n", "SHA256:\n", "SHA512:\n" }; + struct release_entry *plainentry, *signedentry, *detachedentry; + + // TODO: check for existence of Release file here first? + if (onlyifneeded && !release->new) { + return RET_NOTHING; + } + + (void)time(&t); + gmt = gmtime(&t); + if (FAILEDTOALLOC(gmt)) + return RET_ERROR_OOM; + s=strftime(buffer, 99, "%a, %d %b %Y %H:%M:%S UTC", gmt); + if (s == 0 || s >= 99) { + fprintf(stderr, "strftime is doing strange things...\n"); + return RET_ERROR; + } + if (distribution->validfor > 0) { + t += distribution->validfor; + gmt = gmtime(&t); + if (FAILEDTOALLOC(gmt)) + return RET_ERROR_OOM; + s=strftime(untilbuffer, 99, "%a, %d %b %Y %H:%M:%S UTC", gmt); + if (s == 0 || s >= 99) { + fprintf(stderr, +"strftime is doing strange things...\n"); + return RET_ERROR; + } + } + plainentry = newspecialreleaseentry(release, "Release"); + if (FAILEDTOALLOC(plainentry)) + return RET_ERROR_OOM; + signedentry = newspecialreleaseentry(release, "InRelease"); + if (FAILEDTOALLOC(signedentry)) + return RET_ERROR_OOM; + detachedentry = newspecialreleaseentry(release, "Release.gpg"); + if (FAILEDTOALLOC(signedentry)) + return RET_ERROR_OOM; + r = signature_startsignedfile(&release->signedfile); + if (RET_WAS_ERROR(r)) + return r; +#define writestring(s) signedfile_write(release->signedfile, s, strlen(s)) +#define writechar(c) {char __c = c ; signedfile_write(release->signedfile, &__c, 1); } + + if (distribution->origin != NULL) { + writestring("Origin: "); + writestring(distribution->origin); + writechar('\n'); + } + if (distribution->label != NULL) { + writestring("Label: "); + writestring(distribution->label); + writechar('\n'); + } + if (release->fakesuite != NULL) { + writestring("Suite: "); + writestring(release->fakesuite); + writechar('\n'); + } else if (distribution->suite != NULL) { + writestring("Suite: "); + writestring(distribution->suite); + writechar('\n'); + } + writestring("Codename: "); + if (release->fakecodename != NULL) + writestring(release->fakecodename); + else + writestring(distribution->codename); + if (distribution->version != NULL) { + writestring("\nVersion: "); + writestring(distribution->version); + } + writestring("\nDate: "); + writestring(buffer); + if (distribution->validfor > 0) { + writestring("\nValid-Until: "); + writestring(untilbuffer); + } + writestring("\nArchitectures:"); + for (i = 0 ; i < distribution->architectures.count ; i++) { + architecture_t a = distribution->architectures.atoms[i]; + + /* Debian's topmost Release files do not list it, + * so we won't either */ + if (a == architecture_source) + continue; + writechar(' '); + writestring(atoms_architectures[a]); + } + writestring("\nComponents:"); + for (i = 0 ; i < distribution->components.count ; i++) { + component_t c = distribution->components.atoms[i]; + const char *cn = atoms_components[c]; + + writechar(' '); + if (componentneedsfake(cn, release)) { + writestring(release->fakecomponentprefix); + writechar('/'); + } + writestring(cn); + } + if (distribution->description != NULL) { + writestring("\nDescription: "); + writestring(distribution->description); + } + if (distribution->signed_by != NULL) { + writestring("\nSigned-By: "); + writestring(distribution->signed_by); + } + if (distribution->notautomatic != NULL) { + writestring("\nNotAutomatic: "); + writestring(distribution->notautomatic); + } + if (distribution->butautomaticupgrades != NULL) { + writestring("\nButAutomaticUpgrades: "); + writestring(distribution->butautomaticupgrades); + } + writechar('\n'); + + for (cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++) { + assert (release_checksum_headers[cs] != NULL); + writestring(release_checksum_headers[cs]); + for (file = release->files ; file != NULL ; file = file->next) { + const char *hash, *size; + size_t hashlen, sizelen; + if (file->checksums == NULL) + continue; + if (!checksums_gethashpart(file->checksums, cs, + &hash, &hashlen, &size, &sizelen)) + continue; + writechar(' '); + signedfile_write(release->signedfile, hash, hashlen); + writechar(' '); + signedfile_write(release->signedfile, size, sizelen); + writechar(' '); + writestring(file->relativefilename); + writechar('\n'); + } + } + r = signedfile_create(release->signedfile, + plainentry->fulltemporaryfilename, + &signedentry->fulltemporaryfilename, + &detachedentry->fulltemporaryfilename, + &distribution->signwith, !global.keeptemporaries); + if (RET_WAS_ERROR(r)) { + signedfile_free(release->signedfile); + release->signedfile = NULL; + return r; + } + omitunusedspecialreleaseentry(release, signedentry); + omitunusedspecialreleaseentry(release, detachedentry); + return RET_OK; +} + +static inline void release_toouthook(struct release *release, struct distribution *distribution) { + struct release_entry *file; + char *reldir; + + if (release->snapshotname != NULL) { + reldir = mprintf("dists/%s/snapshots/%s", + distribution->codename, release->snapshotname); + if (FAILEDTOALLOC(reldir)) + return; + outhook_send("BEGIN-SNAPSHOT", distribution->codename, + reldir, release->snapshotname); + } else { + reldir = mprintf("dists/%s", distribution->codename); + if (FAILEDTOALLOC(reldir)) + return; + outhook_send("BEGIN-DISTRIBUTION", distribution->codename, + reldir, distribution->suite); + } + + for (file = release->files ; file != NULL ; file = file->next) { + /* relf chks ffn ftfn symt + * name chks NULL NULL NULL: added old filename or virtual file + * name chks file NULL NULL: renamed new file and published + * name NULL file NULL NULL: renamed new file + * name NULL NULL NULL NULL: deleted file + * name NULL NULL NULL file: created symlink */ + + /* should already be in place: */ + assert (file->fulltemporaryfilename == NULL); + /* symlinks are special: */ + if (file->symlinktarget != NULL) { + outhook_send("DISTSYMLINK", + reldir, + file->relativefilename, + file->symlinktarget); + } else if (file->fullfinalfilename != NULL) { + outhook_send("DISTFILE", reldir, + file->relativefilename, + file->fullfinalfilename); + } else if (file->checksums == NULL){ + outhook_send("DISTDELETE", reldir, + file->relativefilename, NULL); + } + /* would be nice to distinguish kept and virtual files... */ + } + + if (release->snapshotname != NULL) { + outhook_send("END-SNAPSHOT", distribution->codename, + reldir, release->snapshotname); + } else { + outhook_send("END-DISTRIBUTION", distribution->codename, + reldir, distribution->suite); + } + free(reldir); +} + +/* Generate a main "Release" file for a distribution */ +retvalue release_finish(/*@only@*/struct release *release, struct distribution *distribution) { + retvalue result, r; + int e; + struct release_entry *file; + bool somethingwasdone; + + somethingwasdone = false; + result = RET_OK; + + for (file = release->files ; file != NULL ; file = file->next) { + assert (file->relativefilename != NULL); + if (file->checksums == NULL + && file->fullfinalfilename != NULL + && file->fulltemporaryfilename == NULL + && file->symlinktarget == NULL) { + e = unlink(file->fullfinalfilename); + if (e < 0) { + e = errno; + fprintf(stderr, +"Error %d deleting %s: %s. (Will be ignored)\n", + e, file->fullfinalfilename, + strerror(e)); + } + free(file->fullfinalfilename); + file->fullfinalfilename = NULL; + } else if (file->fulltemporaryfilename != NULL) { + assert (file->fullfinalfilename != NULL); + assert (file->symlinktarget == NULL); + + e = rename(file->fulltemporaryfilename, + file->fullfinalfilename); + if (e < 0) { + e = errno; + fprintf(stderr, +"Error %d moving %s to %s: %s!\n", + e, file->fulltemporaryfilename, + file->fullfinalfilename, + strerror(e)); + r = RET_ERRNO(e); + /* after something was done, do not stop + * but try to do as much as possible */ + if (!somethingwasdone) { + release_free(release); + return r; + } + RET_UPDATE(result, r); + } else { + somethingwasdone = true; + free(file->fulltemporaryfilename); + file->fulltemporaryfilename = NULL; + } + } else if (file->symlinktarget != NULL) { + assert (file->fullfinalfilename != NULL); + + (void)unlink(file->fullfinalfilename); + e = symlink(file->symlinktarget, file->fullfinalfilename); + if (e != 0) { + e = errno; + fprintf(stderr, +"Error %d creating symlink '%s' -> '%s': %s.\n", + e, file->fullfinalfilename, + file->symlinktarget, + strerror(e)); + r = RET_ERRNO(e); + /* after something was done, do not stop + * but try to do as much as possible */ + if (!somethingwasdone) { + release_free(release); + return r; + } + RET_UPDATE(result, r); + } + } + } + if (RET_WAS_ERROR(result) && somethingwasdone) { + fprintf(stderr, +"ATTENTION: some files were already moved to place, some could not be.\n" +"The generated index files for %s might be in a inconsistent state\n" +"and currently not useable! You should remove the reason for the failure\n" +"(most likely bad access permissions) and export the affected distributions\n" +"manually (via reprepro export codenames) as soon as possible!\n", + distribution->codename); + } + if (release->cachedb != NULL) { + // TODO: split this in removing before and adding later? + // remember which file were changed in case of error, so + // only those are changed... + /* now update the cache database, + * so we find those the next time */ + r = storechecksums(release); + RET_UPDATE(result, r); + + r = table_close(release->cachedb); + release->cachedb = NULL; + RET_ENDUPDATE(result, r); + } + release_toouthook(release, distribution); + /* free everything */ + release_free(release); + return result; +} + +retvalue release_mkdir(struct release *release, const char *relativedirectory) { + char *dirname; + retvalue r; + + dirname = calc_dirconcat(release->dirofdist, relativedirectory); + if (FAILEDTOALLOC(dirname)) + return RET_ERROR_OOM; + // TODO: in some far future, remember which dirs were created so that + r = dirs_make_recursive(dirname); + free(dirname); + return r; +} |