Commit 224848656b45718ffd238961a636ef83dd006e63

Stefan Sperling 2018-03-13T18:28:40

process delta chains in memory if max size is < 32 MB

diff --git a/lib/delta.c b/lib/delta.c
index f8ab225..8b82432 100644
--- a/lib/delta.c
+++ b/lib/delta.c
@@ -225,6 +225,41 @@ copy_from_delta(const uint8_t **p, size_t *remain, size_t len, FILE *outfile)
 	return NULL;
 }
 
+static const struct got_error *
+parse_delta_sizes(uint64_t *base_size, uint64_t *result_size,
+    const uint8_t **p, size_t *remain)
+{
+	const struct got_error *err;
+
+	/* Read the two size fields at the beginning of the stream. */
+	err = parse_size(base_size, p, remain);
+	if (err)
+		return err;
+	err = next_delta_byte(p, remain);
+	if (err)
+		return err;
+	err = parse_size(result_size, p, remain);
+	if (err)
+		return err;
+
+	return NULL;
+}
+
+const struct got_error *
+got_delta_get_sizes(uint64_t *base_size, uint64_t *result_size,
+    const uint8_t *delta_buf, size_t delta_len)
+{
+	size_t remain;
+	const uint8_t *p;
+
+	if (delta_len < GOT_DELTA_STREAM_LENGTH_MIN)
+		return got_error(GOT_ERR_BAD_DELTA);
+
+	p = delta_buf;
+	remain = delta_len;
+	return parse_delta_sizes(base_size, result_size, &p, &remain);
+}
+
 const struct got_error *
 got_delta_apply(FILE *base_file, const uint8_t *delta_buf,
     size_t delta_len, FILE *outfile)
@@ -242,15 +277,7 @@ got_delta_apply(FILE *base_file, const uint8_t *delta_buf,
 
 	p = delta_buf;
 	remain = delta_len;
-
-	/* Read the two size fields at the beginning of the stream. */
-	err = parse_size(&base_size, &p, &remain);
-	if (err)
-		return err;
-	err = next_delta_byte(&p, &remain);
-	if (err)
-		return err;
-	err = parse_size(&result_size, &p, &remain);
+	err = parse_delta_sizes(&base_size, &result_size, &p, &remain);
 	if (err)
 		return err;
 
diff --git a/lib/got_delta_lib.h b/lib/got_delta_lib.h
index c0738c0..c92a6d0 100644
--- a/lib/got_delta_lib.h
+++ b/lib/got_delta_lib.h
@@ -34,6 +34,8 @@ struct got_delta *got_delta_open(const char *, off_t, size_t, int, size_t,
 void got_delta_close(struct got_delta *);
 const struct got_error *got_delta_chain_get_base_type(int *,
     struct got_delta_chain *);
+const struct got_error *got_delta_get_sizes(uint64_t *, uint64_t *,
+    const uint8_t *, size_t);
 const struct got_error *got_delta_apply(FILE *, const uint8_t *, size_t,
     FILE *);
 
diff --git a/lib/pack.c b/lib/pack.c
index 1c07f63..52d0602 100644
--- a/lib/pack.c
+++ b/lib/pack.c
@@ -920,21 +920,89 @@ got_packfile_open_object(struct got_object **obj, struct got_object_id *id,
 }
 
 static const struct got_error *
+get_delta_sizes(uint64_t *base_size, uint64_t *result_size,
+    struct got_delta *delta)
+{
+	const struct got_error *err;
+	uint8_t *delta_buf = NULL;
+	size_t delta_len = 0;
+	FILE *delta_file;
+
+	delta_file = fopen(delta->path_packfile, "rb");
+	if (delta_file == NULL)
+		return got_error_from_errno();
+
+	if (fseeko(delta_file, delta->data_offset, SEEK_CUR) != 0) {
+		err = got_error_from_errno();
+		fclose(delta_file);
+		return err;
+	}
+
+	err = got_inflate_to_mem(&delta_buf, &delta_len, delta_file);
+	fclose(delta_file);
+	if (err)
+		return err;
+
+	err = got_delta_get_sizes(base_size, result_size, delta_buf, delta_len);
+	free(delta_buf);
+	return err;
+}
+
+static const struct got_error *
+get_delta_chain_max_size(uint64_t *max_size, struct got_delta_chain *deltas)
+{
+	struct got_delta *delta;
+	uint64_t base_size = 0, result_size = 0;
+
+	*max_size = 0;
+	SIMPLEQ_FOREACH(delta, &deltas->entries, entry) {
+		/* Plain object types are the delta base. */
+		if (delta->type != GOT_OBJ_TYPE_COMMIT &&
+		    delta->type != GOT_OBJ_TYPE_TREE &&
+		    delta->type != GOT_OBJ_TYPE_BLOB &&
+		    delta->type != GOT_OBJ_TYPE_TAG) {
+			const struct got_error *err;
+			err = get_delta_sizes(&base_size, &result_size, delta);
+			if (err)
+				return err;
+		} else
+			base_size = delta->size;
+		if (base_size > *max_size)
+			*max_size = base_size;
+		if (result_size > *max_size)
+			*max_size = result_size;
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
 dump_delta_chain(struct got_delta_chain *deltas, FILE *outfile)
 {
 	const struct got_error *err = NULL;
 	struct got_delta *delta;
-	FILE *base_file, *accum_file;
+	FILE *base_file = NULL, *accum_file = NULL;
+	uint64_t max_size;
 	int n = 0;
 
 	if (SIMPLEQ_EMPTY(&deltas->entries))
 		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
 
-	base_file = got_opentemp();
+	err = get_delta_chain_max_size(&max_size, deltas);
+	if (err)
+		return err;
+
+	if (max_size < GOT_DELTA_RESULT_SIZE_CACHED_MAX)
+		base_file = fmemopen(NULL, max_size, "w+");
+	else
+		base_file = got_opentemp();
 	if (base_file == NULL)
 		return got_error_from_errno();
 
-	accum_file = got_opentemp();
+	if (max_size < GOT_DELTA_RESULT_SIZE_CACHED_MAX)
+		accum_file = fmemopen(NULL, max_size, "w+");
+	else
+		accum_file = got_opentemp();
 	if (accum_file == NULL) {
 		err = got_error_from_errno();
 		fclose(base_file);
@@ -953,7 +1021,6 @@ dump_delta_chain(struct got_delta_chain *deltas, FILE *outfile)
 			goto done;
 		}
 
-
 		if (n == 0) {
 			/* Plain object types are the delta base. */
 			if (delta->type != GOT_OBJ_TYPE_COMMIT &&