| 1 | #define USE_THE_REPOSITORY_VARIABLE |
| 2 | #define DISABLE_SIGN_COMPARE_WARNINGS |
| 3 | |
| 4 | #include "git-compat-util.h" |
| 5 | #include "chunk-format.h" |
| 6 | #include "csum-file.h" |
| 7 | #include "gettext.h" |
| 8 | #include "hash.h" |
| 9 | #include "trace2.h" |
| 10 | |
| 11 | /* |
| 12 | * When writing a chunk-based file format, collect the chunks in |
| 13 | * an array of chunk_info structs. The size stores the _expected_ |
| 14 | * amount of data that will be written by write_fn. |
| 15 | */ |
| 16 | struct chunk_info { |
| 17 | uint32_t id; |
| 18 | uint64_t size; |
| 19 | chunk_write_fn write_fn; |
| 20 | |
| 21 | const void *start; |
| 22 | }; |
| 23 | |
| 24 | struct chunkfile { |
| 25 | struct hashfile *f; |
| 26 | |
| 27 | struct chunk_info *chunks; |
| 28 | size_t chunks_nr; |
| 29 | size_t chunks_alloc; |
| 30 | }; |
| 31 | |
| 32 | struct chunkfile *init_chunkfile(struct hashfile *f) |
| 33 | { |
| 34 | struct chunkfile *cf = xcalloc(1, sizeof(*cf)); |
| 35 | cf->f = f; |
| 36 | return cf; |
| 37 | } |
| 38 | |
| 39 | void free_chunkfile(struct chunkfile *cf) |
| 40 | { |
| 41 | if (!cf) |
| 42 | return; |
| 43 | free(cf->chunks); |
| 44 | free(cf); |
| 45 | } |
| 46 | |
| 47 | int get_num_chunks(struct chunkfile *cf) |
| 48 | { |
| 49 | return cf->chunks_nr; |
| 50 | } |
| 51 | |
| 52 | void add_chunk(struct chunkfile *cf, |
| 53 | uint32_t id, |
| 54 | size_t size, |
| 55 | chunk_write_fn fn) |
| 56 | { |
| 57 | ALLOC_GROW(cf->chunks, cf->chunks_nr + 1, cf->chunks_alloc); |
| 58 | |
| 59 | cf->chunks[cf->chunks_nr].id = id; |
| 60 | cf->chunks[cf->chunks_nr].write_fn = fn; |
| 61 | cf->chunks[cf->chunks_nr].size = size; |
| 62 | cf->chunks_nr++; |
| 63 | } |
| 64 | |
| 65 | int write_chunkfile(struct chunkfile *cf, void *data) |
| 66 | { |
| 67 | int i, result = 0; |
| 68 | uint64_t cur_offset = hashfile_total(cf->f); |
| 69 | |
| 70 | trace2_region_enter("chunkfile", "write", the_repository); |
| 71 | |
| 72 | /* Add the table of contents to the current offset */ |
| 73 | cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE; |
| 74 | |
| 75 | for (i = 0; i < cf->chunks_nr; i++) { |
| 76 | hashwrite_be32(cf->f, cf->chunks[i].id); |
| 77 | hashwrite_be64(cf->f, cur_offset); |
| 78 | |
| 79 | cur_offset += cf->chunks[i].size; |
| 80 | } |
| 81 | |
| 82 | /* Trailing entry marks the end of the chunks */ |
| 83 | hashwrite_be32(cf->f, 0); |
| 84 | hashwrite_be64(cf->f, cur_offset); |
| 85 | |
| 86 | for (i = 0; i < cf->chunks_nr; i++) { |
| 87 | off_t start_offset = hashfile_total(cf->f); |
| 88 | result = cf->chunks[i].write_fn(cf->f, data); |
| 89 | |
| 90 | if (result) |
| 91 | goto cleanup; |
| 92 | |
| 93 | if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size) |
| 94 | BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead", |
| 95 | cf->chunks[i].size, cf->chunks[i].id, |
| 96 | hashfile_total(cf->f) - start_offset); |
| 97 | } |
| 98 | |
| 99 | cleanup: |
| 100 | trace2_region_leave("chunkfile", "write", the_repository); |
| 101 | return result; |
| 102 | } |
| 103 | |
| 104 | int read_table_of_contents(struct chunkfile *cf, |
| 105 | const unsigned char *mfile, |
| 106 | size_t mfile_size, |
| 107 | uint64_t toc_offset, |
| 108 | int toc_length, |
| 109 | unsigned expected_alignment) |
| 110 | { |
| 111 | int i; |
| 112 | uint32_t chunk_id; |
| 113 | const unsigned char *table_of_contents = mfile + toc_offset; |
| 114 | |
| 115 | ALLOC_GROW(cf->chunks, toc_length, cf->chunks_alloc); |
| 116 | |
| 117 | while (toc_length--) { |
| 118 | uint64_t chunk_offset, next_chunk_offset; |
| 119 | |
| 120 | chunk_id = get_be32(table_of_contents); |
| 121 | chunk_offset = get_be64(table_of_contents + 4); |
| 122 | |
| 123 | if (!chunk_id) { |
| 124 | error(_("terminating chunk id appears earlier than expected")); |
| 125 | return 1; |
| 126 | } |
| 127 | if (chunk_offset % expected_alignment != 0) { |
| 128 | error(_("chunk id %"PRIx32" not %d-byte aligned"), |
| 129 | chunk_id, expected_alignment); |
| 130 | return 1; |
| 131 | } |
| 132 | |
| 133 | table_of_contents += CHUNK_TOC_ENTRY_SIZE; |
| 134 | next_chunk_offset = get_be64(table_of_contents + 4); |
| 135 | |
| 136 | if (next_chunk_offset < chunk_offset || |
| 137 | next_chunk_offset > mfile_size - the_hash_algo->rawsz) { |
| 138 | error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""), |
| 139 | chunk_offset, next_chunk_offset); |
| 140 | return -1; |
| 141 | } |
| 142 | |
| 143 | for (i = 0; i < cf->chunks_nr; i++) { |
| 144 | if (cf->chunks[i].id == chunk_id) { |
| 145 | error(_("duplicate chunk ID %"PRIx32" found"), |
| 146 | chunk_id); |
| 147 | return -1; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | cf->chunks[cf->chunks_nr].id = chunk_id; |
| 152 | cf->chunks[cf->chunks_nr].start = mfile + chunk_offset; |
| 153 | cf->chunks[cf->chunks_nr].size = next_chunk_offset - chunk_offset; |
| 154 | cf->chunks_nr++; |
| 155 | } |
| 156 | |
| 157 | chunk_id = get_be32(table_of_contents); |
| 158 | if (chunk_id) { |
| 159 | error(_("final chunk has non-zero id %"PRIx32""), chunk_id); |
| 160 | return -1; |
| 161 | } |
| 162 | |
| 163 | return 0; |
| 164 | } |
| 165 | |
| 166 | struct pair_chunk_data { |
| 167 | const unsigned char **p; |
| 168 | size_t *size; |
| 169 | }; |
| 170 | |
| 171 | static int pair_chunk_fn(const unsigned char *chunk_start, |
| 172 | size_t chunk_size, |
| 173 | void *data) |
| 174 | { |
| 175 | struct pair_chunk_data *pcd = data; |
| 176 | *pcd->p = chunk_start; |
| 177 | *pcd->size = chunk_size; |
| 178 | return 0; |
| 179 | } |
| 180 | |
| 181 | int pair_chunk(struct chunkfile *cf, |
| 182 | uint32_t chunk_id, |
| 183 | const unsigned char **p, |
| 184 | size_t *size) |
| 185 | { |
| 186 | struct pair_chunk_data pcd = { .p = p, .size = size }; |
| 187 | return read_chunk(cf, chunk_id, pair_chunk_fn, &pcd); |
| 188 | } |
| 189 | |
| 190 | int read_chunk(struct chunkfile *cf, |
| 191 | uint32_t chunk_id, |
| 192 | chunk_read_fn fn, |
| 193 | void *data) |
| 194 | { |
| 195 | int i; |
| 196 | |
| 197 | for (i = 0; i < cf->chunks_nr; i++) { |
| 198 | if (cf->chunks[i].id == chunk_id) |
| 199 | return fn(cf->chunks[i].start, cf->chunks[i].size, data); |
| 200 | } |
| 201 | |
| 202 | return CHUNK_NOT_FOUND; |
| 203 | } |
| 204 | |
| 205 | uint8_t oid_version(const struct git_hash_algo *algop) |
| 206 | { |
| 207 | switch (hash_algo_by_ptr(algop)) { |
| 208 | case GIT_HASH_SHA1: |
| 209 | return 1; |
| 210 | case GIT_HASH_SHA256: |
| 211 | return 2; |
| 212 | default: |
| 213 | die(_("invalid hash version")); |
| 214 | } |
| 215 | } |