summaryrefslogtreecommitdiffstats
path: root/exports.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--exports.c548
1 files changed, 548 insertions, 0 deletions
diff --git a/exports.c b/exports.c
new file mode 100644
index 0000000..aef8a85
--- /dev/null
+++ b/exports.c
@@ -0,0 +1,548 @@
+/* This file is part of "reprepro"
+ * Copyright (C) 2005,2007,2008,2009,2016 Bernhard R. Link
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+#include <config.h>
+
+#include <errno.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "error.h"
+#include "mprintf.h"
+#include "strlist.h"
+#include "names.h"
+#include "dirs.h"
+#include "database.h"
+#include "target.h"
+#include "exports.h"
+#include "configparser.h"
+#include "filecntl.h"
+#include "hooks.h"
+#include "package.h"
+
+static const char *exportdescription(const struct exportmode *mode, char *buffer, size_t buffersize) {
+ char *result = buffer;
+ enum indexcompression ic;
+ static const char* compression_names[ic_count] = {
+ "uncompressed"
+ ,"gzipped"
+#ifdef HAVE_LIBBZ2
+ ,"bzip2ed"
+#endif
+#ifdef HAVE_LIBLZMA
+ ,"xzed"
+#endif
+ };
+ bool needcomma = false,
+ needellipsis = false;
+
+ assert (buffersize > 50);
+ *buffer++ = ' '; buffersize--;
+ *buffer++ = '('; buffersize--;
+ for (ic = ic_first ; ic < ic_count ; ic++) {
+ if ((mode->compressions & IC_FLAG(ic)) != 0) {
+ size_t l = strlen(compression_names[ic]);
+ assert (buffersize > l+3);
+ if (needcomma) {
+ *buffer++ = ','; buffersize--;
+ }
+ memcpy(buffer, compression_names[ic], l);
+ buffer += l; buffersize -= l;
+ needcomma = true;
+ }
+ }
+ /* should be long enough for the previous things in all cases */
+ assert (buffersize > 10);
+ if (mode->hooks.count > 0) {
+ int i;
+
+ if (needcomma) {
+ *buffer++ = ','; buffersize--;
+ }
+ strcpy(buffer, "script: ");
+ buffer += 8; buffersize -= 8;
+ needcomma = false;
+
+ for (i = 0 ; i < mode->hooks.count ; i++) {
+ const char *hook = dirs_basename(mode->hooks.values[i]);
+ size_t l = strlen(hook);
+
+ if (buffersize < 6) {
+ needellipsis = true;
+ break;
+ }
+ if (needcomma) {
+ *buffer++ = ','; buffersize--;
+ }
+
+ if (l > buffersize - 5) {
+ memcpy(buffer, hook, buffersize-5);
+ buffer += (buffersize-5);
+ buffersize -= (buffersize-5);
+ needellipsis = true;
+ break;
+ } else {
+ memcpy(buffer, hook, l);
+ buffer += l; buffersize -= l;
+ assert (buffersize >= 2);
+ }
+ needcomma = true;
+ }
+ }
+ if (needellipsis) {
+ /* moveing backward here is easier than checking above */
+ if (buffersize < 5) {
+ buffer -= (5 - buffersize);
+ buffersize = 5;
+ }
+ *buffer++ = '.'; buffersize--;
+ *buffer++ = '.'; buffersize--;
+ *buffer++ = '.'; buffersize--;
+ }
+ assert (buffersize >= 2);
+ *buffer++ = ')'; buffersize--;
+ *buffer = '\0';
+ return result;
+}
+
+retvalue exportmode_init(/*@out@*/struct exportmode *mode, bool uncompressed, /*@null@*/const char *release, const char *indexfile) {
+ strlist_init(&mode->hooks);
+ mode->compressions = IC_FLAG(ic_gzip) | (uncompressed
+ ? IC_FLAG(ic_uncompressed) : 0);
+ mode->filename = strdup(indexfile);
+ if (FAILEDTOALLOC(mode->filename))
+ return RET_ERROR_OOM;
+ if (release == NULL)
+ mode->release = NULL;
+ else {
+ mode->release = strdup(release);
+ if (FAILEDTOALLOC(mode->release))
+ return RET_ERROR_OOM;
+ }
+ return RET_OK;
+}
+
+// TODO: check for scripts in confdir early...
+retvalue exportmode_set(struct exportmode *mode, struct configiterator *iter) {
+ retvalue r;
+ char *word;
+
+ r = config_getword(iter, &word);
+ if (RET_WAS_ERROR(r))
+ return r;
+ if (r == RET_NOTHING) {
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u: Unexpected end of field!\n"
+"Filename to use for index files (Packages, Sources, ...) missing.\n",
+ config_filename(iter),
+ config_markerline(iter), config_markercolumn(iter));
+ return RET_ERROR_MISSING;
+ }
+ assert (word[0] != '\0');
+
+ if (word[0] == '.') {
+ free(word);
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u: filename for index files expected!\n",
+ config_filename(iter),
+ config_markerline(iter), config_markercolumn(iter));
+ return RET_ERROR;
+ }
+
+ free(mode->release);
+ mode->release = NULL;
+ free(mode->filename);
+ mode->filename = word;
+
+ r = config_getword(iter, &word);
+ if (RET_WAS_ERROR(r))
+ return r;
+ if (r == RET_NOTHING)
+ word = NULL;
+ if (r != RET_NOTHING && word[0] != '.') {
+ assert (word[0] != '\0');
+ mode->release = word;
+ r = config_getword(iter, &word);
+ if (RET_WAS_ERROR(r))
+ return r;
+ }
+ if (r == RET_NOTHING) {
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u: Unexpected end of field!\n"
+"Compression identifiers ('.', '.gz' or '.bz2') missing.\n",
+ config_filename(iter),
+ config_markerline(iter), config_markercolumn(iter));
+ return RET_ERROR;
+ }
+ if (word[0] != '.') {
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u:\n"
+"Compression extension ('.', '.gz' or '.bz2') expected.\n",
+ config_filename(iter),
+ config_markerline(iter), config_markercolumn(iter));
+ free(word);
+ return RET_ERROR;
+ }
+ mode->compressions = 0;
+ while (r != RET_NOTHING && word[0] == '.') {
+ if (word[1] == '\0')
+ mode->compressions |= IC_FLAG(ic_uncompressed);
+ else if (word[1] == 'g' && word[2] == 'z' &&
+ word[3] == '\0')
+ mode->compressions |= IC_FLAG(ic_gzip);
+#ifdef HAVE_LIBBZ2
+ else if (word[1] == 'b' && word[2] == 'z' && word[3] == '2' &&
+ word[4] == '\0')
+ mode->compressions |= IC_FLAG(ic_bzip2);
+#endif
+#ifdef HAVE_LIBLZMA
+ else if (word[1] == 'x' && word[2] == 'z' &&word[3] == '\0')
+ mode->compressions |= IC_FLAG(ic_xz);
+#endif
+ else {
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u:\n"
+"Unsupported compression extension '%s'!\n",
+ config_filename(iter),
+ config_markerline(iter),
+ config_markercolumn(iter),
+ word);
+ free(word);
+ return RET_ERROR;
+ }
+ free(word);
+ r = config_getword(iter, &word);
+ if (RET_WAS_ERROR(r))
+ return r;
+ }
+ while (r != RET_NOTHING) {
+ if (word[0] == '.') {
+ fprintf(stderr,
+"Error parsing %s, line %u, column %u:\n"
+"Scripts starting with dot are forbidden to avoid ambiguity ('%s')!\n"
+"Try to put all compressions first and then all scripts to avoid this.\n",
+ config_filename(iter),
+ config_markerline(iter),
+ config_markercolumn(iter),
+ word);
+ free(word);
+ return RET_ERROR;
+ } else {
+ char *fullfilename = configfile_expandname(word, word);
+ if (FAILEDTOALLOC(fullfilename))
+ return RET_ERROR_OOM;
+ r = strlist_add(&mode->hooks, fullfilename);
+ if (RET_WAS_ERROR(r))
+ return r;
+ }
+ r = config_getword(iter, &word);
+ if (RET_WAS_ERROR(r))
+ return r;
+ }
+ return RET_OK;
+}
+
+static retvalue gotfilename(const char *relname, size_t l, struct release *release) {
+
+ if (l > 12 && memcmp(relname+l-12, ".tobedeleted", 12) == 0) {
+ char *filename;
+
+ filename = strndup(relname, l - 12);
+ if (FAILEDTOALLOC(filename))
+ return RET_ERROR_OOM;
+ return release_adddel(release, filename);
+
+ } else if (l > 4 && memcmp(relname+(l-4), ".new", 4) == 0) {
+ char *filename, *tmpfilename;
+
+ filename = strndup(relname, l - 4);
+ if (FAILEDTOALLOC(filename))
+ return RET_ERROR_OOM;
+ tmpfilename = strndup(relname, l);
+ if (FAILEDTOALLOC(tmpfilename)) {
+ free(filename);
+ return RET_ERROR_OOM;
+ }
+ return release_addnew(release, tmpfilename, filename);
+ } else if (l > 5 && memcmp(relname + (l-5), ".new.", 5) == 0) {
+ char *filename, *tmpfilename;
+
+ filename = strndup(relname, l-5);
+ if (FAILEDTOALLOC(filename))
+ return RET_ERROR_OOM;
+ tmpfilename = strndup(relname, l-1);
+ if (FAILEDTOALLOC(tmpfilename)) {
+ free(filename);
+ return RET_ERROR_OOM;
+ }
+ return release_addsilentnew(release, tmpfilename, filename);
+ } else if (l > 5 && memcmp(relname + (l-5), ".keep", 5) == 0) {
+ return RET_OK;
+ } else {
+ char *filename;
+
+ filename = strndup(relname, l);
+ if (FAILEDTOALLOC(filename))
+ return RET_ERROR_OOM;
+ return release_addold(release, filename);
+ }
+}
+
+static retvalue callexporthook(/*@null@*/const char *hook, const char *relfilename, const char *mode, struct release *release) {
+ pid_t f, c;
+ int status;
+ int io[2];
+ char buffer[1000];
+ int already = 0;
+
+ if (hook == NULL)
+ return RET_NOTHING;
+
+ status = pipe(io);
+ if (status < 0) {
+ int e = errno;
+ fprintf(stderr, "Error %d creating pipe: %s!\n",
+ e, strerror(e));
+ return RET_ERRNO(e);
+ }
+
+ f = fork();
+ if (f < 0) {
+ int e = errno;
+ (void)close(io[0]);
+ (void)close(io[1]);
+ fprintf(stderr, "Error %d while forking for exporthook: %s\n",
+ e, strerror(e));
+ return RET_ERRNO(e);
+ }
+ if (f == 0) {
+ char *reltmpfilename;
+ int e;
+
+ if (dup2(io[1], 3) < 0) {
+ e = errno;
+ fprintf(stderr, "Error %d dup2'ing fd %d to 3: %s\n",
+ e, io[1], strerror(e));
+ exit(255);
+ }
+ /* "Doppelt haelt besser": */
+ if (io[0] != 3)
+ (void)close(io[0]);
+ if (io[1] != 3)
+ (void)close(io[1]);
+ closefrom(4);
+ /* backward compatibility */
+ reltmpfilename = calc_addsuffix(relfilename, "new");
+ if (reltmpfilename == NULL) {
+ exit(255);
+ }
+ sethookenvironment(causingfile, NULL, NULL, NULL);
+ (void)execl(hook, hook, release_dirofdist(release),
+ reltmpfilename, relfilename, mode,
+ ENDOFARGUMENTS);
+ e = errno;
+ fprintf(stderr, "Error %d while executing '%s': %s\n",
+ e, hook, strerror(e));
+ exit(255);
+ }
+ close(io[1]);
+ markcloseonexec(io[0]);
+
+ if (verbose > 6)
+ printf("Called %s '%s' '%s.new' '%s' '%s'\n",
+ hook, release_dirofdist(release),
+ relfilename, relfilename, mode);
+ /* read what comes from the client */
+ while (true) {
+ ssize_t r;
+ int last, j;
+
+ r = read(io[0], buffer + already, 999 - already);
+ if (r < 0) {
+ int e = errno;
+ fprintf(stderr,
+"Error %d reading from exporthook: %s!\n",
+ e, strerror(e));
+ break;
+ }
+
+ already += r;
+ if (r == 0) {
+ buffer[already] = '\0';
+ already++;
+ }
+ last = 0;
+ for (j = 0 ; j < already ; j++) {
+ if (buffer[j] == '\n' || buffer[j] == '\0') {
+ int next = j+1;
+ int e = (j>0)?(j-1):j;
+ retvalue ret;
+
+ while (last < j && xisspace(buffer[last]))
+ last++;
+ if (last >= j) {
+ last = next;
+ continue;
+ }
+ while (xisspace(buffer[e])) {
+ e--;
+ assert (e >= last);
+ }
+
+ ret = gotfilename(buffer + last, e - last + 1,
+ release);
+ if (RET_WAS_ERROR(ret)) {
+ (void)close(io[0]);
+ return ret;
+ }
+ last = next;
+ }
+ }
+ if (last > 0) {
+ if (already > last)
+ memmove(buffer, buffer + last, already - last);
+ already -= last;
+ }
+ if (r == 0)
+ break;
+ }
+ (void)close(io[0]);
+ do {
+ c = waitpid(f, &status, WUNTRACED);
+ if (c < 0) {
+ int e = errno;
+ fprintf(stderr,
+"Error %d while waiting for hook '%s' to finish: %s\n", e, hook, strerror(e));
+ return RET_ERRNO(e);
+ }
+ } while (c != f);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == 0) {
+ if (verbose > 6)
+ printf("Exporthook successfully returned!\n");
+ return RET_OK;
+ } else {
+ fprintf(stderr,
+"Exporthook failed with exitcode %d!\n",
+ (int)WEXITSTATUS(status));
+ return RET_ERROR;
+ }
+ } else if (WIFSIGNALED(status)) {
+ fprintf(stderr, "Exporthook killed by signal %d!\n",
+ (int)(WTERMSIG(status)));
+ return RET_ERROR;
+ } else {
+ fprintf(stderr,
+"Exporthook terminated abnormally. (status is %x)!\n",
+ status);
+ return RET_ERROR;
+ }
+}
+
+retvalue export_target(const char *relativedir, struct target *target, const struct exportmode *exportmode, struct release *release, bool onlyifmissing, bool snapshot) {
+ retvalue r;
+ struct filetorelease *file;
+ const char *status;
+ char *relfilename;
+ char buffer[100];
+ struct package_cursor iterator;
+
+ relfilename = calc_dirconcat(relativedir, exportmode->filename);
+ if (FAILEDTOALLOC(relfilename))
+ return RET_ERROR_OOM;
+
+ r = release_startfile(release, relfilename, exportmode->compressions,
+ onlyifmissing, &file);
+ if (RET_WAS_ERROR(r)) {
+ free(relfilename);
+ return r;
+ }
+ if (RET_IS_OK(r)) {
+ if (release_oldexists(file)) {
+ if (verbose > 5)
+ printf(" replacing '%s/%s'%s\n",
+ release_dirofdist(release), relfilename,
+ exportdescription(exportmode, buffer, 100));
+ status = "change";
+ } else {
+ if (verbose > 5)
+ printf(" creating '%s/%s'%s\n",
+ release_dirofdist(release), relfilename,
+ exportdescription(exportmode, buffer, 100));
+ status = "new";
+ }
+ r = package_openiterator(target, READONLY, true, &iterator);
+ if (RET_WAS_ERROR(r)) {
+ release_abortfile(file);
+ free(relfilename);
+ return r;
+ }
+ while (package_next(&iterator)) {
+ if (iterator.current.controllen == 0)
+ continue;
+ (void)release_writedata(file, iterator.current.control,
+ iterator.current.controllen);
+ (void)release_writestring(file, "\n");
+ if (iterator.current.control[iterator.current.controllen-1] != '\n')
+ (void)release_writestring(file, "\n");
+ }
+ r = package_closeiterator(&iterator);
+ if (RET_WAS_ERROR(r)) {
+ release_abortfile(file);
+ free(relfilename);
+ return r;
+ }
+ r = release_finishfile(release, file);
+ if (RET_WAS_ERROR(r)) {
+ free(relfilename);
+ return r;
+ }
+ } else {
+ if (verbose > 9)
+ printf(" keeping old '%s/%s'%s\n",
+ release_dirofdist(release), relfilename,
+ exportdescription(exportmode, buffer, 100));
+ status = "old";
+ }
+ if (!snapshot) {
+ int i;
+
+ for (i = 0 ; i < exportmode->hooks.count ; i++) {
+ const char *hook = exportmode->hooks.values[i];
+
+ r = callexporthook(hook, relfilename, status, release);
+ if (RET_WAS_ERROR(r)) {
+ free(relfilename);
+ return r;
+ }
+ }
+ }
+ free(relfilename);
+ return RET_OK;
+}
+
+void exportmode_done(struct exportmode *mode) {
+ assert (mode != NULL);
+ free(mode->filename);
+ strlist_done(&mode->hooks);
+ free(mode->release);
+}