Branch data Line data Source code
1 : : #include "sute.h"
2 : : #include "monocypher-ed25519.h"
3 : : #include <errno.h>
4 : : #include <fcntl.h>
5 : : #include <ftw.h>
6 : : #include <stdlib.h>
7 : :
8 : : static const char *copy_source_root_for_nftw = NULL;
9 : : static const char *copy_destination_root_for_nftw = NULL;
10 : : static size_t copy_source_root_length_for_nftw = 0U;
11 : : static size_t copy_destination_root_length_for_nftw = 0U;
12 : :
13 : : /**
14 : : * @brief Reset nftw copy context to an empty state
15 : : */
16 : 178 : static void reset_nftw_copy_context(void)
17 : : {
18 : 178 : copy_source_root_for_nftw = NULL;
19 : 178 : copy_destination_root_for_nftw = NULL;
20 : 178 : copy_source_root_length_for_nftw = 0U;
21 : 178 : copy_destination_root_length_for_nftw = 0U;
22 : 178 : }
23 : :
24 : : /**
25 : : * @brief Open a writable file stream with explicit create mode 0600
26 : : *
27 : : * Supports only `"ab"` and `"wb"` modes
28 : : *
29 : : * @param[in] file_path Managed path string passed directly to `open()`
30 : : * @param[in] stream_open_mode Mode string for `fdopen()`
31 : : * @param[out] opened_file_stream_out Output writable stream
32 : : *
33 : : * @return Return status code
34 : : */
35 : 69 : Return open_file_stream(
36 : : const memory *file_path,
37 : : const char *stream_open_mode,
38 : : FILE **opened_file_stream_out)
39 : : {
40 : : /* Status returned by this function through provide()
41 : : Default value assumes successful completion */
42 : 69 : Return status = SUCCESS;
43 : 69 : int writable_file_descriptor = -1;
44 : 69 : int file_open_flags = 0;
45 : 69 : const char *file_path_string = NULL;
46 : :
47 [ + - + - : 69 : if(file_path == NULL || stream_open_mode == NULL || opened_file_stream_out == NULL)
- + ]
48 : : {
49 : 0 : status = FAILURE;
50 : : }
51 : :
52 : 69 : IF(opened_file_stream_out != NULL)
53 : : {
54 : 69 : *opened_file_stream_out = NULL;
55 : : }
56 : :
57 [ + - ]: 69 : if(SUCCESS == status)
58 : : {
59 : 69 : file_path_string = m_text(file_path);
60 : : }
61 : :
62 [ + - ]: 69 : if(SUCCESS == status)
63 : : {
64 [ + + ]: 69 : if(strcmp(stream_open_mode,"ab") == 0)
65 : : {
66 : 36 : file_open_flags = O_WRONLY | O_CREAT | O_APPEND;
67 [ + - ]: 33 : } else if(strcmp(stream_open_mode,"wb") == 0){
68 : 33 : file_open_flags = O_WRONLY | O_CREAT | O_TRUNC;
69 : : } else {
70 : 0 : status = FAILURE;
71 : : }
72 : : }
73 : :
74 [ + - ]: 69 : if(SUCCESS == status)
75 : : {
76 : 69 : writable_file_descriptor = open(file_path_string,file_open_flags,0600);
77 : :
78 [ - + ]: 69 : if(writable_file_descriptor < 0)
79 : : {
80 : 0 : status = FAILURE;
81 : : }
82 : : }
83 : :
84 [ + - ]: 69 : if(SUCCESS == status)
85 : : {
86 : 69 : *opened_file_stream_out = fdopen(writable_file_descriptor,stream_open_mode);
87 : :
88 [ - + ]: 69 : if(*opened_file_stream_out == NULL)
89 : : {
90 : 0 : (void)close(writable_file_descriptor);
91 : 0 : status = FAILURE;
92 : : }
93 : : }
94 : :
95 : 69 : return(status);
96 : : }
97 : :
98 : : /**
99 : : * @brief Verify access to a file path relative to a test root under TMPDIR
100 : : *
101 : : * Builds the absolute root path from TMPDIR, opens that root once, and checks
102 : : * @p relative_path through file_check_access(). This mirrors the application's
103 : : * current traversal access model while keeping tests compact
104 : : *
105 : : * @param[in] root_path_to_tmpdir Root directory path relative to TMPDIR
106 : : * @param[in] relative_path Path to check relative to the opened root
107 : : * @param[in] mode Access mode passed to file_check_access()
108 : : * @param[in] expected_status Required access-check result
109 : : *
110 : : * @return SUCCESS when the access check matches @p expected_status, otherwise FAILURE
111 : : */
112 : 44 : Return expect_file_access_from_root(
113 : : const char *root_path_to_tmpdir,
114 : : const char *relative_path,
115 : : const int mode,
116 : : FileAccessStatus expected_status)
117 : : {
118 : : /* Status returned by this function through provide()
119 : : Default value assumes successful completion */
120 : 44 : Return status = SUCCESS;
121 : 44 : int root_directory_fd = -1;
122 : :
123 : 44 : m_create(char,root_path,MEMORY_STRING);
124 : 44 : m_create(char,relative_path_memory,MEMORY_STRING);
125 : :
126 [ + - - + ]: 44 : if(root_path_to_tmpdir == NULL || relative_path == NULL)
127 : : {
128 : 0 : status = FAILURE;
129 : : }
130 : :
131 [ + - ]: 44 : if(SUCCESS == status)
132 : : {
133 : 44 : status = construct_path(root_path_to_tmpdir,root_path);
134 : : }
135 : :
136 [ + - ]: 44 : if(SUCCESS == status)
137 : : {
138 : 44 : status = m_copy_string(relative_path_memory,strlen(relative_path) + 1U,relative_path);
139 : : }
140 : :
141 [ + - ]: 44 : if(SUCCESS == status)
142 : : {
143 [ - + ]: 44 : if(directory_open(root_path,&root_directory_fd) != FILE_ACCESS_ALLOWED)
144 : : {
145 : 0 : status = FAILURE;
146 : : }
147 : : }
148 : :
149 [ + - ]: 44 : if(SUCCESS == status)
150 : : {
151 : 44 : const FileAccessStatus access_status = file_check_access(
152 : : root_directory_fd,
153 : : relative_path_memory,
154 : : mode);
155 : :
156 [ - + ]: 44 : if(access_status != expected_status)
157 : : {
158 : 0 : status = FAILURE;
159 : : }
160 : : }
161 : :
162 [ + - ]: 44 : if(root_directory_fd >= 0)
163 : : {
164 [ - + ]: 44 : if(close(root_directory_fd) != 0)
165 : : {
166 : 0 : status = FAILURE;
167 : : }
168 : : }
169 : :
170 : 44 : m_del(relative_path_memory);
171 : 44 : m_del(root_path);
172 : :
173 : 44 : return(status);
174 : : }
175 : :
176 : : /**
177 : : * @brief Apply source mtime to destination path using utimensat
178 : : *
179 : : * @param[in] destination_path Destination filesystem path
180 : : * @param[in] source_stat Source stat metadata
181 : : * @param[in] utimensat_flags Flags forwarded to utimensat
182 : : *
183 : : * @return 0 on success, non-zero on failure
184 : : */
185 : 7364 : static int apply_mtime_from_source_stat(
186 : : const char *destination_path,
187 : : const struct stat *source_stat,
188 : : const int utimensat_flags)
189 : : {
190 : 7364 : int status = 0;
191 : :
192 [ + - - + ]: 7364 : if(destination_path == NULL || source_stat == NULL)
193 : : {
194 : 0 : status = -1;
195 : : }
196 : :
197 [ + - ]: 7364 : if(status == 0)
198 : : {
199 : 7364 : struct timespec times[2] = {0};
200 : :
201 : 7364 : times[0].tv_nsec = UTIME_OMIT;
202 : 7364 : times[1] = source_stat->st_mtim;
203 : :
204 [ - + ]: 7364 : if(utimensat(0,destination_path,times,utimensat_flags) != 0)
205 : : {
206 : 0 : status = -1;
207 : : }
208 : : }
209 : :
210 : 7364 : return(status);
211 : : }
212 : :
213 : : /**
214 : : * @brief Remove callback used by nftw
215 : : *
216 : : * @param[in] path Current path from nftw walk
217 : : * @param[in] stat_buffer Unused file stat pointer from nftw
218 : : * @param[in] type_flag Unused type flag from nftw
219 : : * @param[in] ftw_buffer Unused nftw frame data
220 : : *
221 : : * @return 0 on success, non-zero on failure
222 : : */
223 : 7334 : static int nftw_remove_callback(
224 : : const char *path,
225 : : const struct stat *stat_buffer,
226 : : const int type_flag,
227 : : struct FTW *ftw_buffer)
228 : : {
229 : : (void)stat_buffer;
230 : : (void)type_flag;
231 : : (void)ftw_buffer;
232 : :
233 [ + + ]: 7334 : if(remove(path) != 0)
234 : : {
235 : 1 : echo(STDERR,"delete_path: remove failed for %s: %s\n",path,strerror(errno));
236 : 1 : return(-1);
237 : : }
238 : :
239 : 7333 : return(0);
240 : : }
241 : :
242 : : /**
243 : : * @brief Build destination path for nftw copy callback
244 : : *
245 : : * @param[in] source_path Current source path from nftw
246 : : * @param[out] destination_path_out Destination path descriptor
247 : : *
248 : : * @return 0 on success, non-zero on failure
249 : : */
250 : 12707 : static int build_copy_destination_path_for_nftw(
251 : : const char *source_path,
252 : : memory *destination_path_out)
253 : : {
254 : 12707 : int status = 0;
255 : :
256 [ + - ]: 12707 : if(source_path == NULL
257 [ + - ]: 12707 : || destination_path_out == NULL
258 [ + - ]: 12707 : || copy_source_root_for_nftw == NULL
259 [ - + ]: 12707 : || copy_destination_root_for_nftw == NULL)
260 : : {
261 : 0 : status = -1;
262 : : }
263 : :
264 [ + - - + ]: 12707 : if(status == 0 && strncmp(source_path,
265 : : copy_source_root_for_nftw,
266 : : copy_source_root_length_for_nftw) != 0)
267 : : {
268 : 0 : status = -1;
269 : : }
270 : :
271 [ + - ]: 12707 : if(status == 0)
272 : : {
273 : 12707 : const char *relative_path = source_path + copy_source_root_length_for_nftw;
274 : :
275 [ + + ]: 12707 : if(relative_path[0] == '/')
276 : : {
277 : 12467 : relative_path++;
278 : : }
279 : :
280 [ - + ]: 12707 : if(m_copy_string(destination_path_out,
281 : : copy_destination_root_length_for_nftw + 1U,
282 : : copy_destination_root_for_nftw) != SUCCESS)
283 : : {
284 : 0 : status = -1;
285 : : }
286 : :
287 [ + - + + ]: 12707 : if(status == 0 && relative_path[0] != '\0')
288 : : {
289 [ - + ]: 12467 : if(m_concat_literal(destination_path_out,"/") != SUCCESS)
290 : : {
291 : 0 : status = -1;
292 : : }
293 : : }
294 : :
295 [ + - + + ]: 12707 : if(status == 0 && relative_path[0] != '\0')
296 : : {
297 [ - + ]: 12467 : if(m_concat_string(destination_path_out,relative_path) != SUCCESS)
298 : : {
299 : 0 : status = -1;
300 : : }
301 : : }
302 : : }
303 : :
304 : 12707 : return(status);
305 : : }
306 : :
307 : : /**
308 : : * @brief Create one directory and accept an existing path that resolves to a directory
309 : : *
310 : : * Existing symlinks to directories are treated as valid path components
311 : : *
312 : : * @param[in] directory_path Absolute directory path
313 : : *
314 : : * @return 0 on success, non-zero on failure
315 : : */
316 : 694 : static int create_directory_if_missing(const char *directory_path)
317 : : {
318 : 694 : int status = 0;
319 : 694 : struct stat directory_stat = {0};
320 : :
321 [ - + ]: 694 : if(directory_path == NULL)
322 : : {
323 : 0 : status = -1;
324 : : }
325 : :
326 [ + - + + : 694 : if(status == 0 && mkdir(directory_path,0700) != 0 && errno != EEXIST)
- + ]
327 : : {
328 : 0 : status = -1;
329 : : }
330 : :
331 [ + - - + ]: 694 : if(status == 0 && stat(directory_path,&directory_stat) != 0)
332 : : {
333 : 0 : status = -1;
334 : : }
335 : :
336 [ + - + + ]: 694 : if(status == 0 && !S_ISDIR(directory_stat.st_mode))
337 : : {
338 : 1 : status = -1;
339 : : }
340 : :
341 : 694 : return(status);
342 : : }
343 : :
344 : : /**
345 : : * @brief Construct absolute path from environment root and relative child path
346 : : *
347 : : * @param[in] environment_variable_name Environment variable containing root path
348 : : * @param[in] relative_path Relative child path to append
349 : : * @param[out] absolute_path_out Output absolute path buffer
350 : : *
351 : : * @return Return status code
352 : : */
353 : 24 : static Return construct_path_from_environment_variable(
354 : : const char *environment_variable_name,
355 : : const char *relative_path,
356 : : memory *absolute_path_out)
357 : : {
358 : : /* Status returned by this function through provide()
359 : : Default value assumes successful completion */
360 : 24 : Return status = SUCCESS;
361 : 24 : const char *root_path = NULL;
362 : 24 : size_t absolute_path_size = 0U;
363 : :
364 [ + - + - : 24 : if(environment_variable_name == NULL || relative_path == NULL || absolute_path_out == NULL)
- + ]
365 : : {
366 : 0 : status = FAILURE;
367 : : }
368 : :
369 [ + - ]: 24 : if(SUCCESS == status)
370 : : {
371 : 24 : root_path = getenv(environment_variable_name);
372 : :
373 [ - + ]: 24 : if(root_path == NULL)
374 : : {
375 : 0 : status = FAILURE;
376 : : }
377 : : }
378 : :
379 [ + - ]: 24 : if(SUCCESS == status)
380 : : {
381 : 24 : absolute_path_size = strlen(root_path) + strlen(relative_path) + 2U;
382 : 24 : status = m_resize(absolute_path_out,absolute_path_size);
383 : : }
384 : :
385 [ + - ]: 24 : if(SUCCESS == status)
386 : : {
387 : 24 : char *absolute_path_data = m_data(char,absolute_path_out);
388 : :
389 [ - + ]: 24 : if(absolute_path_data == NULL)
390 : : {
391 : 0 : status = FAILURE;
392 : : } else {
393 : 24 : int print_result = snprintf(absolute_path_data,absolute_path_size,"%s/%s",root_path,relative_path);
394 : :
395 [ - + ]: 24 : if(print_result < 0)
396 : : {
397 : 0 : status = FAILURE;
398 : : } else {
399 : 24 : status = m_finalize_string(absolute_path_out,(size_t)print_result);
400 : : }
401 : : }
402 : : }
403 : :
404 : 24 : return(status);
405 : : }
406 : :
407 : : /**
408 : : * @brief Copy regular file contents into destination path
409 : : *
410 : : * @param[in] source_path Source regular file path
411 : : * @param[in] destination_path Destination regular file path
412 : : * @param[in] destination_mode Mode for destination file
413 : : *
414 : : * @return 0 on success, non-zero on failure
415 : : */
416 : 1857 : static int copy_regular_file_contents(
417 : : const char *source_path,
418 : : const char *destination_path,
419 : : const mode_t destination_mode)
420 : : {
421 : 1857 : int status = 0;
422 : 1857 : int source_fd = -1;
423 : 1857 : int destination_fd = -1;
424 : : unsigned char buffer[65536];
425 : :
426 [ + - - + ]: 1857 : if(source_path == NULL || destination_path == NULL)
427 : : {
428 : 0 : status = -1;
429 : : }
430 : :
431 [ + - ]: 1857 : if(status == 0)
432 : : {
433 : 1857 : source_fd = open(source_path,O_RDONLY);
434 : :
435 [ - + ]: 1857 : if(source_fd < 0)
436 : : {
437 : 0 : status = -1;
438 : : }
439 : : }
440 : :
441 [ + - ]: 1857 : if(status == 0)
442 : : {
443 : 1857 : destination_fd = open(destination_path,
444 : : O_WRONLY | O_CREAT | O_TRUNC,
445 : : destination_mode & (mode_t)07777);
446 : :
447 [ - + ]: 1857 : if(destination_fd < 0)
448 : : {
449 : 0 : status = -1;
450 : : }
451 : : }
452 : :
453 [ + - - + ]: 1857 : if(status == 0 && fchmod(destination_fd,destination_mode & (mode_t)07777) != 0)
454 : : {
455 : 0 : status = -1;
456 : : }
457 : :
458 [ + - ]: 5422 : while(status == 0)
459 : : {
460 : 5422 : const ssize_t bytes_read = read(source_fd,buffer,sizeof(buffer));
461 : :
462 [ - + ]: 5422 : if(bytes_read < 0)
463 : : {
464 [ # # ]: 0 : if(errno == EINTR)
465 : : {
466 : 0 : continue;
467 : : }
468 : 0 : status = -1;
469 : 0 : break;
470 : : }
471 : :
472 [ + + ]: 5422 : if(bytes_read == 0)
473 : : {
474 : 1857 : break;
475 : : }
476 : :
477 : 3565 : ssize_t offset = 0;
478 : :
479 [ + + ]: 7130 : while(offset < bytes_read)
480 : : {
481 : 3565 : const ssize_t bytes_written = write(destination_fd,
482 : : buffer + offset,
483 : 3565 : (size_t)(bytes_read - offset));
484 : :
485 [ - + ]: 3565 : if(bytes_written < 0)
486 : : {
487 [ # # ]: 0 : if(errno == EINTR)
488 : : {
489 : 0 : continue;
490 : : }
491 : 0 : status = -1;
492 : 0 : break;
493 : : }
494 : :
495 : 3565 : offset += bytes_written;
496 : : }
497 : : }
498 : :
499 [ + - - + ]: 1857 : if(source_fd >= 0 && close(source_fd) != 0)
500 : : {
501 : 0 : status = -1;
502 : : }
503 : :
504 [ + - - + ]: 1857 : if(destination_fd >= 0 && close(destination_fd) != 0)
505 : : {
506 : 0 : status = -1;
507 : : }
508 : :
509 : 1857 : return(status);
510 : : }
511 : :
512 : : /**
513 : : * @brief Copy symbolic link target from source to destination
514 : : *
515 : : * @param[in] source_path Source symbolic link path
516 : : * @param[in] destination_path Destination symbolic link path
517 : : *
518 : : * @return 0 on success, non-zero on failure
519 : : */
520 : 106 : static int copy_symbolic_link(
521 : : const char *source_path,
522 : : const char *destination_path)
523 : : {
524 : 106 : int status = 0;
525 : 106 : size_t link_target_buffer_size = 256U;
526 : 106 : char *link_target = NULL;
527 : :
528 [ + - - + ]: 106 : if(source_path == NULL || destination_path == NULL)
529 : : {
530 : 0 : status = -1;
531 : : }
532 : :
533 [ + - ]: 106 : if(status == 0)
534 : : {
535 : 106 : link_target = malloc(link_target_buffer_size);
536 : :
537 [ - + ]: 106 : if(link_target == NULL)
538 : : {
539 : 0 : status = -1;
540 : : }
541 : : }
542 : :
543 [ + - ]: 106 : while(status == 0)
544 : : {
545 : 106 : ssize_t link_target_size = readlink(source_path,link_target,link_target_buffer_size - 1U);
546 : :
547 [ - + ]: 106 : if(link_target_size < 0)
548 : : {
549 : 0 : status = -1;
550 : 0 : break;
551 : : }
552 : :
553 [ + - ]: 106 : if((size_t)link_target_size < link_target_buffer_size - 1U)
554 : : {
555 : 106 : link_target[link_target_size] = '\0';
556 : 106 : break;
557 : : }
558 : :
559 : 0 : link_target_buffer_size *= 2U;
560 : 0 : char *resized_link_target = realloc(link_target,link_target_buffer_size);
561 : :
562 [ # # ]: 0 : if(resized_link_target == NULL)
563 : : {
564 : 0 : status = -1;
565 : : } else {
566 : 0 : link_target = resized_link_target;
567 : : }
568 : : }
569 : :
570 [ + - - + ]: 106 : if(status == 0 && symlink(link_target,destination_path) != 0)
571 : : {
572 : 0 : status = -1;
573 : : }
574 : :
575 : 106 : free(link_target);
576 : :
577 : 106 : return(status);
578 : : }
579 : :
580 : : /**
581 : : * @brief Copy callback used by nftw
582 : : *
583 : : * @param[in] path Current path from nftw walk
584 : : * @param[in] stat_buffer File stat pointer from nftw
585 : : * @param[in] type_flag nftw node type
586 : : * @param[in] ftw_buffer Unused nftw frame data
587 : : *
588 : : * @return 0 on success, non-zero on failure
589 : : */
590 : 7306 : static int nftw_copy_callback(
591 : : const char *path,
592 : : const struct stat *stat_buffer,
593 : : const int type_flag,
594 : : struct FTW *ftw_buffer)
595 : : {
596 : : (void)ftw_buffer;
597 : :
598 : 7306 : int status = 0;
599 : 7306 : m_create(char,destination_path,MEMORY_STRING);
600 : 7306 : const char *destination_path_string = NULL;
601 : :
602 [ - + ]: 7306 : if(stat_buffer == NULL)
603 : : {
604 : 0 : status = -1;
605 : : }
606 : :
607 [ + - - + ]: 7306 : if(status == 0 && build_copy_destination_path_for_nftw(path,destination_path) != 0)
608 : : {
609 : 0 : status = -1;
610 : : }
611 : :
612 [ + - ]: 7306 : if(status == 0)
613 : : {
614 : 7306 : destination_path_string = m_text(destination_path);
615 : :
616 [ - + ]: 7306 : if(destination_path_string == NULL)
617 : : {
618 : 0 : status = -1;
619 : : }
620 : : }
621 : :
622 [ + - ]: 7306 : if(status == 0)
623 : : {
624 [ + + + - ]: 7306 : switch(type_flag)
625 : : {
626 : 5401 : case FTW_D:
627 [ - + - - ]: 5401 : if(mkdir(destination_path_string,stat_buffer->st_mode & (mode_t)07777) != 0 && errno != EEXIST)
628 : : {
629 : 0 : status = -1;
630 : : }
631 : :
632 [ + - - + ]: 5401 : if(status == 0 && chmod(destination_path_string,stat_buffer->st_mode & (mode_t)07777) != 0)
633 : : {
634 : 0 : status = -1;
635 : : }
636 : 5401 : break;
637 : 1799 : case FTW_F:
638 [ - + ]: 1799 : if(copy_regular_file_contents(path,destination_path_string,stat_buffer->st_mode) != 0)
639 : : {
640 : 0 : status = -1;
641 : : }
642 : :
643 [ + - - + ]: 1799 : if(status == 0 && apply_mtime_from_source_stat(destination_path_string,stat_buffer,0) != 0)
644 : : {
645 : 0 : status = -1;
646 : : }
647 : 1799 : break;
648 : 106 : case FTW_SL:
649 : : #ifdef FTW_SLN
650 : : case FTW_SLN:
651 : : #endif
652 : :
653 [ + - - + ]: 106 : if(unlink(destination_path_string) != 0 && errno != ENOENT)
654 : : {
655 : 0 : status = -1;
656 : : }
657 : :
658 [ + - - + ]: 106 : if(status == 0 && copy_symbolic_link(path,destination_path_string) != 0)
659 : : {
660 : 0 : status = -1;
661 : : }
662 : : #ifdef AT_SYMLINK_NOFOLLOW
663 [ + - ]: 106 : if(status == 0
664 [ - + ]: 106 : && apply_mtime_from_source_stat(destination_path_string,stat_buffer,AT_SYMLINK_NOFOLLOW) != 0)
665 : : {
666 : 0 : status = -1;
667 : : }
668 : : #endif
669 : 106 : break;
670 : 0 : default:
671 : 0 : status = -1;
672 : 0 : break;
673 : : }
674 : : }
675 : :
676 : 7306 : m_del(destination_path);
677 : :
678 : 7306 : return(status);
679 : : }
680 : :
681 : : /**
682 : : * @brief Post-order callback to restore copied directory mtimes
683 : : *
684 : : * @param[in] path Current source path from nftw walk
685 : : * @param[in] stat_buffer Source stat metadata
686 : : * @param[in] type_flag nftw node type
687 : : * @param[in] ftw_buffer Unused nftw frame data
688 : : *
689 : : * @return 0 on success, non-zero on failure
690 : : */
691 : 7306 : static int nftw_sync_directory_mtime_callback(
692 : : const char *path,
693 : : const struct stat *stat_buffer,
694 : : const int type_flag,
695 : : struct FTW *ftw_buffer)
696 : : {
697 : : (void)ftw_buffer;
698 : :
699 : 7306 : int status = 0;
700 : 7306 : m_create(char,destination_path,MEMORY_STRING);
701 : 7306 : const char *destination_path_string = NULL;
702 : 7306 : bool is_directory = false;
703 : :
704 [ - + ]: 7306 : if(stat_buffer == NULL)
705 : : {
706 : 0 : status = -1;
707 : : }
708 : :
709 [ - + ]: 7306 : if(type_flag == FTW_D)
710 : : {
711 : 0 : is_directory = true;
712 : : }
713 : : #ifdef FTW_DP
714 [ + + ]: 7306 : if(type_flag == FTW_DP)
715 : : {
716 : 5401 : is_directory = true;
717 : : }
718 : : #endif
719 : :
720 [ + - + + ]: 7306 : if(status == 0 && is_directory == true)
721 : : {
722 [ - + ]: 5401 : if(build_copy_destination_path_for_nftw(path,destination_path) != 0)
723 : : {
724 : 0 : status = -1;
725 : : }
726 : : }
727 : :
728 [ + - + + ]: 7306 : if(status == 0 && is_directory == true)
729 : : {
730 : 5401 : destination_path_string = m_text(destination_path);
731 : :
732 [ - + ]: 5401 : if(destination_path_string == NULL)
733 : : {
734 : 0 : status = -1;
735 : : }
736 : : }
737 : :
738 [ + - + + ]: 7306 : if(status == 0 && is_directory == true)
739 : : {
740 [ - + ]: 5401 : if(apply_mtime_from_source_stat(destination_path_string,stat_buffer,0) != 0)
741 : : {
742 : 0 : status = -1;
743 : : }
744 : : }
745 : :
746 : 7306 : m_del(destination_path);
747 : :
748 : 7306 : return(status);
749 : : }
750 : :
751 : : /**
752 : : * @brief Truncate an existing file to zero bytes by reopening it in binary write mode
753 : : *
754 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
755 : : *
756 : : * @return Return status code:
757 : : * - SUCCESS: File was truncated successfully
758 : : * - FAILURE: Path construction or file operation failed
759 : : */
760 : 9 : Return truncate_file_to_zero_size(const char *relative_path_to_tmpdir)
761 : : {
762 : : /* Status returned by this function through provide()
763 : : Default value assumes successful completion */
764 : 9 : Return status = SUCCESS;
765 : 9 : FILE *file = NULL;
766 : :
767 : 9 : m_create(char,absolute_path,MEMORY_STRING);
768 : :
769 [ - + ]: 9 : if(relative_path_to_tmpdir == NULL)
770 : : {
771 : 0 : status = FAILURE;
772 : : }
773 : :
774 [ + - ]: 9 : if(SUCCESS == status)
775 : : {
776 : 9 : status = construct_path(relative_path_to_tmpdir,absolute_path);
777 : : }
778 : :
779 [ + - ]: 9 : if(SUCCESS == status)
780 : : {
781 : 9 : status = open_file_stream(
782 : : absolute_path,
783 : : "wb",
784 : : &file);
785 : : }
786 : :
787 : 9 : IF(file != NULL && fclose(file) != 0)
788 : : {
789 : 0 : status = FAILURE;
790 : : }
791 : :
792 : 9 : m_del(absolute_path);
793 : :
794 : 9 : return(status);
795 : : }
796 : :
797 : : /**
798 : : * @brief Create symbolic link by path relative to TMPDIR
799 : : *
800 : : * The target string is stored in the link exactly as provided
801 : : *
802 : : * @param[in] link_target Literal target string for the symbolic link
803 : : * @param[in] relative_link_path_to_tmpdir Symbolic link path relative to TMPDIR
804 : : *
805 : : * @return Return status code:
806 : : * - SUCCESS: Symbolic link was created successfully
807 : : * - FAILURE: Validation, path construction, or symlink creation failed
808 : : */
809 : 14 : Return create_symlink(
810 : : const char *link_target,
811 : : const char *relative_link_path_to_tmpdir)
812 : : {
813 : : /* Status returned by this function through provide()
814 : : Default value assumes successful completion */
815 : 14 : Return status = SUCCESS;
816 : :
817 : 14 : m_create(char,absolute_path,MEMORY_STRING);
818 : :
819 [ + - - + ]: 14 : if(link_target == NULL || relative_link_path_to_tmpdir == NULL)
820 : : {
821 : 0 : status = FAILURE;
822 : : }
823 : :
824 [ + - ]: 14 : if(SUCCESS == status)
825 : : {
826 : 14 : status = construct_path(relative_link_path_to_tmpdir,absolute_path);
827 : : }
828 : :
829 [ + - - + ]: 14 : if(SUCCESS == status && symlink(link_target,m_text(absolute_path)) != 0)
830 : : {
831 : 0 : status = FAILURE;
832 : : }
833 : :
834 : 14 : m_del(absolute_path);
835 : :
836 : 14 : return(status);
837 : : }
838 : :
839 : : /**
840 : : * @brief Change file mode by path relative to TMPDIR
841 : : *
842 : : * The provided mode is applied exactly with native `chmod()`
843 : : *
844 : : * @param[in] relative_path_to_tmpdir File or directory path relative to TMPDIR
845 : : * @param[in] new_mode Exact mode value to apply
846 : : *
847 : : * @return Return status code:
848 : : * - SUCCESS: File mode was changed successfully
849 : : * - FAILURE: Validation, path construction, or chmod failed
850 : : */
851 : 32 : Return change_mode(
852 : : const char *relative_path_to_tmpdir,
853 : : unsigned int new_mode)
854 : : {
855 : : /* Status returned by this function through provide()
856 : : Default value assumes successful completion */
857 : 32 : Return status = SUCCESS;
858 : 32 : m_create(char,absolute_path,MEMORY_STRING);
859 : :
860 [ - + ]: 32 : if(relative_path_to_tmpdir == NULL)
861 : : {
862 : 0 : status = FAILURE;
863 : : }
864 : :
865 [ + - ]: 32 : if(SUCCESS == status)
866 : : {
867 : 32 : status = construct_path(relative_path_to_tmpdir,absolute_path);
868 : : }
869 : :
870 [ + - - + ]: 32 : if(SUCCESS == status && chmod(m_text(absolute_path),(mode_t)new_mode) != 0)
871 : : {
872 : 0 : status = FAILURE;
873 : : }
874 : :
875 : 32 : m_del(absolute_path);
876 : :
877 : 32 : return(status);
878 : : }
879 : :
880 : : /**
881 : : * @brief Create directory tree by path relative to TMPDIR
882 : : *
883 : : * Empty path resolves to TMPDIR root
884 : : * Existing directories are preserved
885 : : * Existing symlinks to directories in the TMPDIR prefix are accepted
886 : : *
887 : : * @param[in] relative_path_to_tmpdir Directory path relative to TMPDIR
888 : : *
889 : : * @return Return status code:
890 : : * - SUCCESS: Directory tree exists after the call
891 : : * - FAILURE: Path construction or directory creation failed
892 : : */
893 : 123 : Return create_directory(const char *relative_path_to_tmpdir)
894 : : {
895 : : /* Status returned by this function through provide()
896 : : Default value assumes successful completion */
897 : 123 : Return status = SUCCESS;
898 : 123 : char *absolute_path_data = NULL;
899 : 123 : m_create(char,absolute_path,MEMORY_STRING);
900 : :
901 [ - + ]: 123 : if(relative_path_to_tmpdir == NULL)
902 : : {
903 : 0 : status = FAILURE;
904 : : }
905 : :
906 [ + - ]: 123 : if(SUCCESS == status)
907 : : {
908 : 123 : status = construct_path(relative_path_to_tmpdir,absolute_path);
909 : : }
910 : :
911 [ + - ]: 123 : if(SUCCESS == status)
912 : : {
913 : 123 : absolute_path_data = m_data(char,absolute_path);
914 : :
915 [ - + ]: 123 : if(absolute_path_data == NULL)
916 : : {
917 : 0 : status = FAILURE;
918 : : }
919 : : }
920 : :
921 [ + - ]: 123 : if(SUCCESS == status)
922 : : {
923 : 123 : size_t absolute_path_length = strlen(absolute_path_data);
924 : :
925 [ + - - + ]: 123 : while(absolute_path_length > 1U && absolute_path_data[absolute_path_length - 1U] == '/')
926 : : {
927 : 0 : absolute_path_data[absolute_path_length - 1U] = '\0';
928 : 0 : absolute_path_length--;
929 : : }
930 : : }
931 : :
932 [ + - ]: 123 : if(SUCCESS == status)
933 : : {
934 : 123 : for(char *path_cursor = absolute_path_data + 1;
935 [ + + + + ]: 6794 : SUCCESS == status && *path_cursor != '\0';
936 : 6671 : path_cursor++)
937 : : {
938 [ + + ]: 6671 : if(*path_cursor == '/')
939 : : {
940 : 572 : *path_cursor = '\0';
941 : :
942 [ + + ]: 572 : if(create_directory_if_missing(absolute_path_data) != 0)
943 : : {
944 : 1 : status = FAILURE;
945 : : }
946 : 572 : *path_cursor = '/';
947 : : }
948 : : }
949 : : }
950 : :
951 [ + + ]: 123 : if(SUCCESS == status)
952 : : {
953 [ - + ]: 122 : if(create_directory_if_missing(absolute_path_data) != 0)
954 : : {
955 : 0 : status = FAILURE;
956 : : }
957 : : }
958 : :
959 : 123 : m_del(absolute_path);
960 : :
961 : 123 : return(status);
962 : : }
963 : :
964 : : /**
965 : : * @brief Remove file or directory tree by path relative to TMPDIR
966 : : *
967 : : * When relative_path_to_tmpdir is an empty string, the function targets TMPDIR itself
968 : : * Missing paths are treated as a hard failure to keep test cleanup strict and deterministic
969 : : *
970 : : * @param[in] relative_path_to_tmpdir File or directory path relative to TMPDIR
971 : : *
972 : : * @return Return status code:
973 : : * - SUCCESS: Path was removed
974 : : * - FAILURE: Path construction or remove traversal failed
975 : : */
976 : 404 : Return delete_path(const char *relative_path_to_tmpdir)
977 : : {
978 : : /* Status returned by this function through provide()
979 : : Default value assumes successful completion */
980 : 404 : Return status = SUCCESS;
981 : 404 : struct stat path_stat = {0};
982 : 404 : m_create(char,absolute_path,MEMORY_STRING);
983 : :
984 [ + + ]: 404 : if(relative_path_to_tmpdir == NULL)
985 : : {
986 : 1 : echo(STDERR,"delete_path: relative path must not be NULL\n");
987 : 1 : status = FAILURE;
988 : : }
989 : :
990 [ + + ]: 404 : if(SUCCESS == status)
991 : : {
992 : 403 : status = construct_path(relative_path_to_tmpdir,absolute_path);
993 : :
994 [ - + ]: 403 : if(SUCCESS != status)
995 : : {
996 : 0 : echo(STDERR,"delete_path: failed to construct absolute path for \"%s\"\n",relative_path_to_tmpdir);
997 : : }
998 : : }
999 : :
1000 [ + + ]: 404 : if(SUCCESS == status)
1001 : : {
1002 [ + + ]: 403 : if(lstat(m_text(absolute_path),&path_stat) != 0)
1003 : : {
1004 : 1 : echo(STDERR,"delete_path: lstat failed for %s: %s\n",m_text(absolute_path),strerror(errno));
1005 : 1 : status = FAILURE;
1006 : : }
1007 : : }
1008 : :
1009 [ + + ]: 404 : if(SUCCESS == status)
1010 : : {
1011 [ + + ]: 402 : if(S_ISDIR(path_stat.st_mode))
1012 : : {
1013 : 119 : long sc_open_max = sysconf(_SC_OPEN_MAX);
1014 : :
1015 [ - + ]: 119 : if(sc_open_max <= 0)
1016 : : {
1017 : 0 : sc_open_max = 512;
1018 : : }
1019 : :
1020 [ + + ]: 119 : if(nftw(m_text(absolute_path),nftw_remove_callback,(int)sc_open_max,FTW_DEPTH | FTW_PHYS) != 0)
1021 : : {
1022 : 1 : echo(STDERR,"delete_path: nftw failed for %s: %s\n",m_text(absolute_path),strerror(errno));
1023 : 1 : status = FAILURE;
1024 : : }
1025 [ + + ]: 283 : } else if(remove(m_text(absolute_path)) != 0){
1026 : 1 : echo(STDERR,"delete_path: remove failed for %s: %s\n",m_text(absolute_path),strerror(errno));
1027 : 1 : status = FAILURE;
1028 : : }
1029 : : }
1030 : :
1031 : 404 : m_del(absolute_path);
1032 : :
1033 : 404 : return(status);
1034 : : }
1035 : :
1036 : : /**
1037 : : * @brief Remove a file or directory tree when it exists
1038 : : *
1039 : : * This helper is intended for cleanup paths that may or may not have been
1040 : : * created before a test scenario stopped. Missing paths are accepted. Any
1041 : : * other filesystem error is reported as a failure so stale test state is not
1042 : : * silently ignored
1043 : : *
1044 : : * @param[in] relative_path_to_tmpdir File or directory path relative to TMPDIR
1045 : : *
1046 : : * @return Return status code:
1047 : : * - SUCCESS: Path was absent or removed successfully
1048 : : * - FAILURE: Validation, path construction, stat, or removal failed
1049 : : */
1050 : 10 : Return delete_path_if_present(const char *relative_path_to_tmpdir)
1051 : : {
1052 : : /* Status returned by this function through provide()
1053 : : Default value assumes successful completion */
1054 : 10 : Return status = SUCCESS;
1055 : 10 : struct stat path_stat = {0};
1056 : 10 : m_create(char,absolute_path,MEMORY_STRING);
1057 : :
1058 [ - + ]: 10 : if(relative_path_to_tmpdir == NULL)
1059 : : {
1060 : 0 : status = FAILURE;
1061 : : }
1062 : :
1063 [ + - ]: 10 : if(SUCCESS == status)
1064 : : {
1065 : 10 : status = construct_path(relative_path_to_tmpdir,absolute_path);
1066 : : }
1067 : :
1068 [ + - ]: 10 : if(SUCCESS == status)
1069 : : {
1070 : 10 : errno = 0;
1071 : :
1072 [ + - ]: 10 : if(lstat(m_text(absolute_path),&path_stat) == 0)
1073 : : {
1074 : 10 : status = delete_path(relative_path_to_tmpdir);
1075 : :
1076 [ # # ]: 0 : } else if(errno != ENOENT){
1077 : 0 : status = FAILURE;
1078 : : }
1079 : : }
1080 : :
1081 : 10 : m_del(absolute_path);
1082 : :
1083 : 10 : return(status);
1084 : : }
1085 : :
1086 : : /**
1087 : : * @brief Prepare an isolated working copy of the huge-file fixture
1088 : : *
1089 : : * The fixture is copied from ORIGIN_DIR into TMPDIR and the output path points
1090 : : * to the copied `hugetestfile`. The optional stat output lets tests assert
1091 : : * interruption offsets against the real file size
1092 : : *
1093 : : * @param[out] huge_file_path Absolute path to the copied fixture file
1094 : : * @param[out] huge_file_stat_out Optional file metadata read from the copied fixture
1095 : : *
1096 : : * @return Return status code:
1097 : : * - SUCCESS: Fixture copy is ready and output path was filled
1098 : : * - FAILURE: Environment setup, copy, path construction, or stat failed
1099 : : */
1100 : 9 : Return prepare_huge_fixture(
1101 : : memory *huge_file_path,
1102 : : struct stat *huge_file_stat_out)
1103 : : {
1104 : : /* Status returned by this function through provide()
1105 : : Default value assumes successful completion */
1106 : 9 : Return status = SUCCESS;
1107 : 9 : struct stat huge_file_stat = {0};
1108 : : static const char huge_fixture_root[] = "tests/fixtures/huge";
1109 : : static const char huge_fixture_file[] = "tests/fixtures/huge/hugetestfile";
1110 : :
1111 [ - + ]: 9 : if(huge_file_path == NULL)
1112 : : {
1113 : 0 : status = FAILURE;
1114 : : }
1115 : :
1116 [ + - ]: 9 : if(SUCCESS == status)
1117 : : {
1118 : 9 : status = set_environment_variable("TESTING","true");
1119 : : }
1120 : :
1121 [ + - ]: 9 : if(SUCCESS == status)
1122 : : {
1123 : 9 : status = create_directory("tests/fixtures");
1124 : : }
1125 : :
1126 [ + - ]: 9 : if(SUCCESS == status)
1127 : : {
1128 : 9 : status = copy_from_origin(huge_fixture_root,huge_fixture_root,REQUIRE_SOURCE_EXISTS);
1129 : : }
1130 : :
1131 [ + - ]: 9 : if(SUCCESS == status)
1132 : : {
1133 : 9 : status = construct_path(huge_fixture_file,huge_file_path);
1134 : : }
1135 : :
1136 [ + - - + ]: 9 : if(SUCCESS == status && stat(m_text(huge_file_path),&huge_file_stat) != 0)
1137 : : {
1138 : 0 : status = FAILURE;
1139 : : }
1140 : :
1141 [ + - - + ]: 9 : if(SUCCESS == status && huge_file_stat.st_size <= 0)
1142 : : {
1143 : 0 : status = FAILURE;
1144 : : }
1145 : :
1146 [ + - + + ]: 9 : if(SUCCESS == status && huge_file_stat_out != NULL)
1147 : : {
1148 : 7 : *huge_file_stat_out = huge_file_stat;
1149 : : }
1150 : :
1151 : 9 : return(status);
1152 : : }
1153 : :
1154 : : /**
1155 : : * @brief Copy filesystem object from one absolute path to another
1156 : : *
1157 : : * @param[in] source_absolute_path Source absolute path
1158 : : * @param[in] destination_absolute_path Destination absolute path
1159 : : * @param[in] flags Behavior flags such as @ref REQUIRE_SOURCE_EXISTS or @ref ALLOW_MISSING_SOURCE
1160 : : *
1161 : : * @return Return status code
1162 : : */
1163 : 178 : static Return copy_absolute_path(
1164 : : const char *source_absolute_path,
1165 : : const char *destination_absolute_path,
1166 : : unsigned int flags)
1167 : : {
1168 : : /* Status returned by this function through provide()
1169 : : Default value assumes successful completion */
1170 : 178 : Return status = SUCCESS;
1171 : 178 : struct stat source_stat = {0};
1172 : 178 : struct stat destination_stat = {0};
1173 : 178 : bool destination_exists = false;
1174 : :
1175 [ + - - + ]: 178 : if(source_absolute_path == NULL || destination_absolute_path == NULL)
1176 : : {
1177 : 0 : status = FAILURE;
1178 : : }
1179 : :
1180 [ + - - + ]: 178 : if(SUCCESS == status && lstat(source_absolute_path,&source_stat) != 0)
1181 : : {
1182 [ # # # # ]: 0 : if(errno == ENOENT && (flags & ALLOW_MISSING_SOURCE) != 0U)
1183 : : {
1184 : 0 : return(SUCCESS);
1185 : : }
1186 : :
1187 : 0 : status = FAILURE;
1188 : : }
1189 : :
1190 [ + - + + ]: 178 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
1191 : : {
1192 : 120 : size_t source_length = strlen(source_absolute_path);
1193 : :
1194 [ + - - + ]: 120 : while(source_length > 1U && source_absolute_path[source_length - 1U] == '/')
1195 : : {
1196 : 0 : source_length--;
1197 : : }
1198 : :
1199 [ - + ]: 120 : if(strncmp(destination_absolute_path,source_absolute_path,source_length) == 0
1200 [ # # ]: 0 : && (destination_absolute_path[source_length] == '\0'
1201 [ # # ]: 0 : || destination_absolute_path[source_length] == '/'))
1202 : : {
1203 : 0 : status = FAILURE;
1204 : : }
1205 : : }
1206 : :
1207 [ + - ]: 178 : if(SUCCESS == status)
1208 : : {
1209 [ + + ]: 178 : if(lstat(destination_absolute_path,&destination_stat) == 0)
1210 : : {
1211 : 8 : destination_exists = true;
1212 [ - + ]: 170 : } else if(errno != ENOENT){
1213 : 0 : status = FAILURE;
1214 : : }
1215 : : }
1216 : :
1217 [ + - + + ]: 178 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
1218 : : {
1219 [ - + ]: 120 : if(destination_exists == true)
1220 : : {
1221 : 0 : status = FAILURE;
1222 : : } else {
1223 : 120 : copy_source_root_for_nftw = source_absolute_path;
1224 : 120 : copy_destination_root_for_nftw = destination_absolute_path;
1225 : :
1226 [ + - ]: 120 : if(copy_source_root_for_nftw == NULL
1227 [ - + ]: 120 : || copy_destination_root_for_nftw == NULL)
1228 : : {
1229 : 0 : status = FAILURE;
1230 : : }
1231 : : }
1232 : : }
1233 : :
1234 [ + - + + : 178 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode) && destination_exists == false)
+ - ]
1235 : : {
1236 : 120 : copy_source_root_length_for_nftw = strlen(copy_source_root_for_nftw);
1237 : 120 : copy_destination_root_length_for_nftw = strlen(copy_destination_root_for_nftw);
1238 : :
1239 [ + - ]: 120 : if(copy_source_root_length_for_nftw == 0U
1240 [ - + ]: 120 : || copy_destination_root_length_for_nftw == 0U)
1241 : : {
1242 : 0 : status = FAILURE;
1243 : : }
1244 : : }
1245 : :
1246 [ + - + + ]: 178 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
1247 : : {
1248 [ + - ]: 120 : if(destination_exists == false)
1249 : : {
1250 [ - + ]: 120 : if(nftw(source_absolute_path,nftw_copy_callback,64,FTW_PHYS) != 0)
1251 : : {
1252 : 0 : status = FAILURE;
1253 : : }
1254 : :
1255 [ + - ]: 120 : if(SUCCESS == status
1256 [ - + ]: 120 : && nftw(source_absolute_path,
1257 : : nftw_sync_directory_mtime_callback,
1258 : : 64,
1259 : : FTW_DEPTH | FTW_PHYS) != 0)
1260 : : {
1261 : 0 : status = FAILURE;
1262 : : }
1263 : : }
1264 [ + - + - ]: 58 : } else if(SUCCESS == status && S_ISREG(source_stat.st_mode)){
1265 [ + + - + ]: 58 : if(destination_exists == true && S_ISDIR(destination_stat.st_mode))
1266 : : {
1267 : 0 : status = FAILURE;
1268 : : }
1269 : :
1270 [ + - ]: 58 : if(SUCCESS == status)
1271 : : {
1272 [ + + - + ]: 58 : if(unlink(destination_absolute_path) != 0 && errno != ENOENT)
1273 : : {
1274 : 0 : status = FAILURE;
1275 : : }
1276 : : }
1277 : :
1278 [ + - ]: 58 : if(SUCCESS == status
1279 [ - + ]: 58 : && copy_regular_file_contents(source_absolute_path,
1280 : : destination_absolute_path,
1281 : : source_stat.st_mode) != 0)
1282 : : {
1283 : 0 : status = FAILURE;
1284 : : }
1285 : :
1286 [ + - ]: 58 : if(SUCCESS == status
1287 [ - + ]: 58 : && apply_mtime_from_source_stat(destination_absolute_path,&source_stat,0) != 0)
1288 : : {
1289 : 0 : status = FAILURE;
1290 : : }
1291 [ # # # # ]: 0 : } else if(SUCCESS == status && S_ISLNK(source_stat.st_mode)){
1292 [ # # # # ]: 0 : if(destination_exists == true && S_ISDIR(destination_stat.st_mode))
1293 : : {
1294 : 0 : status = FAILURE;
1295 : : }
1296 : :
1297 [ # # # # ]: 0 : if(SUCCESS == status && destination_exists == true
1298 [ # # ]: 0 : && unlink(destination_absolute_path) != 0)
1299 : : {
1300 : 0 : status = FAILURE;
1301 : : }
1302 : :
1303 [ # # # # ]: 0 : if(SUCCESS == status && copy_symbolic_link(source_absolute_path,destination_absolute_path) != 0)
1304 : : {
1305 : 0 : status = FAILURE;
1306 : : }
1307 : :
1308 : : #ifdef AT_SYMLINK_NOFOLLOW
1309 [ # # ]: 0 : if(SUCCESS == status
1310 [ # # ]: 0 : && apply_mtime_from_source_stat(destination_absolute_path,
1311 : : &source_stat,
1312 : : AT_SYMLINK_NOFOLLOW) != 0)
1313 : : {
1314 : 0 : status = FAILURE;
1315 : : }
1316 : : #endif
1317 [ # # ]: 0 : } else if(SUCCESS == status){
1318 : 0 : status = FAILURE;
1319 : : }
1320 : :
1321 : 178 : reset_nftw_copy_context();
1322 : :
1323 : 178 : return(status);
1324 : : }
1325 : :
1326 : : /**
1327 : : * @brief Copy file or directory tree by path relative to TMPDIR
1328 : : *
1329 : : * Empty source or destination path resolves to TMPDIR root
1330 : : * Directory copy into itself or into its own subtree is rejected
1331 : : *
1332 : : * @param[in] relative_source_path Source file or directory path relative to TMPDIR
1333 : : * @param[in] relative_destination_path Destination file or directory path relative to TMPDIR
1334 : : *
1335 : : * @return Return status code:
1336 : : * - SUCCESS: Source path was copied to destination path
1337 : : * - FAILURE: Validation, path resolution, stat lookup, or copy operation failed
1338 : : */
1339 : 154 : Return copy_path(
1340 : : const char *relative_source_path,
1341 : : const char *relative_destination_path)
1342 : : {
1343 : : /* Status returned by this function through provide()
1344 : : Default value assumes successful completion */
1345 : 154 : Return status = SUCCESS;
1346 : 154 : m_create(char,source_absolute_path,MEMORY_STRING);
1347 : 154 : m_create(char,destination_absolute_path,MEMORY_STRING);
1348 : :
1349 [ + - - + ]: 154 : if(relative_source_path == NULL || relative_destination_path == NULL)
1350 : : {
1351 : 0 : status = FAILURE;
1352 : : }
1353 : :
1354 [ + - ]: 154 : if(SUCCESS == status)
1355 : : {
1356 : 154 : status = construct_path(relative_source_path,source_absolute_path);
1357 : : }
1358 : :
1359 [ + - ]: 154 : if(SUCCESS == status)
1360 : : {
1361 : 154 : status = construct_path(relative_destination_path,destination_absolute_path);
1362 : : }
1363 : :
1364 [ + - ]: 154 : if(SUCCESS == status)
1365 : : {
1366 : 154 : status = copy_absolute_path(m_text(source_absolute_path),
1367 : 154 : m_text(destination_absolute_path),
1368 : : REQUIRE_SOURCE_EXISTS);
1369 : : }
1370 : 154 : m_del(destination_absolute_path);
1371 : 154 : m_del(source_absolute_path);
1372 : :
1373 : 154 : return(status);
1374 : : }
1375 : :
1376 : : /**
1377 : : * @brief Copy file or directory tree from ORIGIN_DIR into TMPDIR
1378 : : *
1379 : : * @param[in] relative_source_path_to_origin_dir Source path relative to ORIGIN_DIR
1380 : : * @param[in] relative_destination_path_to_tmpdir Destination path relative to TMPDIR
1381 : : * @param[in] flags Behavior flags such as @ref REQUIRE_SOURCE_EXISTS or @ref ALLOW_MISSING_SOURCE
1382 : : *
1383 : : * @return Return status code:
1384 : : * - SUCCESS: Source path was copied or was absent and allowed to be missing
1385 : : * - FAILURE: Validation, path resolution, stat lookup, or copy operation failed
1386 : : */
1387 : 24 : Return copy_from_origin(
1388 : : const char *relative_source_path_to_origin_dir,
1389 : : const char *relative_destination_path_to_tmpdir,
1390 : : unsigned int flags)
1391 : : {
1392 : : /* Status returned by this function through provide()
1393 : : Default value assumes successful completion */
1394 : 24 : Return status = SUCCESS;
1395 : 24 : m_create(char,source_absolute_path,MEMORY_STRING);
1396 : 24 : m_create(char,destination_absolute_path,MEMORY_STRING);
1397 : :
1398 [ + - - + ]: 24 : if(relative_source_path_to_origin_dir == NULL || relative_destination_path_to_tmpdir == NULL)
1399 : : {
1400 : 0 : status = FAILURE;
1401 : : }
1402 : :
1403 [ + - ]: 24 : if(SUCCESS == status)
1404 : : {
1405 : 24 : status = construct_path_from_environment_variable("ORIGIN_DIR",
1406 : : relative_source_path_to_origin_dir,
1407 : : source_absolute_path);
1408 : : }
1409 : :
1410 [ + - ]: 24 : if(SUCCESS == status)
1411 : : {
1412 : 24 : status = construct_path(relative_destination_path_to_tmpdir,destination_absolute_path);
1413 : : }
1414 : :
1415 [ + - ]: 24 : if(SUCCESS == status)
1416 : : {
1417 : 24 : status = copy_absolute_path(m_text(source_absolute_path),
1418 : 24 : m_text(destination_absolute_path),
1419 : : flags);
1420 : : }
1421 : :
1422 : 24 : m_del(destination_absolute_path);
1423 : 24 : m_del(source_absolute_path);
1424 : :
1425 : 24 : return(status);
1426 : : }
1427 : :
1428 : : /**
1429 : : * @brief Move file or directory by path relative to TMPDIR using native rename
1430 : : *
1431 : : * Empty source or destination path resolves to TMPDIR root
1432 : : *
1433 : : * @param[in] relative_source_path Source file or directory path relative to TMPDIR
1434 : : * @param[in] relative_destination_path Destination file or directory path relative to TMPDIR
1435 : : *
1436 : : * @return Return status code:
1437 : : * - SUCCESS: Source path was moved to destination path
1438 : : * - FAILURE: Validation, path resolution, or native rename failed
1439 : : */
1440 : 210 : Return move_path(
1441 : : const char *relative_source_path,
1442 : : const char *relative_destination_path)
1443 : : {
1444 : : /* Status returned by this function through provide()
1445 : : Default value assumes successful completion */
1446 : 210 : Return status = SUCCESS;
1447 : 210 : m_create(char,source_absolute_path,MEMORY_STRING);
1448 : 210 : m_create(char,destination_absolute_path,MEMORY_STRING);
1449 : :
1450 [ + - - + ]: 210 : if(relative_source_path == NULL || relative_destination_path == NULL)
1451 : : {
1452 : 0 : status = FAILURE;
1453 : : }
1454 : :
1455 [ + - ]: 210 : if(SUCCESS == status)
1456 : : {
1457 : 210 : status = construct_path(relative_source_path,source_absolute_path);
1458 : : }
1459 : :
1460 [ + - ]: 210 : if(SUCCESS == status)
1461 : : {
1462 : 210 : status = construct_path(relative_destination_path,destination_absolute_path);
1463 : : }
1464 : :
1465 [ + - - + ]: 210 : if(SUCCESS == status && rename(m_text(source_absolute_path),m_text(destination_absolute_path)) != 0)
1466 : : {
1467 : 0 : status = FAILURE;
1468 : : }
1469 : :
1470 : 210 : m_del(destination_absolute_path);
1471 : 210 : m_del(source_absolute_path);
1472 : :
1473 : 210 : return(status);
1474 : : }
1475 : :
1476 : : /**
1477 : : * @brief Build the hidden backup path for a mutable fixture
1478 : : *
1479 : : * The returned path mirrors the fixture path under `.fixture_backups`
1480 : : *
1481 : : * @param[in] fixture_path Fixture path relative to TMPDIR
1482 : : * @param[out] backup_path_out Output hidden backup path relative to TMPDIR
1483 : : *
1484 : : * @return Return status code:
1485 : : * - SUCCESS: Hidden backup path was constructed
1486 : : * - FAILURE: Validation or string construction failed
1487 : : */
1488 : 204 : static Return construct_mutable_fixture_backup_path(
1489 : : const char *fixture_path,
1490 : : memory *backup_path_out)
1491 : : {
1492 : : /* Status returned by this function through provide()
1493 : : Default value assumes successful completion */
1494 : 204 : Return status = SUCCESS;
1495 : : static const char mutable_fixture_backup_root[] = ".fixture_backups";
1496 : :
1497 [ + - + - : 204 : if(fixture_path == NULL || backup_path_out == NULL || fixture_path[0] == '\0')
- + ]
1498 : : {
1499 : 0 : status = FAILURE;
1500 : : }
1501 : :
1502 [ + - ]: 204 : if(SUCCESS == status)
1503 : : {
1504 : 204 : status = m_copy_string(backup_path_out,
1505 : : sizeof(mutable_fixture_backup_root),
1506 : : mutable_fixture_backup_root);
1507 : : }
1508 : :
1509 [ + - ]: 204 : if(SUCCESS == status)
1510 : : {
1511 : 204 : status = m_to_string(backup_path_out);
1512 : : }
1513 : :
1514 [ + - ]: 204 : if(SUCCESS == status)
1515 : : {
1516 : 204 : status = m_concat_literal(backup_path_out,"/");
1517 : : }
1518 : :
1519 [ + - ]: 204 : if(SUCCESS == status)
1520 : : {
1521 : 204 : status = m_concat_string(backup_path_out,strlen(fixture_path) + 1U,fixture_path);
1522 : : }
1523 : :
1524 : 204 : return(status);
1525 : : }
1526 : :
1527 : : /**
1528 : : * @brief Derive the parent directory of a mutable-fixture backup path
1529 : : *
1530 : : * @param[in] backup_path Hidden backup path relative to TMPDIR
1531 : : * @param[out] backup_parent_path_out Output parent directory relative to TMPDIR
1532 : : *
1533 : : * @return Return status code:
1534 : : * - SUCCESS: Parent directory path was derived
1535 : : * - FAILURE: Validation, copy, or parent extraction failed
1536 : : */
1537 : 102 : static Return construct_mutable_fixture_backup_parent_path(
1538 : : const memory *backup_path,
1539 : : memory *backup_parent_path_out)
1540 : : {
1541 : : /* Status returned by this function through provide()
1542 : : Default value assumes successful completion */
1543 : 102 : Return status = SUCCESS;
1544 : 102 : char *backup_parent_path_string = NULL;
1545 : :
1546 [ + - - + ]: 102 : if(backup_path == NULL || backup_parent_path_out == NULL)
1547 : : {
1548 : 0 : status = FAILURE;
1549 : : }
1550 : :
1551 [ + - ]: 102 : if(SUCCESS == status)
1552 : : {
1553 : 102 : status = m_copy(backup_parent_path_out,backup_path);
1554 : : }
1555 : :
1556 [ + - ]: 102 : if(SUCCESS == status)
1557 : : {
1558 : 102 : backup_parent_path_string = m_data(char,backup_parent_path_out);
1559 : :
1560 [ - + ]: 102 : if(backup_parent_path_string == NULL)
1561 : : {
1562 : 0 : status = FAILURE;
1563 : : }
1564 : : }
1565 : :
1566 [ + - ]: 102 : if(SUCCESS == status)
1567 : : {
1568 : 102 : char *last_separator = strrchr(backup_parent_path_string,'/');
1569 : :
1570 [ - + ]: 102 : if(last_separator == NULL)
1571 : : {
1572 : 0 : status = FAILURE;
1573 : : } else {
1574 : 102 : *last_separator = '\0';
1575 : : }
1576 : : }
1577 : :
1578 : 102 : return(status);
1579 : : }
1580 : :
1581 : : /**
1582 : : * @brief Prepare a mutable working copy for a fixture path relative to TMPDIR
1583 : : *
1584 : : * The pristine backup is stored under the hidden `.fixture_backups` root
1585 : : *
1586 : : * @param[in] fixture_path Fixture path relative to TMPDIR
1587 : : *
1588 : : * @return Return status code:
1589 : : * - SUCCESS: Pristine backup was created and working copy was restored
1590 : : * - FAILURE: Validation, backup path construction, or fixture copy failed
1591 : : */
1592 : 102 : Return prepare_mutable_fixture(const char *fixture_path)
1593 : : {
1594 : : /* Status returned by this function through provide()
1595 : : Default value assumes successful completion */
1596 : 102 : Return status = SUCCESS;
1597 : 102 : m_create(char,backup_path,MEMORY_STRING);
1598 : 102 : m_create(char,backup_parent_path,MEMORY_STRING);
1599 : :
1600 [ + - - + ]: 102 : if(fixture_path == NULL || fixture_path[0] == '\0')
1601 : : {
1602 : 0 : status = FAILURE;
1603 : : }
1604 : :
1605 [ + - ]: 102 : if(SUCCESS == status)
1606 : : {
1607 : 102 : status = construct_mutable_fixture_backup_path(fixture_path,backup_path);
1608 : : }
1609 : :
1610 [ + - ]: 102 : if(SUCCESS == status)
1611 : : {
1612 : 102 : status = construct_mutable_fixture_backup_parent_path(backup_path,backup_parent_path);
1613 : : }
1614 : :
1615 [ + - ]: 102 : if(SUCCESS == status)
1616 : : {
1617 : 102 : status = create_directory(m_text(backup_parent_path));
1618 : : }
1619 : :
1620 [ + - ]: 102 : if(SUCCESS == status)
1621 : : {
1622 : 102 : status = move_path(fixture_path,m_text(backup_path));
1623 : : }
1624 : :
1625 [ + - ]: 102 : if(SUCCESS == status)
1626 : : {
1627 : 102 : status = copy_path(m_text(backup_path),fixture_path);
1628 : : }
1629 : :
1630 : 102 : m_del(backup_parent_path);
1631 : 102 : m_del(backup_path);
1632 : :
1633 : 102 : return(status);
1634 : : }
1635 : :
1636 : : /**
1637 : : * @brief Restore a fixture path from the hidden mutable-fixture backup
1638 : : *
1639 : : * The working copy is deleted before the pristine backup is moved back
1640 : : *
1641 : : * @param[in] fixture_path Fixture path relative to TMPDIR
1642 : : *
1643 : : * @return Return status code:
1644 : : * - SUCCESS: Working copy was removed and pristine backup was restored
1645 : : * - FAILURE: Validation, backup path construction, or restore failed
1646 : : */
1647 : 102 : Return restore_mutable_fixture(const char *fixture_path)
1648 : : {
1649 : : /* Status returned by this function through provide()
1650 : : Default value assumes successful completion */
1651 : 102 : Return status = SUCCESS;
1652 : 102 : m_create(char,backup_path,MEMORY_STRING);
1653 : :
1654 [ + - - + ]: 102 : if(fixture_path == NULL || fixture_path[0] == '\0')
1655 : : {
1656 : 0 : status = FAILURE;
1657 : : }
1658 : :
1659 [ + - ]: 102 : if(SUCCESS == status)
1660 : : {
1661 : 102 : status = construct_mutable_fixture_backup_path(fixture_path,backup_path);
1662 : : }
1663 : :
1664 [ + - ]: 102 : if(SUCCESS == status)
1665 : : {
1666 : 102 : status = delete_path(fixture_path);
1667 : : }
1668 : :
1669 [ + - ]: 102 : if(SUCCESS == status)
1670 : : {
1671 : 102 : status = move_path(m_text(backup_path),fixture_path);
1672 : : }
1673 : :
1674 : 102 : m_del(backup_path);
1675 : :
1676 : 102 : return(status);
1677 : : }
1678 : :
1679 : : /**
1680 : : * @brief Make a file sparse by extending logical size while keeping allocated blocks unchanged
1681 : : *
1682 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
1683 : : * @param[out] new_size_out Output for the new logical size after sparse growth
1684 : : * @param[out] blocks_after_change_out Output for allocated blocks after sparse growth
1685 : : *
1686 : : * @return Return status code:
1687 : : * - SUCCESS: Sparse size change completed and outputs were filled
1688 : : * - FAILURE: Validation or filesystem operation failed
1689 : : */
1690 : 2 : Return make_sparse_size_change_without_allocated_block_growth(
1691 : : const char *relative_path_to_tmpdir,
1692 : : off_t *new_size_out,
1693 : : blkcnt_t *blocks_after_change_out)
1694 : : {
1695 : : /* Status returned by this function through provide()
1696 : : Default value assumes successful completion */
1697 : 2 : Return status = SUCCESS;
1698 : 2 : struct stat before_stat = {0};
1699 : 2 : struct stat after_stat = {0};
1700 : 2 : m_create(char,absolute_path,MEMORY_STRING);
1701 : :
1702 [ + - + - : 2 : if(relative_path_to_tmpdir == NULL || new_size_out == NULL || blocks_after_change_out == NULL)
- + ]
1703 : : {
1704 : 0 : status = FAILURE;
1705 : : }
1706 : :
1707 [ + - ]: 2 : if(SUCCESS == status)
1708 : : {
1709 : 2 : status = construct_path(relative_path_to_tmpdir,absolute_path);
1710 : : }
1711 : :
1712 [ + - ]: 2 : if(SUCCESS == status)
1713 : : {
1714 : 2 : status = get_file_stat(m_text(absolute_path),&before_stat);
1715 : : }
1716 : :
1717 [ + - ]: 2 : if(SUCCESS == status)
1718 : : {
1719 : 2 : const off_t grown_size = before_stat.st_size + (off_t)131072;
1720 : :
1721 : : // Grow logical size via truncate to create a sparse tail without writing payload bytes
1722 [ - + ]: 2 : if(grown_size <= before_stat.st_size)
1723 : : {
1724 : 0 : status = FAILURE;
1725 [ - + ]: 2 : } else if(truncate(m_text(absolute_path),grown_size) != 0){
1726 : 0 : status = FAILURE;
1727 : : }
1728 : : }
1729 : :
1730 [ + - ]: 2 : if(SUCCESS == status)
1731 : : {
1732 : 2 : status = get_file_stat(m_text(absolute_path),&after_stat);
1733 : : }
1734 : :
1735 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_size <= before_stat.st_size)
1736 : : {
1737 : 0 : status = FAILURE;
1738 : : }
1739 : :
1740 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_blocks != before_stat.st_blocks)
1741 : : {
1742 : 0 : status = FAILURE;
1743 : : }
1744 : :
1745 [ + - ]: 2 : if(SUCCESS == status)
1746 : : {
1747 : 2 : *new_size_out = after_stat.st_size;
1748 : 2 : *blocks_after_change_out = after_stat.st_blocks;
1749 : : }
1750 : :
1751 : 2 : m_del(absolute_path);
1752 : :
1753 : 2 : return(status);
1754 : : }
1755 : :
1756 : : /**
1757 : : * @brief Rewrite file content with dense bytes while preserving logical size
1758 : : *
1759 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
1760 : : * @param[in] target_size Logical size to keep after rewrite
1761 : : * @param[in] blocks_before_rewrite Allocated blocks before rewrite
1762 : : *
1763 : : * @return Return status code:
1764 : : * - SUCCESS: Dense rewrite completed with unchanged logical size and a different allocated block count
1765 : : * - FAILURE: Validation or filesystem operation failed
1766 : : */
1767 : 2 : Return rewrite_file_dense_with_same_size(
1768 : : const char *relative_path_to_tmpdir,
1769 : : const off_t target_size,
1770 : : const blkcnt_t blocks_before_rewrite)
1771 : : {
1772 : : /* Status returned by this function through provide()
1773 : : Default value assumes successful completion */
1774 : 2 : Return status = SUCCESS;
1775 : 2 : FILE *file = NULL;
1776 : 2 : struct stat after_stat = {0};
1777 : : unsigned char buffer[4096];
1778 : 2 : m_create(char,absolute_path,MEMORY_STRING);
1779 : :
1780 : 2 : memset(buffer,'X',sizeof(buffer));
1781 : :
1782 [ + - - + ]: 2 : if(relative_path_to_tmpdir == NULL || target_size <= 0)
1783 : : {
1784 : 0 : status = FAILURE;
1785 : : }
1786 : :
1787 [ + - ]: 2 : if(SUCCESS == status)
1788 : : {
1789 : 2 : status = construct_path(relative_path_to_tmpdir,absolute_path);
1790 : : }
1791 : :
1792 [ + - ]: 2 : if(SUCCESS == status)
1793 : : {
1794 : : // Rewrite the whole file with real bytes while keeping the same logical size
1795 : 2 : status = open_file_stream(
1796 : : absolute_path,
1797 : : "wb",
1798 : : &file);
1799 : : }
1800 : :
1801 : 2 : off_t written = 0;
1802 : :
1803 [ + - + + ]: 68 : while(SUCCESS == status && written < target_size)
1804 : : {
1805 : 66 : const off_t remaining = target_size - written;
1806 : 66 : size_t chunk = sizeof(buffer);
1807 : :
1808 [ + + ]: 66 : if(remaining < (off_t)chunk)
1809 : : {
1810 : 2 : chunk = (size_t)remaining;
1811 : : }
1812 : :
1813 [ - + ]: 66 : if(fwrite(buffer,sizeof(unsigned char),chunk,file) != chunk)
1814 : : {
1815 : 0 : status = FAILURE;
1816 : : } else {
1817 : 66 : written += (off_t)chunk;
1818 : : }
1819 : : }
1820 : :
1821 : 2 : IF(file != NULL)
1822 : : {
1823 [ - + ]: 2 : if(fclose(file) != 0)
1824 : : {
1825 : 0 : status = FAILURE;
1826 : : }
1827 : : }
1828 : :
1829 [ + - ]: 2 : if(SUCCESS == status)
1830 : : {
1831 : 2 : status = get_file_stat(m_text(absolute_path),&after_stat);
1832 : : }
1833 : :
1834 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_size != target_size)
1835 : : {
1836 : 0 : status = FAILURE;
1837 : : }
1838 : :
1839 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_blocks == blocks_before_rewrite)
1840 : : {
1841 : 0 : status = FAILURE;
1842 : : }
1843 : :
1844 : 2 : m_del(absolute_path);
1845 : :
1846 : 2 : return(status);
1847 : : }
1848 : :
1849 : : /**
1850 : : * @brief Compute SHA512 for a file using Monocypher
1851 : : *
1852 : : * @param[in] file_path File path passed directly to `fopen()`
1853 : : * @param[out] sha512_out Output SHA512 digest buffer
1854 : : *
1855 : : * @return Return status code:
1856 : : * - SUCCESS: SHA512 digest computed successfully
1857 : : * - FAILURE: Validation, I/O, or digest-size check failed
1858 : : */
1859 : 24 : Return compute_file_sha512_monocypher(
1860 : : const char *file_path,
1861 : : unsigned char *sha512_out)
1862 : : {
1863 : : /* Status returned by this function through provide()
1864 : : Default value assumes successful completion */
1865 : 24 : Return status = SUCCESS;
1866 : 24 : FILE *file = NULL;
1867 : 24 : crypto_sha512_ctx context = {0};
1868 : : unsigned char buffer[65536];
1869 : :
1870 [ + - - + ]: 24 : if(file_path == NULL || sha512_out == NULL)
1871 : : {
1872 : 0 : status = FAILURE;
1873 : : }
1874 : :
1875 : : if(SUCCESS == status && SHA512_DIGEST_LENGTH != 64)
1876 : : {
1877 : : status = FAILURE;
1878 : : }
1879 : :
1880 [ + - ]: 24 : if(SUCCESS == status)
1881 : : {
1882 : 24 : file = fopen(file_path,"rb");
1883 : :
1884 [ - + ]: 24 : if(file == NULL)
1885 : : {
1886 : 0 : status = FAILURE;
1887 : : }
1888 : : }
1889 : :
1890 [ + - ]: 24 : if(SUCCESS == status)
1891 : : {
1892 : 24 : crypto_sha512_init(&context);
1893 : : }
1894 : :
1895 [ + - ]: 1002 : while(SUCCESS == status)
1896 : : {
1897 : 1002 : const size_t bytes_read = fread(buffer,sizeof(unsigned char),sizeof(buffer),file);
1898 : :
1899 [ + + ]: 1002 : if(bytes_read == 0U)
1900 : : {
1901 [ - + ]: 24 : if(ferror(file) != 0)
1902 : : {
1903 : 0 : status = FAILURE;
1904 : : }
1905 : 24 : break;
1906 : : }
1907 : :
1908 : 978 : crypto_sha512_update(&context,buffer,bytes_read);
1909 : : }
1910 : :
1911 : 24 : IF(SUCCESS == status)
1912 : : {
1913 : 24 : crypto_sha512_final(&context,sha512_out);
1914 : : }
1915 : :
1916 : 24 : IF(file != NULL)
1917 : : {
1918 : 24 : (void)fclose(file);
1919 : : }
1920 : :
1921 : 24 : return(status);
1922 : : }
1923 : :
1924 : : /**
1925 : : * @brief Append one byte to a file using native C file I/O
1926 : : *
1927 : : * @param[in] file_path_buffer Managed path string passed directly to `open_file_stream()`
1928 : : * @param[in] byte Byte value to append
1929 : : *
1930 : : * @return Return status code:
1931 : : * - SUCCESS: Byte appended successfully
1932 : : * - FAILURE: Validation or I/O failed
1933 : : */
1934 : 2 : Return append_byte_to_file(
1935 : : const memory *file_path_buffer,
1936 : : unsigned char byte)
1937 : : {
1938 : : /* Status returned by this function through provide()
1939 : : Default value assumes successful completion */
1940 : 2 : Return status = SUCCESS;
1941 : 2 : FILE *file = NULL;
1942 : :
1943 [ - + ]: 2 : if(file_path_buffer == NULL)
1944 : : {
1945 : 0 : status = FAILURE;
1946 : : }
1947 : :
1948 [ + - ]: 2 : if(SUCCESS == status)
1949 : : {
1950 : 2 : status = open_file_stream(
1951 : : file_path_buffer,
1952 : : "ab",
1953 : : &file);
1954 : : }
1955 : :
1956 [ + - ]: 2 : if(SUCCESS == status)
1957 : : {
1958 [ - + ]: 2 : if(fwrite(&byte,sizeof(unsigned char),1U,file) != 1U)
1959 : : {
1960 : 0 : status = FAILURE;
1961 : : }
1962 : : }
1963 : :
1964 : 2 : IF(file != NULL)
1965 : : {
1966 [ - + ]: 2 : if(fclose(file) != 0)
1967 : : {
1968 : 0 : status = FAILURE;
1969 : : }
1970 : : }
1971 : :
1972 : 2 : return(status);
1973 : : }
1974 : :
1975 : : /**
1976 : : * @brief Write string to file with append or replace mode
1977 : : *
1978 : : * The function writes bytes exactly as provided and never appends a newline
1979 : : *
1980 : : * @param[in] file_content String payload to write
1981 : : * @param[in] file_path File path relative to TMPDIR
1982 : : * @param[in] write_flags One of FILE_WRITE_APPEND or FILE_WRITE_REPLACE
1983 : : *
1984 : : * @return Return status code:
1985 : : * - SUCCESS: The string bytes were written successfully
1986 : : * - FAILURE: Validation, path resolution, or file I/O failed
1987 : : */
1988 : 53 : Return write_string_to_file(
1989 : : const char *file_content,
1990 : : const char *file_path,
1991 : : const unsigned int write_flags)
1992 : : {
1993 : : /* Status returned by this function through provide()
1994 : : Default value assumes successful completion */
1995 : 53 : Return status = SUCCESS;
1996 : 53 : FILE *file = NULL;
1997 : 53 : const char *open_mode = NULL;
1998 : 53 : m_create(char,absolute_path,MEMORY_STRING);
1999 : :
2000 [ + - - + ]: 53 : if(file_content == NULL || file_path == NULL)
2001 : : {
2002 : 0 : status = FAILURE;
2003 : : }
2004 : :
2005 [ + - ]: 53 : if(SUCCESS == status)
2006 : : {
2007 [ + + ]: 53 : if(write_flags == FILE_WRITE_APPEND)
2008 : : {
2009 : 34 : open_mode = "ab";
2010 [ + - ]: 19 : } else if(write_flags == FILE_WRITE_REPLACE){
2011 : 19 : open_mode = "wb";
2012 : : } else {
2013 : 0 : status = FAILURE;
2014 : : }
2015 : : }
2016 : :
2017 [ + - ]: 53 : if(SUCCESS == status)
2018 : : {
2019 : 53 : status = construct_path(file_path,absolute_path);
2020 : : }
2021 : :
2022 [ + - ]: 53 : if(SUCCESS == status)
2023 : : {
2024 : 53 : status = open_file_stream(
2025 : : absolute_path,
2026 : : open_mode,
2027 : : &file);
2028 : : }
2029 : :
2030 [ + - ]: 53 : if(SUCCESS == status)
2031 : : {
2032 : 53 : const size_t bytes_to_write = strlen(file_content);
2033 : :
2034 [ + - ]: 53 : if(bytes_to_write > 0U
2035 [ - + ]: 53 : && fwrite(file_content,sizeof(char),bytes_to_write,file) != bytes_to_write)
2036 : : {
2037 : 0 : status = FAILURE;
2038 : : }
2039 : : }
2040 : :
2041 : 53 : IF(file != NULL && fclose(file) != 0)
2042 : : {
2043 : 0 : status = FAILURE;
2044 : : }
2045 : :
2046 : 53 : m_del(absolute_path);
2047 : :
2048 : 53 : return(status);
2049 : : }
2050 : :
2051 : : /**
2052 : : * @brief Append string bytes to file without newline
2053 : : *
2054 : : * @param[in] file_content String payload to append
2055 : : * @param[in] file_path File path relative to TMPDIR
2056 : : *
2057 : : * @return Return status code
2058 : : */
2059 : 32 : Return add_string_to(
2060 : : const char *file_content,
2061 : : const char *file_path)
2062 : : {
2063 : 32 : return(write_string_to_file(file_content,file_path,FILE_WRITE_APPEND));
2064 : : }
2065 : :
2066 : : /**
2067 : : * @brief Replace file content with string bytes without newline
2068 : : *
2069 : : * @param[in] file_content String payload to write
2070 : : * @param[in] file_path File path relative to TMPDIR
2071 : : *
2072 : : * @return Return status code
2073 : : */
2074 : 19 : Return replase_to_string(
2075 : : const char *file_content,
2076 : : const char *file_path)
2077 : : {
2078 : 19 : return(write_string_to_file(file_content,file_path,FILE_WRITE_REPLACE));
2079 : : }
2080 : :
2081 : : /**
2082 : : * @brief Set target file mtime to source mtime plus a nanosecond delta using native POSIX calls
2083 : : *
2084 : : * Relative paths are resolved from TMPDIR with construct_path
2085 : : * Source and target can be the same file
2086 : : * If relative_source_path is NULL, relative_target_path is used as source
2087 : : * If relative_target_path is NULL, the function returns FAILURE
2088 : : * The delta is applied in nanoseconds and can be any signed integer value
2089 : : * If mtime_delta_nanoseconds is 0, target mtime is set to source mtime
2090 : : * atime is preserved with UTIME_OMIT
2091 : : * Even when resulting mtime equals current mtime, successful metadata update may still change ctime
2092 : : * ctime cannot be set directly from userspace and will change automatically after metadata update
2093 : : *
2094 : : * @param[in] relative_source_path Relative path from TMPDIR to source file or NULL
2095 : : * @param[in] relative_target_path Relative path from TMPDIR to target file
2096 : : * @param[in] mtime_delta_nanoseconds Signed nanosecond delta applied to source mtime
2097 : : *
2098 : : * @return Return status code:
2099 : : * - SUCCESS: Target mtime was updated
2100 : : * - FAILURE: Validation, path resolution, stat, normalization, or timestamp update failed
2101 : : */
2102 : 20 : Return touch_file_mtime_with_reference_delta_ns(
2103 : : const char *relative_source_path,
2104 : : const char *relative_target_path,
2105 : : int64_t mtime_delta_nanoseconds)
2106 : : {
2107 : : /* Status returned by this function through provide()
2108 : : Default value assumes successful completion */
2109 : 20 : Return status = SUCCESS;
2110 : 20 : struct stat source_file_stat = {0};
2111 : 20 : struct timespec target_times[2] = {{0}};
2112 : 20 : const char *source_relative_path = relative_source_path;
2113 : 20 : m_create(char,source_absolute_path,MEMORY_STRING);
2114 : 20 : m_create(char,target_absolute_path,MEMORY_STRING);
2115 : :
2116 : : // Require a target path because the mtime update is applied to this file
2117 [ - + ]: 20 : if(relative_target_path == NULL)
2118 : : {
2119 : 0 : status = FAILURE;
2120 : : }
2121 : :
2122 : : // Reuse target as source when source path is not provided
2123 [ + - + + ]: 20 : if(SUCCESS == status && source_relative_path == NULL)
2124 : : {
2125 : 14 : source_relative_path = relative_target_path;
2126 : : }
2127 : :
2128 : : // Resolve source path relative to TMPDIR
2129 [ + - ]: 20 : if(SUCCESS == status)
2130 : : {
2131 : 20 : status = construct_path(source_relative_path,source_absolute_path);
2132 : : }
2133 : :
2134 : : // Resolve target path relative to TMPDIR
2135 [ + - ]: 20 : if(SUCCESS == status)
2136 : : {
2137 : 20 : status = construct_path(relative_target_path,target_absolute_path);
2138 : : }
2139 : :
2140 : : // Read source stat to use its mtime as the reference point
2141 [ + - ]: 20 : if(SUCCESS == status)
2142 : : {
2143 : 20 : status = get_file_stat(m_text(source_absolute_path),&source_file_stat);
2144 : : }
2145 : :
2146 : : // Build target mtime by applying and normalizing nanosecond delta
2147 [ + - ]: 20 : if(SUCCESS == status)
2148 : : {
2149 : 20 : const intmax_t nanoseconds_per_second = 1000000000;
2150 : 20 : intmax_t target_seconds = (intmax_t)source_file_stat.st_mtim.tv_sec
2151 : 20 : + (intmax_t)(mtime_delta_nanoseconds / nanoseconds_per_second);
2152 : 20 : long target_nanoseconds = source_file_stat.st_mtim.tv_nsec
2153 : 20 : + (long)(mtime_delta_nanoseconds % nanoseconds_per_second);
2154 : :
2155 : : // Normalize nanosecond overflow into next second
2156 [ - + ]: 20 : if(target_nanoseconds >= (long)nanoseconds_per_second)
2157 : : {
2158 : 0 : target_nanoseconds -= (long)nanoseconds_per_second;
2159 : 0 : target_seconds++;
2160 : : // Normalize negative nanoseconds by borrowing one second
2161 [ - + ]: 20 : } else if(target_nanoseconds < 0){
2162 : 0 : target_nanoseconds += (long)nanoseconds_per_second;
2163 : 0 : target_seconds--;
2164 : : }
2165 : :
2166 : 20 : time_t normalized_target_seconds = (time_t)target_seconds;
2167 : :
2168 : : // Ensure computed seconds value is representable as time_t
2169 [ - + ]: 20 : if((intmax_t)normalized_target_seconds != target_seconds)
2170 : : {
2171 : 0 : status = FAILURE;
2172 : : } else {
2173 : : // Keep atime unchanged and prepare mtime for utimensat
2174 : 20 : target_times[0].tv_nsec = UTIME_OMIT;
2175 : 20 : target_times[1].tv_sec = normalized_target_seconds;
2176 : 20 : target_times[1].tv_nsec = target_nanoseconds;
2177 : : }
2178 : : }
2179 : :
2180 : : // Apply the prepared timestamp values to the target file
2181 [ + - ]: 20 : if(SUCCESS == status)
2182 : : {
2183 [ - + ]: 20 : if(utimensat(0,m_text(target_absolute_path),target_times,0) != 0)
2184 : : {
2185 : 0 : status = FAILURE;
2186 : : }
2187 : : }
2188 : :
2189 : 20 : m_del(source_absolute_path);
2190 : 20 : m_del(target_absolute_path);
2191 : :
2192 : 20 : return(status);
2193 : : }
2194 : :
2195 : : /**
2196 : : * @brief Modify first two bytes of a file and restore atime/mtime best effort
2197 : : *
2198 : : * @param[in] relative_path Relative path from TMPDIR to the target file
2199 : : *
2200 : : * @return Return status code:
2201 : : * - SUCCESS: File bytes were modified
2202 : : * - FAILURE: Validation or filesystem operation failed
2203 : : */
2204 : 8 : Return tamper_locked_file_bytes(const char *relative_path)
2205 : : {
2206 : : /* Status returned by this function through provide()
2207 : : Default value assumes successful completion */
2208 : 8 : Return status = SUCCESS;
2209 : 8 : int fd = -1;
2210 : 8 : struct stat before = {0};
2211 : 8 : unsigned char buffer[2] = {0};
2212 : 8 : m_create(char,file_path,MEMORY_STRING);
2213 : :
2214 : : // Validate input path before any filesystem operations
2215 [ + - - + ]: 8 : if(SUCCESS == status && relative_path == NULL)
2216 : : {
2217 : 0 : status = FAILURE;
2218 : : }
2219 : :
2220 : : // Resolve path relative to TMPDIR
2221 [ + - ]: 8 : if(SUCCESS == status)
2222 : : {
2223 : 8 : status = construct_path(relative_path,file_path);
2224 : : }
2225 : :
2226 : : // Open file for in-place read and write operations
2227 [ + - - + ]: 8 : if(SUCCESS == status && (fd = open(m_text(file_path),O_RDWR)) < 0)
2228 : : {
2229 : 0 : status = FAILURE;
2230 : : }
2231 : :
2232 : : // Read file metadata to preserve timestamps later
2233 [ + - - + ]: 8 : if(SUCCESS == status && fstat(fd,&before) != 0)
2234 : : {
2235 : 0 : status = FAILURE;
2236 : : }
2237 : :
2238 : : // Require at least two bytes because exactly two bytes are modified
2239 [ + - - + ]: 8 : if(SUCCESS == status && before.st_size < (off_t)sizeof(buffer))
2240 : : {
2241 : 0 : status = FAILURE;
2242 : : }
2243 : :
2244 : : // Read first two bytes that will be modified
2245 [ + - - + ]: 8 : if(SUCCESS == status && pread(fd,buffer,sizeof(buffer),0) != (ssize_t)sizeof(buffer))
2246 : : {
2247 : 0 : status = FAILURE;
2248 : : }
2249 : :
2250 : : // Flip both bytes to guarantee content and checksum change
2251 [ + - ]: 8 : if(SUCCESS == status)
2252 : : {
2253 : 8 : buffer[0] = (unsigned char)~buffer[0];
2254 : 8 : buffer[1] = (unsigned char)~buffer[1];
2255 : : }
2256 : :
2257 : : // Write modified bytes back to file start
2258 [ + - - + ]: 8 : if(SUCCESS == status && pwrite(fd,buffer,sizeof(buffer),0) != (ssize_t)sizeof(buffer))
2259 : : {
2260 : 0 : status = FAILURE;
2261 : : }
2262 : :
2263 : : // Restore atime and mtime best effort after content tampering
2264 [ + - ]: 8 : if(SUCCESS == status)
2265 : : {
2266 : 8 : struct timespec times[2] = {{0}};
2267 : :
2268 : : // Best effort restore for atime and mtime while ctime still changes on POSIX
2269 : 8 : times[0] = before.st_atim;
2270 : 8 : times[1] = before.st_mtim;
2271 : :
2272 [ - + ]: 8 : if(futimens(fd,times) != 0)
2273 : : {
2274 : 0 : status = FAILURE;
2275 : : }
2276 : : }
2277 : :
2278 : : // Close descriptor on all paths where open succeeded
2279 [ + - ]: 8 : if(fd >= 0)
2280 : : {
2281 : 8 : (void)close(fd);
2282 : : }
2283 : :
2284 : 8 : m_del(file_path);
2285 : :
2286 : 8 : return(status);
2287 : : }
|