Branch data Line data Source code
1 : : #include "sute.h"
2 : :
3 : : static const char test0030_fixture_root[] = "tests/fixtures/diffs/diff1";
4 : :
5 : : /**
6 : : * @brief Verify that a checksum-locked file size change is reported but not saved
7 : : *
8 : : * Covers README Example 10, case 1
9 : : *
10 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
11 : : * After one locked file grows on disk, the update run with `--rehash-locked`
12 : : * must warn about possible data corruption and keep the original database row
13 : : */
14 : 2 : static Return test0030_1(void)
15 : : {
16 : 2 : INITTEST;
17 : :
18 : : /*
19 : : * Allocate managed buffers for the captured application output and
20 : : * the expected regular-expression template used by this test
21 : : */
22 : 2 : m_create(char,result,MEMORY_STRING);
23 : 2 : m_create(char,pattern,MEMORY_STRING);
24 : :
25 : : /*
26 : : * Name the database, the protected database key, and the fixture file
27 : : * that will be changed on disk during the second half of the test
28 : : */
29 : 2 : const char *db_filename = "lock_s1.db";
30 : 2 : const char *locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
31 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
32 : :
33 : : /*
34 : : * Keep before-and-after snapshots of the protected database row.
35 : : * They prove that a warning result did not silently update locked data
36 : : */
37 : 2 : CmpctStat db_stat_before = {0};
38 : 2 : CmpctStat db_stat_after = {0};
39 : 2 : sqlite3_int64 offset_before = 0;
40 : 2 : sqlite3_int64 offset_after = 0;
41 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
42 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
43 : :
44 : : /*
45 : : * Enable deterministic TESTING output so the captured application log
46 : : * can be compared against a template
47 : : */
48 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
49 : :
50 : : /*
51 : : * Work on a mutable copy of the fixture tree.
52 : : * The original fixture is restored during cleanup
53 : : */
54 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
55 : :
56 : : /*
57 : : * Create the baseline database.
58 : : * The lock pattern protects every relative path under the path1/ subtree
59 : : */
60 : 2 : const char *arguments = "--database=lock_s1.db --progress "
61 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
62 : :
63 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
64 : :
65 : : /*
66 : : * Verify the baseline run output.
67 : : * It must show a new database with path1/ files recorded as checksum-locked
68 : : */
69 : 2 : const char *filename = "templates/0030_001_1.txt";
70 : :
71 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
72 : :
73 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
74 : :
75 : : /*
76 : : * Read the locked row before corrupting the on-disk file.
77 : : * These values are expected to remain unchanged after the warning run
78 : : */
79 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
80 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
81 : :
82 : : /*
83 : : * Grow one checksum-locked file on disk.
84 : : * This creates a size mismatch against the protected database row
85 : : */
86 : 2 : ASSERT(SUCCESS == write_string_to_file("pad",locked_file_path,FILE_WRITE_APPEND));
87 : :
88 : : /*
89 : : * Run an update with deep locked-file verification enabled.
90 : : * The size mismatch must be reported as a warning, not saved to the DB
91 : : */
92 : 2 : arguments = "--update --rehash-locked --lock-checksum=\"^path1/.*\" "
93 : : "--database=lock_s1.db tests/fixtures/diffs/diff1";
94 : :
95 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
96 : :
97 : : /*
98 : : * Verify the warning output.
99 : : * The changed locked file must be reported as possible data corruption
100 : : */
101 : 2 : filename = "templates/0030_001_2.txt";
102 : :
103 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
104 : :
105 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
106 : :
107 : : /*
108 : : * Re-read the same protected row after the warning run.
109 : : * The row must keep its original metadata, offset, and SHA512 checksum
110 : : */
111 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
112 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
113 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
114 : 2 : ASSERT(offset_before == offset_after);
115 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
116 : :
117 : : /*
118 : : * Remove the test database and restore the mutable fixture.
119 : : * This returns the workspace to the state expected by the next test
120 : : */
121 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
122 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
123 : :
124 : : /*
125 : : * Release managed buffers allocated by this test before returning
126 : : */
127 : 2 : m_del(pattern);
128 : 2 : m_del(result);
129 : :
130 : 2 : RETURN_STATUS;
131 : : }
132 : :
133 : : /**
134 : : * @brief Verify that watched timestamp drift on a locked file is reported but not saved
135 : : *
136 : : * Covers README Example 10, case 4
137 : : *
138 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
139 : : * After one locked file gets a metadata-only timestamp change, the update run
140 : : * with `--watch-timestamps` must warn and keep the original database row
141 : : */
142 : 2 : static Return test0030_2(void)
143 : : {
144 : 2 : INITTEST;
145 : :
146 : : /*
147 : : * Allocate managed buffers for the captured application output and
148 : : * the expected regular-expression template used by this test
149 : : */
150 : 2 : m_create(char,result,MEMORY_STRING);
151 : 2 : m_create(char,pattern,MEMORY_STRING);
152 : :
153 : : /*
154 : : * Name the database, the protected database key, and the fixture file
155 : : * whose timestamps will be changed without touching file contents
156 : : */
157 : 2 : const char *db_filename = "lock_s2.db";
158 : 2 : const char *locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
159 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
160 : :
161 : : /*
162 : : * Keep before-and-after snapshots of the protected database row.
163 : : * They prove that a warning result did not silently update locked data
164 : : */
165 : 2 : CmpctStat db_stat_before = {0};
166 : 2 : CmpctStat db_stat_after = {0};
167 : 2 : sqlite3_int64 offset_before = 0;
168 : 2 : sqlite3_int64 offset_after = 0;
169 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
170 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
171 : :
172 : : /*
173 : : * Enable deterministic TESTING output so the captured application log
174 : : * can be compared against a template
175 : : */
176 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
177 : :
178 : : /*
179 : : * Work on a mutable copy of the fixture tree.
180 : : * The original fixture is restored during cleanup
181 : : */
182 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
183 : :
184 : : /*
185 : : * Create the baseline database.
186 : : * The lock pattern protects every relative path under the path1/ subtree
187 : : */
188 : 2 : const char *arguments = "--database=lock_s2.db --progress "
189 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
190 : :
191 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
192 : :
193 : : /*
194 : : * Verify the baseline run output.
195 : : * It must show a new database with path1/ files recorded as checksum-locked
196 : : */
197 : 2 : const char *filename = "templates/0030_002_1.txt";
198 : :
199 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
200 : :
201 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
202 : :
203 : : /*
204 : : * Read the locked row before changing file timestamps.
205 : : * These values are expected to remain unchanged after the warning run
206 : : */
207 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
208 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
209 : :
210 : : /*
211 : : * Change only timestamps for one checksum-locked file.
212 : : * The file size and contents stay unchanged, but ctime and mtime drift
213 : : */
214 : 2 : ASSERT(SUCCESS == touch_file_mtime_with_reference_delta_ns(NULL,locked_file_path,999));
215 : :
216 : : /*
217 : : * Run an update that treats timestamp drift as meaningful.
218 : : * The drift must be reported as a warning, not saved to the DB
219 : : */
220 : 2 : arguments = "--update --watch-timestamps --lock-checksum=\"^path1/.*\" "
221 : : "--database=lock_s2.db tests/fixtures/diffs/diff1";
222 : :
223 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
224 : :
225 : : /*
226 : : * Verify the warning output.
227 : : * The changed locked file must be reported with ctime and mtime drift
228 : : */
229 : 2 : filename = "templates/0030_002_2.txt";
230 : :
231 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
232 : :
233 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
234 : :
235 : : /*
236 : : * Re-read the same protected row after the warning run.
237 : : * The row must keep its original metadata, offset, and SHA512 checksum
238 : : */
239 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
240 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
241 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
242 : 2 : ASSERT(offset_before == offset_after);
243 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
244 : :
245 : : /*
246 : : * Remove the test database and restore the mutable fixture.
247 : : * This returns the workspace to the state expected by the next test
248 : : */
249 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
250 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
251 : :
252 : : /*
253 : : * Release managed buffers allocated by this test before returning
254 : : */
255 : 2 : m_del(pattern);
256 : 2 : m_del(result);
257 : :
258 : 2 : RETURN_STATUS;
259 : : }
260 : :
261 : : /**
262 : : * @brief Verify that locked timestamp drift is ignored when timestamp watching is off
263 : : *
264 : : * Covers README Example 10, case 2
265 : : *
266 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
267 : : * After one locked file gets a metadata-only timestamp change, the update run
268 : : * without `--watch-timestamps` or `--rehash-locked` must finish successfully
269 : : * and keep the original database row
270 : : */
271 : 2 : static Return test0030_3(void)
272 : : {
273 : 2 : INITTEST;
274 : :
275 : : /*
276 : : * Allocate managed buffers for the captured application output,
277 : : * the expected regular-expression template, and the resolved fixture path
278 : : */
279 : 2 : m_create(char,result,MEMORY_STRING);
280 : 2 : m_create(char,pattern,MEMORY_STRING);
281 : 2 : m_create(char,target_path,MEMORY_STRING);
282 : :
283 : : /*
284 : : * Name the database, the protected database key, and the fixture file
285 : : * whose timestamps will be changed without touching file contents
286 : : */
287 : 2 : const char *db_filename = "lock_s3.db";
288 : 2 : const char *locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
289 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
290 : :
291 : : /*
292 : : * Keep before-and-after snapshots of the protected database row.
293 : : * They prove that ignored timestamp drift did not silently update locked data
294 : : */
295 : 2 : CmpctStat db_stat_before = {0};
296 : 2 : CmpctStat db_stat_after = {0};
297 : 2 : sqlite3_int64 offset_before = 0;
298 : 2 : sqlite3_int64 offset_after = 0;
299 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
300 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
301 : 2 : struct stat file_stat_before = {0};
302 : 2 : struct stat file_stat_after = {0};
303 : :
304 : : /*
305 : : * Enable deterministic TESTING output so the captured application log
306 : : * can be compared against a template
307 : : */
308 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
309 : :
310 : : /*
311 : : * Work on a mutable copy of the fixture tree.
312 : : * The original fixture is restored during cleanup
313 : : */
314 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
315 : :
316 : : /*
317 : : * Create the baseline database.
318 : : * The lock pattern protects every relative path under the path1/ subtree
319 : : */
320 : 2 : const char *arguments = "--database=lock_s3.db --progress "
321 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
322 : :
323 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
324 : :
325 : : /*
326 : : * Verify the baseline run output.
327 : : * It must show a new database with path1/ files recorded as checksum-locked
328 : : */
329 : 2 : const char *filename = "templates/0030_003_1.txt";
330 : :
331 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
332 : :
333 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
334 : :
335 : : /*
336 : : * Resolve the changed fixture path and read its matching locked DB row.
337 : : * Before any drift, the file timestamps should match the stored metadata
338 : : */
339 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
340 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
341 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
342 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_before));
343 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_before));
344 : :
345 : : /*
346 : : * Change only timestamps for one checksum-locked file.
347 : : * The file size and contents stay unchanged, but ctime and mtime drift
348 : : */
349 : 2 : ASSERT(SUCCESS == touch_file_mtime_with_reference_delta_ns(NULL,locked_file_path,999));
350 : :
351 : : /*
352 : : * Confirm that the test setup produced real timestamp drift.
353 : : * The stored DB metadata must no longer match the current file timestamps
354 : : */
355 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_after));
356 : 2 : ASSERT(false == cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_after));
357 : :
358 : : /*
359 : : * Run an update without timestamp watching or deep locked-file rehashing.
360 : : * The timestamp drift must be ignored and the run must finish successfully
361 : : */
362 : 2 : arguments = "--update --lock-checksum=\"^path1/.*\" "
363 : : "--database=lock_s3.db tests/fixtures/diffs/diff1";
364 : :
365 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
366 : :
367 : : /*
368 : : * Verify the successful output.
369 : : * It must report that no database changes were needed
370 : : */
371 : 2 : filename = "templates/0030_003_2.txt";
372 : :
373 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
374 : :
375 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
376 : :
377 : : /*
378 : : * Re-read the same protected row after the successful update run.
379 : : * The row must keep its original metadata, offset, and SHA512 checksum
380 : : */
381 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
382 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
383 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
384 : 2 : ASSERT(offset_before == offset_after);
385 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
386 : :
387 : : /*
388 : : * Remove the test database and restore the mutable fixture.
389 : : * This returns the workspace to the state expected by the next test
390 : : */
391 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
392 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
393 : :
394 : : /*
395 : : * Release managed buffers allocated by this test before returning
396 : : */
397 : 2 : m_del(target_path);
398 : 2 : m_del(pattern);
399 : 2 : m_del(result);
400 : :
401 : 2 : RETURN_STATUS;
402 : : }
403 : :
404 : : /**
405 : : * @brief Verify that locked timestamp drift is safely synchronized after rehash
406 : : *
407 : : * Covers README Example 10, case 5
408 : : *
409 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
410 : : * After one locked file gets a metadata-only timestamp change, the update run
411 : : * with `--watch-timestamps` and `--rehash-locked` must rehash the file,
412 : : * finish successfully, and store the new timestamps without changing its checksum
413 : : */
414 : 2 : static Return test0030_4(void)
415 : : {
416 : 2 : INITTEST;
417 : :
418 : : /*
419 : : * Allocate managed buffers for the captured application output,
420 : : * the expected regular-expression template, and the resolved fixture path
421 : : */
422 : 2 : m_create(char,result,MEMORY_STRING);
423 : 2 : m_create(char,pattern,MEMORY_STRING);
424 : 2 : m_create(char,target_path,MEMORY_STRING);
425 : :
426 : : /*
427 : : * Name the database, the protected database key, and the fixture file
428 : : * whose timestamps will be changed without touching file contents
429 : : */
430 : 2 : const char *db_filename = "lock_s4.db";
431 : 2 : const char *locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
432 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
433 : :
434 : : /*
435 : : * Keep file and database snapshots around the timestamp drift.
436 : : * They prove that the drift is real and later synchronized to the database
437 : : */
438 : 2 : struct stat file_stat_before = {0};
439 : 2 : struct stat file_stat_drift = {0};
440 : 2 : struct stat file_stat_after = {0};
441 : 2 : CmpctStat db_stat_before = {0};
442 : 2 : CmpctStat db_stat_after = {0};
443 : 2 : sqlite3_int64 offset_before = 0;
444 : 2 : sqlite3_int64 offset_after = 0;
445 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
446 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
447 : :
448 : : /*
449 : : * Enable deterministic TESTING output so the captured application log
450 : : * can be compared against a template
451 : : */
452 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
453 : :
454 : : /*
455 : : * Work on a mutable copy of the fixture tree.
456 : : * The original fixture is restored during cleanup
457 : : */
458 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
459 : :
460 : : /*
461 : : * Create the baseline database.
462 : : * The lock pattern protects every relative path under the path1/ subtree
463 : : */
464 : 2 : const char *arguments = "--database=lock_s4.db --progress "
465 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
466 : :
467 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
468 : :
469 : : /*
470 : : * Verify the baseline run output.
471 : : * It must show a new database with path1/ files recorded as checksum-locked
472 : : */
473 : 2 : const char *filename = "templates/0030_004_1.txt";
474 : :
475 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
476 : :
477 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
478 : :
479 : : /*
480 : : * Resolve the changed fixture path and read its matching locked DB row.
481 : : * Before any drift, the file timestamps should match the stored metadata
482 : : */
483 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
484 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
485 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
486 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_before));
487 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_before));
488 : :
489 : : /*
490 : : * Change only timestamps for one checksum-locked file.
491 : : * The file size and contents stay unchanged, but ctime and mtime drift
492 : : */
493 : 2 : ASSERT(SUCCESS == touch_file_mtime_with_reference_delta_ns(NULL,locked_file_path,999));
494 : :
495 : : /*
496 : : * Confirm that the test setup produced real timestamp drift.
497 : : * The stored DB metadata must no longer match the current file timestamps
498 : : */
499 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_drift));
500 : 2 : ASSERT(false == cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_drift));
501 : :
502 : : /*
503 : : * Run an update that watches timestamps and rehashes locked files.
504 : : * The checksum match must make the timestamp-only drift safe to save
505 : : */
506 : 2 : arguments = "--update --watch-timestamps --rehash-locked "
507 : : "--lock-checksum=\"^path1/.*\" --database=lock_s4.db "
508 : : "tests/fixtures/diffs/diff1";
509 : :
510 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
511 : :
512 : : /*
513 : : * Verify the successful update output.
514 : : * The changed locked file must be reported as rehashed and synchronized
515 : : */
516 : 2 : filename = "templates/0030_004_2.txt";
517 : :
518 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
519 : :
520 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
521 : :
522 : : /*
523 : : * Re-read the protected row and current file state after the update.
524 : : * The database timestamps must now match the file again
525 : : */
526 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
527 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
528 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_after));
529 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_after,&file_stat_after));
530 : :
531 : : /*
532 : : * Verify that only metadata timestamps changed in the stored row.
533 : : * Size, allocation, identity, SHA512 offset, and SHA512 digest must stay stable
534 : : */
535 : 2 : ASSERT(db_stat_before.st_size == db_stat_after.st_size);
536 : 2 : ASSERT(db_stat_before.st_blocks == db_stat_after.st_blocks);
537 : 2 : ASSERT(db_stat_before.st_dev == db_stat_after.st_dev);
538 : 2 : ASSERT(db_stat_before.st_ino == db_stat_after.st_ino);
539 : 2 : ASSERT(offset_before == offset_after);
540 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
541 : :
542 : : /*
543 : : * Remove the test database and restore the mutable fixture.
544 : : * This returns the workspace to the state expected by the next test
545 : : */
546 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
547 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
548 : :
549 : : /*
550 : : * Release managed buffers allocated by this test before returning
551 : : */
552 : 2 : m_del(target_path);
553 : 2 : m_del(pattern);
554 : 2 : m_del(result);
555 : :
556 : 2 : RETURN_STATUS;
557 : : }
558 : :
559 : : /**
560 : : * @brief Verify that locked timestamp drift is synchronized by rehash alone
561 : : *
562 : : * Covers README Example 10, case 5
563 : : *
564 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
565 : : * After one locked file gets a metadata-only timestamp change, the update run
566 : : * with `--rehash-locked` and without `--watch-timestamps` must rehash the file,
567 : : * finish successfully, and store the new timestamps without changing its checksum
568 : : */
569 : 2 : static Return test0030_5(void)
570 : : {
571 : 2 : INITTEST;
572 : :
573 : : /*
574 : : * Allocate managed buffers for the captured application output,
575 : : * the expected regular-expression template, and the resolved fixture path
576 : : */
577 : 2 : m_create(char,result,MEMORY_STRING);
578 : 2 : m_create(char,pattern,MEMORY_STRING);
579 : 2 : m_create(char,target_path,MEMORY_STRING);
580 : :
581 : : /*
582 : : * Name the database, the protected database key, and the fixture file
583 : : * whose timestamps will be changed without touching file contents
584 : : */
585 : 2 : const char *db_filename = "lock_s5.db";
586 : 2 : const char *locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
587 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
588 : :
589 : : /*
590 : : * Keep file and database snapshots around the timestamp drift.
591 : : * They prove that the drift is real and later synchronized to the database
592 : : */
593 : 2 : struct stat file_stat_before = {0};
594 : 2 : struct stat file_stat_drift = {0};
595 : 2 : struct stat file_stat_after = {0};
596 : 2 : CmpctStat db_stat_before = {0};
597 : 2 : CmpctStat db_stat_after = {0};
598 : 2 : sqlite3_int64 offset_before = 0;
599 : 2 : sqlite3_int64 offset_after = 0;
600 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
601 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
602 : :
603 : : /*
604 : : * Enable deterministic TESTING output so the captured application log
605 : : * can be compared against a template
606 : : */
607 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
608 : :
609 : : /*
610 : : * Work on a mutable copy of the fixture tree.
611 : : * The original fixture is restored during cleanup
612 : : */
613 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
614 : :
615 : : /*
616 : : * Create the baseline database.
617 : : * The lock pattern protects every relative path under the path1/ subtree
618 : : */
619 : 2 : const char *arguments = "--database=lock_s5.db --progress "
620 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
621 : :
622 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
623 : :
624 : : /*
625 : : * Verify the baseline run output.
626 : : * It must show a new database with path1/ files recorded as checksum-locked
627 : : */
628 : 2 : const char *filename = "templates/0030_005_1.txt";
629 : :
630 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
631 : :
632 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
633 : :
634 : : /*
635 : : * Resolve the changed fixture path and read its matching locked DB row.
636 : : * Before any drift, the file timestamps should match the stored metadata
637 : : */
638 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
639 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
640 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
641 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_before));
642 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_before));
643 : :
644 : : /*
645 : : * Change only timestamps for one checksum-locked file.
646 : : * The file size and contents stay unchanged, but ctime and mtime drift
647 : : */
648 : 2 : ASSERT(SUCCESS == touch_file_mtime_with_reference_delta_ns(NULL,locked_file_path,999));
649 : :
650 : : /*
651 : : * Confirm that the test setup produced real timestamp drift.
652 : : * The stored DB metadata must no longer match the current file timestamps
653 : : */
654 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_drift));
655 : 2 : ASSERT(false == cmpctstat_matches_stat_timestamps(&db_stat_before,&file_stat_drift));
656 : :
657 : : /*
658 : : * Run an update that rehashes locked files without timestamp watching.
659 : : * The checksum match must make the timestamp-only drift safe to save
660 : : */
661 : 2 : arguments = "--update --rehash-locked --lock-checksum=\"^path1/.*\" "
662 : : "--database=lock_s5.db tests/fixtures/diffs/diff1";
663 : :
664 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
665 : :
666 : : /*
667 : : * Verify the successful update output.
668 : : * All locked files must be reported as successfully rehashed
669 : : */
670 : 2 : filename = "templates/0030_005_2.txt";
671 : :
672 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
673 : :
674 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
675 : :
676 : : /*
677 : : * Re-read the protected row and current file state after the update.
678 : : * The database timestamps must now match the file again
679 : : */
680 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
681 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
682 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_after));
683 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_after,&file_stat_after));
684 : :
685 : : /*
686 : : * Verify that only metadata timestamps changed in the stored row.
687 : : * Size, allocation, identity, SHA512 offset, and SHA512 digest must stay stable
688 : : */
689 : 2 : ASSERT(db_stat_before.st_size == db_stat_after.st_size);
690 : 2 : ASSERT(db_stat_before.st_blocks == db_stat_after.st_blocks);
691 : 2 : ASSERT(db_stat_before.st_dev == db_stat_after.st_dev);
692 : 2 : ASSERT(db_stat_before.st_ino == db_stat_after.st_ino);
693 : 2 : ASSERT(offset_before == offset_after);
694 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
695 : :
696 : : /*
697 : : * Remove the test database and restore the mutable fixture.
698 : : * This returns the workspace to the state expected by the next test
699 : : */
700 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
701 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
702 : :
703 : : /*
704 : : * Release managed buffers allocated by this test before returning
705 : : */
706 : 2 : m_del(target_path);
707 : 2 : m_del(pattern);
708 : 2 : m_del(result);
709 : :
710 : 2 : RETURN_STATUS;
711 : : }
712 : :
713 : : /**
714 : : * @brief Verify mixed locked-file results during a watched rehash audit
715 : : *
716 : : * Covers README Example 10
717 : : *
718 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
719 : : * One locked file then receives a metadata-only timestamp change, while another
720 : : * locked file keeps its on-disk contents but gets a corrupted stored SHA512 value.
721 : : * The update run with `--watch-timestamps` and `--rehash-locked` must synchronize
722 : : * the safe timestamp drift, report the checksum mismatch as corruption, and leave
723 : : * the mismatched DB checksum untouched
724 : : */
725 : 2 : static Return test0030_6(void)
726 : : {
727 : 2 : INITTEST;
728 : :
729 : : /*
730 : : * Allocate managed buffers for the captured application output,
731 : : * the expected regular-expression template, and resolved fixture paths
732 : : */
733 : 2 : m_create(char,result,MEMORY_STRING);
734 : 2 : m_create(char,pattern,MEMORY_STRING);
735 : 2 : m_create(char,drift_target_path,MEMORY_STRING);
736 : 2 : m_create(char,mismatch_target_path,MEMORY_STRING);
737 : :
738 : : /*
739 : : * Name the database, the locked file that will drift only by timestamps,
740 : : * and the locked file whose stored checksum will be corrupted in the DB
741 : : */
742 : 2 : const char *db_filename = "lock_s6.db";
743 : 2 : const char *drift_relative_path = "path1/AAA/BCB/CCC/a.txt";
744 : 2 : const char *drift_file_path = "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt";
745 : 2 : const char *mismatch_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
746 : 2 : const char *mismatch_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
747 : :
748 : : /*
749 : : * Keep file and database snapshots for the timestamp-drift row.
750 : : * They prove that the safe drift is saved without changing the checksum
751 : : */
752 : 2 : struct stat drift_file_stat_before = {0};
753 : 2 : struct stat drift_file_stat_after_touch = {0};
754 : 2 : struct stat drift_file_stat_after_update = {0};
755 : 2 : CmpctStat drift_db_stat_before = {0};
756 : 2 : CmpctStat drift_db_stat_after = {0};
757 : 2 : sqlite3_int64 drift_offset_before = 0;
758 : 2 : sqlite3_int64 drift_offset_after = 0;
759 : 2 : unsigned char drift_sha512_before[SHA512_DIGEST_LENGTH] = {0};
760 : 2 : unsigned char drift_sha512_after[SHA512_DIGEST_LENGTH] = {0};
761 : :
762 : : /*
763 : : * Keep checksum snapshots for the mismatch row.
764 : : * They prove that a detected corruption warning does not silently repair the DB
765 : : */
766 : 2 : CmpctStat mismatch_db_stat_before = {0};
767 : 2 : CmpctStat mismatch_db_stat_after = {0};
768 : 2 : sqlite3_int64 mismatch_offset_before = 0;
769 : 2 : sqlite3_int64 mismatch_offset_tampered = 0;
770 : 2 : sqlite3_int64 mismatch_offset_after = 0;
771 : 2 : unsigned char mismatch_sha512_before[SHA512_DIGEST_LENGTH] = {0};
772 : 2 : unsigned char mismatch_sha512_tampered[SHA512_DIGEST_LENGTH] = {0};
773 : 2 : unsigned char mismatch_sha512_after[SHA512_DIGEST_LENGTH] = {0};
774 : 2 : unsigned char mismatch_file_sha512[SHA512_DIGEST_LENGTH] = {0};
775 : :
776 : : /*
777 : : * Enable deterministic TESTING output so the captured application log
778 : : * can be compared against a template
779 : : */
780 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
781 : :
782 : : /*
783 : : * Work on a mutable copy of the fixture tree.
784 : : * The original fixture is restored during cleanup
785 : : */
786 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
787 : :
788 : : /*
789 : : * Create the baseline database.
790 : : * The lock pattern protects every relative path under the path1/ subtree
791 : : */
792 : 2 : const char *arguments = "--database=lock_s6.db --progress "
793 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
794 : :
795 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
796 : :
797 : : /*
798 : : * Verify the baseline run output.
799 : : * It must show a new database with path1/ files recorded as checksum-locked
800 : : */
801 : 2 : const char *filename = "templates/0030_006_1.txt";
802 : :
803 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
804 : :
805 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
806 : :
807 : : /*
808 : : * Resolve fixture paths and read the rows that will be exercised.
809 : : * The baseline checksum for the mismatch row must match the real file
810 : : */
811 : 2 : ASSERT(SUCCESS == construct_path(drift_file_path,drift_target_path));
812 : 2 : ASSERT(SUCCESS == construct_path(mismatch_file_path,mismatch_target_path));
813 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,drift_relative_path,&drift_db_stat_before));
814 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,drift_relative_path,&drift_offset_before,drift_sha512_before));
815 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(drift_target_path),&drift_file_stat_before));
816 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&drift_db_stat_before,&drift_file_stat_before));
817 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,mismatch_relative_path,&mismatch_db_stat_before));
818 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,mismatch_relative_path,&mismatch_offset_before,mismatch_sha512_before));
819 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(mismatch_target_path),mismatch_file_sha512));
820 : 2 : ASSERT(0 == memcmp(mismatch_sha512_before,mismatch_file_sha512,(size_t)SHA512_DIGEST_LENGTH));
821 : :
822 : : /*
823 : : * Change only timestamps for one checksum-locked file.
824 : : * The file size and contents stay unchanged, but ctime and mtime drift
825 : : */
826 : 2 : ASSERT(SUCCESS == touch_file_mtime_with_reference_delta_ns(NULL,drift_file_path,999));
827 : :
828 : : /*
829 : : * Confirm that the test setup produced real timestamp drift.
830 : : * The stored DB metadata must no longer match the current file timestamps
831 : : */
832 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(drift_target_path),&drift_file_stat_after_touch));
833 : 2 : ASSERT(false == cmpctstat_matches_stat_timestamps(&drift_db_stat_before,&drift_file_stat_after_touch));
834 : :
835 : : /*
836 : : * Corrupt the stored checksum for a different locked file.
837 : : * The file on disk stays unchanged, so the next rehash must detect a mismatch
838 : : */
839 : 2 : ASSERT(SUCCESS == db_tamper_sha512(db_filename,mismatch_relative_path));
840 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,mismatch_relative_path,&mismatch_offset_tampered,mismatch_sha512_tampered));
841 : 2 : ASSERT(mismatch_offset_before == mismatch_offset_tampered);
842 : 2 : ASSERT(0 != memcmp(mismatch_sha512_before,mismatch_sha512_tampered,(size_t)SHA512_DIGEST_LENGTH));
843 : 2 : ASSERT(0 != memcmp(mismatch_file_sha512,mismatch_sha512_tampered,(size_t)SHA512_DIGEST_LENGTH));
844 : :
845 : : /*
846 : : * Run an update that watches timestamps and rehashes locked files.
847 : : * The safe drift must be saved, while the checksum mismatch must warn
848 : : */
849 : 2 : arguments = "--update --watch-timestamps --rehash-locked "
850 : : "--lock-checksum=\"^path1/.*\" --database=lock_s6.db "
851 : : "tests/fixtures/diffs/diff1";
852 : :
853 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
854 : :
855 : : /*
856 : : * Verify the warning output against the real scenario.
857 : : * It must show synchronized timestamp drift and a separate checksum mismatch
858 : : */
859 : 2 : filename = "templates/0030_006_2.txt";
860 : :
861 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
862 : :
863 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
864 : :
865 : : /*
866 : : * Re-read the drift row and current file state after the warning run.
867 : : * The database timestamps must now match the file again
868 : : */
869 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,drift_relative_path,&drift_db_stat_after));
870 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,drift_relative_path,&drift_offset_after,drift_sha512_after));
871 : 2 : ASSERT(SUCCESS == get_file_stat(m_text(drift_target_path),&drift_file_stat_after_update));
872 : 2 : ASSERT(cmpctstat_matches_stat_timestamps(&drift_db_stat_after,&drift_file_stat_after_update));
873 : :
874 : : /*
875 : : * Verify that only metadata timestamps changed in the safely rehashed row.
876 : : * Size, allocation, identity, SHA512 offset, and SHA512 digest must stay stable
877 : : */
878 : 2 : ASSERT(drift_db_stat_before.st_size == drift_db_stat_after.st_size);
879 : 2 : ASSERT(drift_db_stat_before.st_blocks == drift_db_stat_after.st_blocks);
880 : 2 : ASSERT(drift_db_stat_before.st_dev == drift_db_stat_after.st_dev);
881 : 2 : ASSERT(drift_db_stat_before.st_ino == drift_db_stat_after.st_ino);
882 : 2 : ASSERT(drift_offset_before == drift_offset_after);
883 : 2 : ASSERT(0 == memcmp(drift_sha512_before,drift_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
884 : :
885 : : /*
886 : : * Re-read the mismatched row after the warning run.
887 : : * The corrupted stored checksum must remain unchanged instead of being repaired
888 : : */
889 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,mismatch_relative_path,&mismatch_db_stat_after));
890 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,mismatch_relative_path,&mismatch_offset_after,mismatch_sha512_after));
891 : 2 : ASSERT(0 == memcmp(&mismatch_db_stat_before,&mismatch_db_stat_after,sizeof(CmpctStat)));
892 : 2 : ASSERT(mismatch_offset_tampered == mismatch_offset_after);
893 : 2 : ASSERT(0 == memcmp(mismatch_sha512_tampered,mismatch_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
894 : 2 : ASSERT(0 != memcmp(mismatch_file_sha512,mismatch_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
895 : :
896 : : /*
897 : : * Remove the test database and restore the mutable fixture.
898 : : * This returns the workspace to the state expected by the next test
899 : : */
900 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
901 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
902 : :
903 : : /*
904 : : * Release managed buffers allocated by this test before returning
905 : : */
906 : 2 : m_del(mismatch_target_path);
907 : 2 : m_del(drift_target_path);
908 : 2 : m_del(pattern);
909 : 2 : m_del(result);
910 : :
911 : 2 : RETURN_STATUS;
912 : : }
913 : :
914 : : /**
915 : : * @brief Verify that locked content corruption is reported but not saved
916 : : *
917 : : * Covers README Example 10
918 : : *
919 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
920 : : * After bytes inside one locked file change on disk, the update run with
921 : : * `--rehash-locked` must detect the checksum mismatch, report corruption,
922 : : * and keep the original database row untouched
923 : : */
924 : 2 : static Return test0030_7(void)
925 : : {
926 : 2 : INITTEST;
927 : :
928 : : /*
929 : : * Allocate managed buffers for the captured application output,
930 : : * the expected regular-expression template, and the resolved fixture path
931 : : */
932 : 2 : m_create(char,result,MEMORY_STRING);
933 : 2 : m_create(char,pattern,MEMORY_STRING);
934 : 2 : m_create(char,target_path,MEMORY_STRING);
935 : :
936 : : /*
937 : : * Name the database, the protected database key, and the fixture file
938 : : * whose on-disk bytes will be changed without changing its logical size
939 : : */
940 : 2 : const char *db_filename = "lock_s7.db";
941 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
942 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
943 : :
944 : : /*
945 : : * Keep file and database snapshots around the content tampering.
946 : : * They prove that the warning run does not silently trust corrupted data
947 : : */
948 : 2 : CmpctStat db_stat_before = {0};
949 : 2 : CmpctStat db_stat_after = {0};
950 : 2 : sqlite3_int64 offset_before = 0;
951 : 2 : sqlite3_int64 offset_after = 0;
952 : 2 : unsigned char db_sha512_before[SHA512_DIGEST_LENGTH] = {0};
953 : 2 : unsigned char db_sha512_after[SHA512_DIGEST_LENGTH] = {0};
954 : 2 : unsigned char file_sha512_before[SHA512_DIGEST_LENGTH] = {0};
955 : 2 : unsigned char file_sha512_after_tamper[SHA512_DIGEST_LENGTH] = {0};
956 : :
957 : : /*
958 : : * Enable deterministic TESTING output so the captured application log
959 : : * can be compared against a template
960 : : */
961 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
962 : :
963 : : /*
964 : : * Work on a mutable copy of the fixture tree.
965 : : * The original fixture is restored during cleanup
966 : : */
967 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
968 : :
969 : : /*
970 : : * Create the baseline database.
971 : : * The lock pattern protects every relative path under the path1/ subtree
972 : : */
973 : 2 : const char *arguments = "--database=lock_s7.db --progress "
974 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
975 : :
976 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
977 : :
978 : : /*
979 : : * Verify the baseline run output.
980 : : * It must show a new database with path1/ files recorded as checksum-locked
981 : : */
982 : 2 : const char *filename = "templates/0030_007_1.txt";
983 : :
984 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
985 : :
986 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
987 : :
988 : : /*
989 : : * Resolve the target fixture path and read its matching locked DB row.
990 : : * Before tampering, the stored checksum must match the real file checksum
991 : : */
992 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
993 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
994 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,db_sha512_before));
995 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_before));
996 : 2 : ASSERT(0 == memcmp(db_sha512_before,file_sha512_before,(size_t)SHA512_DIGEST_LENGTH));
997 : :
998 : : /*
999 : : * Change bytes inside one checksum-locked file on disk.
1000 : : * The helper preserves logical size, so the mismatch is about content integrity
1001 : : */
1002 : 2 : ASSERT(SUCCESS == tamper_locked_file_bytes(locked_file_path));
1003 : :
1004 : : /*
1005 : : * Confirm that the test setup produced real content corruption.
1006 : : * The current file checksum must no longer match the protected DB checksum
1007 : : */
1008 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_after_tamper));
1009 : 2 : ASSERT(0 != memcmp(file_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1010 : 2 : ASSERT(0 != memcmp(db_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1011 : :
1012 : : /*
1013 : : * Run an update with deep locked-file verification enabled.
1014 : : * The content mismatch must be reported as a warning, not saved to the DB
1015 : : */
1016 : 2 : arguments = "--update --rehash-locked --lock-checksum=\"^path1/.*\" "
1017 : : "--database=lock_s7.db tests/fixtures/diffs/diff1";
1018 : :
1019 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1020 : :
1021 : : /*
1022 : : * Verify the warning output against the real scenario.
1023 : : * The changed locked file must be reported as checksum corruption
1024 : : */
1025 : 2 : filename = "templates/0030_007_2.txt";
1026 : :
1027 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1028 : :
1029 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1030 : :
1031 : : /*
1032 : : * Re-read the same protected row after the warning run.
1033 : : * The row must keep its original metadata, offset, and SHA512 checksum
1034 : : */
1035 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
1036 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,db_sha512_after));
1037 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1038 : 2 : ASSERT(offset_before == offset_after);
1039 : 2 : ASSERT(0 == memcmp(db_sha512_before,db_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1040 : 2 : ASSERT(0 != memcmp(db_sha512_after,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1041 : :
1042 : : /*
1043 : : * Remove the test database and restore the mutable fixture.
1044 : : * This returns the workspace to the state expected by the next test
1045 : : */
1046 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1047 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1048 : :
1049 : : /*
1050 : : * Release managed buffers allocated by this test before returning
1051 : : */
1052 : 2 : m_del(target_path);
1053 : 2 : m_del(pattern);
1054 : 2 : m_del(result);
1055 : :
1056 : 2 : RETURN_STATUS;
1057 : : }
1058 : :
1059 : : /**
1060 : : * @brief Verify that watched locked content corruption is reported but not saved
1061 : : *
1062 : : * Covers README Example 10
1063 : : *
1064 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1065 : : * After bytes inside one locked file change on disk, the update run with
1066 : : * `--watch-timestamps` and `--rehash-locked` must detect checksum corruption.
1067 : : * Timestamp watching must not make the corrupted database row updateable
1068 : : */
1069 : 2 : static Return test0030_8(void)
1070 : : {
1071 : 2 : INITTEST;
1072 : :
1073 : : /*
1074 : : * Allocate managed buffers for the captured application output,
1075 : : * the expected regular-expression template, and the resolved fixture path
1076 : : */
1077 : 2 : m_create(char,result,MEMORY_STRING);
1078 : 2 : m_create(char,pattern,MEMORY_STRING);
1079 : 2 : m_create(char,target_path,MEMORY_STRING);
1080 : :
1081 : : /*
1082 : : * Name the database, the protected database key, and the fixture file
1083 : : * whose on-disk bytes will be changed without changing its logical size
1084 : : */
1085 : 2 : const char *db_filename = "lock_s8.db";
1086 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1087 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1088 : :
1089 : : /*
1090 : : * Keep file and database snapshots around the content tampering.
1091 : : * They prove that the warning run does not silently trust corrupted data
1092 : : */
1093 : 2 : CmpctStat db_stat_before = {0};
1094 : 2 : CmpctStat db_stat_after = {0};
1095 : 2 : sqlite3_int64 offset_before = 0;
1096 : 2 : sqlite3_int64 offset_after = 0;
1097 : 2 : unsigned char db_sha512_before[SHA512_DIGEST_LENGTH] = {0};
1098 : 2 : unsigned char db_sha512_after[SHA512_DIGEST_LENGTH] = {0};
1099 : 2 : unsigned char file_sha512_before[SHA512_DIGEST_LENGTH] = {0};
1100 : 2 : unsigned char file_sha512_after_tamper[SHA512_DIGEST_LENGTH] = {0};
1101 : :
1102 : : /*
1103 : : * Enable deterministic TESTING output so the captured application log
1104 : : * can be compared against a template
1105 : : */
1106 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1107 : :
1108 : : /*
1109 : : * Work on a mutable copy of the fixture tree.
1110 : : * The original fixture is restored during cleanup
1111 : : */
1112 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1113 : :
1114 : : /*
1115 : : * Create the baseline database.
1116 : : * The lock pattern protects every relative path under the path1/ subtree
1117 : : */
1118 : 2 : const char *arguments = "--database=lock_s8.db --progress "
1119 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1120 : :
1121 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1122 : :
1123 : : /*
1124 : : * Verify the baseline run output.
1125 : : * It must show a new database with path1/ files recorded as checksum-locked
1126 : : */
1127 : 2 : const char *filename = "templates/0030_008_1.txt";
1128 : :
1129 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1130 : :
1131 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1132 : :
1133 : : /*
1134 : : * Resolve the target fixture path and read its matching locked DB row.
1135 : : * Before tampering, the stored checksum must match the real file checksum
1136 : : */
1137 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
1138 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
1139 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,db_sha512_before));
1140 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_before));
1141 : 2 : ASSERT(0 == memcmp(db_sha512_before,file_sha512_before,(size_t)SHA512_DIGEST_LENGTH));
1142 : :
1143 : : /*
1144 : : * Change bytes inside one checksum-locked file on disk.
1145 : : * The helper preserves logical size, so the mismatch is about content integrity
1146 : : */
1147 : 2 : ASSERT(SUCCESS == tamper_locked_file_bytes(locked_file_path));
1148 : :
1149 : : /*
1150 : : * Confirm that the test setup produced real content corruption.
1151 : : * The current file checksum must no longer match the protected DB checksum
1152 : : */
1153 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_after_tamper));
1154 : 2 : ASSERT(0 != memcmp(file_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1155 : 2 : ASSERT(0 != memcmp(db_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1156 : :
1157 : : /*
1158 : : * Run an update that watches timestamps and rehashes locked files.
1159 : : * The content mismatch must be reported as a warning, not saved to the DB
1160 : : */
1161 : 2 : arguments = "--update --watch-timestamps --rehash-locked "
1162 : : "--lock-checksum=\"^path1/.*\" --database=lock_s8.db "
1163 : : "tests/fixtures/diffs/diff1";
1164 : :
1165 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1166 : :
1167 : : /*
1168 : : * Verify the warning output against the real scenario.
1169 : : * The changed locked file must be reported as checksum corruption
1170 : : */
1171 : 2 : filename = "templates/0030_008_2.txt";
1172 : :
1173 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1174 : :
1175 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1176 : :
1177 : : /*
1178 : : * Re-read the same protected row after the warning run.
1179 : : * The row must keep its original metadata, offset, and SHA512 checksum
1180 : : */
1181 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
1182 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,db_sha512_after));
1183 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1184 : 2 : ASSERT(offset_before == offset_after);
1185 : 2 : ASSERT(0 == memcmp(db_sha512_before,db_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1186 : 2 : ASSERT(0 != memcmp(db_sha512_after,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
1187 : :
1188 : : /*
1189 : : * Remove the test database and restore the mutable fixture.
1190 : : * This returns the workspace to the state expected by the next test
1191 : : */
1192 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1193 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1194 : :
1195 : : /*
1196 : : * Release managed buffers allocated by this test before returning
1197 : : */
1198 : 2 : m_del(target_path);
1199 : 2 : m_del(pattern);
1200 : 2 : m_del(result);
1201 : :
1202 : 2 : RETURN_STATUS;
1203 : : }
1204 : :
1205 : : /**
1206 : : * @brief Verify that watched unchanged locked files finish successfully
1207 : : *
1208 : : * Covers README Example 10, case 3
1209 : : *
1210 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1211 : : * When the locked files still match the stored metadata and `--rehash-locked`
1212 : : * is not used, `--watch-timestamps` must not create warnings or updates.
1213 : : * All protected rows must stay unchanged in the database
1214 : : */
1215 : 2 : static Return test0030_9(void)
1216 : : {
1217 : 2 : INITTEST;
1218 : :
1219 : : /*
1220 : : * Allocate managed buffers for the captured application output,
1221 : : * the expected regular-expression template, and reusable resolved paths
1222 : : */
1223 : 2 : m_create(char,result,MEMORY_STRING);
1224 : 2 : m_create(char,pattern,MEMORY_STRING);
1225 : 2 : m_create(char,target_path,MEMORY_STRING);
1226 : :
1227 : : /*
1228 : : * Name the database and every file protected by the lock pattern.
1229 : : * This test checks all protected rows, because no locked path should change
1230 : : */
1231 : 2 : const char *db_filename = "lock_s9.db";
1232 : 2 : const char *locked_relative_paths[] = {
1233 : : "path1/AAA/BCB/CCC/a.txt",
1234 : : "path1/AAA/ZAW/A/b/c/a_file.txt",
1235 : : "path1/AAA/ZAW/D/e/f/b_file.txt",
1236 : : };
1237 : 2 : const char *locked_file_paths[] = {
1238 : : "tests/fixtures/diffs/diff1/path1/AAA/BCB/CCC/a.txt",
1239 : : "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt",
1240 : : "tests/fixtures/diffs/diff1/path1/AAA/ZAW/D/e/f/b_file.txt",
1241 : : };
1242 : 2 : const size_t locked_path_count = sizeof(locked_relative_paths) / sizeof(locked_relative_paths[0]);
1243 : :
1244 : : /*
1245 : : * Keep before-and-after snapshots of every protected database row.
1246 : : * They prove that a successful watched update did not rewrite locked data
1247 : : */
1248 : 2 : CmpctStat db_stat_before[3] = {0};
1249 : 2 : CmpctStat db_stat_after[3] = {0};
1250 : 2 : struct stat file_stat_before[3] = {0};
1251 : 2 : struct stat file_stat_after[3] = {0};
1252 : 2 : sqlite3_int64 offset_before[3] = {0};
1253 : 2 : sqlite3_int64 offset_after[3] = {0};
1254 : 2 : unsigned char sha512_before[3][SHA512_DIGEST_LENGTH] = {0};
1255 : 2 : unsigned char sha512_after[3][SHA512_DIGEST_LENGTH] = {0};
1256 : :
1257 : : /*
1258 : : * Enable deterministic TESTING output so the captured application log
1259 : : * can be compared against a template
1260 : : */
1261 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1262 : :
1263 : : /*
1264 : : * Work on a mutable copy of the fixture tree.
1265 : : * The original fixture is restored during cleanup
1266 : : */
1267 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1268 : :
1269 : : /*
1270 : : * Create the baseline database.
1271 : : * The lock pattern protects every relative path under the path1/ subtree
1272 : : */
1273 : 2 : const char *arguments = "--database=lock_s9.db --progress "
1274 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1275 : :
1276 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1277 : :
1278 : : /*
1279 : : * Verify the baseline run output.
1280 : : * It must show a new database with path1/ files recorded as checksum-locked
1281 : : */
1282 : 2 : const char *filename = "templates/0030_009_1.txt";
1283 : :
1284 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1285 : :
1286 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1287 : :
1288 : : /*
1289 : : * Read all protected rows and current file timestamps before the update.
1290 : : * For this README case, every locked file must still match the database
1291 : : */
1292 [ + + ]: 8 : for(size_t index = 0; index < locked_path_count; index++)
1293 : : {
1294 : 6 : ASSERT(SUCCESS == construct_path(locked_file_paths[index],target_path));
1295 : 6 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_paths[index],&db_stat_before[index]));
1296 : 6 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_paths[index],&offset_before[index],sha512_before[index]));
1297 : 6 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_before[index]));
1298 : 6 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_before[index],&file_stat_before[index]));
1299 : : }
1300 : :
1301 : : /*
1302 : : * Run an update that watches timestamps but does not rehash locked files.
1303 : : * Because all protected metadata still matches, the run must be successful
1304 : : */
1305 : 2 : arguments = "--update --watch-timestamps --lock-checksum=\"^path1/.*\" "
1306 : : "--database=lock_s9.db tests/fixtures/diffs/diff1";
1307 : :
1308 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1309 : :
1310 : : /*
1311 : : * Verify the successful output.
1312 : : * It must report no changes and no locked-file warnings
1313 : : */
1314 : 2 : filename = "templates/0030_009_2.txt";
1315 : :
1316 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1317 : :
1318 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1319 : :
1320 : : /*
1321 : : * Re-read all protected rows after the successful watched update.
1322 : : * Metadata, SHA512 offsets, and SHA512 digests must remain unchanged
1323 : : */
1324 [ + + ]: 8 : for(size_t index = 0; index < locked_path_count; index++)
1325 : : {
1326 : 6 : ASSERT(SUCCESS == construct_path(locked_file_paths[index],target_path));
1327 : 6 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_paths[index],&db_stat_after[index]));
1328 : 6 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_paths[index],&offset_after[index],sha512_after[index]));
1329 : 6 : ASSERT(SUCCESS == get_file_stat(m_text(target_path),&file_stat_after[index]));
1330 : 6 : ASSERT(cmpctstat_matches_stat_timestamps(&db_stat_after[index],&file_stat_after[index]));
1331 : 6 : ASSERT(0 == memcmp(&db_stat_before[index],&db_stat_after[index],sizeof(CmpctStat)));
1332 : 6 : ASSERT(offset_before[index] == offset_after[index]);
1333 : 6 : ASSERT(0 == memcmp(sha512_before[index],sha512_after[index],(size_t)SHA512_DIGEST_LENGTH));
1334 : : }
1335 : :
1336 : : /*
1337 : : * Remove the test database and restore the mutable fixture.
1338 : : * This returns the workspace to the state expected by the next test
1339 : : */
1340 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1341 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1342 : :
1343 : : /*
1344 : : * Release managed buffers allocated by this test before returning
1345 : : */
1346 : 2 : m_del(target_path);
1347 : 2 : m_del(pattern);
1348 : 2 : m_del(result);
1349 : :
1350 : 2 : RETURN_STATUS;
1351 : : }
1352 : :
1353 : : /**
1354 : : * @brief Verify that deleting a locked file warns and keeps the DB row
1355 : : *
1356 : : * Covers README Example 9
1357 : : *
1358 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1359 : : * When a locked file disappears before an update, the program must warn and
1360 : : * keep every locked row in the database. The deleted row must remain unchanged
1361 : : */
1362 : 2 : static Return test0030_10(void)
1363 : : {
1364 : 2 : INITTEST;
1365 : :
1366 : : /*
1367 : : * Allocate managed buffers for the captured application output,
1368 : : * the expected regular-expression template, and the resolved deleted path
1369 : : */
1370 : 2 : m_create(char,result,MEMORY_STRING);
1371 : 2 : m_create(char,pattern,MEMORY_STRING);
1372 : 2 : m_create(char,target_path,MEMORY_STRING);
1373 : :
1374 : : /*
1375 : : * Name the database, the deleted locked row, and the matching fixture file.
1376 : : * The other locked rows are checked separately to prove the whole lock set survives
1377 : : */
1378 : 2 : const char *db_filename = "lock_s10.db";
1379 : 2 : const char *deleted_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1380 : 2 : const char *deleted_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1381 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
1382 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
1383 : :
1384 : : /*
1385 : : * Keep before-and-after snapshots of the deleted locked row.
1386 : : * They prove that reporting the missing file did not rewrite protected data
1387 : : */
1388 : 2 : CmpctStat db_stat_before = {0};
1389 : 2 : CmpctStat db_stat_after = {0};
1390 : 2 : sqlite3_int64 offset_before = 0;
1391 : 2 : sqlite3_int64 offset_after = 0;
1392 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
1393 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
1394 : 2 : bool row_exists = false;
1395 : :
1396 : : /*
1397 : : * Enable deterministic TESTING output so the captured application log
1398 : : * can be compared against a template
1399 : : */
1400 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1401 : :
1402 : : /*
1403 : : * Work on a mutable copy of the fixture tree.
1404 : : * The original fixture is restored during cleanup
1405 : : */
1406 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1407 : :
1408 : : /*
1409 : : * Create the baseline database.
1410 : : * The lock pattern protects every relative path under the path1/ subtree
1411 : : */
1412 : 2 : const char *arguments = "--database=lock_s10.db --progress "
1413 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1414 : :
1415 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1416 : :
1417 : : /*
1418 : : * Verify the baseline run output.
1419 : : * It must show a new database with path1/ files recorded as checksum-locked
1420 : : */
1421 : 2 : const char *filename = "templates/0030_010_1.txt";
1422 : :
1423 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1424 : :
1425 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1426 : :
1427 : : /*
1428 : : * Read the locked row before deleting its file.
1429 : : * The same values must still be present after the warning run
1430 : : */
1431 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_before));
1432 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_before,sha512_before));
1433 : 2 : ASSERT(SUCCESS == construct_path(deleted_file_path,target_path));
1434 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_ACCESS_ALLOWED));
1435 : :
1436 : : /*
1437 : : * Delete one checksum-locked file from disk.
1438 : : * This must be treated as a lock violation, not as a normal missing-file cleanup
1439 : : */
1440 : 2 : ASSERT(SUCCESS == delete_path(deleted_file_path));
1441 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_NOT_FOUND));
1442 : :
1443 : : /*
1444 : : * Run an update with the same lock pattern.
1445 : : * The missing locked file must be reported as a warning and kept in the DB
1446 : : */
1447 : 2 : arguments = "--update --lock-checksum=\"^path1/.*\" "
1448 : : "--database=lock_s10.db tests/fixtures/diffs/diff1";
1449 : :
1450 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1451 : :
1452 : : /*
1453 : : * Verify the warning output against the real scenario.
1454 : : * The deleted locked file must be reported as disappeared from disk
1455 : : */
1456 : 2 : filename = "templates/0030_010_2.txt";
1457 : :
1458 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1459 : :
1460 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1461 : :
1462 : : /*
1463 : : * Re-read the deleted file row after the warning run.
1464 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
1465 : : */
1466 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_after));
1467 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_after,sha512_after));
1468 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1469 : 2 : ASSERT(offset_before == offset_after);
1470 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1471 : :
1472 : : /*
1473 : : * Confirm that the full locked path set remains in the database.
1474 : : * A missing locked file must not cause any protected row to be dropped
1475 : : */
1476 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,deleted_relative_path,&row_exists));
1477 : 2 : ASSERT(row_exists == true);
1478 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
1479 : 2 : ASSERT(row_exists == true);
1480 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
1481 : 2 : ASSERT(row_exists == true);
1482 : :
1483 : : /*
1484 : : * Remove the test database and restore the mutable fixture.
1485 : : * This returns the workspace to the state expected by the next test
1486 : : */
1487 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1488 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1489 : :
1490 : : /*
1491 : : * Release managed buffers allocated by this test before returning
1492 : : */
1493 : 2 : m_del(target_path);
1494 : 2 : m_del(pattern);
1495 : 2 : m_del(result);
1496 : :
1497 : 2 : RETURN_STATUS;
1498 : : }
1499 : :
1500 : : /**
1501 : : * @brief Verify that --db-drop-inaccessible does not drop an access-denied locked file
1502 : : *
1503 : : * Covers README Examples 9 and 11
1504 : : *
1505 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1506 : : * Then a test hook makes one locked file look access-denied during an update run
1507 : : * with `--db-drop-inaccessible`. The program must warn about the locked file
1508 : : * and keep the protected database row unchanged
1509 : : */
1510 : 2 : static Return test0030_11(void)
1511 : : {
1512 : 2 : INITTEST;
1513 : :
1514 : : /*
1515 : : * Allocate managed buffers for captured application output, the expected
1516 : : * regular-expression template, and the resolved locked file path
1517 : : */
1518 : 2 : m_create(char,result,MEMORY_STRING);
1519 : 2 : m_create(char,pattern,MEMORY_STRING);
1520 : 2 : m_create(char,target_path,MEMORY_STRING);
1521 : :
1522 : : /*
1523 : : * Name the database, the access-denied locked row, and the matching
1524 : : * fixture file. The other locked rows prove that the whole lock set survives
1525 : : */
1526 : 2 : const char *db_filename = "lock_s11.db";
1527 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1528 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1529 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
1530 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
1531 : :
1532 : : /*
1533 : : * Keep before-and-after snapshots of the access-denied locked row.
1534 : : * They prove that a warning result did not rewrite protected data
1535 : : */
1536 : 2 : CmpctStat db_stat_before = {0};
1537 : 2 : CmpctStat db_stat_after = {0};
1538 : 2 : sqlite3_int64 offset_before = 0;
1539 : 2 : sqlite3_int64 offset_after = 0;
1540 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
1541 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
1542 : 2 : bool row_exists = false;
1543 : :
1544 : : /*
1545 : : * Enable deterministic TESTING output so the captured application log
1546 : : * can be compared against a template
1547 : : */
1548 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1549 : :
1550 : : /*
1551 : : * Work on a mutable copy of the fixture tree.
1552 : : * The original fixture is restored during cleanup
1553 : : */
1554 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1555 : :
1556 : : /*
1557 : : * Create the baseline database.
1558 : : * The lock pattern protects every relative path under the path1/ subtree
1559 : : */
1560 : 2 : const char *arguments = "--database=lock_s11.db --progress "
1561 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1562 : :
1563 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1564 : :
1565 : : /*
1566 : : * Verify the baseline run output.
1567 : : * It must show a new database with path1/ files recorded as checksum-locked
1568 : : */
1569 : 2 : const char *filename = "templates/0030_011_1.txt";
1570 : :
1571 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1572 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1573 : :
1574 : : /*
1575 : : * Read the locked row before making access checks fail.
1576 : : * The same values must still be present after the warning run
1577 : : */
1578 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
1579 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
1580 : :
1581 : : /*
1582 : : * Confirm the fixture file itself is present and readable.
1583 : : * This test covers an access-denied report, not a missing-file scenario
1584 : : */
1585 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
1586 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
1587 : :
1588 : : /*
1589 : : * Force access checks for exactly this locked file to return access denied.
1590 : : * This models a protected file that the application can see in the tree but cannot read
1591 : : */
1592 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
1593 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_DENIED"));
1594 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_DENIED));
1595 : :
1596 : : /*
1597 : : * Run an update that would normally drop inaccessible records.
1598 : : * The checksum lock must override that cleanup and turn the condition into a warning
1599 : : */
1600 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
1601 : : "--database=lock_s11.db tests/fixtures/diffs/diff1";
1602 : :
1603 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1604 : :
1605 : : /*
1606 : : * Disable the access-check hook before inspecting output and database state.
1607 : : * Later assertions should observe the real filesystem again
1608 : : */
1609 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
1610 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
1611 : :
1612 : : /*
1613 : : * Verify the warning output against the real command-line scenario.
1614 : : * The run must report checksum-locked access denial and return WARNING
1615 : : */
1616 : 2 : filename = "templates/0030_011_2.txt";
1617 : :
1618 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1619 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1620 : :
1621 : : /*
1622 : : * Re-read the access-denied locked row after the warning run.
1623 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
1624 : : */
1625 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
1626 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
1627 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1628 : 2 : ASSERT(offset_before == offset_after);
1629 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1630 : :
1631 : : /*
1632 : : * Confirm that the full locked path set remains in the database.
1633 : : * --db-drop-inaccessible must not drop any checksum-locked row
1634 : : */
1635 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
1636 : 2 : ASSERT(row_exists == true);
1637 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
1638 : 2 : ASSERT(row_exists == true);
1639 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
1640 : 2 : ASSERT(row_exists == true);
1641 : :
1642 : : /*
1643 : : * Remove the test database and restore the mutable fixture.
1644 : : * This returns the workspace to the state expected by the next test
1645 : : */
1646 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1647 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1648 : :
1649 : : /*
1650 : : * Release managed buffers allocated by this test before returning
1651 : : */
1652 : 2 : m_del(target_path);
1653 : 2 : m_del(pattern);
1654 : 2 : m_del(result);
1655 : :
1656 : 2 : RETURN_STATUS;
1657 : : }
1658 : :
1659 : : /**
1660 : : * @brief Verify that --db-drop-inaccessible does not drop a locked file after an access-check failure
1661 : : *
1662 : : * Covers README Examples 9 and 11
1663 : : *
1664 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1665 : : * Then a test hook makes one locked file return an access-check failure during
1666 : : * an update run with `--db-drop-inaccessible`. The program must warn about the
1667 : : * locked file and keep the protected database row unchanged
1668 : : */
1669 : 2 : static Return test0030_12(void)
1670 : : {
1671 : 2 : INITTEST;
1672 : :
1673 : : /*
1674 : : * Allocate managed buffers for captured application output, the expected
1675 : : * regular-expression template, and the resolved locked file path
1676 : : */
1677 : 2 : m_create(char,result,MEMORY_STRING);
1678 : 2 : m_create(char,pattern,MEMORY_STRING);
1679 : 2 : m_create(char,target_path,MEMORY_STRING);
1680 : :
1681 : : /*
1682 : : * Name the database, the locked row that will fail access checking, and
1683 : : * the matching fixture file. The other locked rows prove that the whole lock set survives
1684 : : */
1685 : 2 : const char *db_filename = "lock_s12.db";
1686 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1687 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1688 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
1689 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
1690 : :
1691 : : /*
1692 : : * Keep before-and-after snapshots of the locked row that will fail access checking.
1693 : : * They prove that a warning result did not rewrite protected data
1694 : : */
1695 : 2 : CmpctStat db_stat_before = {0};
1696 : 2 : CmpctStat db_stat_after = {0};
1697 : 2 : sqlite3_int64 offset_before = 0;
1698 : 2 : sqlite3_int64 offset_after = 0;
1699 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
1700 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
1701 : 2 : bool row_exists = false;
1702 : :
1703 : : /*
1704 : : * Enable deterministic TESTING output so the captured application log
1705 : : * can be compared against a template
1706 : : */
1707 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1708 : :
1709 : : /*
1710 : : * Work on a mutable copy of the fixture tree.
1711 : : * The original fixture is restored during cleanup
1712 : : */
1713 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1714 : :
1715 : : /*
1716 : : * Create the baseline database.
1717 : : * The lock pattern protects every relative path under the path1/ subtree
1718 : : */
1719 : 2 : const char *arguments = "--database=lock_s12.db --progress "
1720 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1721 : :
1722 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1723 : :
1724 : : /*
1725 : : * Verify the baseline run output.
1726 : : * It must show a new database with path1/ files recorded as checksum-locked
1727 : : */
1728 : 2 : const char *filename = "templates/0030_012_1.txt";
1729 : :
1730 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1731 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1732 : :
1733 : : /*
1734 : : * Read the locked row before making access checks fail.
1735 : : * The same values must still be present after the warning run
1736 : : */
1737 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
1738 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
1739 : :
1740 : : /*
1741 : : * Confirm the fixture file itself is present and readable.
1742 : : * This test covers an access-check failure, not a missing-file scenario
1743 : : */
1744 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
1745 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
1746 : :
1747 : : /*
1748 : : * Force access checks for exactly this locked file to return an access-check failure.
1749 : : * This models an unexpected access-check problem while the checksum lock is active
1750 : : */
1751 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
1752 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_ERROR"));
1753 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ERROR));
1754 : :
1755 : : /*
1756 : : * Run an update that would normally drop inaccessible records.
1757 : : * The checksum lock must override that cleanup and turn the condition into a warning
1758 : : */
1759 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
1760 : : "--database=lock_s12.db tests/fixtures/diffs/diff1";
1761 : :
1762 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1763 : :
1764 : : /*
1765 : : * Disable the access-check hook before inspecting output and database state.
1766 : : * Later assertions should observe the real filesystem again
1767 : : */
1768 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
1769 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
1770 : :
1771 : : /*
1772 : : * Verify the warning output against the real command-line scenario.
1773 : : * The run must report checksum-locked access-check failure and return WARNING
1774 : : */
1775 : 2 : filename = "templates/0030_012_2.txt";
1776 : :
1777 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1778 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1779 : :
1780 : : /*
1781 : : * Re-read the access-check-failed locked row after the warning run.
1782 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
1783 : : */
1784 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
1785 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
1786 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1787 : 2 : ASSERT(offset_before == offset_after);
1788 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1789 : :
1790 : : /*
1791 : : * Confirm that the full locked path set remains in the database.
1792 : : * --db-drop-inaccessible must not drop any checksum-locked row
1793 : : */
1794 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
1795 : 2 : ASSERT(row_exists == true);
1796 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
1797 : 2 : ASSERT(row_exists == true);
1798 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
1799 : 2 : ASSERT(row_exists == true);
1800 : :
1801 : : /*
1802 : : * Remove the test database and restore the mutable fixture.
1803 : : * This returns the workspace to the state expected by the next test
1804 : : */
1805 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1806 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1807 : :
1808 : : /*
1809 : : * Release managed buffers allocated by this test before returning
1810 : : */
1811 : 2 : m_del(target_path);
1812 : 2 : m_del(pattern);
1813 : 2 : m_del(result);
1814 : :
1815 : 2 : RETURN_STATUS;
1816 : : }
1817 : :
1818 : : /**
1819 : : * @brief Verify that --db-drop-ignored does not drop a deleted checksum-locked file
1820 : : *
1821 : : * Covers README Example 9
1822 : : *
1823 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1824 : : * Then one locked file is deleted and the update run ignores the whole protected
1825 : : * subtree with `--db-drop-ignored`. The program must warn about the missing
1826 : : * locked file and keep the protected database row unchanged
1827 : : */
1828 : 2 : static Return test0030_13(void)
1829 : : {
1830 : 2 : INITTEST;
1831 : :
1832 : : /*
1833 : : * Allocate managed buffers for captured application output, the expected
1834 : : * regular-expression template, and the resolved deleted file path
1835 : : */
1836 : 2 : m_create(char,result,MEMORY_STRING);
1837 : 2 : m_create(char,pattern,MEMORY_STRING);
1838 : 2 : m_create(char,target_path,MEMORY_STRING);
1839 : :
1840 : : /*
1841 : : * Name the database, the deleted locked row, and the matching fixture file.
1842 : : * The other locked rows prove that the whole lock set survives
1843 : : */
1844 : 2 : const char *db_filename = "lock_s13.db";
1845 : 2 : const char *deleted_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1846 : 2 : const char *deleted_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1847 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
1848 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
1849 : :
1850 : : /*
1851 : : * Keep before-and-after snapshots of the deleted locked row.
1852 : : * They prove that a warning result did not rewrite protected data
1853 : : */
1854 : 2 : CmpctStat db_stat_before = {0};
1855 : 2 : CmpctStat db_stat_after = {0};
1856 : 2 : sqlite3_int64 offset_before = 0;
1857 : 2 : sqlite3_int64 offset_after = 0;
1858 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
1859 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
1860 : 2 : bool row_exists = false;
1861 : :
1862 : : /*
1863 : : * Enable deterministic TESTING output so the captured application log
1864 : : * can be compared against a template
1865 : : */
1866 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
1867 : :
1868 : : /*
1869 : : * Work on a mutable copy of the fixture tree.
1870 : : * The original fixture is restored during cleanup
1871 : : */
1872 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
1873 : :
1874 : : /*
1875 : : * Create the baseline database.
1876 : : * The lock pattern protects every relative path under the path1/ subtree
1877 : : */
1878 : 2 : const char *arguments = "--database=lock_s13.db --progress "
1879 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
1880 : :
1881 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
1882 : :
1883 : : /*
1884 : : * Verify the baseline run output.
1885 : : * It must show a new database with path1/ files recorded as checksum-locked
1886 : : */
1887 : 2 : const char *filename = "templates/0030_013_1.txt";
1888 : :
1889 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1890 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1891 : :
1892 : : /*
1893 : : * Read the locked row before deleting its file.
1894 : : * The same values must still be present after the warning run
1895 : : */
1896 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_before));
1897 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_before,sha512_before));
1898 : :
1899 : : /*
1900 : : * Confirm the fixture file exists, then delete it from disk.
1901 : : * The later update must treat this as a checksum-lock violation, not ignored cleanup
1902 : : */
1903 : 2 : ASSERT(SUCCESS == construct_path(deleted_file_path,target_path));
1904 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_ACCESS_ALLOWED));
1905 : 2 : ASSERT(SUCCESS == delete_path(deleted_file_path));
1906 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_NOT_FOUND));
1907 : :
1908 : : /*
1909 : : * Run an update that ignores the whole protected subtree and allows ignored DB cleanup.
1910 : : * The checksum lock must override --db-drop-ignored for the missing protected file
1911 : : */
1912 : 2 : arguments = "--update --lock-checksum=\"^path1/.*\" "
1913 : : "--ignore=\"^path1/.*\" --db-drop-ignored "
1914 : : "--database=lock_s13.db tests/fixtures/diffs/diff1";
1915 : :
1916 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
1917 : :
1918 : : /*
1919 : : * Verify the warning output against the real command-line scenario.
1920 : : * The deleted locked file must be reported as disappeared and kept in the DB
1921 : : */
1922 : 2 : filename = "templates/0030_013_2.txt";
1923 : :
1924 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
1925 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
1926 : :
1927 : : /*
1928 : : * Re-read the deleted locked row after the warning run.
1929 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
1930 : : */
1931 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_after));
1932 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_after,sha512_after));
1933 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
1934 : 2 : ASSERT(offset_before == offset_after);
1935 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
1936 : :
1937 : : /*
1938 : : * Confirm that the full locked path set remains in the database.
1939 : : * --db-drop-ignored must not drop any checksum-locked row
1940 : : */
1941 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,deleted_relative_path,&row_exists));
1942 : 2 : ASSERT(row_exists == true);
1943 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
1944 : 2 : ASSERT(row_exists == true);
1945 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
1946 : 2 : ASSERT(row_exists == true);
1947 : :
1948 : : /*
1949 : : * Remove the test database and restore the mutable fixture.
1950 : : * This returns the workspace to the state expected by the next test
1951 : : */
1952 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
1953 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
1954 : :
1955 : : /*
1956 : : * Release managed buffers allocated by this test before returning
1957 : : */
1958 : 2 : m_del(target_path);
1959 : 2 : m_del(pattern);
1960 : 2 : m_del(result);
1961 : :
1962 : 2 : RETURN_STATUS;
1963 : : }
1964 : :
1965 : : /**
1966 : : * @brief Verify that --rehash-locked still checks ignored checksum-locked files
1967 : : *
1968 : : * Covers README Examples 9, 10, and 11
1969 : : *
1970 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
1971 : : * Then one locked file is changed on disk and the update run ignores the whole
1972 : : * protected subtree. `--rehash-locked` must still rehash the locked files,
1973 : : * report the corrupted file, and keep the protected database row unchanged
1974 : : */
1975 : 2 : static Return test0030_14(void)
1976 : : {
1977 : 2 : INITTEST;
1978 : :
1979 : : /*
1980 : : * Allocate managed buffers for captured application output, the expected
1981 : : * regular-expression template, and the resolved corrupted file path
1982 : : */
1983 : 2 : m_create(char,result,MEMORY_STRING);
1984 : 2 : m_create(char,pattern,MEMORY_STRING);
1985 : 2 : m_create(char,target_path,MEMORY_STRING);
1986 : :
1987 : : /*
1988 : : * Name the database, the protected database key, and the fixture file
1989 : : * whose on-disk bytes will be changed while the path is also ignored
1990 : : */
1991 : 2 : const char *db_filename = "lock_s14.db";
1992 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
1993 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
1994 : :
1995 : : /*
1996 : : * Keep file and database snapshots around the content tampering.
1997 : : * They prove that ignored locked corruption is detected but not saved
1998 : : */
1999 : 2 : CmpctStat db_stat_before = {0};
2000 : 2 : CmpctStat db_stat_after = {0};
2001 : 2 : sqlite3_int64 offset_before = 0;
2002 : 2 : sqlite3_int64 offset_after = 0;
2003 : 2 : unsigned char db_sha512_before[SHA512_DIGEST_LENGTH] = {0};
2004 : 2 : unsigned char db_sha512_after[SHA512_DIGEST_LENGTH] = {0};
2005 : 2 : unsigned char file_sha512_before[SHA512_DIGEST_LENGTH] = {0};
2006 : 2 : unsigned char file_sha512_after_tamper[SHA512_DIGEST_LENGTH] = {0};
2007 : :
2008 : : /*
2009 : : * Enable deterministic TESTING output so the captured application log
2010 : : * can be compared against a template
2011 : : */
2012 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2013 : :
2014 : : /*
2015 : : * Work on a mutable copy of the fixture tree.
2016 : : * The original fixture is restored during cleanup
2017 : : */
2018 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2019 : :
2020 : : /*
2021 : : * Create the baseline database.
2022 : : * The lock pattern protects every relative path under the path1/ subtree
2023 : : */
2024 : 2 : const char *arguments = "--database=lock_s14.db --progress "
2025 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2026 : :
2027 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2028 : :
2029 : : /*
2030 : : * Verify the baseline run output.
2031 : : * It must show a new database with path1/ files recorded as checksum-locked
2032 : : */
2033 : 2 : const char *filename = "templates/0030_014_1.txt";
2034 : :
2035 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2036 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2037 : :
2038 : : /*
2039 : : * Resolve the target fixture path and read its matching locked DB row.
2040 : : * Before tampering, the stored checksum must match the real file checksum
2041 : : */
2042 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2043 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2044 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,db_sha512_before));
2045 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_before));
2046 : 2 : ASSERT(0 == memcmp(db_sha512_before,file_sha512_before,(size_t)SHA512_DIGEST_LENGTH));
2047 : :
2048 : : /*
2049 : : * Change bytes inside one checksum-locked file on disk.
2050 : : * The helper preserves logical size, so the mismatch is about content integrity
2051 : : */
2052 : 2 : ASSERT(SUCCESS == tamper_locked_file_bytes(locked_file_path));
2053 : :
2054 : : /*
2055 : : * Confirm that the test setup produced real content corruption.
2056 : : * The current file checksum must no longer match the protected DB checksum
2057 : : */
2058 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_after_tamper));
2059 : 2 : ASSERT(0 != memcmp(file_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2060 : 2 : ASSERT(0 != memcmp(db_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2061 : :
2062 : : /*
2063 : : * Run an update that ignores the protected subtree but rehashes locked files.
2064 : : * The ignored corrupted file must still be reported as a checksum violation
2065 : : */
2066 : 2 : arguments = "--update --rehash-locked --lock-checksum=\"^path1/.*\" "
2067 : : "--ignore=\"^path1/.*\" --database=lock_s14.db "
2068 : : "tests/fixtures/diffs/diff1";
2069 : :
2070 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2071 : :
2072 : : /*
2073 : : * Verify the warning output against the real command-line scenario.
2074 : : * Ignored directories are reported, but locked files are still rehashed
2075 : : */
2076 : 2 : filename = "templates/0030_014_2.txt";
2077 : :
2078 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2079 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2080 : :
2081 : : /*
2082 : : * Re-read the same protected row after the warning run.
2083 : : * The row must keep its original metadata, offset, and SHA512 checksum
2084 : : */
2085 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
2086 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,db_sha512_after));
2087 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2088 : 2 : ASSERT(offset_before == offset_after);
2089 : 2 : ASSERT(0 == memcmp(db_sha512_before,db_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2090 : 2 : ASSERT(0 != memcmp(db_sha512_after,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2091 : :
2092 : : /*
2093 : : * Remove the test database and restore the mutable fixture.
2094 : : * This returns the workspace to the state expected by the next test
2095 : : */
2096 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2097 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2098 : :
2099 : : /*
2100 : : * Release managed buffers allocated by this test before returning
2101 : : */
2102 : 2 : m_del(target_path);
2103 : 2 : m_del(pattern);
2104 : 2 : m_del(result);
2105 : :
2106 : 2 : RETURN_STATUS;
2107 : : }
2108 : :
2109 : : /**
2110 : : * @brief Verify that --include does not weaken checksum-lock cleanup protection
2111 : : *
2112 : : * Covers README Example 9
2113 : : *
2114 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
2115 : : * Then one locked file is deleted while the update run ignores the whole
2116 : : * protected subtree and restores only a different locked file through `--include`.
2117 : : * `--db-drop-ignored` must not remove the missing locked row: the program must
2118 : : * report a warning and keep the protected database row unchanged
2119 : : */
2120 : 2 : static Return test0030_15(void)
2121 : : {
2122 : 2 : INITTEST;
2123 : :
2124 : : /*
2125 : : * Allocate managed buffers for captured application output, the expected
2126 : : * regular-expression template, and the resolved deleted file path
2127 : : */
2128 : 2 : m_create(char,result,MEMORY_STRING);
2129 : 2 : m_create(char,pattern,MEMORY_STRING);
2130 : 2 : m_create(char,target_path,MEMORY_STRING);
2131 : :
2132 : : /*
2133 : : * Name the database, the deleted protected row, and the other locked rows
2134 : : * that must stay in the database after ignored cleanup
2135 : : */
2136 : 2 : const char *db_filename = "lock_s15.db";
2137 : 2 : const char *deleted_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2138 : 2 : const char *deleted_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2139 : 2 : const char *other_locked_relative_path = "path1/AAA/ZAW/D/e/f/b_file.txt";
2140 : 2 : const char *included_locked_relative_path = "path1/AAA/BCB/CCC/a.txt";
2141 : :
2142 : : /*
2143 : : * Keep a before/after snapshot of the deleted locked row.
2144 : : * These values prove that warning cleanup did not rewrite protected data
2145 : : */
2146 : 2 : CmpctStat db_stat_before = {0};
2147 : 2 : CmpctStat db_stat_after = {0};
2148 : 2 : sqlite3_int64 offset_before = 0;
2149 : 2 : sqlite3_int64 offset_after = 0;
2150 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
2151 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
2152 : 2 : bool row_exists = false;
2153 : :
2154 : : /*
2155 : : * Enable deterministic TESTING output so the captured application log
2156 : : * can be compared against a template
2157 : : */
2158 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2159 : :
2160 : : /*
2161 : : * Work on a mutable copy of the fixture tree.
2162 : : * The original fixture is restored during cleanup
2163 : : */
2164 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2165 : :
2166 : : /*
2167 : : * Create the baseline database.
2168 : : * The lock pattern protects every relative path under the path1/ subtree
2169 : : */
2170 : 2 : const char *arguments = "--database=lock_s15.db --progress "
2171 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2172 : :
2173 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2174 : :
2175 : : /*
2176 : : * Verify the baseline run output.
2177 : : * It must show a new database with path1/ files recorded as checksum-locked
2178 : : */
2179 : 2 : const char *filename = "templates/0030_015_1.txt";
2180 : :
2181 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2182 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2183 : :
2184 : : /*
2185 : : * Read the locked row before deleting its file.
2186 : : * The same values must still be present after the warning run
2187 : : */
2188 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_before));
2189 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_before,sha512_before));
2190 : :
2191 : : /*
2192 : : * Confirm the fixture file exists, then delete it from disk.
2193 : : * The later update must treat this as a checksum-lock violation, not ignored cleanup
2194 : : */
2195 : 2 : ASSERT(SUCCESS == construct_path(deleted_file_path,target_path));
2196 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_ACCESS_ALLOWED));
2197 : 2 : ASSERT(SUCCESS == delete_path(deleted_file_path));
2198 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,deleted_relative_path,R_OK,FILE_NOT_FOUND));
2199 : :
2200 : : /*
2201 : : * Run an update that ignores the protected subtree, restores one different
2202 : : * locked file through --include, and allows ignored DB cleanup.
2203 : : * The deleted locked row is outside the restored include subset, but the
2204 : : * checksum lock must still override --db-drop-ignored
2205 : : */
2206 : 2 : arguments = "--update --lock-checksum=\"^path1/.*\" "
2207 : : "--ignore=\"^path1/.*\" --include=\"^path1/AAA/BCB/CCC/a\\.txt$\" "
2208 : : "--db-drop-ignored --database=lock_s15.db tests/fixtures/diffs/diff1";
2209 : :
2210 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2211 : :
2212 : : /*
2213 : : * Verify the warning output against the real command-line scenario.
2214 : : * The deleted locked file must be reported as disappeared and kept in the DB
2215 : : */
2216 : 2 : filename = "templates/0030_015_2.txt";
2217 : :
2218 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2219 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2220 : :
2221 : : /*
2222 : : * Re-read the deleted locked row after the warning run.
2223 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
2224 : : */
2225 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,deleted_relative_path,&db_stat_after));
2226 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,deleted_relative_path,&offset_after,sha512_after));
2227 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2228 : 2 : ASSERT(offset_before == offset_after);
2229 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2230 : :
2231 : : /*
2232 : : * Confirm that the deleted locked row, another ignored locked row, and the
2233 : : * included locked row all remain in the database
2234 : : */
2235 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,deleted_relative_path,&row_exists));
2236 : 2 : ASSERT(row_exists == true);
2237 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path,&row_exists));
2238 : 2 : ASSERT(row_exists == true);
2239 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,included_locked_relative_path,&row_exists));
2240 : 2 : ASSERT(row_exists == true);
2241 : :
2242 : : /*
2243 : : * Remove the test database and restore the mutable fixture.
2244 : : * This returns the workspace to the state expected by the next test
2245 : : */
2246 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2247 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2248 : :
2249 : : /*
2250 : : * Release managed buffers allocated by this test before returning
2251 : : */
2252 : 2 : m_del(target_path);
2253 : 2 : m_del(pattern);
2254 : 2 : m_del(result);
2255 : :
2256 : 2 : RETURN_STATUS;
2257 : : }
2258 : :
2259 : : /**
2260 : : * @brief Verify that --rehash-locked checks locked files outside the restored --include subset
2261 : : *
2262 : : * Covers README Examples 9 and 10
2263 : : *
2264 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
2265 : : * Then one locked file is changed on disk while the update run ignores the whole
2266 : : * protected subtree and restores only a different locked file through `--include`.
2267 : : * `--rehash-locked` must still rehash the corrupted locked file, report a warning,
2268 : : * and keep the protected database row unchanged
2269 : : */
2270 : 2 : static Return test0030_16(void)
2271 : : {
2272 : 2 : INITTEST;
2273 : :
2274 : : /*
2275 : : * Allocate managed buffers for captured application output, the expected
2276 : : * regular-expression template, and the resolved corrupted file path
2277 : : */
2278 : 2 : m_create(char,result,MEMORY_STRING);
2279 : 2 : m_create(char,pattern,MEMORY_STRING);
2280 : 2 : m_create(char,target_path,MEMORY_STRING);
2281 : :
2282 : : /*
2283 : : * Name the database, the protected database key, and the fixture file
2284 : : * whose on-disk bytes will be changed outside the restored include subset
2285 : : */
2286 : 2 : const char *db_filename = "lock_s16.db";
2287 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2288 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2289 : :
2290 : : /*
2291 : : * Keep file and database snapshots around the content tampering.
2292 : : * They prove that ignored locked corruption is detected but not saved
2293 : : */
2294 : 2 : CmpctStat db_stat_before = {0};
2295 : 2 : CmpctStat db_stat_after = {0};
2296 : 2 : struct stat file_stat_before = {0};
2297 : 2 : struct stat file_stat_after_tamper = {0};
2298 : 2 : sqlite3_int64 offset_before = 0;
2299 : 2 : sqlite3_int64 offset_after = 0;
2300 : 2 : unsigned char db_sha512_before[SHA512_DIGEST_LENGTH] = {0};
2301 : 2 : unsigned char db_sha512_after[SHA512_DIGEST_LENGTH] = {0};
2302 : 2 : unsigned char file_sha512_before[SHA512_DIGEST_LENGTH] = {0};
2303 : 2 : unsigned char file_sha512_after_tamper[SHA512_DIGEST_LENGTH] = {0};
2304 : :
2305 : : /*
2306 : : * Enable deterministic TESTING output so the captured application log
2307 : : * can be compared against a template
2308 : : */
2309 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2310 : :
2311 : : /*
2312 : : * Work on a mutable copy of the fixture tree.
2313 : : * The original fixture is restored during cleanup
2314 : : */
2315 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2316 : :
2317 : : /*
2318 : : * Create the baseline database.
2319 : : * The lock pattern protects every relative path under the path1/ subtree
2320 : : */
2321 : 2 : const char *arguments = "--database=lock_s16.db --progress "
2322 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2323 : :
2324 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2325 : :
2326 : : /*
2327 : : * Verify the baseline run output.
2328 : : * It must show a new database with path1/ files recorded as checksum-locked
2329 : : */
2330 : 2 : const char *filename = "templates/0030_016_1.txt";
2331 : :
2332 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2333 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2334 : :
2335 : : /*
2336 : : * Resolve the target fixture path and read its matching locked DB row.
2337 : : * Before tampering, the stored checksum must match the real file checksum
2338 : : */
2339 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2340 : 2 : ASSERT(0 == stat(m_text(target_path),&file_stat_before));
2341 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2342 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,db_sha512_before));
2343 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_before));
2344 : 2 : ASSERT(0 == memcmp(db_sha512_before,file_sha512_before,(size_t)SHA512_DIGEST_LENGTH));
2345 : :
2346 : : /*
2347 : : * Change bytes inside one checksum-locked file on disk.
2348 : : * The helper preserves logical size, so the mismatch is about content integrity
2349 : : */
2350 : 2 : ASSERT(SUCCESS == tamper_locked_file_bytes(locked_file_path));
2351 : :
2352 : : /*
2353 : : * Confirm that the test setup produced real content corruption.
2354 : : * The file size must stay the same, while the checksum must differ
2355 : : */
2356 : 2 : ASSERT(0 == stat(m_text(target_path),&file_stat_after_tamper));
2357 : 2 : ASSERT(file_stat_before.st_size == file_stat_after_tamper.st_size);
2358 : 2 : ASSERT(SUCCESS == compute_file_sha512_monocypher(m_text(target_path),file_sha512_after_tamper));
2359 : 2 : ASSERT(0 != memcmp(file_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2360 : 2 : ASSERT(0 != memcmp(db_sha512_before,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2361 : :
2362 : : /*
2363 : : * Run an update that ignores the protected subtree, restores one different
2364 : : * locked file through --include, and rehashes locked files.
2365 : : * The corrupted locked row is outside the restored include subset, but
2366 : : * --rehash-locked must still check it and report a checksum violation
2367 : : */
2368 : 2 : arguments = "--update --rehash-locked --lock-checksum=\"^path1/.*\" "
2369 : : "--ignore=\"^path1/.*\" --include=\"^path1/AAA/BCB/CCC/a\\.txt$\" "
2370 : : "--database=lock_s16.db tests/fixtures/diffs/diff1";
2371 : :
2372 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2373 : :
2374 : : /*
2375 : : * Verify the warning output against the real command-line scenario.
2376 : : * Ignored directories are reported, but locked files are still rehashed
2377 : : */
2378 : 2 : filename = "templates/0030_016_2.txt";
2379 : :
2380 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2381 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2382 : :
2383 : : /*
2384 : : * Re-read the same protected row after the warning run.
2385 : : * The row must keep its original metadata, offset, and SHA512 checksum
2386 : : */
2387 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
2388 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,db_sha512_after));
2389 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2390 : 2 : ASSERT(offset_before == offset_after);
2391 : 2 : ASSERT(0 == memcmp(db_sha512_before,db_sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2392 : 2 : ASSERT(0 != memcmp(db_sha512_after,file_sha512_after_tamper,(size_t)SHA512_DIGEST_LENGTH));
2393 : :
2394 : : /*
2395 : : * Remove the test database and restore the mutable fixture.
2396 : : * This returns the workspace to the state expected by the next test
2397 : : */
2398 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2399 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2400 : :
2401 : : /*
2402 : : * Release managed buffers allocated by this test before returning
2403 : : */
2404 : 2 : m_del(target_path);
2405 : 2 : m_del(pattern);
2406 : 2 : m_del(result);
2407 : :
2408 : 2 : RETURN_STATUS;
2409 : : }
2410 : :
2411 : : /**
2412 : : * @brief Verify that --ignore does not hide access denial for a checksum-locked file
2413 : : *
2414 : : * Covers README Examples 9 and 11
2415 : : *
2416 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
2417 : : * Then a test hook makes one locked file look access-denied during an update run
2418 : : * that also ignores the protected subtree and enables `--db-drop-inaccessible`.
2419 : : * The program must warn about the ignored locked file and keep the protected
2420 : : * database row unchanged
2421 : : */
2422 : 2 : static Return test0030_17(void)
2423 : : {
2424 : 2 : INITTEST;
2425 : :
2426 : : /*
2427 : : * Allocate managed buffers for captured application output, the expected
2428 : : * regular-expression template, and the resolved locked file path
2429 : : */
2430 : 2 : m_create(char,result,MEMORY_STRING);
2431 : 2 : m_create(char,pattern,MEMORY_STRING);
2432 : 2 : m_create(char,target_path,MEMORY_STRING);
2433 : :
2434 : : /*
2435 : : * Name the database, the access-denied locked row, and the matching fixture file.
2436 : : * The other locked rows prove that the whole lock set survives ignored cleanup
2437 : : */
2438 : 2 : const char *db_filename = "lock_s17.db";
2439 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2440 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2441 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
2442 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
2443 : :
2444 : : /*
2445 : : * Keep before-and-after snapshots of the access-denied locked row.
2446 : : * They prove that a warning result did not rewrite protected data
2447 : : */
2448 : 2 : CmpctStat db_stat_before = {0};
2449 : 2 : CmpctStat db_stat_after = {0};
2450 : 2 : sqlite3_int64 offset_before = 0;
2451 : 2 : sqlite3_int64 offset_after = 0;
2452 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
2453 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
2454 : 2 : bool row_exists = false;
2455 : :
2456 : : /*
2457 : : * Enable deterministic TESTING output so the captured application log
2458 : : * can be compared against a template
2459 : : */
2460 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2461 : :
2462 : : /*
2463 : : * Work on a mutable copy of the fixture tree.
2464 : : * The original fixture is restored during cleanup
2465 : : */
2466 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2467 : :
2468 : : /*
2469 : : * Create the baseline database.
2470 : : * The lock pattern protects every relative path under the path1/ subtree
2471 : : */
2472 : 2 : const char *arguments = "--database=lock_s17.db --progress "
2473 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2474 : :
2475 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2476 : :
2477 : : /*
2478 : : * Verify the baseline run output.
2479 : : * It must show a new database with path1/ files recorded as checksum-locked
2480 : : */
2481 : 2 : const char *filename = "templates/0030_017_1.txt";
2482 : :
2483 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2484 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2485 : :
2486 : : /*
2487 : : * Read the locked row before making access checks fail.
2488 : : * The same values must still be present after the warning run
2489 : : */
2490 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2491 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
2492 : :
2493 : : /*
2494 : : * Confirm the fixture file itself is present and readable.
2495 : : * This test covers access denial for an existing ignored locked file
2496 : : */
2497 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2498 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
2499 : :
2500 : : /*
2501 : : * Force access checks for exactly this locked file to return access denied.
2502 : : * This models a visible protected file that cannot be read during traversal
2503 : : */
2504 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
2505 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_DENIED"));
2506 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_DENIED));
2507 : :
2508 : : /*
2509 : : * Run an update that ignores the protected subtree and would normally drop
2510 : : * inaccessible records. The checksum lock must override both conditions
2511 : : */
2512 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
2513 : : "--ignore=\"^path1/.*\" --database=lock_s17.db tests/fixtures/diffs/diff1";
2514 : :
2515 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2516 : :
2517 : : /*
2518 : : * Disable the access-check hook before inspecting output and database state.
2519 : : * Later assertions should observe the real filesystem again
2520 : : */
2521 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
2522 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
2523 : :
2524 : : /*
2525 : : * Verify the warning output against the real command-line scenario.
2526 : : * The ignored locked file must still report checksum-locked access denial
2527 : : */
2528 : 2 : filename = "templates/0030_017_2.txt";
2529 : :
2530 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2531 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2532 : :
2533 : : /*
2534 : : * Re-read the access-denied locked row after the warning run.
2535 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
2536 : : */
2537 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
2538 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
2539 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2540 : 2 : ASSERT(offset_before == offset_after);
2541 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2542 : :
2543 : : /*
2544 : : * Confirm that the full locked path set remains in the database.
2545 : : * --ignore and --db-drop-inaccessible must not drop any checksum-locked row
2546 : : */
2547 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
2548 : 2 : ASSERT(row_exists == true);
2549 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
2550 : 2 : ASSERT(row_exists == true);
2551 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
2552 : 2 : ASSERT(row_exists == true);
2553 : :
2554 : : /*
2555 : : * Remove the test database and restore the mutable fixture.
2556 : : * This returns the workspace to the state expected by the next test
2557 : : */
2558 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2559 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2560 : :
2561 : : /*
2562 : : * Release managed buffers allocated by this test before returning
2563 : : */
2564 : 2 : m_del(target_path);
2565 : 2 : m_del(pattern);
2566 : 2 : m_del(result);
2567 : :
2568 : 2 : RETURN_STATUS;
2569 : : }
2570 : :
2571 : : /**
2572 : : * @brief Verify that --ignore does not hide access-check failure for a checksum-locked file
2573 : : *
2574 : : * Covers README Examples 9 and 11
2575 : : *
2576 : : * Creates a baseline database with the `path1/` subtree protected by `--lock-checksum`.
2577 : : * Then a test hook makes one locked file return an access-check failure during
2578 : : * an update run that also ignores the protected subtree and enables
2579 : : * `--db-drop-inaccessible`. The program must warn about the ignored locked file
2580 : : * and keep the protected database row unchanged
2581 : : */
2582 : 2 : static Return test0030_18(void)
2583 : : {
2584 : 2 : INITTEST;
2585 : :
2586 : : /*
2587 : : * Allocate managed buffers for captured application output, the expected
2588 : : * regular-expression template, and the resolved locked file path
2589 : : */
2590 : 2 : m_create(char,result,MEMORY_STRING);
2591 : 2 : m_create(char,pattern,MEMORY_STRING);
2592 : 2 : m_create(char,target_path,MEMORY_STRING);
2593 : :
2594 : : /*
2595 : : * Name the database, the locked row that will fail access checking, and the
2596 : : * matching fixture file. The other locked rows prove that the whole lock set survives ignored cleanup
2597 : : */
2598 : 2 : const char *db_filename = "lock_s18.db";
2599 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2600 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2601 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
2602 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
2603 : :
2604 : : /*
2605 : : * Keep before-and-after snapshots of the locked row that will fail access checking.
2606 : : * They prove that a warning result did not rewrite protected data
2607 : : */
2608 : 2 : CmpctStat db_stat_before = {0};
2609 : 2 : CmpctStat db_stat_after = {0};
2610 : 2 : sqlite3_int64 offset_before = 0;
2611 : 2 : sqlite3_int64 offset_after = 0;
2612 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
2613 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
2614 : 2 : bool row_exists = false;
2615 : :
2616 : : /*
2617 : : * Enable deterministic TESTING output so the captured application log
2618 : : * can be compared against a template
2619 : : */
2620 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2621 : :
2622 : : /*
2623 : : * Work on a mutable copy of the fixture tree.
2624 : : * The original fixture is restored during cleanup
2625 : : */
2626 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2627 : :
2628 : : /*
2629 : : * Create the baseline database.
2630 : : * The lock pattern protects every relative path under the path1/ subtree
2631 : : */
2632 : 2 : const char *arguments = "--database=lock_s18.db --progress "
2633 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2634 : :
2635 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2636 : :
2637 : : /*
2638 : : * Verify the baseline run output.
2639 : : * It must show a new database with path1/ files recorded as checksum-locked
2640 : : */
2641 : 2 : const char *filename = "templates/0030_018_1.txt";
2642 : :
2643 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2644 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2645 : :
2646 : : /*
2647 : : * Read the locked row before making access checks fail.
2648 : : * The same values must still be present after the warning run
2649 : : */
2650 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2651 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
2652 : :
2653 : : /*
2654 : : * Confirm the fixture file itself is present and readable.
2655 : : * This test covers an access-check failure for an existing ignored locked file
2656 : : */
2657 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2658 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
2659 : :
2660 : : /*
2661 : : * Force access checks for exactly this locked file to return an access-check failure.
2662 : : * This models an unexpected access-check problem during ignored locked traversal
2663 : : */
2664 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
2665 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_ERROR"));
2666 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ERROR));
2667 : :
2668 : : /*
2669 : : * Run an update that ignores the protected subtree and would normally drop
2670 : : * inaccessible records. The checksum lock must override both conditions
2671 : : */
2672 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
2673 : : "--ignore=\"^path1/.*\" --database=lock_s18.db tests/fixtures/diffs/diff1";
2674 : :
2675 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2676 : :
2677 : : /*
2678 : : * Disable the access-check hook before inspecting output and database state.
2679 : : * Later assertions should observe the real filesystem again
2680 : : */
2681 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
2682 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
2683 : :
2684 : : /*
2685 : : * Verify the warning output against the real command-line scenario.
2686 : : * The ignored locked file must still report checksum-locked access-check failure
2687 : : */
2688 : 2 : filename = "templates/0030_018_2.txt";
2689 : :
2690 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2691 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2692 : :
2693 : : /*
2694 : : * Re-read the access-check-failed locked row after the warning run.
2695 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
2696 : : */
2697 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
2698 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
2699 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2700 : 2 : ASSERT(offset_before == offset_after);
2701 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2702 : :
2703 : : /*
2704 : : * Confirm that the full locked path set remains in the database.
2705 : : * --ignore and --db-drop-inaccessible must not drop any checksum-locked row
2706 : : */
2707 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
2708 : 2 : ASSERT(row_exists == true);
2709 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
2710 : 2 : ASSERT(row_exists == true);
2711 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
2712 : 2 : ASSERT(row_exists == true);
2713 : :
2714 : : /*
2715 : : * Remove the test database and restore the mutable fixture.
2716 : : * This returns the workspace to the state expected by the next test
2717 : : */
2718 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2719 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2720 : :
2721 : : /*
2722 : : * Release managed buffers allocated by this test before returning
2723 : : */
2724 : 2 : m_del(target_path);
2725 : 2 : m_del(pattern);
2726 : 2 : m_del(result);
2727 : :
2728 : 2 : RETURN_STATUS;
2729 : : }
2730 : :
2731 : : /**
2732 : : * @brief Verify that cleanup keeps an access-denied checksum-locked row
2733 : : *
2734 : : * Covers README Examples 9 and 11
2735 : : *
2736 : : * Creates a baseline database with `path1/` protected by `--lock-checksum`.
2737 : : * Then the test makes one locked file unreadable and skips `file_list`, so
2738 : : * `db_delete_missing_metadata` must handle the unavailable DB row directly.
2739 : : * Even with `--db-drop-inaccessible`, the checksum lock must report a warning
2740 : : * and keep the protected database row unchanged
2741 : : */
2742 : 2 : static Return test0030_19(void)
2743 : : {
2744 : 2 : INITTEST;
2745 : :
2746 : : /*
2747 : : * Allocate managed buffers for captured application output, the expected
2748 : : * regular-expression template, and the resolved access-denied file path
2749 : : */
2750 : 2 : m_create(char,result,MEMORY_STRING);
2751 : 2 : m_create(char,pattern,MEMORY_STRING);
2752 : 2 : m_create(char,target_path,MEMORY_STRING);
2753 : :
2754 : : /*
2755 : : * Name the database, the protected database key, and the fixture file
2756 : : * whose read access will be denied during cleanup-only processing
2757 : : */
2758 : 2 : const char *db_filename = "lock_s19.db";
2759 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2760 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2761 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
2762 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
2763 : :
2764 : : /*
2765 : : * Keep database snapshots around the warning run.
2766 : : * They prove that cleanup reports the access denial but does not rewrite the locked row
2767 : : */
2768 : 2 : CmpctStat db_stat_before = {0};
2769 : 2 : CmpctStat db_stat_after = {0};
2770 : 2 : sqlite3_int64 offset_before = 0;
2771 : 2 : sqlite3_int64 offset_after = 0;
2772 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
2773 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
2774 : 2 : bool row_exists = false;
2775 : :
2776 : : /*
2777 : : * Enable deterministic TESTING output so the captured application log
2778 : : * can be compared against a template
2779 : : */
2780 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2781 : :
2782 : : /*
2783 : : * Work on a mutable copy of the fixture tree.
2784 : : * The original fixture is restored during cleanup
2785 : : */
2786 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2787 : :
2788 : : /*
2789 : : * Create the baseline database.
2790 : : * The lock pattern protects every relative path under the path1/ subtree
2791 : : */
2792 : 2 : const char *arguments = "--database=lock_s19.db --progress "
2793 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2794 : :
2795 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2796 : :
2797 : : /*
2798 : : * Verify the baseline run output.
2799 : : * It must show a new database with path1/ files recorded as checksum-locked
2800 : : */
2801 : 2 : const char *filename = "templates/0030_019_1.txt";
2802 : :
2803 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2804 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2805 : :
2806 : : /*
2807 : : * Read the locked row before making its file unreadable.
2808 : : * The same values must still be present after cleanup reports the warning
2809 : : */
2810 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2811 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
2812 : :
2813 : : /*
2814 : : * Confirm the fixture file exists and is readable, then deny read access.
2815 : : * This makes the later cleanup pass handle a real access-denied DB row
2816 : : */
2817 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2818 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
2819 : 2 : ASSERT(SUCCESS == change_mode(locked_file_path,0000));
2820 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_DENIED));
2821 : :
2822 : : /*
2823 : : * Skip both file_list passes.
2824 : : * This forces db_delete_missing_metadata to be the code path that checks the locked DB row
2825 : : */
2826 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST","1"));
2827 : :
2828 : : /*
2829 : : * Run an update that would normally drop inaccessible records.
2830 : : * The checksum lock must override that cleanup and turn the condition into a warning
2831 : : */
2832 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
2833 : : "--database=lock_s19.db tests/fixtures/diffs/diff1";
2834 : :
2835 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2836 : :
2837 : : /*
2838 : : * Re-enable file_list and restore file permissions before inspecting state.
2839 : : * The captured output still reflects the access-denied cleanup run above
2840 : : */
2841 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST",""));
2842 : 2 : ASSERT(SUCCESS == change_mode(locked_file_path,0644));
2843 : :
2844 : : /*
2845 : : * Verify the warning output against the real command-line scenario.
2846 : : * With file_list bypassed, db_delete_missing_metadata must report access denied
2847 : : */
2848 : 2 : filename = "templates/0030_019_2.txt";
2849 : :
2850 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2851 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2852 : :
2853 : : /*
2854 : : * Re-read the access-denied locked row after the warning run.
2855 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
2856 : : */
2857 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
2858 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
2859 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
2860 : 2 : ASSERT(offset_before == offset_after);
2861 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
2862 : :
2863 : : /*
2864 : : * Confirm that the full locked path set remains in the database.
2865 : : * --db-drop-inaccessible must not drop any checksum-locked row during cleanup
2866 : : */
2867 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
2868 : 2 : ASSERT(row_exists == true);
2869 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
2870 : 2 : ASSERT(row_exists == true);
2871 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
2872 : 2 : ASSERT(row_exists == true);
2873 : :
2874 : : /*
2875 : : * Remove the test database and restore the mutable fixture.
2876 : : * This returns the workspace to the state expected by the next test
2877 : : */
2878 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
2879 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
2880 : :
2881 : : /*
2882 : : * Release managed buffers allocated by this test before returning
2883 : : */
2884 : 2 : m_del(target_path);
2885 : 2 : m_del(pattern);
2886 : 2 : m_del(result);
2887 : :
2888 : 2 : RETURN_STATUS;
2889 : : }
2890 : :
2891 : : /**
2892 : : * @brief Verify that cleanup keeps a checksum-locked row after an access-check failure
2893 : : *
2894 : : * Covers README Examples 9 and 11
2895 : : *
2896 : : * Creates a baseline database with `path1/` protected by `--lock-checksum`.
2897 : : * Then the test makes access checking fail for one locked file and skips
2898 : : * `file_list`, so `db_delete_missing_metadata` must handle the unavailable DB
2899 : : * row directly. Even with `--db-drop-inaccessible`, the checksum lock must
2900 : : * report a warning and keep the protected database row unchanged
2901 : : */
2902 : 2 : static Return test0030_20(void)
2903 : : {
2904 : 2 : INITTEST;
2905 : :
2906 : : /*
2907 : : * Allocate managed buffers for captured application output, the expected
2908 : : * regular-expression template, and the resolved access-failed file path
2909 : : */
2910 : 2 : m_create(char,result,MEMORY_STRING);
2911 : 2 : m_create(char,pattern,MEMORY_STRING);
2912 : 2 : m_create(char,target_path,MEMORY_STRING);
2913 : :
2914 : : /*
2915 : : * Name the database, the protected database key, and the fixture file
2916 : : * whose access check will fail during cleanup-only processing
2917 : : */
2918 : 2 : const char *db_filename = "lock_s20.db";
2919 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
2920 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
2921 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
2922 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
2923 : :
2924 : : /*
2925 : : * Keep database snapshots around the warning run.
2926 : : * They prove that cleanup reports the access-check failure but does not rewrite the locked row
2927 : : */
2928 : 2 : CmpctStat db_stat_before = {0};
2929 : 2 : CmpctStat db_stat_after = {0};
2930 : 2 : sqlite3_int64 offset_before = 0;
2931 : 2 : sqlite3_int64 offset_after = 0;
2932 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
2933 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
2934 : 2 : bool row_exists = false;
2935 : :
2936 : : /*
2937 : : * Enable deterministic TESTING output so the captured application log
2938 : : * can be compared against a template
2939 : : */
2940 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
2941 : :
2942 : : /*
2943 : : * Work on a mutable copy of the fixture tree.
2944 : : * The original fixture is restored during cleanup
2945 : : */
2946 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
2947 : :
2948 : : /*
2949 : : * Create the baseline database.
2950 : : * The lock pattern protects every relative path under the path1/ subtree
2951 : : */
2952 : 2 : const char *arguments = "--database=lock_s20.db --progress "
2953 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
2954 : :
2955 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
2956 : :
2957 : : /*
2958 : : * Verify the baseline run output.
2959 : : * It must show a new database with path1/ files recorded as checksum-locked
2960 : : */
2961 : 2 : const char *filename = "templates/0030_020_1.txt";
2962 : :
2963 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
2964 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
2965 : :
2966 : : /*
2967 : : * Read the locked row before making its access check fail.
2968 : : * The same values must still be present after cleanup reports the warning
2969 : : */
2970 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
2971 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
2972 : :
2973 : : /*
2974 : : * Confirm the fixture file itself is present and readable.
2975 : : * This test covers an access-check failure for an existing DB row
2976 : : */
2977 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
2978 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
2979 : :
2980 : : /*
2981 : : * Skip both file_list passes and force access checks for exactly this
2982 : : * locked file to return an access-check failure
2983 : : */
2984 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST","1"));
2985 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
2986 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_ERROR"));
2987 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ERROR));
2988 : :
2989 : : /*
2990 : : * Run an update that would normally drop inaccessible records.
2991 : : * The checksum lock must override that cleanup and turn the condition into a warning
2992 : : */
2993 : 2 : arguments = "--update --db-drop-inaccessible --lock-checksum=\"^path1/.*\" "
2994 : : "--database=lock_s20.db tests/fixtures/diffs/diff1";
2995 : :
2996 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
2997 : :
2998 : : /*
2999 : : * Disable test hooks before inspecting output and database state.
3000 : : * Later assertions should observe the real filesystem again
3001 : : */
3002 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST",""));
3003 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
3004 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
3005 : :
3006 : : /*
3007 : : * Verify the warning output against the real command-line scenario.
3008 : : * With file_list bypassed, db_delete_missing_metadata must report access-check failure
3009 : : */
3010 : 2 : filename = "templates/0030_020_2.txt";
3011 : :
3012 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
3013 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
3014 : :
3015 : : /*
3016 : : * Re-read the access-check-failed locked row after the warning run.
3017 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
3018 : : */
3019 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
3020 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
3021 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
3022 : 2 : ASSERT(offset_before == offset_after);
3023 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
3024 : :
3025 : : /*
3026 : : * Confirm that the full locked path set remains in the database.
3027 : : * --db-drop-inaccessible must not drop any checksum-locked row during cleanup
3028 : : */
3029 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
3030 : 2 : ASSERT(row_exists == true);
3031 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
3032 : 2 : ASSERT(row_exists == true);
3033 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
3034 : 2 : ASSERT(row_exists == true);
3035 : :
3036 : : /*
3037 : : * Remove the test database and restore the mutable fixture.
3038 : : * This returns the workspace to the state expected by the next test
3039 : : */
3040 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
3041 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
3042 : :
3043 : : /*
3044 : : * Release managed buffers allocated by this test before returning
3045 : : */
3046 : 2 : m_del(target_path);
3047 : 2 : m_del(pattern);
3048 : 2 : m_del(result);
3049 : :
3050 : 2 : RETURN_STATUS;
3051 : : }
3052 : :
3053 : : /**
3054 : : * @brief Verify that cleanup keeps an ignored checksum-locked row after an access-check failure
3055 : : *
3056 : : * Covers README Examples 9 and 11
3057 : : *
3058 : : * Creates a baseline database with `path1/` protected by `--lock-checksum`.
3059 : : * Then the test makes access checking fail for one locked file and skips
3060 : : * `file_list`, so `db_delete_missing_metadata` must handle an ignored DB row
3061 : : * directly. Even with `--ignore` and `--db-drop-ignored`, the checksum lock
3062 : : * must report a warning and keep the protected database row unchanged
3063 : : */
3064 : 2 : static Return test0030_21(void)
3065 : : {
3066 : 2 : INITTEST;
3067 : :
3068 : : /*
3069 : : * Allocate managed buffers for captured application output, the expected
3070 : : * regular-expression template, and the resolved access-failed file path
3071 : : */
3072 : 2 : m_create(char,result,MEMORY_STRING);
3073 : 2 : m_create(char,pattern,MEMORY_STRING);
3074 : 2 : m_create(char,target_path,MEMORY_STRING);
3075 : :
3076 : : /*
3077 : : * Name the database, the protected database key, and the fixture file
3078 : : * whose access check will fail during ignored cleanup-only processing
3079 : : */
3080 : 2 : const char *db_filename = "lock_s21.db";
3081 : 2 : const char *locked_relative_path = "path1/AAA/ZAW/A/b/c/a_file.txt";
3082 : 2 : const char *locked_file_path = "tests/fixtures/diffs/diff1/path1/AAA/ZAW/A/b/c/a_file.txt";
3083 : 2 : const char *other_locked_relative_path_1 = "path1/AAA/ZAW/D/e/f/b_file.txt";
3084 : 2 : const char *other_locked_relative_path_2 = "path1/AAA/BCB/CCC/a.txt";
3085 : :
3086 : : /*
3087 : : * Keep database snapshots around the warning run.
3088 : : * They prove that ignored cleanup reports the access-check failure but does not rewrite the locked row
3089 : : */
3090 : 2 : CmpctStat db_stat_before = {0};
3091 : 2 : CmpctStat db_stat_after = {0};
3092 : 2 : sqlite3_int64 offset_before = 0;
3093 : 2 : sqlite3_int64 offset_after = 0;
3094 : 2 : unsigned char sha512_before[SHA512_DIGEST_LENGTH] = {0};
3095 : 2 : unsigned char sha512_after[SHA512_DIGEST_LENGTH] = {0};
3096 : 2 : bool row_exists = false;
3097 : :
3098 : : /*
3099 : : * Enable deterministic TESTING output so the captured application log
3100 : : * can be compared against a template
3101 : : */
3102 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
3103 : :
3104 : : /*
3105 : : * Work on a mutable copy of the fixture tree.
3106 : : * The original fixture is restored during cleanup
3107 : : */
3108 : 2 : ASSERT(SUCCESS == prepare_mutable_fixture("tests/fixtures/diffs/diff1"));
3109 : :
3110 : : /*
3111 : : * Create the baseline database.
3112 : : * The lock pattern protects every relative path under the path1/ subtree
3113 : : */
3114 : 2 : const char *arguments = "--database=lock_s21.db --progress "
3115 : : "--lock-checksum=\"^path1/.*\" tests/fixtures/diffs/diff1";
3116 : :
3117 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
3118 : :
3119 : : /*
3120 : : * Verify the baseline run output.
3121 : : * It must show a new database with path1/ files recorded as checksum-locked
3122 : : */
3123 : 2 : const char *filename = "templates/0030_021_1.txt";
3124 : :
3125 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
3126 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
3127 : :
3128 : : /*
3129 : : * Read the locked row before making its access check fail.
3130 : : * The same values must still be present after ignored cleanup reports the warning
3131 : : */
3132 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_before));
3133 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_before,sha512_before));
3134 : :
3135 : : /*
3136 : : * Confirm the fixture file itself is present and readable.
3137 : : * This test covers an access-check failure for an existing ignored DB row
3138 : : */
3139 : 2 : ASSERT(SUCCESS == construct_path(locked_file_path,target_path));
3140 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ALLOWED));
3141 : :
3142 : : /*
3143 : : * Skip both file_list passes and force access checks for exactly this
3144 : : * locked file to return an access-check failure
3145 : : */
3146 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST","1"));
3147 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",locked_relative_path));
3148 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS","FILE_ACCESS_ERROR"));
3149 : 2 : ASSERT(SUCCESS == expect_file_access_from_root(test0030_fixture_root,locked_relative_path,R_OK,FILE_ACCESS_ERROR));
3150 : :
3151 : : /*
3152 : : * Run an update that would normally drop ignored records.
3153 : : * The checksum lock must override that cleanup and turn the condition into a warning
3154 : : */
3155 : 2 : arguments = "--update --lock-checksum=\"^path1/.*\" "
3156 : : "--ignore=\"^path1/.*\" --db-drop-ignored "
3157 : : "--database=lock_s21.db tests/fixtures/diffs/diff1";
3158 : :
3159 : 2 : ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
3160 : :
3161 : : /*
3162 : : * Disable test hooks before inspecting output and database state.
3163 : : * Later assertions should observe the real filesystem again
3164 : : */
3165 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_SKIP_FILE_LIST",""));
3166 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_SUFFIX",""));
3167 : 2 : ASSERT(SUCCESS == set_environment_variable("TESTITALL_TEST_ENV_FILE_ACCESS_STATUS",""));
3168 : :
3169 : : /*
3170 : : * Verify the warning output against the real command-line scenario.
3171 : : * With file_list bypassed, ignored cleanup must report access-check failure
3172 : : */
3173 : 2 : filename = "templates/0030_021_2.txt";
3174 : :
3175 : 2 : ASSERT(SUCCESS == get_file_content(filename,pattern));
3176 : 2 : ASSERT(SUCCESS == match_pattern(result,pattern,filename));
3177 : :
3178 : : /*
3179 : : * Re-read the ignored access-check-failed locked row after the warning run.
3180 : : * Its metadata, SHA512 offset, and SHA512 digest must stay unchanged
3181 : : */
3182 : 2 : ASSERT(SUCCESS == db_read_cmpctstat_by_relative_path(db_filename,locked_relative_path,&db_stat_after));
3183 : 2 : ASSERT(SUCCESS == read_final_sha512_from_db(db_filename,locked_relative_path,&offset_after,sha512_after));
3184 : 2 : ASSERT(0 == memcmp(&db_stat_before,&db_stat_after,sizeof(CmpctStat)));
3185 : 2 : ASSERT(offset_before == offset_after);
3186 : 2 : ASSERT(0 == memcmp(sha512_before,sha512_after,(size_t)SHA512_DIGEST_LENGTH));
3187 : :
3188 : : /*
3189 : : * Confirm that the full locked path set remains in the database.
3190 : : * --db-drop-ignored must not drop any checksum-locked row during cleanup
3191 : : */
3192 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,locked_relative_path,&row_exists));
3193 : 2 : ASSERT(row_exists == true);
3194 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_1,&row_exists));
3195 : 2 : ASSERT(row_exists == true);
3196 : 2 : ASSERT(SUCCESS == db_relative_path_exists(db_filename,other_locked_relative_path_2,&row_exists));
3197 : 2 : ASSERT(row_exists == true);
3198 : :
3199 : : /*
3200 : : * Remove the test database and restore the mutable fixture.
3201 : : * This returns the workspace to the state expected by the next test
3202 : : */
3203 : 2 : ASSERT(SUCCESS == delete_path(db_filename));
3204 : 2 : ASSERT(SUCCESS == restore_mutable_fixture("tests/fixtures/diffs/diff1"));
3205 : :
3206 : : /*
3207 : : * Release managed buffers allocated by this test before returning
3208 : : */
3209 : 2 : m_del(target_path);
3210 : 2 : m_del(pattern);
3211 : 2 : m_del(result);
3212 : :
3213 : 2 : RETURN_STATUS;
3214 : : }
3215 : :
3216 : : /**
3217 : : * @brief Run the README lock-checksum scenario tests
3218 : : *
3219 : : * Covers README Examples 9 and 10
3220 : : *
3221 : : * Exercises protected checksum paths, locked-file rehashing, timestamp
3222 : : * watching, and interactions with cleanup options for inaccessible or ignored files
3223 : : */
3224 : 2 : Return test0030(void)
3225 : : {
3226 : 2 : INITTEST;
3227 : :
3228 : 2 : SLOWTEST;
3229 : :
3230 : 2 : TEST(test0030_1,"Locked size change warns and keeps the protected DB row unchanged");
3231 : 2 : TEST(test0030_2,"Watched locked timestamp drift warns and keeps the protected DB row unchanged");
3232 : 2 : TEST(test0030_3,"Unwatched locked timestamp drift is ignored and keeps the protected DB row unchanged");
3233 : 2 : TEST(test0030_4,"--rehash-locked synchronizes watched timestamp drift after checksum match");
3234 : 2 : TEST(test0030_5,"--rehash-locked synchronizes timestamp drift even without --watch-timestamps");
3235 : 2 : TEST(test0030_6,"Watched rehash reports a stored locked-checksum mismatch");
3236 : 2 : TEST(test0030_7,"--rehash-locked reports locked content corruption and keeps the DB row unchanged");
3237 : 2 : TEST(test0030_8,"Watched --rehash-locked reports locked content corruption and keeps the DB row unchanged");
3238 : 2 : TEST(test0030_9,"Watched unchanged locked files complete successfully without DB drift");
3239 : 2 : TEST(test0030_10,"Deleted locked file warns and keeps the protected DB row unchanged");
3240 : 2 : TEST(test0030_11,"--db-drop-inaccessible does not drop an access-denied locked file");
3241 : 2 : TEST(test0030_12,"--db-drop-inaccessible does not drop a locked file after access-check failure");
3242 : 2 : TEST(test0030_13,"--db-drop-ignored does not drop a deleted checksum-locked file");
3243 : 2 : TEST(test0030_14,"--rehash-locked still detects corruption in an ignored locked file");
3244 : 2 : TEST(test0030_15,"--include does not let --db-drop-ignored drop a deleted locked file");
3245 : 2 : TEST(test0030_16,"--rehash-locked checks ignored locked corruption outside the restored include subset");
3246 : 2 : TEST(test0030_17,"--ignore does not let --db-drop-inaccessible drop an access-denied locked file");
3247 : 2 : TEST(test0030_18,"--ignore does not let --db-drop-inaccessible drop a locked file after access-check failure");
3248 : 2 : TEST(test0030_19,"Cleanup preserves an access-denied checksum-locked row despite --db-drop-inaccessible");
3249 : 2 : TEST(test0030_20,"Cleanup preserves a checksum-locked row after access-check failure");
3250 : 2 : TEST(test0030_21,"Cleanup preserves an ignored checksum-locked row after access-check failure");
3251 : :
3252 : 2 : RETURN_STATUS;
3253 : : }
|