| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * mpq-update.c -- private filesystem update transactions. | ||
| 3 | * | ||
| 4 | * Copyright (c) 2003-2026 Maik Broemme <mbroemme@libmpq.org> | ||
| 5 | * | ||
| 6 | * This file is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU Lesser General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2.1 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 10 | * | ||
| 11 | * This file is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU Lesser General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU Lesser General Public License | ||
| 17 | * along with this file; if not, see <https://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #ifndef _WIN32 | ||
| 21 | #define _POSIX_C_SOURCE 200809L | ||
| 22 | #endif | ||
| 23 | |||
| 24 | #include "mpq-update.h" | ||
| 25 | #include "mpq-archive.h" | ||
| 26 | #include "mpq-attributes.h" | ||
| 27 | #include "mpq-block.h" | ||
| 28 | #include "mpq-crypto.h" | ||
| 29 | #include "mpq-endian.h" | ||
| 30 | #include "mpq-mpqe.h" | ||
| 31 | #include "mpq-reader.h" | ||
| 32 | #include "mpq-signature.h" | ||
| 33 | #include "mpq-source.h" | ||
| 34 | #include "mpq-stream.h" | ||
| 35 | #include "mpq-writer.h" | ||
| 36 | #include <stdlib.h> | ||
| 37 | #include <string.h> | ||
| 38 | #include <zlib.h> | ||
| 39 | |||
| 40 | #ifndef _WIN32 | ||
| 41 | #include <sys/stat.h> | ||
| 42 | #include <unistd.h> | ||
| 43 | #endif | ||
| 44 | |||
| 45 | #define LIBMPQ_UPDATE_COPY_SIZE 65536u | ||
| 46 | |||
| 47 | typedef enum | ||
| 48 | { | ||
| 49 | LIBMPQ_UPDATE_REPLACE, | ||
| 50 | LIBMPQ_UPDATE_REMOVE, | ||
| 51 | LIBMPQ_UPDATE_RENAME | ||
| 52 | } mpq_update_action_e; | ||
| 53 | |||
| 54 | struct mpq_update | ||
| 55 | { | ||
| 56 | mpq_directory_s *directory; | ||
| 57 | FILE *working; | ||
| 58 | char *destination; | ||
| 59 | char *temporary; | ||
| 60 | char *working_path; | ||
| 61 | uint64_t device; | ||
| 62 | uint64_t inode; | ||
| 63 | const mpq_update_ops_s *ops; | ||
| 64 | uint8_t mpqe_auth[LIBMPQ_MPQE_AUTH_CODE_MINIMUM]; | ||
| 65 | uint8_t mpqe; | ||
| 66 | uint8_t dirty; | ||
| 67 | #ifndef _WIN32 | ||
| 68 | uint32_t mode; | ||
| 69 | #endif | ||
| 70 | }; | ||
| 71 | |||
| 72 | static const mpq_update_ops_s default_update_ops = { fflush, fclose, libmpq__directory_replace }; | ||
| 73 | |||
| 74 | static void | ||
| 75 | 73 | update_result(int32_t *result, int32_t next) | |
| 76 | { | ||
| 77 |
4/4✓ Branch 0 taken 69 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 68 times.
|
73 | if (*result == 0 && next != 0) |
| 78 | 1 | *result = next; | |
| 79 | 73 | } | |
| 80 | |||
| 81 | static char * | ||
| 82 | 194 | update_working_path(const char *path, const char *temporary) | |
| 83 | { | ||
| 84 | const char *slash; | ||
| 85 | size_t directory_size; | ||
| 86 | size_t temporary_size; | ||
| 87 | char *working_path; | ||
| 88 | |||
| 89 | 194 | slash = strrchr(path, '/'); | |
| 90 | #ifdef _WIN32 | ||
| 91 | { | ||
| 92 | const char *backslash = strrchr(path, '\\'); | ||
| 93 | |||
| 94 | if (backslash != NULL && (slash == NULL || backslash > slash)) | ||
| 95 | slash = backslash; | ||
| 96 | } | ||
| 97 | #endif | ||
| 98 |
1/2✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
|
194 | directory_size = slash == NULL ? 2 : (size_t)(slash - path) + 1; |
| 99 | 194 | temporary_size = strlen(temporary); | |
| 100 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 194 times.
|
194 | if (directory_size > SIZE_MAX - temporary_size - 1) |
| 101 | ✗ | return NULL; | |
| 102 | 194 | working_path = malloc(directory_size + temporary_size + 1); | |
| 103 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 194 times.
|
194 | if (working_path == NULL) |
| 104 | ✗ | return NULL; | |
| 105 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 194 times.
|
194 | if (slash == NULL) { |
| 106 | ✗ | working_path[0] = '.'; | |
| 107 | ✗ | working_path[1] = '/'; | |
| 108 | } else { | ||
| 109 | 194 | memcpy(working_path, path, directory_size); | |
| 110 | } | ||
| 111 | 194 | memcpy(working_path + directory_size, temporary, temporary_size + 1); | |
| 112 | 194 | return working_path; | |
| 113 | } | ||
| 114 | |||
| 115 | static int32_t | ||
| 116 | 47 | update_copy(FILE *input, FILE *output) | |
| 117 | { | ||
| 118 | uint8_t buffer[LIBMPQ_UPDATE_COPY_SIZE]; | ||
| 119 | |||
| 120 | ✗ | for (;;) { | |
| 121 | 47 | size_t size = fread(buffer, 1, sizeof(buffer), input); | |
| 122 | |||
| 123 |
2/4✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 47 times.
|
47 | if (size != 0 && fwrite(buffer, 1, size, output) != size) |
| 124 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 125 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (size != sizeof(buffer)) { |
| 126 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
|
47 | if (ferror(input)) |
| 127 | ✗ | return LIBMPQ_ERROR_READ; | |
| 128 | 47 | break; | |
| 129 | } | ||
| 130 | } | ||
| 131 |
1/2✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
|
47 | return fflush(output) == 0 ? 0 : LIBMPQ_ERROR_WRITE; |
| 132 | } | ||
| 133 | |||
| 134 | static int32_t | ||
| 135 | 36 | update_identity(const mpq_update_s *update) | |
| 136 | { | ||
| 137 | FILE *file; | ||
| 138 | uint64_t device; | ||
| 139 | uint64_t inode; | ||
| 140 | int32_t result; | ||
| 141 | |||
| 142 | /* | ||
| 143 | * This is a best-effort identity recheck immediately before publication. | ||
| 144 | * It does not make the subsequent replacement conditional on that identity. | ||
| 145 | */ | ||
| 146 | 36 | file = libmpq__directory_file_open(update->directory, update->destination, "rb"); | |
| 147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (file == NULL) |
| 148 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 149 | 36 | result = libmpq__file_identity(file, &device, &inode); | |
| 150 |
4/6✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 35 times.
|
36 | if (result == 0 && (device != update->device || inode != update->inode)) |
| 151 | 1 | result = LIBMPQ_ERROR_EXIST; | |
| 152 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
|
36 | if (fclose(file) != 0) |
| 153 | ✗ | update_result(&result, LIBMPQ_ERROR_CLOSE); | |
| 154 | 36 | return result; | |
| 155 | } | ||
| 156 | |||
| 157 | static int32_t | ||
| 158 | 60 | update_cleanup(mpq_update_s *update, uint8_t remove_temporary) | |
| 159 | { | ||
| 160 | 60 | int32_t result = 0; | |
| 161 | |||
| 162 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 39 times.
|
60 | if (update->working != NULL) { |
| 163 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
|
21 | if (update->ops->close(update->working) != 0) |
| 164 | ✗ | update_result(&result, LIBMPQ_ERROR_CLOSE); | |
| 165 | 21 | update->working = NULL; | |
| 166 | } | ||
| 167 |
4/4✓ Branch 0 taken 27 times.
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 1 times.
|
60 | if (remove_temporary && update->temporary != NULL) { |
| 168 | 26 | update_result(&result, libmpq__directory_remove(update->directory, update->temporary)); | |
| 169 | } | ||
| 170 | 60 | free(update->working_path); | |
| 171 | 60 | free(update->temporary); | |
| 172 | 60 | free(update->destination); | |
| 173 | 60 | libmpq__mpqe_clear(update->mpqe_auth, sizeof(update->mpqe_auth)); | |
| 174 | 60 | libmpq__directory_close(update->directory); | |
| 175 | 60 | free(update); | |
| 176 | 60 | return result; | |
| 177 | } | ||
| 178 | |||
| 179 | static int32_t | ||
| 180 | update_copy_range(mpq_archive_s *archive, FILE *output, uint64_t offset, uint64_t size); | ||
| 181 | |||
| 182 | /* Prepare a private working file; MPQE decoding fills it directly. */ | ||
| 183 | static int32_t | ||
| 184 | 62 | update_transaction_begin_source(mpq_update_s **update, const char *path, uint8_t copy_source) | |
| 185 | { | ||
| 186 | mpq_update_s *state; | ||
| 187 | 62 | FILE *input = NULL; | |
| 188 | 62 | char *absolute = NULL; | |
| 189 | int32_t result; | ||
| 190 | |||
| 191 |
2/2✓ Branch 0 taken 61 times.
✓ Branch 1 taken 1 times.
|
62 | if (update != NULL) |
| 192 | 61 | *update = NULL; | |
| 193 |
4/4✓ Branch 0 taken 61 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 60 times.
|
62 | if (update == NULL || path == NULL) |
| 194 | 2 | return LIBMPQ_ERROR_EXIST; | |
| 195 | 60 | state = calloc(1, sizeof(*state)); | |
| 196 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
|
60 | if (state == NULL) |
| 197 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 198 | 60 | state->ops = &default_update_ops; | |
| 199 | 60 | absolute = libmpq__file_absolute_path(path); | |
| 200 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
|
60 | if (absolute == NULL) { |
| 201 | ✗ | result = LIBMPQ_ERROR_OPEN; | |
| 202 | ✗ | goto fail; | |
| 203 | } | ||
| 204 | 60 | result = libmpq__directory_open(absolute, &state->directory, &state->destination); | |
| 205 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
|
60 | if (result != 0) |
| 206 | ✗ | goto fail; | |
| 207 | 60 | result = libmpq__directory_file_validate(state->directory, state->destination); | |
| 208 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 59 times.
|
60 | if (result != 0) |
| 209 | 1 | goto fail; | |
| 210 | 59 | input = libmpq__directory_file_open(state->directory, state->destination, "rb"); | |
| 211 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if (input == NULL) { |
| 212 | ✗ | result = LIBMPQ_ERROR_EXIST; | |
| 213 | ✗ | goto fail; | |
| 214 | } | ||
| 215 | 59 | result = libmpq__file_identity(input, &state->device, &state->inode); | |
| 216 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if (result != 0) |
| 217 | ✗ | goto fail; | |
| 218 | 59 | result = libmpq__directory_temporary_reopenable( | |
| 219 | state->directory, ".libmpq-update-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, &state->temporary, | ||
| 220 | &state->working | ||
| 221 | ); | ||
| 222 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if (result != 0) |
| 223 | ✗ | goto fail; | |
| 224 | 59 | state->working_path = update_working_path(absolute, state->temporary); | |
| 225 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if (state->working_path == NULL) { |
| 226 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 227 | ✗ | goto fail; | |
| 228 | } | ||
| 229 | #ifndef _WIN32 | ||
| 230 | { | ||
| 231 | struct stat status; | ||
| 232 | |||
| 233 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 59 times.
|
59 | if (fstat(fileno(input), &status) != 0) { |
| 234 | ✗ | result = LIBMPQ_ERROR_OPEN; | |
| 235 | ✗ | goto fail; | |
| 236 | } | ||
| 237 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if (!S_ISREG(status.st_mode)) { |
| 238 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 239 | ✗ | goto fail; | |
| 240 | } | ||
| 241 | 59 | state->mode = (uint32_t)(status.st_mode & 07777); | |
| 242 | } | ||
| 243 | #endif | ||
| 244 |
2/2✓ Branch 0 taken 47 times.
✓ Branch 1 taken 12 times.
|
59 | if (copy_source) { |
| 245 | 47 | result = update_copy(input, state->working); | |
| 246 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
|
47 | if (result != 0) |
| 247 | ✗ | goto fail; | |
| 248 | } | ||
| 249 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 59 times.
|
59 | if (fclose(input) != 0) { |
| 250 | ✗ | input = NULL; | |
| 251 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 252 | ✗ | goto fail; | |
| 253 | } | ||
| 254 | 59 | free(absolute); | |
| 255 | 59 | *update = state; | |
| 256 | 59 | return 0; | |
| 257 | |||
| 258 | 1 | fail: | |
| 259 | 1 | free(absolute); | |
| 260 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
1 | if (input != NULL && fclose(input) != 0) |
| 261 | ✗ | update_result(&result, LIBMPQ_ERROR_CLOSE); | |
| 262 | 1 | (void)update_cleanup(state, 1); | |
| 263 | 1 | return result; | |
| 264 | } | ||
| 265 | |||
| 266 | /* Begin an ordinary or embedded update with a byte-for-byte working copy. */ | ||
| 267 | int32_t | ||
| 268 | 50 | libmpq__update_transaction_begin(mpq_update_s **update, const char *path) | |
| 269 | { | ||
| 270 | 50 | return update_transaction_begin_source(update, path, 1); | |
| 271 | } | ||
| 272 | |||
| 273 | /* Decode an authenticated MPQE source directly into its private working file. */ | ||
| 274 | int32_t | ||
| 275 | 14 | libmpq__update_transaction_begin_mpqe( | |
| 276 | mpq_update_s **update, const char *path, const uint8_t *auth_code, size_t auth_code_size | ||
| 277 | ) | ||
| 278 | { | ||
| 279 | 14 | mpq_archive_s *source = NULL; | |
| 280 | 14 | mpq_archive_s *check = NULL; | |
| 281 | mpq_update_s *state; | ||
| 282 | uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE]; | ||
| 283 | uint64_t device; | ||
| 284 | uint64_t inode; | ||
| 285 | int32_t result; | ||
| 286 | |||
| 287 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (update != NULL) |
| 288 | 14 | *update = NULL; | |
| 289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (update == NULL) |
| 290 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 291 | 14 | result = libmpq__mpqe_key(key, auth_code, auth_code_size); | |
| 292 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
|
14 | if (result != 0) |
| 293 | 2 | return result; | |
| 294 | 12 | libmpq__mpqe_clear(key, sizeof(key)); | |
| 295 | 12 | result = update_transaction_begin_source(update, path, 0); | |
| 296 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (result != 0) |
| 297 | ✗ | return result; | |
| 298 | 12 | state = *update; | |
| 299 | 12 | result = libmpq__reader_archive_open_mpqe(&source, path, -1, auth_code, auth_code_size); | |
| 300 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
|
12 | if (result != 0) |
| 301 | 2 | goto fail; | |
| 302 | 10 | result = libmpq__source_file_identity(source->source, &device, &inode); | |
| 303 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 304 | ✗ | goto fail; | |
| 305 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (device != state->device || inode != state->inode) { |
| 306 | ✗ | result = LIBMPQ_ERROR_EXIST; | |
| 307 | ✗ | goto fail; | |
| 308 | } | ||
| 309 | 10 | result = update_copy_range(source, state->working, 0, source->file_size); | |
| 310 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 311 | ✗ | goto fail; | |
| 312 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (fflush(state->working) != 0) { |
| 313 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 314 | ✗ | goto fail; | |
| 315 | } | ||
| 316 | 10 | result = libmpq__reader_archive_open_path(&check, state->working_path, -1); | |
| 317 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 318 | ✗ | goto fail; | |
| 319 | 10 | result = libmpq__archive_close(check); | |
| 320 | 10 | check = NULL; | |
| 321 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 322 | ✗ | goto fail; | |
| 323 | 10 | result = libmpq__archive_close(source); | |
| 324 | 10 | source = NULL; | |
| 325 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 326 | ✗ | goto fail; | |
| 327 | 10 | state->mpqe = 1; | |
| 328 | 10 | memcpy(state->mpqe_auth, auth_code, sizeof(state->mpqe_auth)); | |
| 329 | 10 | return 0; | |
| 330 | |||
| 331 | 2 | fail: | |
| 332 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (check != NULL) |
| 333 | ✗ | (void)libmpq__archive_close(check); | |
| 334 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (source != NULL) |
| 335 | ✗ | (void)libmpq__archive_close(source); | |
| 336 | 2 | (void)update_cleanup(state, 1); | |
| 337 | 2 | *update = NULL; | |
| 338 | 2 | return result; | |
| 339 | } | ||
| 340 | |||
| 341 | const char * | ||
| 342 | 83 | libmpq__update_path(const mpq_update_s *update) | |
| 343 | { | ||
| 344 |
1/2✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
|
83 | return update == NULL ? NULL : update->working_path; |
| 345 | } | ||
| 346 | |||
| 347 | const mpq_update_ops_s * | ||
| 348 | 7 | libmpq__update_ops(const mpq_update_s *update) | |
| 349 | { | ||
| 350 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | return update == NULL ? NULL : update->ops; |
| 351 | } | ||
| 352 | |||
| 353 | void | ||
| 354 | 3 | libmpq__update_set_ops(mpq_update_s *update, const mpq_update_ops_s *ops) | |
| 355 | { | ||
| 356 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | if (update != NULL && ops != NULL && ops->flush != NULL && ops->close != NULL && |
| 357 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | ops->publish != NULL) |
| 358 | 3 | update->ops = ops; | |
| 359 | 3 | } | |
| 360 | |||
| 361 | /* Rewrap a staged plaintext MPQ before publishing the encrypted sibling file. */ | ||
| 362 | static int32_t | ||
| 363 | 7 | update_commit_mpqe(mpq_update_s *update) | |
| 364 | { | ||
| 365 | 7 | mpq_archive_s *check = NULL; | |
| 366 | 7 | FILE *encrypted = NULL; | |
| 367 | 7 | char *encrypted_name = NULL; | |
| 368 | 7 | char *encrypted_path = NULL; | |
| 369 | uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE]; | ||
| 370 | 7 | uint8_t plaintext_removed = 0; | |
| 371 | 7 | uint8_t published = 0; | |
| 372 | int32_t result; | ||
| 373 | |||
| 374 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (update->working == NULL) |
| 375 | ✗ | result = LIBMPQ_ERROR_EXIST; | |
| 376 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
|
7 | else if (update->ops->flush(update->working) != 0) |
| 377 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 378 | else | ||
| 379 | 7 | result = 0; | |
| 380 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (result != 0) |
| 381 | ✗ | goto done; | |
| 382 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
|
7 | if (!update->dirty) { |
| 383 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (update->ops->close(update->working) != 0) |
| 384 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 385 | 1 | update->working = NULL; | |
| 386 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (result == 0) |
| 387 | 1 | result = update_identity(update); | |
| 388 | 1 | goto done; | |
| 389 | } | ||
| 390 | 6 | result = libmpq__mpqe_key(key, update->mpqe_auth, sizeof(update->mpqe_auth)); | |
| 391 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 392 | ✗ | goto done; | |
| 393 | 6 | result = libmpq__directory_temporary_reopenable( | |
| 394 | update->directory, ".libmpq-update-mpqe-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, | ||
| 395 | &encrypted_name, &encrypted | ||
| 396 | ); | ||
| 397 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 398 | ✗ | goto clear_key; | |
| 399 | 6 | encrypted_path = update_working_path(update->working_path, encrypted_name); | |
| 400 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (encrypted_path == NULL) { |
| 401 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 402 | ✗ | goto clear_key; | |
| 403 | } | ||
| 404 | 6 | result = libmpq__writer_mpqe_transform_file(update->working, encrypted, key); | |
| 405 | 6 | libmpq__mpqe_clear(key, sizeof(key)); | |
| 406 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 407 | ✗ | goto done; | |
| 408 | 12 | result = libmpq__reader_archive_open_mpqe( | |
| 409 | 6 | &check, encrypted_path, -1, update->mpqe_auth, sizeof(update->mpqe_auth) | |
| 410 | ); | ||
| 411 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 412 | ✗ | goto done; | |
| 413 | 6 | result = libmpq__archive_close(check); | |
| 414 | 6 | check = NULL; | |
| 415 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 416 | ✗ | goto done; | |
| 417 | #ifndef _WIN32 | ||
| 418 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (fchmod(fileno(encrypted), (mode_t)update->mode) != 0) { |
| 419 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 420 | ✗ | goto done; | |
| 421 | } | ||
| 422 | #else | ||
| 423 | result = | ||
| 424 | libmpq__directory_copy_security(update->directory, update->destination, encrypted_name); | ||
| 425 | if (result != 0) | ||
| 426 | goto done; | ||
| 427 | #endif | ||
| 428 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if (fclose(encrypted) != 0) { |
| 429 | ✗ | encrypted = NULL; | |
| 430 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 431 | ✗ | goto done; | |
| 432 | } | ||
| 433 | 6 | encrypted = NULL; | |
| 434 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if (update->ops->close(update->working) != 0) { |
| 435 | ✗ | update->working = NULL; | |
| 436 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 437 | ✗ | goto done; | |
| 438 | } | ||
| 439 | 6 | update->working = NULL; | |
| 440 | 6 | result = libmpq__directory_remove(update->directory, update->temporary); | |
| 441 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (result != 0) |
| 442 | ✗ | goto done; | |
| 443 | 6 | plaintext_removed = 1; | |
| 444 | 6 | result = update_identity(update); | |
| 445 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (result == 0) { |
| 446 | 6 | result = update->ops->publish(update->directory, encrypted_name, update->destination); | |
| 447 | 6 | published = result == 0; | |
| 448 | } | ||
| 449 | 6 | goto done; | |
| 450 | |||
| 451 | ✗ | clear_key: | |
| 452 | ✗ | libmpq__mpqe_clear(key, sizeof(key)); | |
| 453 | 7 | done: | |
| 454 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (check != NULL) |
| 455 | ✗ | (void)libmpq__archive_close(check); | |
| 456 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
7 | if (encrypted != NULL && fclose(encrypted) != 0) |
| 457 | ✗ | update_result(&result, LIBMPQ_ERROR_CLOSE); | |
| 458 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
7 | if (encrypted_name != NULL && !published) |
| 459 | ✗ | update_result(&result, libmpq__directory_remove(update->directory, encrypted_name)); | |
| 460 | 7 | free(encrypted_name); | |
| 461 | 7 | free(encrypted_path); | |
| 462 | 7 | update_result(&result, update_cleanup(update, (uint8_t)!plaintext_removed)); | |
| 463 | 7 | return result; | |
| 464 | } | ||
| 465 | |||
| 466 | int32_t | ||
| 467 | 38 | libmpq__update_transaction_commit(mpq_update_s *update) | |
| 468 | { | ||
| 469 | 38 | int32_t result = 0; | |
| 470 | int32_t cleanup_result; | ||
| 471 | 38 | uint8_t published = 0; | |
| 472 | |||
| 473 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (update == NULL) |
| 474 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 475 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 31 times.
|
38 | if (update->mpqe) |
| 476 | 7 | return update_commit_mpqe(update); | |
| 477 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (update->working == NULL) { |
| 478 | ✗ | result = LIBMPQ_ERROR_EXIST; | |
| 479 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 30 times.
|
31 | } else if (update->ops->flush(update->working) != 0) { |
| 480 | 1 | result = LIBMPQ_ERROR_WRITE; | |
| 481 | } | ||
| 482 | #ifndef _WIN32 | ||
| 483 |
3/4✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 30 times.
|
31 | if (result == 0 && fchmod(fileno(update->working), (mode_t)update->mode) != 0) |
| 484 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 485 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | if (update->working != NULL) { |
| 486 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 30 times.
|
31 | if (update->ops->close(update->working) != 0) |
| 487 | 1 | update_result(&result, LIBMPQ_ERROR_CLOSE); | |
| 488 | 31 | update->working = NULL; | |
| 489 | } | ||
| 490 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 2 times.
|
31 | if (result == 0) |
| 491 | 29 | result = update_identity(update); | |
| 492 | #else | ||
| 493 | if (result == 0) | ||
| 494 | result = update_identity(update); | ||
| 495 | if (result == 0) { | ||
| 496 | result = libmpq__directory_copy_security( | ||
| 497 | update->directory, update->destination, update->temporary | ||
| 498 | ); | ||
| 499 | } | ||
| 500 | if (update->working != NULL) { | ||
| 501 | if (update->ops->close(update->working) != 0) | ||
| 502 | update_result(&result, LIBMPQ_ERROR_CLOSE); | ||
| 503 | update->working = NULL; | ||
| 504 | } | ||
| 505 | #endif | ||
| 506 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 3 times.
|
31 | if (result == 0) { |
| 507 | 28 | result = update->ops->publish(update->directory, update->temporary, update->destination); | |
| 508 | 28 | published = result == 0; | |
| 509 | } | ||
| 510 | 31 | cleanup_result = update_cleanup(update, (uint8_t)!published); | |
| 511 | 31 | update_result(&result, cleanup_result); | |
| 512 | 31 | return result; | |
| 513 | } | ||
| 514 | |||
| 515 | int32_t | ||
| 516 | 20 | libmpq__update_transaction_abort(mpq_update_s *update) | |
| 517 | { | ||
| 518 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 19 times.
|
20 | if (update == NULL) |
| 519 | 1 | return LIBMPQ_ERROR_EXIST; | |
| 520 | 19 | return update_cleanup(update, 1); | |
| 521 | } | ||
| 522 | |||
| 523 | static int32_t | ||
| 524 | 146 | update_copy_range(mpq_archive_s *archive, FILE *output, uint64_t offset, uint64_t size) | |
| 525 | { | ||
| 526 | uint8_t buffer[LIBMPQ_UPDATE_COPY_SIZE]; | ||
| 527 | |||
| 528 |
2/2✓ Branch 0 taken 146 times.
✓ Branch 1 taken 146 times.
|
292 | while (size != 0) { |
| 529 | 146 | size_t chunk = size < sizeof(buffer) ? (size_t)size : sizeof(buffer); | |
| 530 | 146 | int32_t result = libmpq__source_read_at(archive->source, offset, buffer, chunk); | |
| 531 | |||
| 532 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 146 times.
|
146 | if (result != 0) |
| 533 | ✗ | return result; | |
| 534 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 146 times.
|
146 | if (fwrite(buffer, 1, chunk, output) != chunk) |
| 535 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 536 | 146 | offset += chunk; | |
| 537 | 146 | size -= chunk; | |
| 538 | } | ||
| 539 | 146 | return 0; | |
| 540 | } | ||
| 541 | |||
| 542 | static int32_t | ||
| 543 | 31 | update_read_named(mpq_archive_s *archive, const char *name, uint8_t **data, size_t *size) | |
| 544 | { | ||
| 545 | 31 | mpq_stream_s *stream = NULL; | |
| 546 | 31 | libmpq__off_t length = 0; | |
| 547 | 31 | libmpq__off_t transferred = 0; | |
| 548 | uint8_t *bytes; | ||
| 549 | int32_t result; | ||
| 550 | |||
| 551 | 31 | *data = NULL; | |
| 552 | 31 | *size = 0; | |
| 553 | 31 | result = libmpq__stream_open_name(archive, name, &stream); | |
| 554 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (result != 0) |
| 555 | ✗ | return result; | |
| 556 | 31 | result = libmpq__stream_size(stream, &length); | |
| 557 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
|
31 | if (result != 0 || length < 0 || (uint64_t)length > SIZE_MAX) { |
| 558 | ✗ | (void)libmpq__stream_close(stream); | |
| 559 | ✗ | return result == 0 ? LIBMPQ_ERROR_SIZE : result; | |
| 560 | } | ||
| 561 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | bytes = malloc(length == 0 ? 1u : (size_t)length); |
| 562 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (bytes == NULL) { |
| 563 | ✗ | (void)libmpq__stream_close(stream); | |
| 564 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 565 | } | ||
| 566 | 31 | result = libmpq__stream_read(stream, bytes, length, &transferred); | |
| 567 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
|
31 | if (result == 0 && transferred != length) |
| 568 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 569 |
1/4✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
31 | if (libmpq__stream_close(stream) != 0 && result == 0) |
| 570 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 571 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (result != 0) { |
| 572 | ✗ | free(bytes); | |
| 573 | ✗ | return result; | |
| 574 | } | ||
| 575 | 31 | *data = bytes; | |
| 576 | 31 | *size = (size_t)length; | |
| 577 | 31 | return 0; | |
| 578 | } | ||
| 579 | |||
| 580 | static int32_t | ||
| 581 | 31 | update_listfile( | |
| 582 | mpq_archive_s *archive, const char *old_name, const char *new_name, uint8_t **data, | ||
| 583 | size_t *size, uint32_t *index | ||
| 584 | ) | ||
| 585 | { | ||
| 586 | 31 | uint8_t *original = NULL; | |
| 587 | uint8_t *updated; | ||
| 588 | 31 | size_t original_size = 0; | |
| 589 | 31 | size_t used = 0; | |
| 590 | 31 | size_t pos = 0; | |
| 591 | int32_t result; | ||
| 592 | |||
| 593 | 31 | result = libmpq__file_number(archive, LIBMPQ_LISTFILE_NAME, index); | |
| 594 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (result != 0) |
| 595 | ✗ | return result; | |
| 596 | 31 | result = update_read_named(archive, LIBMPQ_LISTFILE_NAME, &original, &original_size); | |
| 597 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (result != 0) |
| 598 | ✗ | return result; | |
| 599 |
3/4✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
|
31 | if (original_size > SIZE_MAX - strlen(new_name == NULL ? "" : new_name) - 2u) { |
| 600 | ✗ | free(original); | |
| 601 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 602 | } | ||
| 603 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
|
31 | updated = malloc(original_size + strlen(new_name == NULL ? "" : new_name) + 2u); |
| 604 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (updated == NULL) { |
| 605 | ✗ | free(original); | |
| 606 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 607 | } | ||
| 608 |
2/2✓ Branch 0 taken 221 times.
✓ Branch 1 taken 31 times.
|
252 | while (pos < original_size) { |
| 609 | 221 | size_t begin = pos; | |
| 610 | size_t length; | ||
| 611 | char *name; | ||
| 612 | 221 | uint8_t skip = 0; | |
| 613 | |||
| 614 |
4/6✓ Branch 0 taken 2541 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2320 times.
✓ Branch 3 taken 221 times.
✓ Branch 4 taken 2320 times.
✗ Branch 5 not taken.
|
2541 | while (pos < original_size && original[pos] != '\n' && original[pos] != '\r') |
| 615 | 2320 | pos++; | |
| 616 | 221 | length = pos - begin; | |
| 617 |
5/6✓ Branch 0 taken 411 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 221 times.
✓ Branch 3 taken 190 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 190 times.
|
442 | while (pos < original_size && (original[pos] == '\n' || original[pos] == '\r')) |
| 618 | 221 | pos++; | |
| 619 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
|
221 | if (length == 0) |
| 620 | ✗ | continue; | |
| 621 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
|
221 | if (memchr(original + begin, 0, length) != NULL) { |
| 622 | ✗ | free(updated); | |
| 623 | ✗ | free(original); | |
| 624 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 625 | } | ||
| 626 | 221 | name = malloc(length + 1u); | |
| 627 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
|
221 | if (name == NULL) { |
| 628 | ✗ | free(updated); | |
| 629 | ✗ | free(original); | |
| 630 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 631 | } | ||
| 632 | 221 | memcpy(name, original + begin, length); | |
| 633 | 221 | name[length] = '\0'; | |
| 634 |
2/2✓ Branch 1 taken 31 times.
✓ Branch 2 taken 190 times.
|
442 | if (libmpq__crypto_hash_string(name, 0x100) == |
| 635 |
1/2✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
|
252 | libmpq__crypto_hash_string(old_name, 0x100) && |
| 636 | 31 | libmpq__crypto_hash_string(name, 0x200) == libmpq__crypto_hash_string(old_name, 0x200)) | |
| 637 | 31 | skip = 1; | |
| 638 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 214 times.
|
442 | if (libmpq__crypto_hash_string(name, 0x100) == |
| 639 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
228 | libmpq__crypto_hash_string(LIBMPQ_SIGNATURE_NAME, 0x100) && |
| 640 | 7 | libmpq__crypto_hash_string(name, 0x200) == | |
| 641 | 7 | libmpq__crypto_hash_string(LIBMPQ_SIGNATURE_NAME, 0x200)) | |
| 642 | 7 | skip = 1; | |
| 643 | 221 | free(name); | |
| 644 |
2/2✓ Branch 0 taken 183 times.
✓ Branch 1 taken 38 times.
|
221 | if (!skip) { |
| 645 | 183 | memcpy(updated + used, original + begin, length); | |
| 646 | 183 | used += length; | |
| 647 | 183 | updated[used++] = '\n'; | |
| 648 | } | ||
| 649 | } | ||
| 650 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
|
31 | if (new_name != NULL) { |
| 651 | 26 | size_t length = strlen(new_name); | |
| 652 | |||
| 653 | 26 | memcpy(updated + used, new_name, length + 1u); | |
| 654 | 26 | updated[used + length] = '\n'; | |
| 655 | 26 | used += length + 1u; | |
| 656 | } | ||
| 657 | 31 | free(original); | |
| 658 | 31 | *data = updated; | |
| 659 | 31 | *size = used; | |
| 660 | 31 | return 0; | |
| 661 | } | ||
| 662 | |||
| 663 | static uint32_t | ||
| 664 | 48 | update_hash_slot(const mpq_archive_s *archive, const char *name, uint32_t block) | |
| 665 | { | ||
| 666 | 48 | uint32_t a = libmpq__crypto_hash_string(name, 0x100); | |
| 667 | 48 | uint32_t b = libmpq__crypto_hash_string(name, 0x200); | |
| 668 | uint32_t i; | ||
| 669 | |||
| 670 |
1/2✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
|
518 | for (i = 0; i < archive->mpq_header.hash_table_count; i++) { |
| 671 | 518 | const mpq_hash_s *entry = &archive->mpq_hash[i]; | |
| 672 | |||
| 673 |
4/6✓ Branch 0 taken 48 times.
✓ Branch 1 taken 470 times.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
|
518 | if (entry->block_table_index == block && entry->hash_a == a && entry->hash_b == b) |
| 674 | 48 | return i; | |
| 675 | } | ||
| 676 | ✗ | return UINT32_MAX; | |
| 677 | } | ||
| 678 | |||
| 679 | /* Count live aliases without treating empty and deleted hash slots as references. */ | ||
| 680 | static uint32_t | ||
| 681 | 115 | update_block_references(const mpq_archive_s *archive, uint32_t block) | |
| 682 | { | ||
| 683 | 115 | uint32_t references = 0; | |
| 684 | uint32_t i; | ||
| 685 | |||
| 686 |
2/2✓ Branch 0 taken 4608 times.
✓ Branch 1 taken 115 times.
|
4723 | for (i = 0; i < archive->mpq_header.hash_table_count; i++) { |
| 687 | 4608 | uint32_t index = archive->mpq_hash[i].block_table_index; | |
| 688 | |||
| 689 |
4/4✓ Branch 0 taken 907 times.
✓ Branch 1 taken 3701 times.
✓ Branch 2 taken 116 times.
✓ Branch 3 taken 791 times.
|
4608 | if (index < archive->mpq_header.block_table_count && index == block) |
| 690 | 116 | references++; | |
| 691 | } | ||
| 692 | 115 | return references; | |
| 693 | } | ||
| 694 | |||
| 695 | static uint8_t | ||
| 696 | 65 | update_reserved_name(const char *name) | |
| 697 | { | ||
| 698 | static const char *reserved[] = { LIBMPQ_ATTRIBUTES_NAME, LIBMPQ_LISTFILE_NAME, | ||
| 699 | LIBMPQ_SIGNATURE_NAME }; | ||
| 700 | 65 | uint32_t a = libmpq__crypto_hash_string(name, 0x100); | |
| 701 | 65 | uint32_t b = libmpq__crypto_hash_string(name, 0x200); | |
| 702 | size_t i; | ||
| 703 | |||
| 704 |
2/2✓ Branch 0 taken 193 times.
✓ Branch 1 taken 64 times.
|
257 | for (i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) |
| 705 |
3/4✓ Branch 1 taken 1 times.
✓ Branch 2 taken 192 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
194 | if (a == libmpq__crypto_hash_string(reserved[i], 0x100) && |
| 706 | 1 | b == libmpq__crypto_hash_string(reserved[i], 0x200)) | |
| 707 | 1 | return 1; | |
| 708 | 64 | return 0; | |
| 709 | } | ||
| 710 | |||
| 711 | static void | ||
| 712 | 22 | update_delete_hash(mpq_hash_s *entry) | |
| 713 | { | ||
| 714 | 22 | entry->hash_a = 0; | |
| 715 | 22 | entry->hash_b = 0; | |
| 716 | 22 | entry->locale = 0; | |
| 717 | 22 | entry->platform = 0; | |
| 718 | 22 | entry->block_table_index = LIBMPQ_HASH_DELETED; | |
| 719 | 22 | } | |
| 720 | |||
| 721 | static int32_t | ||
| 722 | 10 | update_insert_hash(mpq_archive_s *archive, const char *name, const mpq_hash_s *original) | |
| 723 | { | ||
| 724 | 10 | uint32_t count = archive->mpq_header.hash_table_count; | |
| 725 | uint32_t start; | ||
| 726 | 10 | uint32_t candidate = UINT32_MAX; | |
| 727 | uint32_t i; | ||
| 728 | |||
| 729 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (count == 0) |
| 730 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 731 | 10 | start = libmpq__crypto_hash_string(name, 0) % count; | |
| 732 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | for (i = 0; i < count; i++) { |
| 733 | 10 | uint32_t slot = (start + i) % count; | |
| 734 | 10 | uint32_t index = archive->mpq_hash[slot].block_table_index; | |
| 735 | |||
| 736 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
10 | if (index == UINT32_MAX - 1u && candidate == UINT32_MAX) |
| 737 | ✗ | candidate = slot; | |
| 738 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (index == UINT32_MAX) { |
| 739 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (candidate == UINT32_MAX) |
| 740 | 10 | candidate = slot; | |
| 741 | 10 | break; | |
| 742 | } | ||
| 743 | } | ||
| 744 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (candidate == UINT32_MAX) |
| 745 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 746 | 10 | archive->mpq_hash[candidate] = *original; | |
| 747 | 10 | archive->mpq_hash[candidate].hash_a = libmpq__crypto_hash_string(name, 0x100); | |
| 748 | 10 | archive->mpq_hash[candidate].hash_b = libmpq__crypto_hash_string(name, 0x200); | |
| 749 | 10 | return 0; | |
| 750 | } | ||
| 751 | |||
| 752 | static int32_t | ||
| 753 | 91 | update_encode( | |
| 754 | mpq_update_s *update, mpq_archive_s *archive, FILE *output, const char *name, | ||
| 755 | const uint8_t *data, uint64_t size, const char *source_path, const char *old_name, | ||
| 756 | const mpq_file_options_s *file_options, mpq_block_s *block, mpq_block_ex_s *block_ex, | ||
| 757 | mpq_file_attributes_s *attributes, uint8_t *expected_md5, uint64_t *expected_size | ||
| 758 | ) | ||
| 759 | { | ||
| 760 | 91 | mpq_archive_create_options_s options = { 0, 1, 0, LIBMPQ_ARCHIVE_CREATE_COMPRESSION_EXTENDED, | |
| 761 | 0 }; | ||
| 762 | 91 | mpq_archive_s *writer = NULL; | |
| 763 | 91 | mpq_archive_s *packed = NULL; | |
| 764 | 91 | mpq_writer_s *file_writer = NULL; | |
| 765 | 91 | mpq_stream_s *old_stream = NULL; | |
| 766 | 91 | mpq_stream_s *expected_stream = NULL; | |
| 767 | 91 | FILE *input = NULL; | |
| 768 | 91 | FILE *temporary_file = NULL; | |
| 769 | 91 | char *temporary = NULL; | |
| 770 | 91 | char *temporary_path = NULL; | |
| 771 | libmpq__off_t measured; | ||
| 772 | uint8_t buffer[LIBMPQ_UPDATE_COPY_SIZE]; | ||
| 773 | uint64_t remaining; | ||
| 774 | uint64_t position; | ||
| 775 | mpq_md5_s md5; | ||
| 776 | 91 | uint32_t crc = 0; | |
| 777 | int32_t result; | ||
| 778 | |||
| 779 | 91 | options.version = archive->mpq_header.version; | |
| 780 | 91 | options.sector_size = archive->block_size; | |
| 781 | 91 | result = libmpq__directory_temporary_reopenable( | |
| 782 | update->directory, ".libmpq-packed-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, &temporary, | ||
| 783 | &temporary_file | ||
| 784 | ); | ||
| 785 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 786 | ✗ | return result; | |
| 787 | 91 | temporary_path = update_working_path(update->working_path, temporary); | |
| 788 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (temporary_path == NULL) { |
| 789 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 790 | ✗ | goto done; | |
| 791 | } | ||
| 792 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 86 times.
|
91 | if (source_path != NULL) { |
| 793 | 5 | input = libmpq__file_open(source_path, "rb"); | |
| 794 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (input == NULL) { |
| 795 | ✗ | result = LIBMPQ_ERROR_OPEN; | |
| 796 | ✗ | goto done; | |
| 797 | } | ||
| 798 |
2/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
|
10 | if (libmpq__file_seek(input, 0, SEEK_END) != 0 || |
| 799 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
10 | (measured = libmpq__file_tell(input)) < 0 || |
| 800 | 5 | libmpq__file_seek(input, 0, SEEK_SET) != 0) { | |
| 801 | ✗ | result = LIBMPQ_ERROR_SEEK; | |
| 802 | ✗ | goto done; | |
| 803 | } | ||
| 804 | 5 | size = (uint64_t)measured; | |
| 805 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 82 times.
|
86 | } else if (old_name != NULL) { |
| 806 | 4 | libmpq__off_t old_size = 0; | |
| 807 | |||
| 808 | 4 | result = libmpq__stream_open_name(archive, old_name, &old_stream); | |
| 809 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (result != 0) |
| 810 | ✗ | goto done; | |
| 811 | 4 | result = libmpq__stream_size(old_stream, &old_size); | |
| 812 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (result != 0 || old_size < 0) { |
| 813 | ✗ | result = result == 0 ? LIBMPQ_ERROR_SIZE : result; | |
| 814 | ✗ | goto done; | |
| 815 | } | ||
| 816 | 4 | size = (uint64_t)old_size; | |
| 817 | } | ||
| 818 |
2/4✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 91 times.
|
91 | if (size > UINT32_MAX || size > INT64_MAX || (data != NULL && size > SIZE_MAX)) { |
| 819 | ✗ | result = LIBMPQ_ERROR_SIZE; | |
| 820 | ✗ | goto done; | |
| 821 | } | ||
| 822 | 91 | result = libmpq__writer_archive_create_file(&writer, temporary_path, temporary_file, &options); | |
| 823 | 91 | temporary_file = NULL; | |
| 824 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 825 | ✗ | goto done; | |
| 826 | result = | ||
| 827 | 91 | libmpq__writer_file_begin(writer, name, (libmpq__off_t)size, file_options, &file_writer); | |
| 828 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 829 | ✗ | goto done; | |
| 830 |
4/4✓ Branch 0 taken 69 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 36 times.
|
91 | if (expected_md5 != NULL || |
| 831 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | (attributes != NULL && (attributes->flags & LIBMPQ_ATTRIBUTE_MD5) != 0)) |
| 832 | 55 | libmpq__md5_init(&md5); | |
| 833 | 91 | remaining = size; | |
| 834 | 91 | position = 0; | |
| 835 |
2/2✓ Branch 0 taken 91 times.
✓ Branch 1 taken 91 times.
|
182 | while (remaining != 0) { |
| 836 | 91 | size_t chunk = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer); | |
| 837 | 91 | const uint8_t *bytes = buffer; | |
| 838 | |||
| 839 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 86 times.
|
91 | if (input != NULL) { |
| 840 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if (fread(buffer, 1, chunk, input) != chunk) { |
| 841 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 842 | ✗ | break; | |
| 843 | } | ||
| 844 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 82 times.
|
86 | } else if (old_stream != NULL) { |
| 845 | 4 | libmpq__off_t transferred = 0; | |
| 846 | |||
| 847 | 4 | result = libmpq__stream_read(old_stream, buffer, (libmpq__off_t)chunk, &transferred); | |
| 848 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (result != 0 || transferred != (libmpq__off_t)chunk) { |
| 849 | ✗ | if (result == 0) | |
| 850 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 851 | ✗ | break; | |
| 852 | } | ||
| 853 |
1/2✓ Branch 0 taken 82 times.
✗ Branch 1 not taken.
|
82 | } else if (data != NULL) { |
| 854 | 82 | bytes = data + position; | |
| 855 | } else { | ||
| 856 | ✗ | result = LIBMPQ_ERROR_EXIST; | |
| 857 | ✗ | break; | |
| 858 | } | ||
| 859 | 91 | result = libmpq__writer_file_write(file_writer, bytes, (libmpq__off_t)chunk); | |
| 860 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 861 | ✗ | break; | |
| 862 |
2/2✓ Branch 0 taken 53 times.
✓ Branch 1 taken 38 times.
|
91 | if (attributes != NULL) { |
| 863 |
1/2✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
|
53 | if ((attributes->flags & LIBMPQ_ATTRIBUTE_CRC32) != 0) |
| 864 | 53 | crc = (uint32_t)crc32(crc, bytes, (uInt)chunk); | |
| 865 | } | ||
| 866 |
4/4✓ Branch 0 taken 69 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 36 times.
|
91 | if (expected_md5 != NULL || |
| 867 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | (attributes != NULL && (attributes->flags & LIBMPQ_ATTRIBUTE_MD5) != 0)) |
| 868 | 55 | libmpq__md5_update(&md5, bytes, chunk); | |
| 869 | 91 | remaining -= chunk; | |
| 870 | 91 | position += chunk; | |
| 871 | } | ||
| 872 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 873 | ✗ | goto done; | |
| 874 | 91 | result = libmpq__writer_file_finish(file_writer); | |
| 875 | 91 | file_writer = NULL; | |
| 876 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 877 | ✗ | goto done; | |
| 878 |
2/2✓ Branch 0 taken 53 times.
✓ Branch 1 taken 38 times.
|
91 | if (attributes != NULL) { |
| 879 | 53 | attributes->crc32 = crc; | |
| 880 | 53 | attributes->patch_bit = 0; | |
| 881 | } | ||
| 882 |
4/4✓ Branch 0 taken 69 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 36 times.
|
91 | if (expected_md5 != NULL || |
| 883 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | (attributes != NULL && (attributes->flags & LIBMPQ_ATTRIBUTE_MD5) != 0)) { |
| 884 | uint8_t digest[16]; | ||
| 885 | |||
| 886 | 55 | libmpq__md5_final(&md5, digest); | |
| 887 |
3/4✓ Branch 0 taken 53 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 53 times.
✗ Branch 3 not taken.
|
55 | if (attributes != NULL && (attributes->flags & LIBMPQ_ATTRIBUTE_MD5) != 0) |
| 888 | 53 | memcpy(attributes->md5, digest, sizeof(digest)); | |
| 889 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 33 times.
|
55 | if (expected_md5 != NULL) |
| 890 | 22 | memcpy(expected_md5, digest, sizeof(digest)); | |
| 891 | } | ||
| 892 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 69 times.
|
91 | if (expected_size != NULL) |
| 893 | 22 | *expected_size = size; | |
| 894 | 91 | result = libmpq__archive_close(writer); | |
| 895 | 91 | writer = NULL; | |
| 896 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 897 | ✗ | goto done; | |
| 898 | 91 | result = libmpq__archive_open(&packed, temporary_path, 0); | |
| 899 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (result != 0) |
| 900 | ✗ | goto done; | |
| 901 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 69 times.
|
91 | if (expected_md5 != NULL && |
| 902 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | ((file_options->compression_first | file_options->compression_next) & |
| 903 | (LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO)) != 0) { | ||
| 904 | ✗ | result = libmpq__stream_open_name(packed, name, &expected_stream); | |
| 905 | ✗ | if (result != 0) | |
| 906 | ✗ | goto done; | |
| 907 | ✗ | libmpq__md5_init(&md5); | |
| 908 | ✗ | remaining = size; | |
| 909 | ✗ | while (remaining != 0) { | |
| 910 | ✗ | size_t chunk = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer); | |
| 911 | ✗ | libmpq__off_t transferred = 0; | |
| 912 | |||
| 913 | result = | ||
| 914 | ✗ | libmpq__stream_read(expected_stream, buffer, (libmpq__off_t)chunk, &transferred); | |
| 915 | ✗ | if (result != 0 || transferred != (libmpq__off_t)chunk) { | |
| 916 | ✗ | if (result == 0) | |
| 917 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 918 | ✗ | goto done; | |
| 919 | } | ||
| 920 | ✗ | libmpq__md5_update(&md5, buffer, chunk); | |
| 921 | ✗ | remaining -= chunk; | |
| 922 | } | ||
| 923 | ✗ | libmpq__md5_final(&md5, expected_md5); | |
| 924 | ✗ | result = libmpq__stream_close(expected_stream); | |
| 925 | ✗ | expected_stream = NULL; | |
| 926 | ✗ | if (result != 0) | |
| 927 | ✗ | goto done; | |
| 928 | } | ||
| 929 | 91 | measured = libmpq__file_tell(output); | |
| 930 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (measured < 0) { |
| 931 | ✗ | result = LIBMPQ_ERROR_SEEK; | |
| 932 | ✗ | goto done; | |
| 933 | } | ||
| 934 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if ((uint64_t)measured < (uint64_t)archive->archive_offset) { |
| 935 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 936 | ✗ | goto done; | |
| 937 | } | ||
| 938 | 91 | position = (uint64_t)measured - (uint64_t)archive->archive_offset; | |
| 939 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
91 | if (position > UINT32_MAX && archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_ONE) { |
| 940 | ✗ | result = LIBMPQ_ERROR_SIZE; | |
| 941 | ✗ | goto done; | |
| 942 | } | ||
| 943 | 91 | *block = packed->mpq_block[0]; | |
| 944 | 91 | block->offset = (uint32_t)position; | |
| 945 | 91 | block_ex->offset_high = (uint16_t)(position >> 32); | |
| 946 | 91 | result = update_copy_range( | |
| 947 | 91 | packed, output, packed->mpq_block[0].offset, packed->mpq_block[0].packed_size | |
| 948 | ); | ||
| 949 | |||
| 950 | 91 | done: | |
| 951 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (file_writer != NULL) |
| 952 | ✗ | libmpq__writer_file_abort(file_writer); | |
| 953 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (writer != NULL) |
| 954 | ✗ | (void)libmpq__archive_close(writer); | |
| 955 |
1/2✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
|
91 | if (packed != NULL) |
| 956 | 91 | (void)libmpq__archive_close(packed); | |
| 957 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 87 times.
|
91 | if (old_stream != NULL) |
| 958 | 4 | (void)libmpq__stream_close(old_stream); | |
| 959 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (expected_stream != NULL) |
| 960 | ✗ | (void)libmpq__stream_close(expected_stream); | |
| 961 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 86 times.
|
91 | if (input != NULL) |
| 962 | 5 | (void)fclose(input); | |
| 963 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (temporary_file != NULL) |
| 964 | ✗ | (void)fclose(temporary_file); | |
| 965 |
1/2✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
|
91 | if (temporary != NULL) |
| 966 | 91 | (void)libmpq__directory_remove(update->directory, temporary); | |
| 967 | 91 | free(temporary_path); | |
| 968 | 91 | free(temporary); | |
| 969 | 91 | return result; | |
| 970 | } | ||
| 971 | |||
| 972 | static int32_t | ||
| 973 | 38 | update_write_tables(mpq_archive_s *archive, FILE *output, uint64_t *extent) | |
| 974 | { | ||
| 975 | 38 | uint8_t header[LIBMPQ_HEADER_WIRE_SIZE + LIBMPQ_HEADER_EX_WIRE_SIZE] = { 0 }; | |
| 976 | mpq_header_s wire_header; | ||
| 977 | mpq_header_ex_s wire_ex; | ||
| 978 | int32_t result; | ||
| 979 | uint8_t *raw; | ||
| 980 | uint64_t hash_offset; | ||
| 981 | uint64_t block_offset; | ||
| 982 | 38 | uint64_t block_ex_offset = 0; | |
| 983 | uint64_t end; | ||
| 984 | 38 | uint64_t archive_offset = (uint64_t)archive->archive_offset; | |
| 985 | libmpq__off_t measured; | ||
| 986 | size_t bytes; | ||
| 987 | uint32_t i; | ||
| 988 | |||
| 989 | 38 | measured = libmpq__file_tell(output); | |
| 990 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
38 | if (measured < 0 || (uint64_t)measured < archive_offset) |
| 991 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 992 | 38 | end = (uint64_t)measured - archive_offset; | |
| 993 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (end > UINT32_MAX) |
| 994 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 995 | 38 | hash_offset = end; | |
| 996 | 38 | bytes = (size_t)archive->mpq_header.hash_table_count * LIBMPQ_HASH_ENTRY_WIRE_SIZE; | |
| 997 |
1/2✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
|
38 | raw = malloc(bytes == 0 ? 1u : bytes); |
| 998 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (raw == NULL) |
| 999 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 1000 | 38 | result = libmpq__hash_table_encode( | |
| 1001 | 38 | archive->mpq_hash, archive->mpq_header.hash_table_count, raw, bytes | |
| 1002 | ); | ||
| 1003 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result < 0) { |
| 1004 | ✗ | free(raw); | |
| 1005 | ✗ | return result; | |
| 1006 | } | ||
| 1007 | 38 | libmpq__crypto_encrypt_block( | |
| 1008 | raw, (uint32_t)bytes, libmpq__crypto_hash_string("(hash table)", 0x300) | ||
| 1009 | ); | ||
| 1010 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
|
38 | if (fwrite(raw, 1, bytes, output) != bytes) { |
| 1011 | ✗ | free(raw); | |
| 1012 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 1013 | } | ||
| 1014 | 38 | free(raw); | |
| 1015 | 38 | measured = libmpq__file_tell(output); | |
| 1016 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
38 | if (measured < 0 || (uint64_t)measured < archive_offset) |
| 1017 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1018 | 38 | block_offset = (uint64_t)measured - archive_offset; | |
| 1019 | 38 | bytes = (size_t)archive->mpq_header.block_table_count * LIBMPQ_BLOCK_ENTRY_WIRE_SIZE; | |
| 1020 |
1/2✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
|
38 | raw = malloc(bytes == 0 ? 1u : bytes); |
| 1021 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (raw == NULL) |
| 1022 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 1023 | 38 | result = libmpq__block_table_encode( | |
| 1024 | 38 | archive->mpq_block, archive->mpq_header.block_table_count, raw, bytes | |
| 1025 | ); | ||
| 1026 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result < 0) { |
| 1027 | ✗ | free(raw); | |
| 1028 | ✗ | return result; | |
| 1029 | } | ||
| 1030 | 38 | libmpq__crypto_encrypt_block( | |
| 1031 | raw, (uint32_t)bytes, libmpq__crypto_hash_string("(block table)", 0x300) | ||
| 1032 | ); | ||
| 1033 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
|
38 | if (fwrite(raw, 1, bytes, output) != bytes) { |
| 1034 | ✗ | free(raw); | |
| 1035 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 1036 | } | ||
| 1037 | 38 | free(raw); | |
| 1038 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36 times.
|
38 | if (archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 1039 | 2 | measured = libmpq__file_tell(output); | |
| 1040 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (measured < 0 || (uint64_t)measured < archive_offset) |
| 1041 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1042 | 2 | block_ex_offset = (uint64_t)measured - archive_offset; | |
| 1043 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 2 times.
|
66 | for (i = 0; i < archive->mpq_header.block_table_count; i++) { |
| 1044 | uint8_t word[2]; | ||
| 1045 | |||
| 1046 | result = | ||
| 1047 | 64 | libmpq__block_ex_table_encode(&archive->mpq_block_ex[i], 1, word, sizeof(word)); | |
| 1048 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
|
64 | if (result < 0) |
| 1049 | ✗ | return result; | |
| 1050 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
|
64 | if (fwrite(word, 1, sizeof(word), output) != sizeof(word)) |
| 1051 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 1052 | } | ||
| 1053 | } | ||
| 1054 | 38 | measured = libmpq__file_tell(output); | |
| 1055 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
38 | if (measured < 0 || (uint64_t)measured < archive_offset) |
| 1056 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1057 | 38 | end = (uint64_t)measured - archive_offset; | |
| 1058 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (end > UINT32_MAX) |
| 1059 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 1060 | 38 | wire_header = archive->mpq_header; | |
| 1061 | 38 | wire_header.mpq_magic = LIBMPQ_HEADER; | |
| 1062 | 38 | wire_header.archive_size = (uint32_t)end; | |
| 1063 | 38 | wire_header.hash_table_offset = (uint32_t)hash_offset; | |
| 1064 | 38 | wire_header.block_table_offset = (uint32_t)block_offset; | |
| 1065 | 38 | result = libmpq__header_encode(&wire_header, header, sizeof(header)); | |
| 1066 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result < 0) |
| 1067 | ✗ | return result; | |
| 1068 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 36 times.
|
38 | if (archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 1069 | 2 | wire_ex.extended_offset = block_ex_offset; | |
| 1070 | 2 | wire_ex.hash_table_offset_high = (uint16_t)(hash_offset >> 32); | |
| 1071 | 2 | wire_ex.block_table_offset_high = (uint16_t)(block_offset >> 32); | |
| 1072 | 2 | result = libmpq__header_ex_encode( | |
| 1073 | &wire_ex, header + LIBMPQ_HEADER_WIRE_SIZE, LIBMPQ_HEADER_EX_WIRE_SIZE | ||
| 1074 | ); | ||
| 1075 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (result < 0) |
| 1076 | ✗ | return result; | |
| 1077 | } | ||
| 1078 |
1/2✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
|
38 | if (libmpq__file_seek(output, archive_offset, SEEK_SET) != 0 || |
| 1079 | 38 | fwrite(header, 1, archive->mpq_header.header_size, output) != | |
| 1080 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
|
76 | archive->mpq_header.header_size || |
| 1081 | 38 | libmpq__file_seek(output, archive_offset + end, SEEK_SET) != 0) | |
| 1082 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 1083 | 38 | *extent = end; | |
| 1084 | 38 | return 0; | |
| 1085 | } | ||
| 1086 | |||
| 1087 | /* Decode a complete member for pre-rename capture or rebuilt-copy validation. */ | ||
| 1088 | static int32_t | ||
| 1089 | 42 | update_validate_member( | |
| 1090 | mpq_archive_s *archive, const char *name, const uint8_t *expected_md5, uint64_t expected_size, | ||
| 1091 | uint8_t compare_contents, uint8_t *actual_md5, uint64_t *actual_size | ||
| 1092 | ) | ||
| 1093 | { | ||
| 1094 | mpq_md5_s md5; | ||
| 1095 | uint8_t digest[16]; | ||
| 1096 | 42 | uint8_t *data = NULL; | |
| 1097 | 42 | libmpq__off_t size = 0; | |
| 1098 | 42 | libmpq__off_t transferred = 0; | |
| 1099 | uint32_t number; | ||
| 1100 | int32_t result; | ||
| 1101 | |||
| 1102 | 42 | result = libmpq__file_number(archive, name, &number); | |
| 1103 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (result != 0) |
| 1104 | ✗ | return result; | |
| 1105 | 42 | result = libmpq__file_size_unpacked(archive, number, &size); | |
| 1106 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (result != 0) |
| 1107 | ✗ | return result; | |
| 1108 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (size < 0 || (uint64_t)size > SIZE_MAX) |
| 1109 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 1110 |
3/4✓ Branch 0 taken 32 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
|
42 | if (compare_contents && (uint64_t)size != expected_size) |
| 1111 | ✗ | return LIBMPQ_ERROR_READ; | |
| 1112 |
1/2✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
|
42 | data = malloc(size == 0 ? 1u : (size_t)size); |
| 1113 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (data == NULL) |
| 1114 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 1115 | |||
| 1116 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 34 times.
|
42 | if ((archive->mpq_block[archive->mpq_map[number].block_table_indices].flags & |
| 1117 | LIBMPQ_FLAG_ENCRYPTED) != 0) { | ||
| 1118 | 8 | result = libmpq__reader_offsets_acquire(archive, number, name); | |
| 1119 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (result == 0) { |
| 1120 | 8 | result = libmpq__file_read(archive, number, data, size, &transferred); | |
| 1121 | 8 | update_result(&result, libmpq__reader_offsets_release(archive, number)); | |
| 1122 | } | ||
| 1123 | } else { | ||
| 1124 | 34 | result = libmpq__file_read(archive, number, data, size, &transferred); | |
| 1125 | } | ||
| 1126 |
2/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
|
42 | if (result == 0 && transferred != size) |
| 1127 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 1128 |
4/6✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 32 times.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
|
42 | if (result == 0 && (compare_contents || actual_md5 != NULL)) { |
| 1129 | 42 | libmpq__md5_init(&md5); | |
| 1130 | 42 | libmpq__md5_update(&md5, data, (size_t)size); | |
| 1131 | 42 | libmpq__md5_final(&md5, digest); | |
| 1132 |
3/4✓ Branch 0 taken 32 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
|
42 | if (compare_contents && memcmp(digest, expected_md5, sizeof(digest)) != 0) |
| 1133 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 1134 |
3/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 32 times.
|
42 | if (result == 0 && actual_md5 != NULL) |
| 1135 | 10 | memcpy(actual_md5, digest, sizeof(digest)); | |
| 1136 | } | ||
| 1137 |
3/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 32 times.
|
42 | if (result == 0 && actual_size != NULL) |
| 1138 | 10 | *actual_size = (uint64_t)size; | |
| 1139 | 42 | free(data); | |
| 1140 | 42 | return result; | |
| 1141 | } | ||
| 1142 | |||
| 1143 | static int32_t | ||
| 1144 | 52 | update_apply( | |
| 1145 | mpq_update_s *update, mpq_update_action_e action, const char *old_name, const char *new_name, | ||
| 1146 | const uint8_t *data, libmpq__off_t size, const char *source_path, | ||
| 1147 | const mpq_file_options_s *replacement_options | ||
| 1148 | ) | ||
| 1149 | { | ||
| 1150 | static const uint8_t marker[4] = { 'N', 'G', 'I', 'S' }; | ||
| 1151 | 52 | mpq_archive_s *archive = NULL; | |
| 1152 | 52 | mpq_archive_s *check = NULL; | |
| 1153 | 52 | mpq_file_attributes_s *attributes = NULL; | |
| 1154 | 52 | mpq_file_options_s options = { 0 }; | |
| 1155 | 52 | mpq_hash_s hash = { 0 }; | |
| 1156 | 52 | FILE *rebuilt = NULL; | |
| 1157 | 52 | char *temporary = NULL; | |
| 1158 | 52 | char *rebuilt_path = NULL; | |
| 1159 | 52 | uint8_t *list = NULL; | |
| 1160 | 52 | uint8_t *attribute_data = NULL; | |
| 1161 | 52 | uint64_t extent = 0; | |
| 1162 | 52 | uint64_t suffix = 0; | |
| 1163 | 52 | uint64_t rebuilt_extent = 0; | |
| 1164 | uint32_t number; | ||
| 1165 | uint32_t block; | ||
| 1166 | uint32_t slot; | ||
| 1167 | 52 | uint32_t list_number = 0; | |
| 1168 | 52 | uint32_t attributes_number = 0; | |
| 1169 | 52 | uint32_t signature_number = 0; | |
| 1170 | uint32_t i; | ||
| 1171 | 52 | uint32_t attribute_flags = 0; | |
| 1172 | 52 | size_t list_size = 0; | |
| 1173 | 52 | size_t attribute_size = 0; | |
| 1174 | int32_t result; | ||
| 1175 | 52 | uint8_t has_list = 0; | |
| 1176 | 52 | uint8_t has_attributes = 0; | |
| 1177 | 52 | uint8_t has_signature = 0; | |
| 1178 | 52 | uint8_t expected_md5[16] = { 0 }; | |
| 1179 | 52 | uint64_t expected_size = 0; | |
| 1180 | |||
| 1181 |
6/10✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 52 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
✓ Branch 9 taken 39 times.
|
52 | if (update == NULL || update->working == NULL || old_name == NULL || old_name[0] == '\0' || |
| 1182 |
4/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 30 times.
✓ Branch 5 taken 22 times.
|
52 | (action == LIBMPQ_UPDATE_RENAME && (new_name == NULL || new_name[0] == '\0')) || |
| 1183 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | (action == LIBMPQ_UPDATE_REPLACE && |
| 1184 |
5/8✓ Branch 0 taken 24 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✓ Branch 7 taken 22 times.
|
52 | (size < 0 || (size != 0 && data == NULL && source_path == NULL))) || |
| 1185 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
30 | (action == LIBMPQ_UPDATE_REPLACE && source_path != NULL && source_path[0] == '\0')) |
| 1186 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 1187 |
3/4✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 39 times.
|
52 | if (update_reserved_name(old_name) || |
| 1188 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 12 times.
|
13 | (action == LIBMPQ_UPDATE_RENAME && update_reserved_name(new_name))) |
| 1189 | 1 | return LIBMPQ_ERROR_FORMAT; | |
| 1190 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
|
51 | if (fflush(update->working) != 0) |
| 1191 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 1192 | 51 | result = libmpq__archive_open(&archive, update->working_path, -1); | |
| 1193 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (result != 0) |
| 1194 | ✗ | return result; | |
| 1195 | 51 | result = libmpq__file_number(archive, old_name, &number); | |
| 1196 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 48 times.
|
51 | if (result != 0) |
| 1197 | 3 | goto done; | |
| 1198 | 48 | block = archive->mpq_map[number].block_table_indices; | |
| 1199 | 48 | slot = update_hash_slot(archive, old_name, block); | |
| 1200 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
|
48 | if (slot == UINT32_MAX) { |
| 1201 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 1202 | ✗ | goto done; | |
| 1203 | } | ||
| 1204 | 48 | hash = archive->mpq_hash[slot]; | |
| 1205 |
4/4✓ Branch 0 taken 30 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 23 times.
|
48 | if (action == LIBMPQ_UPDATE_REPLACE && replacement_options != NULL && |
| 1206 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
|
7 | (replacement_options->locale != hash.locale || |
| 1207 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | replacement_options->platform != hash.platform)) { |
| 1208 | 3 | result = LIBMPQ_ERROR_FORMAT; | |
| 1209 | 3 | goto done; | |
| 1210 | } | ||
| 1211 |
4/4✓ Branch 0 taken 18 times.
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 6 times.
|
45 | if ((action == LIBMPQ_UPDATE_REPLACE || |
| 1212 | 12 | (action == LIBMPQ_UPDATE_RENAME && | |
| 1213 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 29 times.
|
44 | (archive->mpq_block[block].flags & LIBMPQ_FLAG_ENCRYPTED) != 0)) && |
| 1214 | 32 | update_block_references(archive, block) > 1) { | |
| 1215 | 3 | result = LIBMPQ_ERROR_FORMAT; | |
| 1216 | 3 | goto done; | |
| 1217 | } | ||
| 1218 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 31 times.
|
42 | if (action == LIBMPQ_UPDATE_RENAME) { |
| 1219 | uint32_t collision; | ||
| 1220 | |||
| 1221 | 11 | result = libmpq__file_number(archive, new_name, &collision); | |
| 1222 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
|
11 | if (result == 0) { |
| 1223 | 1 | result = LIBMPQ_ERROR_EXIST; | |
| 1224 | 1 | goto done; | |
| 1225 | } | ||
| 1226 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != LIBMPQ_ERROR_EXIST) |
| 1227 | ✗ | goto done; | |
| 1228 | } | ||
| 1229 | 41 | result = libmpq__archive_signature_extent(archive, &extent); | |
| 1230 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
|
41 | if (result != 0) |
| 1231 | ✗ | goto done; | |
| 1232 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
|
41 | if ((uint64_t)archive->archive_offset > UINT64_MAX - extent) { |
| 1233 | ✗ | result = LIBMPQ_ERROR_SIZE; | |
| 1234 | ✗ | goto done; | |
| 1235 | } | ||
| 1236 | 41 | suffix = (uint64_t)archive->archive_offset + extent; | |
| 1237 |
3/4✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 34 times.
|
41 | if (suffix <= archive->file_size && archive->file_size - suffix >= LIBMPQ_STRONG_TRAILER_SIZE) { |
| 1238 | uint8_t actual[4]; | ||
| 1239 | |||
| 1240 | 7 | result = libmpq__source_read_at(archive->source, suffix, actual, sizeof(actual)); | |
| 1241 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (result != 0) |
| 1242 | ✗ | goto done; | |
| 1243 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if (memcmp(actual, marker, sizeof(marker)) == 0) |
| 1244 | 7 | suffix += LIBMPQ_STRONG_TRAILER_SIZE; | |
| 1245 | } | ||
| 1246 |
2/2✓ Branch 1 taken 32 times.
✓ Branch 2 taken 9 times.
|
41 | if (libmpq__file_number(archive, LIBMPQ_LISTFILE_NAME, &list_number) == 0) { |
| 1247 | 32 | has_list = 1; | |
| 1248 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 31 times.
|
32 | if (update_block_references(archive, archive->mpq_map[list_number].block_table_indices) != |
| 1249 | 1) { | ||
| 1250 | 1 | result = LIBMPQ_ERROR_FORMAT; | |
| 1251 | 1 | goto done; | |
| 1252 | } | ||
| 1253 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
|
57 | result = update_listfile( |
| 1254 | archive, old_name, | ||
| 1255 | action == LIBMPQ_UPDATE_REMOVE ? NULL | ||
| 1256 | : action == LIBMPQ_UPDATE_RENAME ? new_name | ||
| 1257 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 19 times.
|
26 | : old_name, |
| 1258 | &list, &list_size, &list_number | ||
| 1259 | ); | ||
| 1260 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (result != 0) |
| 1261 | ✗ | goto done; | |
| 1262 | } | ||
| 1263 | 40 | result = libmpq__attributes_load(archive); | |
| 1264 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 2 times.
|
40 | if (result == 0) { |
| 1265 | 38 | has_attributes = 1; | |
| 1266 | 38 | attribute_flags = archive->attributes->flags; | |
| 1267 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
|
38 | if (libmpq__file_number(archive, LIBMPQ_ATTRIBUTES_NAME, &attributes_number) != 0) { |
| 1268 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 1269 | ✗ | goto done; | |
| 1270 | } | ||
| 1271 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 37 times.
|
38 | if (update_block_references( |
| 1272 | 38 | archive, archive->mpq_map[attributes_number].block_table_indices | |
| 1273 | ) != 1) { | ||
| 1274 | 1 | result = LIBMPQ_ERROR_FORMAT; | |
| 1275 | 1 | goto done; | |
| 1276 | } | ||
| 1277 | 37 | attributes = calloc(archive->mpq_header.block_table_count, sizeof(*attributes)); | |
| 1278 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
|
37 | if (attributes == NULL) { |
| 1279 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 1280 | ✗ | goto done; | |
| 1281 | } | ||
| 1282 |
2/2✓ Branch 0 taken 696 times.
✓ Branch 1 taken 37 times.
|
733 | for (i = 0; i < archive->mpq_header.block_table_count; i++) { |
| 1283 | 696 | libmpq__attributes_get(archive->attributes, i, &attributes[i]); | |
| 1284 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 696 times.
|
696 | if (attributes[i].patch_bit != 0) { |
| 1285 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 1286 | ✗ | goto done; | |
| 1287 | } | ||
| 1288 | } | ||
| 1289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (result != LIBMPQ_ERROR_EXIST) { |
| 1290 | ✗ | goto done; | |
| 1291 | } | ||
| 1292 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 32 times.
|
39 | if (libmpq__file_number(archive, LIBMPQ_SIGNATURE_NAME, &signature_number) == 0) { |
| 1293 | 7 | has_signature = 1; | |
| 1294 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
|
7 | if (update_block_references( |
| 1295 | 7 | archive, archive->mpq_map[signature_number].block_table_indices | |
| 1296 | ) != 1) { | ||
| 1297 | 1 | result = LIBMPQ_ERROR_FORMAT; | |
| 1298 | 1 | goto done; | |
| 1299 | } | ||
| 1300 | } | ||
| 1301 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 28 times.
|
38 | if (action == LIBMPQ_UPDATE_RENAME) { |
| 1302 | result = | ||
| 1303 | 10 | update_validate_member(archive, old_name, NULL, 0, 0, expected_md5, &expected_size); | |
| 1304 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 1305 | ✗ | goto done; | |
| 1306 | } | ||
| 1307 | 38 | result = libmpq__directory_temporary_reopenable( | |
| 1308 | update->directory, ".libmpq-rebuild-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, &temporary, | ||
| 1309 | &rebuilt | ||
| 1310 | ); | ||
| 1311 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1312 | ✗ | goto done; | |
| 1313 | 38 | rebuilt_path = update_working_path(update->working_path, temporary); | |
| 1314 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (rebuilt_path == NULL) { |
| 1315 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 1316 | ✗ | goto done; | |
| 1317 | } | ||
| 1318 | 38 | result = update_copy_range(archive, rebuilt, 0, (uint64_t)archive->archive_offset + extent); | |
| 1319 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1320 | ✗ | goto done; | |
| 1321 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 32 times.
|
38 | if (action == LIBMPQ_UPDATE_REMOVE) { |
| 1322 | 6 | update_delete_hash(&archive->mpq_hash[slot]); | |
| 1323 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 times.
|
6 | if (update_block_references(archive, block) == 0) { |
| 1324 | 5 | memset(&archive->mpq_block[block], 0, sizeof(archive->mpq_block[block])); | |
| 1325 | 5 | archive->mpq_block_ex[block].offset_high = 0; | |
| 1326 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (has_attributes) |
| 1327 | 5 | memset(&attributes[block], 0, sizeof(attributes[block])); | |
| 1328 | } | ||
| 1329 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 22 times.
|
32 | } else if (action == LIBMPQ_UPDATE_REPLACE || |
| 1330 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | (archive->mpq_block[block].flags & LIBMPQ_FLAG_ENCRYPTED) != 0) { |
| 1331 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 22 times.
|
26 | if (action == LIBMPQ_UPDATE_RENAME) { |
| 1332 | 4 | uint32_t original_flags = archive->mpq_block[block].flags; | |
| 1333 | |||
| 1334 | 4 | options.flags = original_flags & (LIBMPQ_FILE_FLAG_ENCRYPTED | LIBMPQ_FILE_FLAG_SINGLE | | |
| 1335 | LIBMPQ_FILE_FLAG_SECTOR_CRC); | ||
| 1336 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if ((original_flags & LIBMPQ_FLAG_COMPRESS_MULTI) != 0) { |
| 1337 | 4 | options.flags |= LIBMPQ_FILE_FLAG_COMPRESS; | |
| 1338 | 4 | options.compression_first = LIBMPQ_COMPRESSION_ZLIB; | |
| 1339 | 4 | options.compression_next = LIBMPQ_COMPRESSION_ZLIB; | |
| 1340 | ✗ | } else if ((original_flags & LIBMPQ_FLAG_COMPRESS_PKZIP) != 0) { | |
| 1341 | ✗ | options.flags |= LIBMPQ_FILE_FLAG_IMPLODE; | |
| 1342 | } | ||
| 1343 | } else { | ||
| 1344 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 18 times.
|
22 | if (replacement_options != NULL) |
| 1345 | 4 | options = *replacement_options; | |
| 1346 | } | ||
| 1347 | 26 | options.locale = hash.locale; | |
| 1348 | 26 | options.platform = hash.platform; | |
| 1349 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2 times.
|
26 | if (has_attributes) { |
| 1350 | 24 | attributes[block].flags = attribute_flags; | |
| 1351 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
|
24 | if (action == LIBMPQ_UPDATE_REPLACE) |
| 1352 | 20 | attributes[block].filetime = 0; | |
| 1353 | } | ||
| 1354 |
8/8✓ Branch 0 taken 22 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 22 times.
|
52 | result = update_encode( |
| 1355 | update, archive, rebuilt, action == LIBMPQ_UPDATE_RENAME ? new_name : old_name, data, | ||
| 1356 | (uint64_t)size, source_path, action == LIBMPQ_UPDATE_RENAME ? old_name : NULL, &options, | ||
| 1357 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 22 times.
|
26 | &archive->mpq_block[block], &archive->mpq_block_ex[block], |
| 1358 | 24 | has_attributes ? &attributes[block] : NULL, | |
| 1359 | action == LIBMPQ_UPDATE_REPLACE ? expected_md5 : NULL, | ||
| 1360 | action == LIBMPQ_UPDATE_REPLACE ? &expected_size : NULL | ||
| 1361 | ); | ||
| 1362 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (result != 0) |
| 1363 | ✗ | goto done; | |
| 1364 | } | ||
| 1365 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 28 times.
|
38 | if (action == LIBMPQ_UPDATE_RENAME) { |
| 1366 | 10 | update_delete_hash(&archive->mpq_hash[slot]); | |
| 1367 | 10 | result = update_insert_hash(archive, new_name, &hash); | |
| 1368 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 1369 | ✗ | goto done; | |
| 1370 | } | ||
| 1371 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 32 times.
|
38 | if (has_signature) { |
| 1372 | 6 | uint32_t signature_block = archive->mpq_map[signature_number].block_table_indices; | |
| 1373 | |||
| 1374 |
2/2✓ Branch 0 taken 384 times.
✓ Branch 1 taken 6 times.
|
390 | for (i = 0; i < archive->mpq_header.hash_table_count; i++) |
| 1375 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 378 times.
|
384 | if (archive->mpq_hash[i].block_table_index == signature_block) |
| 1376 | 6 | update_delete_hash(&archive->mpq_hash[i]); | |
| 1377 | 6 | memset( | |
| 1378 | 6 | &archive->mpq_block[signature_block], 0, sizeof(archive->mpq_block[signature_block]) | |
| 1379 | ); | ||
| 1380 | 6 | archive->mpq_block_ex[signature_block].offset_high = 0; | |
| 1381 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (has_attributes) |
| 1382 | 6 | memset(&attributes[signature_block], 0, sizeof(attributes[signature_block])); | |
| 1383 | } | ||
| 1384 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 9 times.
|
38 | if (has_list) { |
| 1385 | 29 | uint32_t list_block = archive->mpq_map[list_number].block_table_indices; | |
| 1386 | |||
| 1387 | 29 | options.flags = LIBMPQ_FILE_FLAG_SINGLE; | |
| 1388 | 29 | options.compression_first = 0; | |
| 1389 | 29 | options.compression_next = 0; | |
| 1390 | 29 | options.locale = 0; | |
| 1391 | 29 | options.platform = 0; | |
| 1392 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | if (has_attributes) |
| 1393 | 29 | attributes[list_block].flags = attribute_flags; | |
| 1394 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | result = update_encode( |
| 1395 | update, archive, rebuilt, LIBMPQ_LISTFILE_NAME, list, list_size, NULL, NULL, &options, | ||
| 1396 | 29 | &archive->mpq_block[list_block], &archive->mpq_block_ex[list_block], | |
| 1397 | 29 | has_attributes ? &attributes[list_block] : NULL, NULL, NULL | |
| 1398 | ); | ||
| 1399 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (result != 0) |
| 1400 | ✗ | goto done; | |
| 1401 | } | ||
| 1402 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 2 times.
|
38 | if (has_attributes) { |
| 1403 | 36 | uint32_t attributes_block = archive->mpq_map[attributes_number].block_table_indices; | |
| 1404 | |||
| 1405 | 36 | result = libmpq__attributes_serialize( | |
| 1406 | 36 | attributes, archive->mpq_header.block_table_count, attributes_block, attribute_flags, | |
| 1407 | &attribute_data, &attribute_size | ||
| 1408 | ); | ||
| 1409 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (result != 0) |
| 1410 | ✗ | goto done; | |
| 1411 | 36 | options.flags = LIBMPQ_FILE_FLAG_SINGLE; | |
| 1412 | 36 | options.compression_first = 0; | |
| 1413 | 36 | options.compression_next = 0; | |
| 1414 | 36 | result = update_encode( | |
| 1415 | update, archive, rebuilt, LIBMPQ_ATTRIBUTES_NAME, attribute_data, attribute_size, NULL, | ||
| 1416 | 36 | NULL, &options, &archive->mpq_block[attributes_block], | |
| 1417 | 36 | &archive->mpq_block_ex[attributes_block], NULL, NULL, NULL | |
| 1418 | ); | ||
| 1419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
|
36 | if (result != 0) |
| 1420 | ✗ | goto done; | |
| 1421 | } | ||
| 1422 | 38 | result = update_write_tables(archive, rebuilt, &rebuilt_extent); | |
| 1423 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1424 | ✗ | goto done; | |
| 1425 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 31 times.
|
38 | if (suffix < archive->file_size) { |
| 1426 | 7 | result = update_copy_range(archive, rebuilt, suffix, archive->file_size - suffix); | |
| 1427 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (result != 0) |
| 1428 | ✗ | goto done; | |
| 1429 | } | ||
| 1430 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
|
38 | if (fflush(rebuilt) != 0) { |
| 1431 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 1432 | ✗ | goto done; | |
| 1433 | } | ||
| 1434 | 38 | result = libmpq__archive_open(&check, rebuilt_path, -1); | |
| 1435 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1436 | ✗ | goto done; | |
| 1437 |
1/2✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
|
38 | if (check->archive_offset != archive->archive_offset || |
| 1438 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | check->mpq_header.archive_size != rebuilt_extent) { |
| 1439 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 1440 | ✗ | goto done; | |
| 1441 | } | ||
| 1442 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 22 times.
|
38 | if (action != LIBMPQ_UPDATE_REPLACE) { |
| 1443 | 16 | result = libmpq__file_number(check, old_name, &number); | |
| 1444 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (result != LIBMPQ_ERROR_EXIST) { |
| 1445 | ✗ | result = result == 0 ? LIBMPQ_ERROR_FORMAT : result; | |
| 1446 | ✗ | goto done; | |
| 1447 | } | ||
| 1448 | } | ||
| 1449 | 38 | result = 0; | |
| 1450 |
4/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 6 times.
|
38 | if (action == LIBMPQ_UPDATE_REPLACE || action == LIBMPQ_UPDATE_RENAME) |
| 1451 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 22 times.
|
32 | result = update_validate_member( |
| 1452 | check, action == LIBMPQ_UPDATE_RENAME ? new_name : old_name, expected_md5, | ||
| 1453 | expected_size, 1, NULL, NULL | ||
| 1454 | ); | ||
| 1455 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1456 | ✗ | goto done; | |
| 1457 | 38 | result = libmpq__archive_close(check); | |
| 1458 | 38 | check = NULL; | |
| 1459 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1460 | ✗ | goto done; | |
| 1461 | 38 | result = libmpq__archive_close(archive); | |
| 1462 | 38 | archive = NULL; | |
| 1463 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (result != 0) |
| 1464 | ✗ | goto done; | |
| 1465 | |||
| 1466 | /* | ||
| 1467 | * Adopt the validated rebuild without replacing a still-open working file. | ||
| 1468 | * This avoids sharing-sensitive replacement of an open destination on | ||
| 1469 | * Windows. Final publication still uses same-directory atomic replacement | ||
| 1470 | * after the working file is closed. | ||
| 1471 | */ | ||
| 1472 | { | ||
| 1473 | 38 | FILE *previous_working = update->working; | |
| 1474 | 38 | char *previous_temporary = update->temporary; | |
| 1475 | 38 | char *previous_path = update->working_path; | |
| 1476 | |||
| 1477 | 38 | update->working = rebuilt; | |
| 1478 | 38 | update->temporary = temporary; | |
| 1479 | 38 | update->working_path = rebuilt_path; | |
| 1480 | 38 | rebuilt = NULL; | |
| 1481 | 38 | temporary = NULL; | |
| 1482 | 38 | rebuilt_path = NULL; | |
| 1483 | 38 | (void)fclose(previous_working); | |
| 1484 | 38 | (void)libmpq__directory_remove(update->directory, previous_temporary); | |
| 1485 | 38 | free(previous_temporary); | |
| 1486 | 38 | free(previous_path); | |
| 1487 | 38 | update->dirty = 1; | |
| 1488 | } | ||
| 1489 | |||
| 1490 | 51 | done: | |
| 1491 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (check != NULL) |
| 1492 | ✗ | (void)libmpq__archive_close(check); | |
| 1493 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 38 times.
|
51 | if (archive != NULL) |
| 1494 | 13 | (void)libmpq__archive_close(archive); | |
| 1495 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (rebuilt != NULL) |
| 1496 | ✗ | (void)fclose(rebuilt); | |
| 1497 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (temporary != NULL) |
| 1498 | ✗ | (void)libmpq__directory_remove(update->directory, temporary); | |
| 1499 | 51 | free(temporary); | |
| 1500 | 51 | free(rebuilt_path); | |
| 1501 | 51 | free(list); | |
| 1502 | 51 | free(attribute_data); | |
| 1503 | 51 | free(attributes); | |
| 1504 | 51 | return result; | |
| 1505 | } | ||
| 1506 | |||
| 1507 | int32_t | ||
| 1508 | 30 | libmpq__update_transaction_replace( | |
| 1509 | mpq_update_s *update, const char *filename, const uint8_t *data, libmpq__off_t size, | ||
| 1510 | const char *source_path, const mpq_file_options_s *options | ||
| 1511 | ) | ||
| 1512 | { | ||
| 1513 | 30 | return update_apply( | |
| 1514 | update, LIBMPQ_UPDATE_REPLACE, filename, NULL, data, size, source_path, options | ||
| 1515 | ); | ||
| 1516 | } | ||
| 1517 | |||
| 1518 | int32_t | ||
| 1519 | 9 | libmpq__update_transaction_remove(mpq_update_s *update, const char *filename) | |
| 1520 | { | ||
| 1521 | 9 | return update_apply(update, LIBMPQ_UPDATE_REMOVE, filename, NULL, NULL, 0, NULL, NULL); | |
| 1522 | } | ||
| 1523 | |||
| 1524 | int32_t | ||
| 1525 | 13 | libmpq__update_transaction_rename( | |
| 1526 | mpq_update_s *update, const char *old_filename, const char *new_filename | ||
| 1527 | ) | ||
| 1528 | { | ||
| 1529 | 13 | return update_apply( | |
| 1530 | update, LIBMPQ_UPDATE_RENAME, old_filename, new_filename, NULL, 0, NULL, NULL | ||
| 1531 | ); | ||
| 1532 | } | ||
| 1533 |