Merge branch 'maint'
[gitweb.git] / http.c
diff --git a/http.c b/http.c
index 0701a6f6eb6de5a454bb451cb0e1683d0757716a..b0499482455e256a3c2c9b6498b21c32d328c5c9 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,4 +1,5 @@
 #include "http.h"
+#include "pack.h"
 
 int data_received;
 int active_requests;
@@ -11,11 +12,15 @@ static CURLM *curlm;
 #ifndef NO_CURL_EASY_DUPHANDLE
 static CURL *curl_default;
 #endif
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
 char curl_errorstr[CURL_ERROR_SIZE];
 
 static int curl_ssl_verify = -1;
 static const char *ssl_cert;
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
 static const char *ssl_key;
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
@@ -29,8 +34,7 @@ static const char *curl_http_proxy;
 static char *user_name, *user_pass;
 
 static struct curl_slist *pragma_header;
-
-struct curl_slist *no_pragma_header;
+static struct curl_slist *no_pragma_header;
 
 static struct active_request_slot *active_queue_head;
 
@@ -122,7 +126,7 @@ static int http_options(const char *var, const char *value, void *cb)
        }
        if (!strcmp("http.sslcert", var))
                return git_config_string(&ssl_cert, var, value);
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
        if (!strcmp("http.sslkey", var))
                return git_config_string(&ssl_key, var, value);
 #endif
@@ -192,7 +196,7 @@ static CURL *get_curl_handle(void)
 
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
        if (ssl_key != NULL)
                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
 #endif
@@ -309,7 +313,7 @@ void http_init(struct remote *remote)
                curl_ssl_verify = 0;
 
        set_from_env(&ssl_cert, "GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
        set_from_env(&ssl_key, "GIT_SSL_KEY");
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
@@ -665,6 +669,23 @@ static char *quote_ref_url(const char *base, const char *ref)
        return strbuf_detach(&buf, NULL);
 }
 
+void append_remote_object_url(struct strbuf *buf, const char *url,
+                             const char *hex,
+                             int only_two_digit_prefix)
+{
+       strbuf_addf(buf, "%s/objects/%.*s/", url, 2, hex);
+       if (!only_two_digit_prefix)
+               strbuf_addf(buf, "%s", hex+2);
+}
+
+char *get_remote_object_url(const char *url, const char *hex,
+                           int only_two_digit_prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
+       return strbuf_detach(&buf, NULL);
+}
+
 /* http_request() targets */
 #define HTTP_REQUEST_STRBUF    0
 #define HTTP_REQUEST_FILE      1
@@ -889,3 +910,334 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
        free(url);
        return ret;
 }
+
+void release_http_pack_request(struct http_pack_request *preq)
+{
+       if (preq->packfile != NULL) {
+               fclose(preq->packfile);
+               preq->packfile = NULL;
+               preq->slot->local = NULL;
+       }
+       if (preq->range_header != NULL) {
+               curl_slist_free_all(preq->range_header);
+               preq->range_header = NULL;
+       }
+       preq->slot = NULL;
+       free(preq->url);
+}
+
+int finish_http_pack_request(struct http_pack_request *preq)
+{
+       int ret;
+       struct packed_git **lst;
+
+       preq->target->pack_size = ftell(preq->packfile);
+
+       if (preq->packfile != NULL) {
+               fclose(preq->packfile);
+               preq->packfile = NULL;
+               preq->slot->local = NULL;
+       }
+
+       ret = move_temp_to_file(preq->tmpfile, preq->filename);
+       if (ret)
+               return ret;
+
+       lst = preq->lst;
+       while (*lst != preq->target)
+               lst = &((*lst)->next);
+       *lst = (*lst)->next;
+
+       if (verify_pack(preq->target))
+               return -1;
+       install_packed_git(preq->target);
+
+       return 0;
+}
+
+struct http_pack_request *new_http_pack_request(
+       struct packed_git *target, const char *base_url)
+{
+       char *url;
+       char *filename;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct strbuf buf = STRBUF_INIT;
+       struct http_pack_request *preq;
+
+       preq = xmalloc(sizeof(*preq));
+       preq->target = target;
+       preq->range_header = NULL;
+
+       end_url_with_slash(&buf, base_url);
+       strbuf_addf(&buf, "objects/pack/pack-%s.pack",
+               sha1_to_hex(target->sha1));
+       url = strbuf_detach(&buf, NULL);
+       preq->url = xstrdup(url);
+
+       filename = sha1_pack_name(target->sha1);
+       snprintf(preq->filename, sizeof(preq->filename), "%s", filename);
+       snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename);
+       preq->packfile = fopen(preq->tmpfile, "a");
+       if (!preq->packfile) {
+               error("Unable to open local file %s for pack",
+                     preq->tmpfile);
+               goto abort;
+       }
+
+       preq->slot = get_active_slot();
+       preq->slot->local = preq->packfile;
+       curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
+               no_pragma_header);
+
+       /*
+        * If there is data present from a previous transfer attempt,
+        * resume where it left off
+        */
+       prev_posn = ftell(preq->packfile);
+       if (prev_posn>0) {
+               if (http_is_verbose)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               preq->range_header = curl_slist_append(NULL, range);
+               curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
+                       preq->range_header);
+       }
+
+       return preq;
+
+abort:
+       free(filename);
+       return NULL;
+}
+
+/* Helpers for fetching objects (loose) */
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+                              void *data)
+{
+       unsigned char expn[4096];
+       size_t size = eltsize * nmemb;
+       int posn = 0;
+       struct http_object_request *freq =
+               (struct http_object_request *)data;
+       do {
+               ssize_t retval = xwrite(freq->localfile,
+                                       (char *) ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       freq->stream.avail_in = size;
+       freq->stream.next_in = ptr;
+       do {
+               freq->stream.next_out = expn;
+               freq->stream.avail_out = sizeof(expn);
+               freq->zret = git_inflate(&freq->stream, Z_SYNC_FLUSH);
+               git_SHA1_Update(&freq->c, expn,
+                               sizeof(expn) - freq->stream.avail_out);
+       } while (freq->stream.avail_in && freq->zret == Z_OK);
+       data_received++;
+       return size;
+}
+
+struct http_object_request *new_http_object_request(const char *base_url,
+       unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename;
+       char prevfile[PATH_MAX];
+       char *url;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct http_object_request *freq;
+
+       freq = xmalloc(sizeof(*freq));
+       hashcpy(freq->sha1, sha1);
+       freq->localfile = -1;
+
+       filename = sha1_file_name(sha1);
+       snprintf(freq->filename, sizeof(freq->filename), "%s", filename);
+       snprintf(freq->tmpfile, sizeof(freq->tmpfile),
+                "%s.temp", filename);
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);
+       unlink_or_warn(prevfile);
+       rename(freq->tmpfile, prevfile);
+       unlink_or_warn(freq->tmpfile);
+
+       if (freq->localfile != -1)
+               error("fd leakage in start: %d", freq->localfile);
+       freq->localfile = open(freq->tmpfile,
+                              O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /*
+        * This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (freq->localfile < 0 && errno == ENOENT) {
+               char *dir = strrchr(freq->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(freq->tmpfile, 0777);
+                       *dir = '/';
+               }
+               freq->localfile = open(freq->tmpfile,
+                                      O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (freq->localfile < 0) {
+               error("Couldn't create temporary file %s for %s: %s",
+                     freq->tmpfile, freq->filename, strerror(errno));
+               goto abort;
+       }
+
+       memset(&freq->stream, 0, sizeof(freq->stream));
+
+       git_inflate_init(&freq->stream);
+
+       git_SHA1_Init(&freq->c);
+
+       url = get_remote_object_url(base_url, hex, 0);
+       freq->url = xstrdup(url);
+
+       /*
+        * If a previous temp file is present, process what was already
+        * fetched.
+        */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    freq) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink_or_warn(prevfile);
+
+       /*
+        * Reset inflate/SHA1 if there was an error reading the previous temp
+        * file; also rewind to the beginning of the local file.
+        */
+       if (prev_read == -1) {
+               memset(&freq->stream, 0, sizeof(freq->stream));
+               git_inflate_init(&freq->stream);
+               git_SHA1_Init(&freq->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(freq->localfile, 0, SEEK_SET);
+                       ftruncate(freq->localfile, 0);
+               }
+       }
+
+       freq->slot = get_active_slot();
+
+       curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+       /*
+        * If we have successfully processed data from a previous fetch
+        * attempt, only fetch the data we don't already have.
+        */
+       if (prev_posn>0) {
+               if (http_is_verbose)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(freq->slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       return freq;
+
+       free(url);
+abort:
+       free(filename);
+       free(freq);
+       return NULL;
+}
+
+void process_http_object_request(struct http_object_request *freq)
+{
+       if (freq->slot == NULL)
+               return;
+       freq->curl_result = freq->slot->curl_result;
+       freq->http_code = freq->slot->http_code;
+       freq->slot = NULL;
+}
+
+int finish_http_object_request(struct http_object_request *freq)
+{
+       struct stat st;
+
+       close(freq->localfile);
+       freq->localfile = -1;
+
+       process_http_object_request(freq);
+
+       if (freq->http_code == 416) {
+               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+       } else if (freq->curl_result != CURLE_OK) {
+               if (stat(freq->tmpfile, &st) == 0)
+                       if (st.st_size == 0)
+                               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+
+       git_inflate_end(&freq->stream);
+       git_SHA1_Final(freq->real_sha1, &freq->c);
+       if (freq->zret != Z_STREAM_END) {
+               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+       if (hashcmp(freq->sha1, freq->real_sha1)) {
+               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+       freq->rename =
+               move_temp_to_file(freq->tmpfile, freq->filename);
+
+       return freq->rename;
+}
+
+void abort_http_object_request(struct http_object_request *freq)
+{
+       unlink_or_warn(freq->tmpfile);
+
+       release_http_object_request(freq);
+}
+
+void release_http_object_request(struct http_object_request *freq)
+{
+       if (freq->localfile != -1) {
+               close(freq->localfile);
+               freq->localfile = -1;
+       }
+       if (freq->url != NULL) {
+               free(freq->url);
+               freq->url = NULL;
+       }
+       freq->slot = NULL;
+}