diff options
Diffstat (limited to 'aptmethod.c')
-rw-r--r-- | aptmethod.c | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/aptmethod.c b/aptmethod.c new file mode 100644 index 0000000..e50449b --- /dev/null +++ b/aptmethod.c @@ -0,0 +1,1216 @@ +/* This file is part of "reprepro" + * Copyright (C) 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 <errno.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> +#include "error.h" +#include "mprintf.h" +#include "strlist.h" +#include "names.h" +#include "dirs.h" +#include "chunks.h" +#include "checksums.h" +#include "files.h" +#include "uncompression.h" +#include "aptmethod.h" +#include "filecntl.h" +#include "hooks.h" + +struct tobedone { + /*@null@*/ + struct tobedone *next; + /* must be saved to know where is should be moved to: */ + /*@notnull@*/ + char *uri; + /*@notnull@*/ + char *filename; + /* in case of redirection, store the originally requested uri: */ + /*@null@*/ + char *original_uri; + /* callback and its data: */ + queue_callback *callback; + /*@null@*/void *privdata1, *privdata2; + /* there is no fallback or that was already used */ + bool lasttry; + /* how often this was redirected */ + unsigned int redirect_count; +}; + +struct aptmethod { + /*@only@*/ /*@null@*/ + struct aptmethod *next; + char *name; + char *baseuri; + /*@null@*/char *fallbackbaseuri; + /*@null@*/char *config; + int mstdin, mstdout; + pid_t child; + + enum { + ams_notstarted=0, + ams_waitforcapabilities, + ams_ok, + ams_failed + } status; + + /*@null@*/struct tobedone *tobedone; + /*@null@*//*@dependent@*/struct tobedone *lasttobedone; + /*@null@*//*@dependent@*/const struct tobedone *nexttosend; + /* what is currently read: */ + /*@null@*/char *inputbuffer; + size_t input_size, alreadyread; + /* What is currently written: */ + /*@null@*/char *command; + size_t alreadywritten, output_length; +}; + +struct aptmethodrun { + struct aptmethod *methods; +}; + +static void todo_free(/*@only@*/ struct tobedone *todo) { + free(todo->filename); + free(todo->original_uri); + free(todo->uri); + free(todo); +} + +static void free_todolist(/*@only@*/ struct tobedone *todo) { + + while (todo != NULL) { + struct tobedone *h = todo->next; + + todo_free(todo); + todo = h; + } +} + +static void aptmethod_free(/*@only@*/struct aptmethod *method) { + if (method == NULL) + return; + free(method->name); + free(method->baseuri); + free(method->config); + free(method->fallbackbaseuri); + free(method->inputbuffer); + free(method->command); + + free_todolist(method->tobedone); + + free(method); +} + +retvalue aptmethod_shutdown(struct aptmethodrun *run) { + retvalue result = RET_OK, r; + struct aptmethod *method, *lastmethod, **method_ptr; + + /* first get rid of everything not running: */ + method_ptr = &run->methods; + while (*method_ptr != NULL) { + + if ((*method_ptr)->child > 0) { + if (verbose > 10) + fprintf(stderr, +"Still waiting for %d\n", (int)(*method_ptr)->child); + method_ptr = &(*method_ptr)->next; + continue; + } else { + /*@only@*/ struct aptmethod *h; + h = (*method_ptr); + *method_ptr = h->next; + h->next = NULL; + aptmethod_free(h); + } + } + + /* finally get rid of all the processes: */ + for (method = run->methods ; method != NULL ; method = method->next) { + if (method->mstdin >= 0) { + (void)close(method->mstdin); + if (verbose > 30) + fprintf(stderr, "Closing stdin of %d\n", + (int)method->child); + } + method->mstdin = -1; + if (method->mstdout >= 0) { + (void)close(method->mstdout); + if (verbose > 30) + fprintf(stderr, "Closing stdout of %d\n", + (int)method->child); + } + method->mstdout = -1; + } + while (run->methods != NULL || uncompress_running()) { + pid_t pid;int status; + + pid = wait(&status); + lastmethod = NULL; method = run->methods; + while (method != NULL) { + if (method->child == pid) { + struct aptmethod *next = method->next; + + if (lastmethod != NULL) { + lastmethod->next = next; + } else + run->methods = next; + + aptmethod_free(method); + pid = -1; + break; + } else { + lastmethod = method; + method = method->next; + } + } + if (pid > 0) { + r = uncompress_checkpid(pid, status); + RET_UPDATE(result, r); + } + } + free(run); + return result; +} + +/******************Initialize the data structures***********************/ + +retvalue aptmethod_initialize_run(struct aptmethodrun **run) { + struct aptmethodrun *r; + + r = zNEW(struct aptmethodrun); + if (FAILEDTOALLOC(r)) + return RET_ERROR_OOM; + *run = r; + return RET_OK; +} + +retvalue aptmethod_newmethod(struct aptmethodrun *run, const char *uri, const char *fallbackuri, const struct strlist *config, struct aptmethod **m) { + struct aptmethod *method; + const char *p; + + method = zNEW(struct aptmethod); + if (FAILEDTOALLOC(method)) + return RET_ERROR_OOM; + method->mstdin = -1; + method->mstdout = -1; + method->child = -1; + method->status = ams_notstarted; + p = uri; + while (*p != '\0' && (*p == '_' || *p == '-' || *p == '+' || + (*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || + (*p>='0' && *p<='9'))) { + p++; + } + if (*p == '\0') { + fprintf(stderr, "No colon found in method-URI '%s'!\n", uri); + free(method); + return RET_ERROR; + } + if (*p != ':') { + fprintf(stderr, +"Unexpected character '%c' in method-URI '%s'!\n", *p, uri); + free(method); + return RET_ERROR; + } + if (p == uri) { + fprintf(stderr, +"Zero-length name in method-URI '%s'!\n", uri); + free(method); + return RET_ERROR; + } + + method->name = strndup(uri, p-uri); + if (FAILEDTOALLOC(method->name)) { + free(method); + return RET_ERROR_OOM; + } + method->baseuri = strdup(uri); + if (FAILEDTOALLOC(method->baseuri)) { + free(method->name); + free(method); + return RET_ERROR_OOM; + } + if (fallbackuri == NULL) + method->fallbackbaseuri = NULL; + else { + method->fallbackbaseuri = strdup(fallbackuri); + if (FAILEDTOALLOC(method->fallbackbaseuri)) { + free(method->baseuri); + free(method->name); + free(method); + return RET_ERROR_OOM; + } + } +#define CONF601 "601 Configuration" +#define CONFITEM "\nConfig-Item: " + if (config->count == 0) + method->config = strdup(CONF601 CONFITEM "Dir=/" "\n\n"); + else + method->config = strlist_concat(config, + CONF601 CONFITEM, CONFITEM, "\n\n"); + if (FAILEDTOALLOC(method->config)) { + free(method->fallbackbaseuri); + free(method->baseuri); + free(method->name); + free(method); + return RET_ERROR_OOM; + } + method->next = run->methods; + run->methods = method; + *m = method; + return RET_OK; +} + +/**************************Fire up a method*****************************/ + +inline static retvalue aptmethod_startup(struct aptmethod *method) { + pid_t f; + int mstdin[2]; + int mstdout[2]; + int r; + + /* When there is nothing to get, there is no reason to startup + * the method. */ + if (method->tobedone == NULL) { + return RET_NOTHING; + } + + /* when we are already running, we are already ready...*/ + if (method->child > 0) { + return RET_OK; + } + + method->status = ams_waitforcapabilities; + + r = pipe(mstdin); + if (r < 0) { + int e = errno; + fprintf(stderr, "Error %d creating pipe: %s\n", + e, strerror(e)); + return RET_ERRNO(e); + } + r = pipe(mstdout); + if (r < 0) { + int e = errno; + (void)close(mstdin[0]); (void)close(mstdin[1]); + fprintf(stderr, "Error %d in pipe syscall: %s\n", + e, strerror(e)); + return RET_ERRNO(e); + } + + if (interrupted()) { + (void)close(mstdin[0]);(void)close(mstdin[1]); + (void)close(mstdout[0]);(void)close(mstdout[1]); + return RET_ERROR_INTERRUPTED; + } + f = fork(); + if (f < 0) { + int e = errno; + (void)close(mstdin[0]); (void)close(mstdin[1]); + (void)close(mstdout[0]); (void)close(mstdout[1]); + fprintf(stderr, "Error %d forking: %s\n", + e, strerror(e)); + return RET_ERRNO(e); + } + if (f == 0) { + char *methodname; + int e; + /* child: */ + (void)close(mstdin[1]); + (void)close(mstdout[0]); + if (dup2(mstdin[0], 0) < 0) { + e = errno; + fprintf(stderr, "Error %d while setting stdin: %s\n", + e, strerror(e)); + exit(255); + } + if (dup2(mstdout[1], 1) < 0) { + e = errno; + fprintf(stderr, "Error %d while setting stdout: %s\n", + e, strerror(e)); + exit(255); + } + closefrom(3); + + methodname = calc_dirconcat(global.methoddir, method->name); + if (FAILEDTOALLOC(methodname)) + exit(255); + + /* not really useful here, unless someone write reprepro + * specific modules (which I hope no one will) */ + sethookenvironment(NULL, NULL, NULL, NULL); + /* actually call the method without any arguments: */ + (void)execl(methodname, methodname, ENDOFARGUMENTS); + + e = errno; + fprintf(stderr, "Error %d while executing '%s': %s\n", + e, methodname, strerror(e)); + exit(255); + } + /* the main program continues... */ + method->child = f; + if (verbose > 10) + fprintf(stderr, +"Method '%s' started as %d\n", method->baseuri, (int)f); + (void)close(mstdin[0]); + (void)close(mstdout[1]); + markcloseonexec(mstdin[1]); + markcloseonexec(mstdout[0]); + method->mstdin = mstdin[1]; + method->mstdout = mstdout[0]; + method->inputbuffer = NULL; + method->input_size = 0; + method->alreadyread = 0; + method->command = NULL; + method->output_length = 0; + method->alreadywritten = 0; + return RET_OK; +} + +/**************************how to add files*****************************/ + +static inline void enqueue(struct aptmethod *method, /*@only@*/struct tobedone *todo) { + todo->next = NULL; + if (method->lasttobedone == NULL) + method->nexttosend = method->lasttobedone = method->tobedone = todo; + else { + method->lasttobedone->next = todo; + method->lasttobedone = todo; + if (method->nexttosend == NULL) + method->nexttosend = todo; + } +} + +static retvalue enqueuenew(struct aptmethod *method, /*@only@*/char *uri, /*@only@*/char *destfile, queue_callback *callback, void *privdata1, void *privdata2) { + struct tobedone *todo; + + if (FAILEDTOALLOC(destfile)) { + free(uri); + return RET_ERROR_OOM; + } + if (FAILEDTOALLOC(uri)) { + free(destfile); + return RET_ERROR_OOM; + } + + todo = NEW(struct tobedone); + if (FAILEDTOALLOC(todo)) { + free(uri); free(destfile); + return RET_ERROR_OOM; + } + + todo->next = NULL; + todo->uri = uri; + todo->filename = destfile; + todo->original_uri = NULL; + todo->callback = callback; + todo->privdata1 = privdata1; + todo->privdata2 = privdata2; + todo->lasttry = method->fallbackbaseuri == NULL; + todo->redirect_count = 0; + enqueue(method, todo); + return RET_OK; +} + +retvalue aptmethod_enqueue(struct aptmethod *method, const char *origfile, /*@only@*/char *destfile, queue_callback *callback, void *privdata1, void *privdata2) { + return enqueuenew(method, + calc_dirconcat(method->baseuri, origfile), + destfile, callback, privdata1, privdata2); +} + +retvalue aptmethod_enqueueindex(struct aptmethod *method, const char *suite, const char *origfile, const char *suffix, const char *destfile, const char *downloadsuffix, queue_callback *callback, void *privdata1, void *privdata2) { + return enqueuenew(method, + mprintf("%s/%s/%s%s", + method->baseuri, suite, origfile, suffix), + mprintf("%s%s", destfile, downloadsuffix), + callback, privdata1, privdata2); +} + +/*****************what to do with received files************************/ + +static retvalue requeue_or_fail(struct aptmethod *method, /*@only@*/struct tobedone *todo) { + retvalue r; + + if (todo->lasttry) { + if (todo->callback == NULL) + r = RET_ERROR; + else + r = todo->callback(qa_error, + todo->privdata1, todo->privdata2, + todo->uri, NULL, todo->filename, + NULL, method->name); + todo_free(todo); + return r; + } else { + size_t l, old_len, new_len; + char *s; + + assert (method->fallbackbaseuri != NULL); + + old_len = strlen(method->baseuri); + new_len = strlen(method->fallbackbaseuri); + l = strlen(todo->uri); + s = malloc(l+new_len+1-old_len); + if (FAILEDTOALLOC(s)) { + todo_free(todo); + return RET_ERROR_OOM; + } + memcpy(s, method->fallbackbaseuri, new_len); + strcpy(s+new_len, todo->uri + old_len); + free(todo->uri); + todo->uri = s; + todo->lasttry = true; + todo->redirect_count = 0; + enqueue(method, todo); + return RET_OK; + } +} + +/* look which file could not be received and remove it: */ +static retvalue urierror(struct aptmethod *method, const char *uri, /*@only@*/char *message) { + struct tobedone *todo, *lasttodo; + + lasttodo = NULL; todo = method->tobedone; + while (todo != NULL) { + if (strcmp(todo->uri, uri) == 0) { + + /* remove item: */ + if (lasttodo == NULL) + method->tobedone = todo->next; + else + lasttodo->next = todo->next; + if (method->nexttosend == todo) { + /* just in case some method received + * files before we request them ;-) */ + method->nexttosend = todo->next; + } + if (method->lasttobedone == todo) { + method->lasttobedone = todo->next; + } + fprintf(stderr, +"aptmethod error receiving '%s':\n'%s'\n", + uri, (message != NULL)?message:""); + /* put message in failed items to show it later? */ + free(message); + return requeue_or_fail(method, todo); + } + lasttodo = todo; + todo = todo->next; + } + /* huh? If if have not asked for it, how can there be errors? */ + fprintf(stderr, +"Method '%s' reported error with unrequested file '%s':\n'%s'!\n", + method->name, uri, message); + free(message); + return RET_ERROR; +} + +/* look which file could not be received and readd the new name... */ +static retvalue uriredirect(struct aptmethod *method, const char *uri, /*@only@*/char *newuri) { + struct tobedone *todo, *lasttodo; + + lasttodo = NULL; todo = method->tobedone; + while (todo != NULL) { + if (strcmp(todo->uri, uri) == 0) { + + /* remove item: */ + if (lasttodo == NULL) + method->tobedone = todo->next; + else + lasttodo->next = todo->next; + if (method->nexttosend == todo) { + /* just in case some method received + * files before we request them ;-) */ + method->nexttosend = todo->next; + } + if (method->lasttobedone == todo) { + method->lasttobedone = todo->next; + } + if (todo->redirect_count < 10) { + if (verbose > 0) + fprintf(stderr, +"aptmethod redirects '%s' to '%s'\n", + uri, newuri); + /* readd with new uri */ + if (todo->original_uri != NULL) + free(todo->uri); + else + todo->original_uri = todo->uri; + todo->uri = newuri; + todo->redirect_count++; + enqueue(method, todo); + return RET_OK; + } + fprintf(stderr, +"redirect loop (or too many redirects) detected, original uri is '%s'\n", + todo->original_uri); + /* put message in failed items to show it later? */ + free(newuri); + return requeue_or_fail(method, todo); + } + lasttodo = todo; + todo = todo->next; + } + /* huh? If if have not asked for it, how can there be errors? */ + fprintf(stderr, +"Method '%s' reported redirect for unrequested file '%s'-> '%s'\n", + method->name, uri, newuri); + free(newuri); + return RET_ERROR; +} + +/* look where a received file has to go to: */ +static retvalue uridone(struct aptmethod *method, const char *uri, const char *filename, /*@only@*//*@null@*/struct checksums *checksumsfromapt) { + struct tobedone *todo, *lasttodo; + retvalue r; + + lasttodo = NULL; todo = method->tobedone; + while (todo != NULL) { + if (strcmp(todo->uri, uri) != 0) { + lasttodo = todo; + todo = todo->next; + continue; + } + + r = todo->callback(qa_got, + todo->privdata1, todo->privdata2, + todo->original_uri? todo->original_uri : todo->uri, + filename, todo->filename, + checksumsfromapt, method->name); + checksums_free(checksumsfromapt); + + /* remove item: */ + if (lasttodo == NULL) + method->tobedone = todo->next; + else + lasttodo->next = todo->next; + if (method->nexttosend == todo) { + /* just in case some method received + * files before we request them ;-) */ + method->nexttosend = todo->next; + } + if (method->lasttobedone == todo) { + method->lasttobedone = todo->next; + } + todo_free(todo); + return r; + } + /* huh? */ + fprintf(stderr, +"Method '%s' retrieved unexpected file '%s' at '%s'!\n", + method->name, uri, filename); + checksums_free(checksumsfromapt); + return RET_ERROR; +} + +/***************************Input and Output****************************/ +static retvalue logmessage(const struct aptmethod *method, const char *chunk, const char *type) { + retvalue r; + char *message; + + r = chunk_getvalue(chunk, "Message", &message); + if (RET_WAS_ERROR(r)) + return r; + if (RET_IS_OK(r)) { + fprintf(stderr, "aptmethod '%s': '%s'\n", + method->baseuri, message); + free(message); + return RET_OK; + } + r = chunk_getvalue(chunk, "URI", &message); + if (RET_WAS_ERROR(r)) + return r; + if (RET_IS_OK(r)) { + fprintf(stderr, "aptmethod %s '%s'\n", type, message); + free(message); + return RET_OK; + } + fprintf(stderr, "aptmethod '%s': '%s'\n", method->baseuri, type); + return RET_OK; +} +static inline retvalue gotcapabilities(struct aptmethod *method, const char *chunk) { + retvalue r; + + r = chunk_gettruth(chunk, "Single-Instance"); + if (RET_WAS_ERROR(r)) + return r; +// TODO: what to do with this? +// if (r != RET_NOTHING) { +// fprintf(stderr, "WARNING: Single-instance not yet supported!\n"); +// } + r = chunk_gettruth(chunk, "Send-Config"); + if (RET_WAS_ERROR(r)) + return r; + if (r != RET_NOTHING) { + assert(method->command == NULL); + method->alreadywritten = 0; + method->command = method->config; + method->config = NULL; + method->output_length = strlen(method->command); + if (verbose > 11) { + fprintf(stderr, "Sending config: '%s'\n", + method->command); + } + } else { + free(method->config); + method->config = NULL; + } + method->status = ams_ok; + return RET_OK; +} + +static inline retvalue goturidone(struct aptmethod *method, const char *chunk) { + static const char * const method_hash_names[cs_COUNT] = + { "MD5-Hash", "SHA1-Hash", "SHA256-Hash", "SHA512-Hash", + "Size" }; + retvalue result, r; + char *uri, *filename; + enum checksumtype type; + char *hashes[cs_COUNT]; + struct checksums *checksums = NULL; + + //TODO: is it worth the mess to make this in-situ? + + r = chunk_getvalue(chunk, "URI", &uri); + if (r == RET_NOTHING) { + fprintf(stderr, +"Missing URI header in uridone received from '%s' method!\n", + method->name); + r = RET_ERROR; + method->status = ams_failed; + } + if (RET_WAS_ERROR(r)) + return r; + + r = chunk_getvalue(chunk, "Filename", &filename); + if (r == RET_NOTHING) { + char *altfilename; + + r = chunk_getvalue(chunk, "Alt-Filename", &altfilename); + if (r == RET_NOTHING) { + fprintf(stderr, +"Missing Filename header in uridone received from '%s' method!\n", + method->name); + r = urierror(method, uri, strdup( +"<no error but missing Filename from apt-method>")); + } else { + r = urierror(method, uri, mprintf( +"<File not there, apt-method suggests '%s' instead>", altfilename)); + free(altfilename); + } + free(uri); + return r; + } + if (RET_WAS_ERROR(r)) { + free(uri); + return r; + } + if (verbose >= 1) + fprintf(stderr, "aptmethod got '%s'\n", uri); + + result = RET_NOTHING; + for (type = cs_md5sum ; type < cs_COUNT ; type++) { + hashes[type] = NULL; + r = chunk_getvalue(chunk, method_hash_names[type], + &hashes[type]); + RET_UPDATE(result, r); + } + if (RET_IS_OK(result) && hashes[cs_md5sum] == NULL) { + /* the lenny version also has this, better ask for + * in case the old MD5-Hash vanishes in the future */ + r = chunk_getvalue(chunk, "MD5Sum-Hash", &hashes[cs_md5sum]); + RET_UPDATE(result, r); + } + if (RET_WAS_ERROR(result)) { + free(uri); free(filename); + for (type = cs_md5sum ; type < cs_COUNT ; type++) + free(hashes[type]); + return result; + } + if (RET_IS_OK(result)) { + /* ignore errors, we can recompute them from the file */ + (void)checksums_init(&checksums, hashes); + } + r = uridone(method, uri, filename, checksums); + free(uri); + free(filename); + return r; +} + +static inline retvalue goturierror(struct aptmethod *method, const char *chunk) { + retvalue r; + char *uri, *message; + + r = chunk_getvalue(chunk, "URI", &uri); + if (r == RET_NOTHING) { + fprintf(stderr, +"Missing URI header in urierror received from '%s' method!\n", method->name); + r = RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + + r = chunk_getvalue(chunk, "Message", &message); + if (r == RET_NOTHING) { + message = NULL; + } + if (RET_WAS_ERROR(r)) { + free(uri); + return r; + } + + r = urierror(method, uri, message); + free(uri); + return r; +} + +static inline retvalue gotredirect(struct aptmethod *method, const char *chunk) { + char *uri, *newuri; + retvalue r; + + r = chunk_getvalue(chunk, "URI", &uri); + if (r == RET_NOTHING) { + fprintf(stderr, +"Missing URI header in uriredirect received from '%s' method!\n", method->name); + r = RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + r = chunk_getvalue(chunk, "New-URI", &newuri); + if (r == RET_NOTHING) { + fprintf(stderr, +"Missing New-URI header in uriredirect received from '%s' method!\n", method->name); + r = RET_ERROR; + } + if (RET_WAS_ERROR(r)) { + free(uri); + return r; + } + r = uriredirect(method, uri, newuri); + free(uri); + return r; +} + +static inline retvalue parsereceivedblock(struct aptmethod *method, const char *input) { + const char *p; + retvalue r; +#define OVERLINE {while (*p != '\0' && *p != '\n') p++; if (*p == '\n') p++; } + + while (*input == '\n' || *input == '\r') + input++; + if (*input == '\0') { + fprintf(stderr, +"Unexpected number of newlines from '%s' method!\n", method->name); + return RET_NOTHING; + } + p = input; + switch ((*(input+1)=='0')?*input:'\0') { + case '1': + switch (*(input+2)) { + /* 100 Capabilities */ + case '0': + OVERLINE; + if (verbose > 14) { + fprintf(stderr, "Got '%s'\n", + input); + } + return gotcapabilities(method, input); + /* 101 Log */ + case '1': + if (verbose > 10) { + OVERLINE; + return logmessage(method, p, "101"); + } + return RET_OK; + /* 102 Status */ + case '2': + if (verbose > 5) { + OVERLINE; + return logmessage(method, p, "102"); + } + return RET_OK; + /* 103 Redirect */ + case '3': + OVERLINE; + return gotredirect(method, p); + default: + fprintf(stderr, +"Error or unsupported message received: '%s'\n", + input); + return RET_ERROR; + } + case '2': + switch (*(input+2)) { + /* 200 URI Start */ + case '0': + if (verbose > 5) { + OVERLINE; + return logmessage(method, p, "start"); + } + return RET_OK; + /* 201 URI Done */ + case '1': + OVERLINE; + return goturidone(method, p); + default: + fprintf(stderr, +"Error or unsupported message received: '%s'\n", + input); + return RET_ERROR; + } + + case '4': + switch (*(input+2)) { + case '0': + OVERLINE; + r = goturierror(method, p); + break; + case '1': + OVERLINE; + (void)logmessage(method, p, "general error"); + method->status = ams_failed; + r = RET_ERROR; + break; + default: + fprintf(stderr, +"Error or unsupported message received: '%s'\n", + input); + r = RET_ERROR; + } + /* a failed download is not a error yet, as it might + * be redone from another source later */ + return r; + default: + fprintf(stderr, +"Unexpected data from '%s' method: '%s'\n", + method->name, input); + return RET_ERROR; + } +} + +static retvalue receivedata(struct aptmethod *method) { + retvalue result; + ssize_t r; + char *p; + int consecutivenewlines; + + assert (method->status != ams_ok || method->tobedone != NULL); + if (method->status != ams_waitforcapabilities + && method->status != ams_ok) + return RET_NOTHING; + + /* First look if we have enough room to read.. */ + if (method->alreadyread + 1024 >= method->input_size) { + char *newptr; + + if (method->input_size >= (size_t)128000) { + fprintf(stderr, +"Ridiculously long answer from method!\n"); + method->status = ams_failed; + return RET_ERROR; + } + + newptr = realloc(method->inputbuffer, method->alreadyread+1024); + if (FAILEDTOALLOC(newptr)) { + return RET_ERROR_OOM; + } + method->inputbuffer = newptr; + method->input_size = method->alreadyread + 1024; + } + assert (method->inputbuffer != NULL); + /* then read as much as the pipe is able to fill of our buffer */ + + r = read(method->mstdout, method->inputbuffer + method->alreadyread, + method->input_size - method->alreadyread - 1); + + if (r < 0) { + int e = errno; + fprintf(stderr, "Error %d reading pipe from aptmethod: %s\n", + e, strerror(e)); + method->status = ams_failed; + return RET_ERRNO(e); + } + method->alreadyread += r; + + result = RET_NOTHING; + while(true) { + retvalue res; + + r = method->alreadyread; + p = method->inputbuffer; + consecutivenewlines = 0; + + while (r > 0) { + if (*p == '\0') { + fprintf(stderr, +"Unexpected Zeroes in method output!\n"); + method->status = ams_failed; + return RET_ERROR; + } else if (*p == '\n') { + consecutivenewlines++; + if (consecutivenewlines >= 2) + break; + } else if (*p != '\r') { + consecutivenewlines = 0; + } + p++; r--; + } + if (r <= 0) { + return result; + } + *p ='\0'; p++; r--; + res = parsereceivedblock(method, method->inputbuffer); + if (r > 0) + memmove(method->inputbuffer, p, r); + method->alreadyread = r; + RET_UPDATE(result, res); + } +} + +static retvalue senddata(struct aptmethod *method) { + size_t l; + ssize_t r; + + if (method->status != ams_ok) + return RET_NOTHING; + + if (method->command == NULL) { + const struct tobedone *todo; + + /* nothing queued to send, nothing to be queued...*/ + todo = method->nexttosend; + if (todo == NULL) + return RET_OK; + + if (interrupted()) + return RET_ERROR_INTERRUPTED; + + method->alreadywritten = 0; + // TODO: make sure this is already checked for earlier... + assert (strchr(todo->uri, '\n') == NULL && + strchr(todo->filename, '\n') == NULL); + /* http-aptmethod seems to loose the last byte, + * if the file is already in place, + * so we better unlink the target first... + * but this is done elsewhere already + unlink(todo->filename); + */ + method->command = mprintf( + "600 URI Acquire\nURI: %s\nFilename: %s\n\n", + todo->uri, todo->filename); + if (FAILEDTOALLOC(method->command)) { + return RET_ERROR_OOM; + } + if (verbose > 20) + fprintf(stderr, "Will sent: '%s'\n", method->command); + method->output_length = strlen(method->command); + method->nexttosend = method->nexttosend->next; + } + + + l = method->output_length - method->alreadywritten; + + r = write(method->mstdin, method->command + method->alreadywritten, l); + if (r < 0) { + int e = errno; + + fprintf(stderr, "Error %d writing to pipe: %s\n", + e, strerror(e)); + //TODO: disable the whole method?? + method->status = ams_failed; + return RET_ERRNO(e); + } else if ((size_t)r < l) { + method->alreadywritten += r; + return RET_OK; + } + + free(method->command); + method->command = NULL; + return RET_OK; +} + +static retvalue checkchilds(struct aptmethodrun *run) { + pid_t child;int status; + retvalue result = RET_OK, r; + + while ((child = waitpid(-1, &status, WNOHANG)) > 0) { + struct aptmethod *method; + + for (method = run->methods ; method != NULL ; + method = method->next) { + if (method->child == child) + break; + } + if (method == NULL) { + /* perhaps an uncompressor terminated */ + r = uncompress_checkpid(child, status); + if (RET_IS_OK(r)) + continue; + if (RET_WAS_ERROR(r)) { + result = r; + continue; + } + else { + fprintf(stderr, +"Unexpected child died (maybe gpg died if signing/verifing was done): %d\n", + (int)child); + continue; + } + } + /* Make sure we do not cope with this child any more */ + if (method->mstdin != -1) { + (void)close(method->mstdin); + method->mstdin = -1; + } + if (method->mstdout != -1) { + (void)close(method->mstdout); + method->mstdout = -1; + } + method->child = -1; + if (method->status != ams_failed) + method->status = ams_notstarted; + + /* say something if it exited unnormal: */ + if (WIFEXITED(status)) { + int exitcode; + + exitcode = WEXITSTATUS(status); + if (exitcode != 0) { + fprintf(stderr, +"Method %s://%s exited with non-zero exit code %d!\n", + method->name, method->baseuri, + exitcode); + method->status = ams_notstarted; + result = RET_ERROR; + } + } else { + fprintf(stderr, "Method %s://%s exited unnormally!\n", + method->name, method->baseuri); + method->status = ams_notstarted; + result = RET_ERROR; + } + } + return result; +} + +/* *workleft is always set, even when return indicated error. + * (workleft < 0 when critical)*/ +static retvalue readwrite(struct aptmethodrun *run, /*@out@*/int *workleft) { + int maxfd, v; + fd_set readfds, writefds; + struct aptmethod *method; + retvalue result, r; + + /* First calculate what to look at: */ + FD_ZERO(&readfds); + FD_ZERO(&writefds); + maxfd = 0; + *workleft = 0; + for (method = run->methods ; method != NULL ; method = method->next) { + if (method->status == ams_ok && + (method->command != NULL || method->nexttosend != NULL)) { + FD_SET(method->mstdin, &writefds); + if (method->mstdin > maxfd) + maxfd = method->mstdin; + (*workleft)++; + if (verbose > 19) + fprintf(stderr, "want to write to '%s'\n", + method->baseuri); + } + if (method->status == ams_waitforcapabilities || + (method->status == ams_ok && + method->tobedone != NULL)) { + FD_SET(method->mstdout, &readfds); + if (method->mstdout > maxfd) + maxfd = method->mstdout; + (*workleft)++; + if (verbose > 19) + fprintf(stderr, "want to read from '%s'\n", + method->baseuri); + } + } + + if (*workleft == 0) + return RET_NOTHING; + + // TODO: think about a timeout... + v = select(maxfd + 1, &readfds, &writefds, NULL, NULL); + if (v < 0) { + int e = errno; + //TODO: handle (e == EINTR) && interrupted() specially + fprintf(stderr, "Select returned error %d: %s\n", + e, strerror(e)); + *workleft = -1; + // TODO: what to do here? + return RET_ERRNO(e); + } + + result = RET_NOTHING; + + maxfd = 0; + for (method = run->methods ; method != NULL ; method = method->next) { + if (method->mstdout != -1 && + FD_ISSET(method->mstdout, &readfds)) { + r = receivedata(method); + RET_UPDATE(result, r); + } + if (method->mstdin != -1 && + FD_ISSET(method->mstdin, &writefds)) { + r = senddata(method); + RET_UPDATE(result, r); + } + } + return result; +} + +retvalue aptmethod_download(struct aptmethodrun *run) { + struct aptmethod *method; + retvalue result, r; + int workleft; + + result = RET_NOTHING; + + /* fire up all methods, removing those that do not work: */ + for (method = run->methods; method != NULL ; method = method->next) { + r = aptmethod_startup(method); + /* do not remove failed methods here any longer, + * and not remove methods having nothing to do, + * as this breaks when no index files are downloaded + * due to all already being in place... */ + RET_UPDATE(result, r); + } + /* waiting for them to finish: */ + do { + r = checkchilds(run); + RET_UPDATE(result, r); + r = readwrite(run, &workleft); + RET_UPDATE(result, r); + // TODO: check interrupted here... + } while (workleft > 0 || uncompress_running()); + + return result; +} + |