GCC Code Coverage Report


Directory: src/
File: src/mpq-update.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 770 997 77.2%
Functions: 29 29 100.0%
Branches: 471 734 64.2%

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