From e9ee84cf28b6c7e5079ab3c020c86a44900a3f46 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 13 Oct 2011 15:12:02 -0700 Subject: [PATCH 1/2] bundle: allowing to read from an unseekable fd We wished that "git bundle" to eventually learn to read from a network socket which is not seekable. The current code opens with fopen(), reads the file halfway and run ftell(), and reopens the same file with open() and seeks, to skip the header. This patch by itself does not reach that goal yet, but I think it is a right step in that direction. Signed-off-by: Junio C Hamano --- bundle.c | 99 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/bundle.c b/bundle.c index f48fd7d4c..3aa715c40 100644 --- a/bundle.c +++ b/bundle.c @@ -23,49 +23,78 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name, list->nr++; } -/* returns an fd */ +/* Eventually this should go to strbuf.[ch] */ +static int strbuf_readline_fd(struct strbuf *sb, int fd) +{ + strbuf_reset(sb); + + while (1) { + char ch; + ssize_t len = xread(fd, &ch, 1); + if (len < 0) + return -1; + strbuf_addch(sb, ch); + if (ch == '\n') + break; + } + return 0; +} + int read_bundle_header(const char *path, struct bundle_header *header) { - char buffer[1024]; - int fd; - long fpos; - FILE *ffd = fopen(path, "rb"); + struct strbuf buf = STRBUF_INIT; + int fd = open(path, O_RDONLY); + int status = 0; - if (!ffd) + if (fd < 0) return error("could not open '%s'", path); - if (!fgets(buffer, sizeof(buffer), ffd) || - strcmp(buffer, bundle_signature)) { - fclose(ffd); - return error("'%s' does not look like a v2 bundle file", path); + + /* The bundle header begins with the signature */ + if (strbuf_readline_fd(&buf, fd) || + strcmp(buf.buf, bundle_signature)) { + error("'%s' does not look like a v2 bundle file", path); + status = -1; + goto abort; } - while (fgets(buffer, sizeof(buffer), ffd) - && buffer[0] != '\n') { - int is_prereq = buffer[0] == '-'; - int offset = is_prereq ? 1 : 0; - int len = strlen(buffer); + + /* The bundle header ends with an empty line */ + while (!strbuf_readline_fd(&buf, fd) && + buf.len && buf.buf[0] != '\n') { unsigned char sha1[20]; - struct ref_list *list = is_prereq ? &header->prerequisites - : &header->references; - char delim; - - if (len && buffer[len - 1] == '\n') - buffer[len - 1] = '\0'; - if (get_sha1_hex(buffer + offset, sha1)) { - warning("unrecognized header: %s", buffer); - continue; + int is_prereq = 0; + + if (*buf.buf == '-') { + is_prereq = 1; + strbuf_remove(&buf, 0, 1); + } + strbuf_rtrim(&buf); + + /* + * Tip lines have object name, SP, and refname. + * Prerequisites have object name that is optionally + * followed by SP and subject line. + */ + if (get_sha1_hex(buf.buf, sha1) || + (40 <= buf.len && !isspace(buf.buf[40])) || + (!is_prereq && buf.len <= 40)) { + error("unrecognized header: %s%s (%d)", + (is_prereq ? "-" : ""), buf.buf, (int)buf.len); + status = -1; + break; + } else { + if (is_prereq) + add_to_ref_list(sha1, "", &header->prerequisites); + else + add_to_ref_list(sha1, buf.buf + 41, &header->references); } - delim = buffer[40 + offset]; - if (!isspace(delim) && (delim != '\0' || !is_prereq)) - die ("invalid header: %s", buffer); - add_to_ref_list(sha1, isspace(delim) ? - buffer + 41 + offset : "", list); } - fpos = ftell(ffd); - fclose(ffd); - fd = open(path, O_RDONLY); - if (fd < 0) - return error("could not open '%s'", path); - lseek(fd, fpos, SEEK_SET); + + abort: + if (status) { + close(fd); + fd = -1; + } + strbuf_release(&buf); return fd; } From 2727b71f0547f5181653940d279854e7924788fb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 13 Oct 2011 15:19:31 -0700 Subject: [PATCH 2/2] bundle: add parse_bundle_header() helper function Move most of the code from read_bundle_header() to parse_bundle_header() that takes a file descriptor that is already opened for reading, and make the former responsible only for opening the file and noticing errors. As a logical consequence of this, is_bundle() helper function can be implemented as a non-complaining variant of read_bundle_header() that does not return an open file descriptor, and can be used to tighten the check used to decide the use of bundle transport in transport_get() function. Signed-off-by: Junio C Hamano --- bundle.c | 39 +++++++++++++++++++++++++++++++-------- bundle.h | 1 + transport.c | 2 +- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/bundle.c b/bundle.c index 3aa715c40..105b00536 100644 --- a/bundle.c +++ b/bundle.c @@ -40,19 +40,18 @@ static int strbuf_readline_fd(struct strbuf *sb, int fd) return 0; } -int read_bundle_header(const char *path, struct bundle_header *header) +static int parse_bundle_header(int fd, struct bundle_header *header, + const char *report_path) { struct strbuf buf = STRBUF_INIT; - int fd = open(path, O_RDONLY); int status = 0; - if (fd < 0) - return error("could not open '%s'", path); - /* The bundle header begins with the signature */ if (strbuf_readline_fd(&buf, fd) || strcmp(buf.buf, bundle_signature)) { - error("'%s' does not look like a v2 bundle file", path); + if (report_path) + error("'%s' does not look like a v2 bundle file", + report_path); status = -1; goto abort; } @@ -77,8 +76,9 @@ int read_bundle_header(const char *path, struct bundle_header *header) if (get_sha1_hex(buf.buf, sha1) || (40 <= buf.len && !isspace(buf.buf[40])) || (!is_prereq && buf.len <= 40)) { - error("unrecognized header: %s%s (%d)", - (is_prereq ? "-" : ""), buf.buf, (int)buf.len); + if (report_path) + error("unrecognized header: %s%s (%d)", + (is_prereq ? "-" : ""), buf.buf, (int)buf.len); status = -1; break; } else { @@ -98,6 +98,29 @@ int read_bundle_header(const char *path, struct bundle_header *header) return fd; } +int read_bundle_header(const char *path, struct bundle_header *header) +{ + int fd = open(path, O_RDONLY); + + if (fd < 0) + return error("could not open '%s'", path); + return parse_bundle_header(fd, header, path); +} + +int is_bundle(const char *path, int quiet) +{ + struct bundle_header header; + int fd = open(path, O_RDONLY); + + if (fd < 0) + return 0; + memset(&header, 0, sizeof(header)); + fd = parse_bundle_header(fd, &header, quiet ? NULL : path); + if (fd >= 0) + close(fd); + return (fd >= 0); +} + static int list_refs(struct ref_list *r, int argc, const char **argv) { int i; diff --git a/bundle.h b/bundle.h index e2aedd60d..c6228e210 100644 --- a/bundle.h +++ b/bundle.h @@ -14,6 +14,7 @@ struct bundle_header { struct ref_list references; }; +int is_bundle(const char *path, int quiet); int read_bundle_header(const char *path, struct bundle_header *header); int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv); diff --git a/transport.c b/transport.c index c9c8056f9..84d6480ce 100644 --- a/transport.c +++ b/transport.c @@ -913,7 +913,7 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; ret->smart_options = NULL; - } else if (is_local(url) && is_file(url)) { + } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; ret->get_refs_list = get_refs_from_bundle;