Branch data Line data Source code
1 : : #include "sute.h"
2 : : #include <errno.h>
3 : : #include <fcntl.h>
4 : : #include <ftw.h>
5 : : #include <stdlib.h>
6 : :
7 : : static const char *copy_source_root_for_nftw = NULL;
8 : : static const char *copy_destination_root_for_nftw = NULL;
9 : : static size_t copy_source_root_length_for_nftw = 0U;
10 : : static size_t copy_destination_root_length_for_nftw = 0U;
11 : :
12 : : /**
13 : : * @brief Reset nftw copy context to an empty state
14 : : */
15 : 132 : static void reset_nftw_copy_context(void)
16 : : {
17 : 132 : copy_source_root_for_nftw = NULL;
18 : 132 : copy_destination_root_for_nftw = NULL;
19 : 132 : copy_source_root_length_for_nftw = 0U;
20 : 132 : copy_destination_root_length_for_nftw = 0U;
21 : 132 : }
22 : :
23 : : /**
24 : : * @brief Open a writable file stream with explicit create mode 0600
25 : : *
26 : : * Supports only `"ab"` and `"wb"` modes
27 : : *
28 : : * @param[in] file_path Managed path string passed directly to `open()`
29 : : * @param[in] stream_open_mode Mode string for `fdopen()`
30 : : * @param[out] opened_file_stream_out Output writable stream
31 : : *
32 : : * @return Return status code
33 : : */
34 : 60 : Return open_file_stream(
35 : : const memory *file_path,
36 : : const char *stream_open_mode,
37 : : FILE **opened_file_stream_out)
38 : : {
39 : : /* Status returned by this function through provide()
40 : : Default value assumes successful completion */
41 : 60 : Return status = SUCCESS;
42 : 60 : int writable_file_descriptor = -1;
43 : 60 : int file_open_flags = 0;
44 : 60 : const char *file_path_string = NULL;
45 : :
46 [ + - + - : 60 : if(file_path == NULL || stream_open_mode == NULL || opened_file_stream_out == NULL)
- + ]
47 : : {
48 : 0 : status = FAILURE;
49 : : }
50 : :
51 [ + - ]: 60 : if(opened_file_stream_out != NULL)
52 : : {
53 : 60 : *opened_file_stream_out = NULL;
54 : : }
55 : :
56 [ + - ]: 60 : if(SUCCESS == status)
57 : : {
58 : 60 : file_path_string = getcstring(file_path);
59 : : }
60 : :
61 [ + - ]: 60 : if(SUCCESS == status)
62 : : {
63 [ + + ]: 60 : if(strcmp(stream_open_mode,"ab") == 0)
64 : : {
65 : 30 : file_open_flags = O_WRONLY | O_CREAT | O_APPEND;
66 [ + - ]: 30 : } else if(strcmp(stream_open_mode,"wb") == 0){
67 : 30 : file_open_flags = O_WRONLY | O_CREAT | O_TRUNC;
68 : : } else {
69 : 0 : status = FAILURE;
70 : : }
71 : : }
72 : :
73 [ + - ]: 60 : if(SUCCESS == status)
74 : : {
75 : 60 : writable_file_descriptor = open(file_path_string,file_open_flags,0600);
76 [ - + ]: 60 : if(writable_file_descriptor < 0)
77 : : {
78 : 0 : status = FAILURE;
79 : : }
80 : : }
81 : :
82 [ + - ]: 60 : if(SUCCESS == status)
83 : : {
84 : 60 : *opened_file_stream_out = fdopen(writable_file_descriptor,stream_open_mode);
85 [ - + ]: 60 : if(*opened_file_stream_out == NULL)
86 : : {
87 : 0 : (void)close(writable_file_descriptor);
88 : 0 : status = FAILURE;
89 : : }
90 : : }
91 : :
92 : 60 : return(status);
93 : : }
94 : :
95 : : /**
96 : : * @brief Apply source mtime to destination path using utimensat
97 : : *
98 : : * @param[in] destination_path Destination filesystem path
99 : : * @param[in] source_stat Source stat metadata
100 : : * @param[in] utimensat_flags Flags forwarded to utimensat
101 : : *
102 : : * @return 0 on success, non-zero on failure
103 : : */
104 : 4722 : static int apply_mtime_from_source_stat(
105 : : const char *destination_path,
106 : : const struct stat *source_stat,
107 : : const int utimensat_flags)
108 : : {
109 : 4722 : int status = 0;
110 : 4722 : struct timespec times[2] = {0};
111 : :
112 [ + - - + ]: 4722 : if(destination_path == NULL || source_stat == NULL)
113 : : {
114 : 0 : status = -1;
115 : : }
116 : :
117 [ + - ]: 4722 : if(status == 0)
118 : : {
119 : 4722 : times[0].tv_nsec = UTIME_OMIT;
120 : 4722 : times[1] = source_stat->st_mtim;
121 [ - + ]: 4722 : if(utimensat(0,destination_path,times,utimensat_flags) != 0)
122 : : {
123 : 0 : status = -1;
124 : : }
125 : : }
126 : :
127 : 4722 : return(status);
128 : : }
129 : :
130 : : /**
131 : : * @brief Remove callback used by nftw
132 : : *
133 : : * @param[in] path Current path from nftw walk
134 : : * @param[in] stat_buffer Unused file stat pointer from nftw
135 : : * @param[in] type_flag Unused type flag from nftw
136 : : * @param[in] ftw_buffer Unused nftw frame data
137 : : *
138 : : * @return 0 on success, non-zero on failure
139 : : */
140 : 4699 : static int nftw_remove_callback(
141 : : const char *path,
142 : : const struct stat *stat_buffer,
143 : : const int type_flag,
144 : : struct FTW *ftw_buffer)
145 : : {
146 : : (void)stat_buffer;
147 : : (void)type_flag;
148 : : (void)ftw_buffer;
149 : :
150 [ + + ]: 4699 : if(remove(path) != 0)
151 : : {
152 : 1 : echo(STDERR,"delete_path: remove failed for %s: %s\n",path,strerror(errno));
153 : 1 : return(-1);
154 : : }
155 : :
156 : 4698 : return(0);
157 : : }
158 : :
159 : : /**
160 : : * @brief Build destination path for nftw copy callback
161 : : *
162 : : * @param[in] source_path Current source path from nftw
163 : : * @param[out] destination_path_out Destination path descriptor
164 : : *
165 : : * @return 0 on success, non-zero on failure
166 : : */
167 : 8165 : static int build_copy_destination_path_for_nftw(
168 : : const char *source_path,
169 : : memory *destination_path_out)
170 : : {
171 : 8165 : int status = 0;
172 : 8165 : const char *relative_path = NULL;
173 : :
174 [ + - ]: 8165 : if(source_path == NULL
175 [ + - ]: 8165 : || destination_path_out == NULL
176 [ + - ]: 8165 : || copy_source_root_for_nftw == NULL
177 [ - + ]: 8165 : || copy_destination_root_for_nftw == NULL)
178 : : {
179 : 0 : status = -1;
180 : : }
181 : :
182 [ + - - + ]: 8165 : if(status == 0 && strncmp(source_path,
183 : : copy_source_root_for_nftw,
184 : : copy_source_root_length_for_nftw) != 0)
185 : : {
186 : 0 : status = -1;
187 : : }
188 : :
189 [ + - ]: 8165 : if(status == 0)
190 : : {
191 : 8165 : relative_path = source_path + copy_source_root_length_for_nftw;
192 : :
193 [ + + ]: 8165 : if(relative_path[0] == '/')
194 : : {
195 : 8017 : relative_path++;
196 : : }
197 : : }
198 : :
199 [ + - ]: 8165 : if(status == 0)
200 : : {
201 [ - + ]: 8165 : if(copy_cstring(destination_path_out,
202 : : copy_destination_root_for_nftw,
203 : : copy_destination_root_length_for_nftw + 1U) != SUCCESS)
204 : : {
205 : 0 : status = -1;
206 : : }
207 : :
208 [ + - + + ]: 8165 : if(status == 0 && relative_path[0] != '\0')
209 : : {
210 [ - + ]: 8017 : if(concat_literal(destination_path_out,"/") != SUCCESS)
211 : : {
212 : 0 : status = -1;
213 : : }
214 : : }
215 : :
216 [ + - + + ]: 8165 : if(status == 0 && relative_path[0] != '\0')
217 : : {
218 [ - + ]: 8017 : if(concat_cstring(destination_path_out,relative_path,strlen(relative_path) + 1U) != SUCCESS)
219 : : {
220 : 0 : status = -1;
221 : : }
222 : : }
223 : : }
224 : :
225 : 8165 : return(status);
226 : : }
227 : :
228 : : /**
229 : : * @brief Create one directory and accept an existing path that resolves to a directory
230 : : *
231 : : * Existing symlinks to directories are treated as valid path components
232 : : *
233 : : * @param[in] directory_path Absolute directory path
234 : : *
235 : : * @return 0 on success, non-zero on failure
236 : : */
237 : 56 : static int create_directory_if_missing(
238 : : const char *directory_path)
239 : : {
240 : 56 : int status = 0;
241 : 56 : struct stat directory_stat = {0};
242 : :
243 [ - + ]: 56 : if(directory_path == NULL)
244 : : {
245 : 0 : status = -1;
246 : : }
247 : :
248 [ + - + + : 56 : if(status == 0 && mkdir(directory_path,0700) != 0 && errno != EEXIST)
- + ]
249 : : {
250 : 0 : status = -1;
251 : : }
252 : :
253 [ + - - + ]: 56 : if(status == 0 && stat(directory_path,&directory_stat) != 0)
254 : : {
255 : 0 : status = -1;
256 : : }
257 : :
258 [ + - + + ]: 56 : if(status == 0 && !S_ISDIR(directory_stat.st_mode))
259 : : {
260 : 1 : status = -1;
261 : : }
262 : :
263 : 56 : return(status);
264 : : }
265 : :
266 : : /**
267 : : * @brief Construct absolute path from environment root and relative child path
268 : : *
269 : : * @param[in] environment_variable_name Environment variable containing root path
270 : : * @param[in] relative_path Relative child path to append
271 : : * @param[out] absolute_path_out Output absolute path buffer
272 : : *
273 : : * @return Return status code
274 : : */
275 : 19 : static Return construct_path_from_environment_variable(
276 : : const char *environment_variable_name,
277 : : const char *relative_path,
278 : : memory *absolute_path_out)
279 : : {
280 : : /* Status returned by this function through provide()
281 : : Default value assumes successful completion */
282 : 19 : Return status = SUCCESS;
283 : 19 : const char *root_path = NULL;
284 : 19 : size_t absolute_path_size = 0U;
285 : :
286 [ + - + - : 19 : if(environment_variable_name == NULL || relative_path == NULL || absolute_path_out == NULL)
- + ]
287 : : {
288 : 0 : status = FAILURE;
289 : : }
290 : :
291 [ + - ]: 19 : if(SUCCESS == status)
292 : : {
293 : 19 : root_path = getenv(environment_variable_name);
294 [ - + ]: 19 : if(root_path == NULL)
295 : : {
296 : 0 : status = FAILURE;
297 : : }
298 : : }
299 : :
300 [ + - ]: 19 : if(SUCCESS == status)
301 : : {
302 : 19 : absolute_path_size = strlen(root_path) + strlen(relative_path) + 2U;
303 : 19 : status = resize(absolute_path_out,absolute_path_size);
304 : : }
305 : :
306 [ + - ]: 19 : if(SUCCESS == status)
307 : : {
308 : 19 : char *absolute_path_data = data(char,absolute_path_out);
309 : :
310 [ - + ]: 19 : if(absolute_path_data == NULL)
311 : : {
312 : 0 : status = FAILURE;
313 [ - + ]: 19 : } else if(snprintf(absolute_path_data,absolute_path_size,"%s/%s",root_path,relative_path) < 0){
314 : 0 : status = FAILURE;
315 : : }
316 : : }
317 : :
318 : 19 : return(status);
319 : : }
320 : :
321 : : /**
322 : : * @brief Copy regular file contents into destination path
323 : : *
324 : : * @param[in] source_path Source regular file path
325 : : * @param[in] destination_path Destination regular file path
326 : : * @param[in] destination_mode Mode for destination file
327 : : *
328 : : * @return 0 on success, non-zero on failure
329 : : */
330 : 1156 : static int copy_regular_file_contents(
331 : : const char *source_path,
332 : : const char *destination_path,
333 : : const mode_t destination_mode)
334 : : {
335 : 1156 : int status = 0;
336 : 1156 : int source_fd = -1;
337 : 1156 : int destination_fd = -1;
338 : : unsigned char buffer[65536];
339 : :
340 [ + - - + ]: 1156 : if(source_path == NULL || destination_path == NULL)
341 : : {
342 : 0 : status = -1;
343 : : }
344 : :
345 [ + - ]: 1156 : if(status == 0)
346 : : {
347 : 1156 : source_fd = open(source_path,O_RDONLY);
348 [ - + ]: 1156 : if(source_fd < 0)
349 : : {
350 : 0 : status = -1;
351 : : }
352 : : }
353 : :
354 [ + - ]: 1156 : if(status == 0)
355 : : {
356 : 1156 : destination_fd = open(destination_path,
357 : : O_WRONLY | O_CREAT | O_TRUNC,
358 : : destination_mode & (mode_t)07777);
359 [ - + ]: 1156 : if(destination_fd < 0)
360 : : {
361 : 0 : status = -1;
362 : : }
363 : : }
364 : :
365 [ + - - + ]: 1156 : if(status == 0 && fchmod(destination_fd,destination_mode & (mode_t)07777) != 0)
366 : : {
367 : 0 : status = -1;
368 : : }
369 : :
370 [ + - ]: 3198 : while(status == 0)
371 : : {
372 : 3198 : const ssize_t bytes_read = read(source_fd,buffer,sizeof(buffer));
373 : :
374 [ - + ]: 3198 : if(bytes_read < 0)
375 : : {
376 [ # # ]: 0 : if(errno == EINTR)
377 : : {
378 : 0 : continue;
379 : : }
380 : 0 : status = -1;
381 : 0 : break;
382 : : }
383 : :
384 [ + + ]: 3198 : if(bytes_read == 0)
385 : : {
386 : 1156 : break;
387 : : }
388 : :
389 : 2042 : ssize_t offset = 0;
390 : :
391 [ + + ]: 4084 : while(offset < bytes_read)
392 : : {
393 : 2042 : const ssize_t bytes_written = write(destination_fd,
394 : : buffer + offset,
395 : 2042 : (size_t)(bytes_read - offset));
396 : :
397 [ - + ]: 2042 : if(bytes_written < 0)
398 : : {
399 [ # # ]: 0 : if(errno == EINTR)
400 : : {
401 : 0 : continue;
402 : : }
403 : 0 : status = -1;
404 : 0 : break;
405 : : }
406 : :
407 : 2042 : offset += bytes_written;
408 : : }
409 : : }
410 : :
411 [ + - - + ]: 1156 : if(source_fd >= 0 && close(source_fd) != 0)
412 : : {
413 : 0 : status = -1;
414 : : }
415 : :
416 [ + - - + ]: 1156 : if(destination_fd >= 0 && close(destination_fd) != 0)
417 : : {
418 : 0 : status = -1;
419 : : }
420 : :
421 : 1156 : return(status);
422 : : }
423 : :
424 : : /**
425 : : * @brief Copy symbolic link target from source to destination
426 : : *
427 : : * @param[in] source_path Source symbolic link path
428 : : * @param[in] destination_path Destination symbolic link path
429 : : *
430 : : * @return 0 on success, non-zero on failure
431 : : */
432 : 65 : static int copy_symbolic_link(
433 : : const char *source_path,
434 : : const char *destination_path)
435 : : {
436 : 65 : int status = 0;
437 : 65 : size_t link_target_buffer_size = 256U;
438 : 65 : char *link_target = NULL;
439 : 65 : ssize_t link_target_size = -1;
440 : :
441 [ + - - + ]: 65 : if(source_path == NULL || destination_path == NULL)
442 : : {
443 : 0 : status = -1;
444 : : }
445 : :
446 [ + - ]: 65 : if(status == 0)
447 : : {
448 : 65 : link_target = malloc(link_target_buffer_size);
449 [ - + ]: 65 : if(link_target == NULL)
450 : : {
451 : 0 : status = -1;
452 : : }
453 : : }
454 : :
455 [ + - ]: 65 : while(status == 0)
456 : : {
457 : 65 : link_target_size = readlink(source_path,link_target,link_target_buffer_size - 1U);
458 : :
459 [ - + ]: 65 : if(link_target_size < 0)
460 : : {
461 : 0 : status = -1;
462 : 0 : break;
463 : : }
464 : :
465 [ + - ]: 65 : if((size_t)link_target_size < link_target_buffer_size - 1U)
466 : : {
467 : 65 : link_target[link_target_size] = '\0';
468 : 65 : break;
469 : : }
470 : :
471 : 0 : link_target_buffer_size *= 2U;
472 : 0 : char *resized_link_target = realloc(link_target,link_target_buffer_size);
473 : :
474 [ # # ]: 0 : if(resized_link_target == NULL)
475 : : {
476 : 0 : status = -1;
477 : : } else {
478 : 0 : link_target = resized_link_target;
479 : : }
480 : : }
481 : :
482 [ + - - + ]: 65 : if(status == 0 && symlink(link_target,destination_path) != 0)
483 : : {
484 : 0 : status = -1;
485 : : }
486 : :
487 : 65 : free(link_target);
488 : :
489 : 65 : return(status);
490 : : }
491 : :
492 : : /**
493 : : * @brief Copy callback used by nftw
494 : : *
495 : : * @param[in] path Current path from nftw walk
496 : : * @param[in] stat_buffer File stat pointer from nftw
497 : : * @param[in] type_flag nftw node type
498 : : * @param[in] ftw_buffer Unused nftw frame data
499 : : *
500 : : * @return 0 on success, non-zero on failure
501 : : */
502 : 4664 : static int nftw_copy_callback(
503 : : const char *path,
504 : : const struct stat *stat_buffer,
505 : : const int type_flag,
506 : : struct FTW *ftw_buffer)
507 : : {
508 : : (void)ftw_buffer;
509 : :
510 : 4664 : int status = 0;
511 : 4664 : create(char,destination_path);
512 : 4664 : const char *destination_path_string = NULL;
513 : :
514 [ - + ]: 4664 : if(stat_buffer == NULL)
515 : : {
516 : 0 : status = -1;
517 : : }
518 : :
519 [ + - - + ]: 4664 : if(status == 0 && build_copy_destination_path_for_nftw(path,destination_path) != 0)
520 : : {
521 : 0 : status = -1;
522 : : }
523 : :
524 [ + - ]: 4664 : if(status == 0)
525 : : {
526 : 4664 : destination_path_string = getcstring(destination_path);
527 [ - + ]: 4664 : if(destination_path_string == NULL)
528 : : {
529 : 0 : status = -1;
530 : : }
531 : : }
532 : :
533 [ + - ]: 4664 : if(status == 0)
534 : : {
535 [ + + + - ]: 4664 : switch(type_flag)
536 : : {
537 : 3501 : case FTW_D:
538 [ - + - - ]: 3501 : if(mkdir(destination_path_string,stat_buffer->st_mode & (mode_t)07777) != 0 && errno != EEXIST)
539 : : {
540 : 0 : status = -1;
541 : : }
542 [ + - - + ]: 3501 : if(status == 0 && chmod(destination_path_string,stat_buffer->st_mode & (mode_t)07777) != 0)
543 : : {
544 : 0 : status = -1;
545 : : }
546 : 3501 : break;
547 : 1098 : case FTW_F:
548 [ - + ]: 1098 : if(copy_regular_file_contents(path,destination_path_string,stat_buffer->st_mode) != 0)
549 : : {
550 : 0 : status = -1;
551 : : }
552 [ + - - + ]: 1098 : if(status == 0 && apply_mtime_from_source_stat(destination_path_string,stat_buffer,0) != 0)
553 : : {
554 : 0 : status = -1;
555 : : }
556 : 1098 : break;
557 : 65 : case FTW_SL:
558 : : #ifdef FTW_SLN
559 : : case FTW_SLN:
560 : : #endif
561 [ + - - + ]: 65 : if(unlink(destination_path_string) != 0 && errno != ENOENT)
562 : : {
563 : 0 : status = -1;
564 : : }
565 [ + - - + ]: 65 : if(status == 0 && copy_symbolic_link(path,destination_path_string) != 0)
566 : : {
567 : 0 : status = -1;
568 : : }
569 : : #ifdef AT_SYMLINK_NOFOLLOW
570 [ + - ]: 65 : if(status == 0
571 [ - + ]: 65 : && apply_mtime_from_source_stat(destination_path_string,stat_buffer,AT_SYMLINK_NOFOLLOW) != 0)
572 : : {
573 : 0 : status = -1;
574 : : }
575 : : #endif
576 : 65 : break;
577 : 0 : default:
578 : 0 : status = -1;
579 : 0 : break;
580 : : }
581 : : }
582 : :
583 : 4664 : del(destination_path);
584 : :
585 : 4664 : return(status);
586 : : }
587 : :
588 : : /**
589 : : * @brief Post-order callback to restore copied directory mtimes
590 : : *
591 : : * @param[in] path Current source path from nftw walk
592 : : * @param[in] stat_buffer Source stat metadata
593 : : * @param[in] type_flag nftw node type
594 : : * @param[in] ftw_buffer Unused nftw frame data
595 : : *
596 : : * @return 0 on success, non-zero on failure
597 : : */
598 : 4664 : static int nftw_sync_directory_mtime_callback(
599 : : const char *path,
600 : : const struct stat *stat_buffer,
601 : : const int type_flag,
602 : : struct FTW *ftw_buffer)
603 : : {
604 : : (void)ftw_buffer;
605 : :
606 : 4664 : int status = 0;
607 : 4664 : create(char,destination_path);
608 : 4664 : const char *destination_path_string = NULL;
609 : 4664 : bool is_directory = false;
610 : :
611 [ - + ]: 4664 : if(stat_buffer == NULL)
612 : : {
613 : 0 : status = -1;
614 : : }
615 : :
616 [ - + ]: 4664 : if(type_flag == FTW_D)
617 : : {
618 : 0 : is_directory = true;
619 : : }
620 : : #ifdef FTW_DP
621 [ + + ]: 4664 : if(type_flag == FTW_DP)
622 : : {
623 : 3501 : is_directory = true;
624 : : }
625 : : #endif
626 : :
627 [ + - + + ]: 4664 : if(status == 0 && is_directory == true)
628 : : {
629 [ - + ]: 3501 : if(build_copy_destination_path_for_nftw(path,destination_path) != 0)
630 : : {
631 : 0 : status = -1;
632 : : }
633 : : }
634 : :
635 [ + - + + ]: 4664 : if(status == 0 && is_directory == true)
636 : : {
637 : 3501 : destination_path_string = getcstring(destination_path);
638 [ - + ]: 3501 : if(destination_path_string == NULL)
639 : : {
640 : 0 : status = -1;
641 : : }
642 : : }
643 : :
644 [ + - + + ]: 4664 : if(status == 0 && is_directory == true)
645 : : {
646 [ - + ]: 3501 : if(apply_mtime_from_source_stat(destination_path_string,stat_buffer,0) != 0)
647 : : {
648 : 0 : status = -1;
649 : : }
650 : : }
651 : :
652 : 4664 : del(destination_path);
653 : :
654 : 4664 : return(status);
655 : : }
656 : :
657 : : /**
658 : : * @brief Truncate an existing file to zero bytes by reopening it in binary write mode
659 : : *
660 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
661 : : *
662 : : * @return Return status code:
663 : : * - SUCCESS: File was truncated successfully
664 : : * - FAILURE: Path construction or file operation failed
665 : : */
666 : 9 : Return truncate_file_to_zero_size(
667 : : const char *relative_path_to_tmpdir)
668 : : {
669 : : /* Status returned by this function through provide()
670 : : Default value assumes successful completion */
671 : 9 : Return status = SUCCESS;
672 : 9 : FILE *file = NULL;
673 : :
674 : 9 : create(char,absolute_path);
675 : :
676 [ - + ]: 9 : if(relative_path_to_tmpdir == NULL)
677 : : {
678 : 0 : status = FAILURE;
679 : : }
680 : :
681 [ + - ]: 9 : if(SUCCESS == status)
682 : : {
683 : 9 : status = construct_path(relative_path_to_tmpdir,absolute_path);
684 : : }
685 : :
686 [ + - ]: 9 : if(SUCCESS == status)
687 : : {
688 : 9 : status = open_file_stream(
689 : : absolute_path,
690 : : "wb",
691 : : &file);
692 : : }
693 : :
694 [ + - - + ]: 9 : if(file != NULL && fclose(file) != 0)
695 : : {
696 : 0 : status = FAILURE;
697 : : }
698 : :
699 : 9 : del(absolute_path);
700 : :
701 : 9 : return(status);
702 : : }
703 : :
704 : : /**
705 : : * @brief Create directory tree by path relative to TMPDIR
706 : : *
707 : : * Empty path resolves to TMPDIR root
708 : : * Existing directories are preserved
709 : : * Existing symlinks to directories in the TMPDIR prefix are accepted
710 : : *
711 : : * @param[in] relative_path_to_tmpdir Directory path relative to TMPDIR
712 : : *
713 : : * @return Return status code:
714 : : * - SUCCESS: Directory tree exists after the call
715 : : * - FAILURE: Path construction or directory creation failed
716 : : */
717 : 14 : Return create_directory(
718 : : const char *relative_path_to_tmpdir)
719 : : {
720 : : /* Status returned by this function through provide()
721 : : Default value assumes successful completion */
722 : 14 : Return status = SUCCESS;
723 : 14 : char *absolute_path_data = NULL;
724 : 14 : size_t absolute_path_length = 0U;
725 : 14 : create(char,absolute_path);
726 : :
727 [ - + ]: 14 : if(relative_path_to_tmpdir == NULL)
728 : : {
729 : 0 : status = FAILURE;
730 : : }
731 : :
732 [ + - ]: 14 : if(SUCCESS == status)
733 : : {
734 : 14 : status = construct_path(relative_path_to_tmpdir,absolute_path);
735 : : }
736 : :
737 [ + - ]: 14 : if(SUCCESS == status)
738 : : {
739 : 14 : absolute_path_data = data(char,absolute_path);
740 [ - + ]: 14 : if(absolute_path_data == NULL)
741 : : {
742 : 0 : status = FAILURE;
743 : : }
744 : : }
745 : :
746 [ + - ]: 14 : if(SUCCESS == status)
747 : : {
748 : 14 : absolute_path_length = strlen(absolute_path_data);
749 : :
750 [ + - - + ]: 14 : while(absolute_path_length > 1U && absolute_path_data[absolute_path_length - 1U] == '/')
751 : : {
752 : 0 : absolute_path_data[absolute_path_length - 1U] = '\0';
753 : 0 : absolute_path_length--;
754 : : }
755 : : }
756 : :
757 [ + - ]: 14 : if(SUCCESS == status)
758 : : {
759 : 14 : for(char *path_cursor = absolute_path_data + 1;
760 [ + + + + ]: 581 : SUCCESS == status && *path_cursor != '\0';
761 : 567 : path_cursor++)
762 : : {
763 [ + + ]: 567 : if(*path_cursor == '/')
764 : : {
765 : 43 : *path_cursor = '\0';
766 [ + + ]: 43 : if(create_directory_if_missing(absolute_path_data) != 0)
767 : : {
768 : 1 : status = FAILURE;
769 : : }
770 : 43 : *path_cursor = '/';
771 : : }
772 : : }
773 : : }
774 : :
775 [ + + ]: 14 : if(SUCCESS == status)
776 : : {
777 [ - + ]: 13 : if(create_directory_if_missing(absolute_path_data) != 0)
778 : : {
779 : 0 : status = FAILURE;
780 : : }
781 : : }
782 : :
783 : 14 : del(absolute_path);
784 : :
785 : 14 : return(status);
786 : : }
787 : :
788 : : /**
789 : : * @brief Remove file or directory tree by path relative to TMPDIR
790 : : *
791 : : * When relative_path_to_tmpdir is an empty string, the function targets TMPDIR itself
792 : : * Missing paths are treated as a hard failure to keep test cleanup strict and deterministic
793 : : *
794 : : * @param[in] relative_path_to_tmpdir File or directory path relative to TMPDIR
795 : : *
796 : : * @return Return status code:
797 : : * - SUCCESS: Path was removed
798 : : * - FAILURE: Path construction or remove traversal failed
799 : : */
800 : 298 : Return delete_path(
801 : : const char *relative_path_to_tmpdir)
802 : : {
803 : : /* Status returned by this function through provide()
804 : : Default value assumes successful completion */
805 : 298 : Return status = SUCCESS;
806 : 298 : struct stat path_stat = {0};
807 : 298 : create(char,absolute_path);
808 : :
809 [ + + ]: 298 : if(relative_path_to_tmpdir == NULL)
810 : : {
811 : 1 : echo(STDERR,"delete_path: relative path must not be NULL\n");
812 : 1 : status = FAILURE;
813 : : }
814 : :
815 [ + + ]: 298 : if(SUCCESS == status)
816 : : {
817 : 297 : status = construct_path(relative_path_to_tmpdir,absolute_path);
818 : :
819 [ - + ]: 297 : if(SUCCESS != status)
820 : : {
821 : 0 : echo(STDERR,"delete_path: failed to construct absolute path for \"%s\"\n",relative_path_to_tmpdir);
822 : : }
823 : : }
824 : :
825 [ + + ]: 298 : if(SUCCESS == status)
826 : : {
827 [ + + ]: 297 : if(lstat(getcstring(absolute_path),&path_stat) != 0)
828 : : {
829 : 1 : echo(STDERR,"delete_path: lstat failed for %s: %s\n",getcstring(absolute_path),strerror(errno));
830 : 1 : status = FAILURE;
831 : : }
832 : : }
833 : :
834 [ + + ]: 298 : if(SUCCESS == status)
835 : : {
836 [ + + ]: 296 : if(S_ISDIR(path_stat.st_mode))
837 : : {
838 : 73 : long sc_open_max = sysconf(_SC_OPEN_MAX);
839 [ - + ]: 73 : if(sc_open_max <= 0)
840 : : {
841 : 0 : sc_open_max = 512;
842 : : }
843 [ + + ]: 73 : if(nftw(getcstring(absolute_path),nftw_remove_callback,(int)sc_open_max,FTW_DEPTH | FTW_PHYS) != 0)
844 : : {
845 : 1 : echo(STDERR,"delete_path: nftw failed for %s: %s\n",getcstring(absolute_path),strerror(errno));
846 : 1 : status = FAILURE;
847 : : }
848 [ + + ]: 223 : } else if(remove(getcstring(absolute_path)) != 0){
849 : 1 : echo(STDERR,"delete_path: remove failed for %s: %s\n",getcstring(absolute_path),strerror(errno));
850 : 1 : status = FAILURE;
851 : : }
852 : : }
853 : :
854 : 298 : del(absolute_path);
855 : :
856 : 298 : return(status);
857 : : }
858 : :
859 : : /**
860 : : * @brief Copy filesystem object from one absolute path to another
861 : : *
862 : : * @param[in] source_absolute_path Source absolute path
863 : : * @param[in] destination_absolute_path Destination absolute path
864 : : * @param[in] flags Behavior flags such as @ref REQUIRE_SOURCE_EXISTS or @ref ALLOW_MISSING_SOURCE
865 : : *
866 : : * @return Return status code
867 : : */
868 : 132 : static Return copy_absolute_path(
869 : : const char *source_absolute_path,
870 : : const char *destination_absolute_path,
871 : : unsigned int flags)
872 : : {
873 : : /* Status returned by this function through provide()
874 : : Default value assumes successful completion */
875 : 132 : Return status = SUCCESS;
876 : 132 : struct stat source_stat = {0};
877 : 132 : struct stat destination_stat = {0};
878 : 132 : bool destination_exists = false;
879 : :
880 [ + - - + ]: 132 : if(source_absolute_path == NULL || destination_absolute_path == NULL)
881 : : {
882 : 0 : status = FAILURE;
883 : : }
884 : :
885 [ + - - + ]: 132 : if(SUCCESS == status && lstat(source_absolute_path,&source_stat) != 0)
886 : : {
887 [ # # # # ]: 0 : if(errno == ENOENT && (flags & ALLOW_MISSING_SOURCE) != 0U)
888 : : {
889 : 0 : return(SUCCESS);
890 : : }
891 : :
892 : 0 : status = FAILURE;
893 : : }
894 : :
895 [ + - + + ]: 132 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
896 : : {
897 : 74 : size_t source_length = strlen(source_absolute_path);
898 : :
899 [ + - - + ]: 74 : while(source_length > 1U && source_absolute_path[source_length - 1U] == '/')
900 : : {
901 : 0 : source_length--;
902 : : }
903 : :
904 [ - + ]: 74 : if(strncmp(destination_absolute_path,source_absolute_path,source_length) == 0
905 [ # # ]: 0 : && (destination_absolute_path[source_length] == '\0'
906 [ # # ]: 0 : || destination_absolute_path[source_length] == '/'))
907 : : {
908 : 0 : status = FAILURE;
909 : : }
910 : : }
911 : :
912 [ + - ]: 132 : if(SUCCESS == status)
913 : : {
914 [ + + ]: 132 : if(lstat(destination_absolute_path,&destination_stat) == 0)
915 : : {
916 : 8 : destination_exists = true;
917 [ - + ]: 124 : } else if(errno != ENOENT){
918 : 0 : status = FAILURE;
919 : : }
920 : : }
921 : :
922 [ + - + + ]: 132 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
923 : : {
924 [ - + ]: 74 : if(destination_exists == true)
925 : : {
926 : 0 : status = FAILURE;
927 : : } else {
928 : 74 : copy_source_root_for_nftw = source_absolute_path;
929 : 74 : copy_destination_root_for_nftw = destination_absolute_path;
930 [ + - ]: 74 : if(copy_source_root_for_nftw == NULL
931 [ - + ]: 74 : || copy_destination_root_for_nftw == NULL)
932 : : {
933 : 0 : status = FAILURE;
934 : : }
935 : : }
936 : : }
937 : :
938 [ + - + + : 132 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode) && destination_exists == false)
+ - ]
939 : : {
940 : 74 : copy_source_root_length_for_nftw = strlen(copy_source_root_for_nftw);
941 : 74 : copy_destination_root_length_for_nftw = strlen(copy_destination_root_for_nftw);
942 : :
943 [ + - ]: 74 : if(copy_source_root_length_for_nftw == 0U
944 [ - + ]: 74 : || copy_destination_root_length_for_nftw == 0U)
945 : : {
946 : 0 : status = FAILURE;
947 : : }
948 : : }
949 : :
950 [ + - + + ]: 132 : if(SUCCESS == status && S_ISDIR(source_stat.st_mode))
951 : : {
952 [ + - ]: 74 : if(destination_exists == false)
953 : : {
954 [ - + ]: 74 : if(nftw(source_absolute_path,nftw_copy_callback,64,FTW_PHYS) != 0)
955 : : {
956 : 0 : status = FAILURE;
957 : : }
958 : :
959 [ + - ]: 74 : if(SUCCESS == status
960 [ - + ]: 74 : && nftw(source_absolute_path,
961 : : nftw_sync_directory_mtime_callback,
962 : : 64,
963 : : FTW_DEPTH | FTW_PHYS) != 0)
964 : : {
965 : 0 : status = FAILURE;
966 : : }
967 : : }
968 [ + - + - ]: 58 : } else if(SUCCESS == status && S_ISREG(source_stat.st_mode)){
969 [ + + - + ]: 58 : if(destination_exists == true && S_ISDIR(destination_stat.st_mode))
970 : : {
971 : 0 : status = FAILURE;
972 : : }
973 : :
974 [ + - ]: 58 : if(SUCCESS == status)
975 : : {
976 [ + + - + ]: 58 : if(unlink(destination_absolute_path) != 0 && errno != ENOENT)
977 : : {
978 : 0 : status = FAILURE;
979 : : }
980 : : }
981 : :
982 [ + - ]: 58 : if(SUCCESS == status
983 [ - + ]: 58 : && copy_regular_file_contents(source_absolute_path,
984 : : destination_absolute_path,
985 : : source_stat.st_mode) != 0)
986 : : {
987 : 0 : status = FAILURE;
988 : : }
989 : :
990 [ + - ]: 58 : if(SUCCESS == status
991 [ - + ]: 58 : && apply_mtime_from_source_stat(destination_absolute_path,&source_stat,0) != 0)
992 : : {
993 : 0 : status = FAILURE;
994 : : }
995 [ # # # # ]: 0 : } else if(SUCCESS == status && S_ISLNK(source_stat.st_mode)){
996 [ # # # # ]: 0 : if(destination_exists == true && S_ISDIR(destination_stat.st_mode))
997 : : {
998 : 0 : status = FAILURE;
999 : : }
1000 : :
1001 [ # # # # ]: 0 : if(SUCCESS == status && destination_exists == true
1002 [ # # ]: 0 : && unlink(destination_absolute_path) != 0)
1003 : : {
1004 : 0 : status = FAILURE;
1005 : : }
1006 : :
1007 [ # # # # ]: 0 : if(SUCCESS == status && copy_symbolic_link(source_absolute_path,destination_absolute_path) != 0)
1008 : : {
1009 : 0 : status = FAILURE;
1010 : : }
1011 : :
1012 : : #ifdef AT_SYMLINK_NOFOLLOW
1013 [ # # ]: 0 : if(SUCCESS == status
1014 [ # # ]: 0 : && apply_mtime_from_source_stat(destination_absolute_path,
1015 : : &source_stat,
1016 : : AT_SYMLINK_NOFOLLOW) != 0)
1017 : : {
1018 : 0 : status = FAILURE;
1019 : : }
1020 : : #endif
1021 [ # # ]: 0 : } else if(SUCCESS == status){
1022 : 0 : status = FAILURE;
1023 : : }
1024 : :
1025 : 132 : reset_nftw_copy_context();
1026 : :
1027 : 132 : return(status);
1028 : : }
1029 : :
1030 : : /**
1031 : : * @brief Copy file or directory tree by path relative to TMPDIR
1032 : : *
1033 : : * Empty source or destination path resolves to TMPDIR root
1034 : : * Directory copy into itself or into its own subtree is rejected
1035 : : *
1036 : : * @param[in] relative_source_path Source file or directory path relative to TMPDIR
1037 : : * @param[in] relative_destination_path Destination file or directory path relative to TMPDIR
1038 : : *
1039 : : * @return Return status code:
1040 : : * - SUCCESS: Source path was copied to destination path
1041 : : * - FAILURE: Validation, path resolution, stat lookup, or copy operation failed
1042 : : */
1043 : 113 : Return copy_path(
1044 : : const char *relative_source_path,
1045 : : const char *relative_destination_path)
1046 : : {
1047 : : /* Status returned by this function through provide()
1048 : : Default value assumes successful completion */
1049 : 113 : Return status = SUCCESS;
1050 : 113 : create(char,source_absolute_path);
1051 : 113 : create(char,destination_absolute_path);
1052 : :
1053 [ + - - + ]: 113 : if(relative_source_path == NULL || relative_destination_path == NULL)
1054 : : {
1055 : 0 : status = FAILURE;
1056 : : }
1057 : :
1058 [ + - ]: 113 : if(SUCCESS == status)
1059 : : {
1060 : 113 : status = construct_path(relative_source_path,source_absolute_path);
1061 : : }
1062 : :
1063 [ + - ]: 113 : if(SUCCESS == status)
1064 : : {
1065 : 113 : status = construct_path(relative_destination_path,destination_absolute_path);
1066 : : }
1067 : :
1068 [ + - ]: 113 : if(SUCCESS == status)
1069 : : {
1070 : 113 : status = copy_absolute_path(getcstring(source_absolute_path),
1071 : : getcstring(destination_absolute_path),
1072 : : REQUIRE_SOURCE_EXISTS);
1073 : : }
1074 : 113 : del(destination_absolute_path);
1075 : 113 : del(source_absolute_path);
1076 : :
1077 : 113 : return(status);
1078 : : }
1079 : :
1080 : : /**
1081 : : * @brief Copy file or directory tree from ORIGIN_DIR into TMPDIR
1082 : : *
1083 : : * @param[in] relative_source_path_to_origin_dir Source path relative to ORIGIN_DIR
1084 : : * @param[in] relative_destination_path_to_tmpdir Destination path relative to TMPDIR
1085 : : * @param[in] flags Behavior flags such as @ref REQUIRE_SOURCE_EXISTS or @ref ALLOW_MISSING_SOURCE
1086 : : *
1087 : : * @return Return status code:
1088 : : * - SUCCESS: Source path was copied or was absent and allowed to be missing
1089 : : * - FAILURE: Validation, path resolution, stat lookup, or copy operation failed
1090 : : */
1091 : 19 : Return copy_from_origin(
1092 : : const char *relative_source_path_to_origin_dir,
1093 : : const char *relative_destination_path_to_tmpdir,
1094 : : unsigned int flags)
1095 : : {
1096 : : /* Status returned by this function through provide()
1097 : : Default value assumes successful completion */
1098 : 19 : Return status = SUCCESS;
1099 : 19 : create(char,source_absolute_path);
1100 : 19 : create(char,destination_absolute_path);
1101 : :
1102 [ + - - + ]: 19 : if(relative_source_path_to_origin_dir == NULL || relative_destination_path_to_tmpdir == NULL)
1103 : : {
1104 : 0 : status = FAILURE;
1105 : : }
1106 : :
1107 [ + - ]: 19 : if(SUCCESS == status)
1108 : : {
1109 : 19 : status = construct_path_from_environment_variable("ORIGIN_DIR",
1110 : : relative_source_path_to_origin_dir,
1111 : : source_absolute_path);
1112 : : }
1113 : :
1114 [ + - ]: 19 : if(SUCCESS == status)
1115 : : {
1116 : 19 : status = construct_path(relative_destination_path_to_tmpdir,destination_absolute_path);
1117 : : }
1118 : :
1119 [ + - ]: 19 : if(SUCCESS == status)
1120 : : {
1121 : 19 : status = copy_absolute_path(getcstring(source_absolute_path),
1122 : : getcstring(destination_absolute_path),
1123 : : flags);
1124 : : }
1125 : :
1126 : 19 : del(destination_absolute_path);
1127 : 19 : del(source_absolute_path);
1128 : :
1129 : 19 : return(status);
1130 : : }
1131 : :
1132 : : /**
1133 : : * @brief Move file or directory by path relative to TMPDIR using native rename
1134 : : *
1135 : : * Empty source or destination path resolves to TMPDIR root
1136 : : *
1137 : : * @param[in] relative_source_path Source file or directory path relative to TMPDIR
1138 : : * @param[in] relative_destination_path Destination file or directory path relative to TMPDIR
1139 : : *
1140 : : * @return Return status code:
1141 : : * - SUCCESS: Source path was moved to destination path
1142 : : * - FAILURE: Validation, path resolution, or native rename failed
1143 : : */
1144 : 128 : Return move_path(
1145 : : const char *relative_source_path,
1146 : : const char *relative_destination_path)
1147 : : {
1148 : : /* Status returned by this function through provide()
1149 : : Default value assumes successful completion */
1150 : 128 : Return status = SUCCESS;
1151 : 128 : create(char,source_absolute_path);
1152 : 128 : create(char,destination_absolute_path);
1153 : :
1154 [ + - - + ]: 128 : if(relative_source_path == NULL || relative_destination_path == NULL)
1155 : : {
1156 : 0 : status = FAILURE;
1157 : : }
1158 : :
1159 [ + - ]: 128 : if(SUCCESS == status)
1160 : : {
1161 : 128 : status = construct_path(relative_source_path,source_absolute_path);
1162 : : }
1163 : :
1164 [ + - ]: 128 : if(SUCCESS == status)
1165 : : {
1166 : 128 : status = construct_path(relative_destination_path,destination_absolute_path);
1167 : : }
1168 : :
1169 [ + - - + ]: 128 : if(SUCCESS == status && rename(getcstring(source_absolute_path),getcstring(destination_absolute_path)) != 0)
1170 : : {
1171 : 0 : status = FAILURE;
1172 : : }
1173 : :
1174 : 128 : del(destination_absolute_path);
1175 : 128 : del(source_absolute_path);
1176 : :
1177 : 128 : return(status);
1178 : : }
1179 : :
1180 : : /**
1181 : : * @brief Make a file sparse by extending logical size while keeping allocated blocks unchanged
1182 : : *
1183 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
1184 : : * @param[out] new_size_out Output for the new logical size after sparse growth
1185 : : * @param[out] blocks_after_change_out Output for allocated blocks after sparse growth
1186 : : *
1187 : : * @return Return status code:
1188 : : * - SUCCESS: Sparse size change completed and outputs were filled
1189 : : * - FAILURE: Validation or filesystem operation failed
1190 : : */
1191 : 2 : Return make_sparse_size_change_without_allocated_block_growth(
1192 : : const char *relative_path_to_tmpdir,
1193 : : off_t *new_size_out,
1194 : : blkcnt_t *blocks_after_change_out)
1195 : : {
1196 : : /* Status returned by this function through provide()
1197 : : Default value assumes successful completion */
1198 : 2 : Return status = SUCCESS;
1199 : 2 : struct stat before_stat = {0};
1200 : 2 : struct stat after_stat = {0};
1201 : 2 : create(char,absolute_path);
1202 : :
1203 [ + - + - : 2 : if(relative_path_to_tmpdir == NULL || new_size_out == NULL || blocks_after_change_out == NULL)
- + ]
1204 : : {
1205 : 0 : status = FAILURE;
1206 : : }
1207 : :
1208 [ + - ]: 2 : if(SUCCESS == status)
1209 : : {
1210 : 2 : status = construct_path(relative_path_to_tmpdir,absolute_path);
1211 : : }
1212 : :
1213 [ + - ]: 2 : if(SUCCESS == status)
1214 : : {
1215 : 2 : status = get_file_stat(getcstring(absolute_path),&before_stat);
1216 : : }
1217 : :
1218 [ + - ]: 2 : if(SUCCESS == status)
1219 : : {
1220 : 2 : const off_t grown_size = before_stat.st_size + (off_t)131072;
1221 : :
1222 : : // Grow logical size via truncate to create a sparse tail without writing payload bytes
1223 [ - + ]: 2 : if(grown_size <= before_stat.st_size)
1224 : : {
1225 : 0 : status = FAILURE;
1226 [ - + ]: 2 : } else if(truncate(getcstring(absolute_path),grown_size) != 0){
1227 : 0 : status = FAILURE;
1228 : : }
1229 : : }
1230 : :
1231 [ + - ]: 2 : if(SUCCESS == status)
1232 : : {
1233 : 2 : status = get_file_stat(getcstring(absolute_path),&after_stat);
1234 : : }
1235 : :
1236 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_size <= before_stat.st_size)
1237 : : {
1238 : 0 : status = FAILURE;
1239 : : }
1240 : :
1241 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_blocks != before_stat.st_blocks)
1242 : : {
1243 : 0 : status = FAILURE;
1244 : : }
1245 : :
1246 [ + - ]: 2 : if(SUCCESS == status)
1247 : : {
1248 : 2 : *new_size_out = after_stat.st_size;
1249 : 2 : *blocks_after_change_out = after_stat.st_blocks;
1250 : : }
1251 : :
1252 : 2 : del(absolute_path);
1253 : :
1254 : 2 : return(status);
1255 : : }
1256 : :
1257 : : /**
1258 : : * @brief Rewrite file content with dense bytes while preserving logical size
1259 : : *
1260 : : * @param[in] relative_path_to_tmpdir Relative path from TMPDIR to the target file
1261 : : * @param[in] target_size Logical size to keep after rewrite
1262 : : * @param[in] blocks_before_rewrite Allocated blocks before rewrite
1263 : : *
1264 : : * @return Return status code:
1265 : : * - SUCCESS: Dense rewrite completed with unchanged logical size and a different allocated block count
1266 : : * - FAILURE: Validation or filesystem operation failed
1267 : : */
1268 : 2 : Return rewrite_file_dense_with_same_size(
1269 : : const char *relative_path_to_tmpdir,
1270 : : const off_t target_size,
1271 : : const blkcnt_t blocks_before_rewrite)
1272 : : {
1273 : : /* Status returned by this function through provide()
1274 : : Default value assumes successful completion */
1275 : 2 : Return status = SUCCESS;
1276 : 2 : FILE *file = NULL;
1277 : 2 : struct stat after_stat = {0};
1278 : : unsigned char buffer[4096];
1279 : 2 : create(char,absolute_path);
1280 : :
1281 : 2 : memset(buffer,'X',sizeof(buffer));
1282 : :
1283 [ + - - + ]: 2 : if(relative_path_to_tmpdir == NULL || target_size <= 0)
1284 : : {
1285 : 0 : status = FAILURE;
1286 : : }
1287 : :
1288 [ + - ]: 2 : if(SUCCESS == status)
1289 : : {
1290 : 2 : status = construct_path(relative_path_to_tmpdir,absolute_path);
1291 : : }
1292 : :
1293 [ + - ]: 2 : if(SUCCESS == status)
1294 : : {
1295 : : // Rewrite the whole file with real bytes while keeping the same logical size
1296 : 2 : status = open_file_stream(
1297 : : absolute_path,
1298 : : "wb",
1299 : : &file);
1300 : : }
1301 : :
1302 : 2 : off_t written = 0;
1303 : :
1304 [ + - + + ]: 68 : while(SUCCESS == status && written < target_size)
1305 : : {
1306 : 66 : const off_t remaining = target_size - written;
1307 : 66 : size_t chunk = sizeof(buffer);
1308 : :
1309 [ + + ]: 66 : if(remaining < (off_t)chunk)
1310 : : {
1311 : 2 : chunk = (size_t)remaining;
1312 : : }
1313 : :
1314 [ - + ]: 66 : if(fwrite(buffer,sizeof(unsigned char),chunk,file) != chunk)
1315 : : {
1316 : 0 : status = FAILURE;
1317 : : } else {
1318 : 66 : written += (off_t)chunk;
1319 : : }
1320 : : }
1321 : :
1322 [ + - ]: 2 : if(file != NULL)
1323 : : {
1324 [ - + ]: 2 : if(fclose(file) != 0)
1325 : : {
1326 : 0 : status = FAILURE;
1327 : : }
1328 : : }
1329 : :
1330 [ + - ]: 2 : if(SUCCESS == status)
1331 : : {
1332 : 2 : status = get_file_stat(getcstring(absolute_path),&after_stat);
1333 : : }
1334 : :
1335 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_size != target_size)
1336 : : {
1337 : 0 : status = FAILURE;
1338 : : }
1339 : :
1340 [ + - - + ]: 2 : if(SUCCESS == status && after_stat.st_blocks == blocks_before_rewrite)
1341 : : {
1342 : 0 : status = FAILURE;
1343 : : }
1344 : :
1345 : 2 : del(absolute_path);
1346 : :
1347 : 2 : return(status);
1348 : : }
1349 : :
1350 : : /**
1351 : : * @brief Compute SHA512 for a file using the project SHA512 library
1352 : : *
1353 : : * @param[in] file_path File path passed directly to `fopen()`
1354 : : * @param[out] sha512_out Output SHA512 digest buffer
1355 : : *
1356 : : * @return Return status code:
1357 : : * - SUCCESS: SHA512 digest computed successfully
1358 : : * - FAILURE: Validation, I/O, or hash operation failed
1359 : : */
1360 : 2 : Return compute_file_sha512(
1361 : : const char *file_path,
1362 : : unsigned char *sha512_out)
1363 : : {
1364 : : /* Status returned by this function through provide()
1365 : : Default value assumes successful completion */
1366 : 2 : Return status = SUCCESS;
1367 : 2 : FILE *file = NULL;
1368 : : unsigned char buffer[65536];
1369 : 2 : SHA512_Context context = {0};
1370 : :
1371 [ + - - + ]: 2 : if(file_path == NULL || sha512_out == NULL)
1372 : : {
1373 : 0 : status = FAILURE;
1374 : : }
1375 : :
1376 [ + - ]: 2 : if(SUCCESS == status)
1377 : : {
1378 : 2 : file = fopen(file_path,"rb");
1379 [ - + ]: 2 : if(file == NULL)
1380 : : {
1381 : 0 : status = FAILURE;
1382 : : }
1383 : : }
1384 : :
1385 [ + - - + ]: 2 : if(SUCCESS == status && sha512_init(&context) == 1)
1386 : : {
1387 : 0 : status = FAILURE;
1388 : : }
1389 : :
1390 [ + - ]: 322 : while(SUCCESS == status)
1391 : : {
1392 : 322 : const size_t bytes_read = fread(buffer,sizeof(unsigned char),sizeof(buffer),file);
1393 : :
1394 [ + + ]: 322 : if(bytes_read == 0U)
1395 : : {
1396 [ - + ]: 2 : if(ferror(file) != 0)
1397 : : {
1398 : 0 : status = FAILURE;
1399 : : }
1400 : 2 : break;
1401 : : }
1402 : :
1403 [ - + ]: 320 : if(sha512_update(&context,buffer,bytes_read) == 1)
1404 : : {
1405 : 0 : status = FAILURE;
1406 : : }
1407 : : }
1408 : :
1409 [ + - - + ]: 2 : if(SUCCESS == status && sha512_final(&context,sha512_out) == 1)
1410 : : {
1411 : 0 : status = FAILURE;
1412 : : }
1413 : :
1414 [ + - ]: 2 : if(file != NULL)
1415 : : {
1416 : 2 : (void)fclose(file);
1417 : : }
1418 : :
1419 : 2 : return(status);
1420 : : }
1421 : :
1422 : : /**
1423 : : * @brief Append one byte to a file using native C file I/O
1424 : : *
1425 : : * @param[in] file_path_buffer Managed path string passed directly to `open_file_stream()`
1426 : : * @param[in] byte Byte value to append
1427 : : *
1428 : : * @return Return status code:
1429 : : * - SUCCESS: Byte appended successfully
1430 : : * - FAILURE: Validation or I/O failed
1431 : : */
1432 : 2 : Return append_byte_to_file(
1433 : : const memory *file_path_buffer,
1434 : : unsigned char byte)
1435 : : {
1436 : : /* Status returned by this function through provide()
1437 : : Default value assumes successful completion */
1438 : 2 : Return status = SUCCESS;
1439 : 2 : FILE *file = NULL;
1440 : :
1441 [ - + ]: 2 : if(file_path_buffer == NULL)
1442 : : {
1443 : 0 : status = FAILURE;
1444 : : }
1445 : :
1446 [ + - ]: 2 : if(SUCCESS == status)
1447 : : {
1448 : 2 : status = open_file_stream(
1449 : : file_path_buffer,
1450 : : "ab",
1451 : : &file);
1452 : : }
1453 : :
1454 [ + - ]: 2 : if(SUCCESS == status)
1455 : : {
1456 [ - + ]: 2 : if(fwrite(&byte,sizeof(unsigned char),1U,file) != 1U)
1457 : : {
1458 : 0 : status = FAILURE;
1459 : : }
1460 : : }
1461 : :
1462 [ + - ]: 2 : if(file != NULL)
1463 : : {
1464 [ - + ]: 2 : if(fclose(file) != 0)
1465 : : {
1466 : 0 : status = FAILURE;
1467 : : }
1468 : : }
1469 : :
1470 : 2 : return(status);
1471 : : }
1472 : :
1473 : : /**
1474 : : * @brief Write string to file with append or replace mode
1475 : : *
1476 : : * The function writes bytes exactly as provided and never appends a newline
1477 : : *
1478 : : * @param[in] file_content String payload to write
1479 : : * @param[in] file_path File path relative to TMPDIR
1480 : : * @param[in] write_flags One of FILE_WRITE_APPEND or FILE_WRITE_REPLACE
1481 : : *
1482 : : * @return Return status code:
1483 : : * - SUCCESS: The string bytes were written successfully
1484 : : * - FAILURE: Validation, path resolution, or file I/O failed
1485 : : */
1486 : 45 : Return write_string_to_file(
1487 : : const char *file_content,
1488 : : const char *file_path,
1489 : : const unsigned int write_flags)
1490 : : {
1491 : : /* Status returned by this function through provide()
1492 : : Default value assumes successful completion */
1493 : 45 : Return status = SUCCESS;
1494 : 45 : FILE *file = NULL;
1495 : 45 : const char *open_mode = NULL;
1496 : 45 : create(char,absolute_path);
1497 : :
1498 [ + - - + ]: 45 : if(file_content == NULL || file_path == NULL)
1499 : : {
1500 : 0 : status = FAILURE;
1501 : : }
1502 : :
1503 [ + - ]: 45 : if(SUCCESS == status)
1504 : : {
1505 [ + + ]: 45 : if(write_flags == FILE_WRITE_APPEND)
1506 : : {
1507 : 28 : open_mode = "ab";
1508 [ + - ]: 17 : } else if(write_flags == FILE_WRITE_REPLACE){
1509 : 17 : open_mode = "wb";
1510 : : } else {
1511 : 0 : status = FAILURE;
1512 : : }
1513 : : }
1514 : :
1515 [ + - ]: 45 : if(SUCCESS == status)
1516 : : {
1517 : 45 : status = construct_path(file_path,absolute_path);
1518 : : }
1519 : :
1520 [ + - ]: 45 : if(SUCCESS == status)
1521 : : {
1522 : 45 : status = open_file_stream(
1523 : : absolute_path,
1524 : : open_mode,
1525 : : &file);
1526 : : }
1527 : :
1528 [ + - ]: 45 : if(SUCCESS == status)
1529 : : {
1530 : 45 : const size_t bytes_to_write = strlen(file_content);
1531 [ + - ]: 45 : if(bytes_to_write > 0U
1532 [ - + ]: 45 : && fwrite(file_content,sizeof(char),bytes_to_write,file) != bytes_to_write)
1533 : : {
1534 : 0 : status = FAILURE;
1535 : : }
1536 : : }
1537 : :
1538 [ + - - + ]: 45 : if(file != NULL && fclose(file) != 0)
1539 : : {
1540 : 0 : status = FAILURE;
1541 : : }
1542 : :
1543 : 45 : del(absolute_path);
1544 : :
1545 : 45 : return(status);
1546 : : }
1547 : :
1548 : : /**
1549 : : * @brief Append string bytes to file without newline
1550 : : *
1551 : : * @param[in] file_content String payload to append
1552 : : * @param[in] file_path File path relative to TMPDIR
1553 : : *
1554 : : * @return Return status code
1555 : : */
1556 : 26 : Return add_string_to(
1557 : : const char *file_content,
1558 : : const char *file_path)
1559 : : {
1560 : 26 : return(write_string_to_file(file_content,file_path,FILE_WRITE_APPEND));
1561 : : }
1562 : :
1563 : : /**
1564 : : * @brief Replace file content with string bytes without newline
1565 : : *
1566 : : * @param[in] file_content String payload to write
1567 : : * @param[in] file_path File path relative to TMPDIR
1568 : : *
1569 : : * @return Return status code
1570 : : */
1571 : 17 : Return replase_to_string(
1572 : : const char *file_content,
1573 : : const char *file_path)
1574 : : {
1575 : 17 : return(write_string_to_file(file_content,file_path,FILE_WRITE_REPLACE));
1576 : : }
1577 : :
1578 : : /**
1579 : : * @brief Set target file mtime to source mtime plus a nanosecond delta using native POSIX calls
1580 : : *
1581 : : * Relative paths are resolved from TMPDIR with construct_path
1582 : : * Source and target can be the same file
1583 : : * If relative_source_path is NULL, relative_target_path is used as source
1584 : : * If relative_target_path is NULL, the function returns FAILURE
1585 : : * The delta is applied in nanoseconds and can be any signed integer value
1586 : : * If mtime_delta_nanoseconds is 0, target mtime is set to source mtime
1587 : : * atime is preserved with UTIME_OMIT
1588 : : * Even when resulting mtime equals current mtime, successful metadata update may still change ctime
1589 : : * ctime cannot be set directly from userspace and will change automatically after metadata update
1590 : : *
1591 : : * @param[in] relative_source_path Relative path from TMPDIR to source file or NULL
1592 : : * @param[in] relative_target_path Relative path from TMPDIR to target file
1593 : : * @param[in] mtime_delta_nanoseconds Signed nanosecond delta applied to source mtime
1594 : : *
1595 : : * @return Return status code:
1596 : : * - SUCCESS: Target mtime was updated
1597 : : * - FAILURE: Validation, path resolution, stat, normalization, or timestamp update failed
1598 : : */
1599 : 20 : Return touch_file_mtime_with_reference_delta_ns(
1600 : : const char *relative_source_path,
1601 : : const char *relative_target_path,
1602 : : int64_t mtime_delta_nanoseconds)
1603 : : {
1604 : : /* Status returned by this function through provide()
1605 : : Default value assumes successful completion */
1606 : 20 : Return status = SUCCESS;
1607 : 20 : struct stat source_file_stat = {0};
1608 : 20 : struct timespec target_times[2] = {{0}};
1609 : 20 : const char *source_relative_path = relative_source_path;
1610 : 20 : create(char,source_absolute_path);
1611 : 20 : create(char,target_absolute_path);
1612 : :
1613 : : // Require a target path because the mtime update is applied to this file
1614 [ - + ]: 20 : if(relative_target_path == NULL)
1615 : : {
1616 : 0 : status = FAILURE;
1617 : : }
1618 : :
1619 : : // Reuse target as source when source path is not provided
1620 [ + - + + ]: 20 : if(SUCCESS == status && source_relative_path == NULL)
1621 : : {
1622 : 14 : source_relative_path = relative_target_path;
1623 : : }
1624 : :
1625 : : // Resolve source path relative to TMPDIR
1626 [ + - ]: 20 : if(SUCCESS == status)
1627 : : {
1628 : 20 : status = construct_path(source_relative_path,source_absolute_path);
1629 : : }
1630 : :
1631 : : // Resolve target path relative to TMPDIR
1632 [ + - ]: 20 : if(SUCCESS == status)
1633 : : {
1634 : 20 : status = construct_path(relative_target_path,target_absolute_path);
1635 : : }
1636 : :
1637 : : // Read source stat to use its mtime as the reference point
1638 [ + - ]: 20 : if(SUCCESS == status)
1639 : : {
1640 : 20 : status = get_file_stat(getcstring(source_absolute_path),&source_file_stat);
1641 : : }
1642 : :
1643 : : // Build target mtime by applying and normalizing nanosecond delta
1644 [ + - ]: 20 : if(SUCCESS == status)
1645 : : {
1646 : 20 : const intmax_t nanoseconds_per_second = 1000000000;
1647 : 20 : intmax_t target_seconds = (intmax_t)source_file_stat.st_mtim.tv_sec
1648 : 20 : + (intmax_t)(mtime_delta_nanoseconds / nanoseconds_per_second);
1649 : 20 : long target_nanoseconds = source_file_stat.st_mtim.tv_nsec
1650 : 20 : + (long)(mtime_delta_nanoseconds % nanoseconds_per_second);
1651 : :
1652 : : // Normalize nanosecond overflow into next second
1653 [ - + ]: 20 : if(target_nanoseconds >= (long)nanoseconds_per_second)
1654 : : {
1655 : 0 : target_nanoseconds -= (long)nanoseconds_per_second;
1656 : 0 : target_seconds++;
1657 : : // Normalize negative nanoseconds by borrowing one second
1658 [ - + ]: 20 : } else if(target_nanoseconds < 0){
1659 : 0 : target_nanoseconds += (long)nanoseconds_per_second;
1660 : 0 : target_seconds--;
1661 : : }
1662 : :
1663 : 20 : time_t normalized_target_seconds = (time_t)target_seconds;
1664 : :
1665 : : // Ensure computed seconds value is representable as time_t
1666 [ - + ]: 20 : if((intmax_t)normalized_target_seconds != target_seconds)
1667 : : {
1668 : 0 : status = FAILURE;
1669 : : } else {
1670 : : // Keep atime unchanged and prepare mtime for utimensat
1671 : 20 : target_times[0].tv_nsec = UTIME_OMIT;
1672 : 20 : target_times[1].tv_sec = normalized_target_seconds;
1673 : 20 : target_times[1].tv_nsec = target_nanoseconds;
1674 : : }
1675 : : }
1676 : :
1677 : : // Apply the prepared timestamp values to the target file
1678 [ + - ]: 20 : if(SUCCESS == status)
1679 : : {
1680 [ - + ]: 20 : if(utimensat(0,getcstring(target_absolute_path),target_times,0) != 0)
1681 : : {
1682 : 0 : status = FAILURE;
1683 : : }
1684 : : }
1685 : :
1686 : 20 : del(source_absolute_path);
1687 : 20 : del(target_absolute_path);
1688 : :
1689 : 20 : return(status);
1690 : : }
1691 : :
1692 : : /**
1693 : : * @brief Modify first two bytes of a file and restore atime/mtime best effort
1694 : : *
1695 : : * @param[in] relative_path Relative path from TMPDIR to the target file
1696 : : *
1697 : : * @return Return status code:
1698 : : * - SUCCESS: File bytes were modified
1699 : : * - FAILURE: Validation or filesystem operation failed
1700 : : */
1701 : 4 : Return tamper_locked_file_bytes(
1702 : : const char *relative_path)
1703 : : {
1704 : : /* Status returned by this function through provide()
1705 : : Default value assumes successful completion */
1706 : 4 : Return status = SUCCESS;
1707 : 4 : int fd = -1;
1708 : 4 : struct stat before = {0};
1709 : 4 : unsigned char buffer[2] = {0};
1710 : 4 : struct timespec times[2] = {{0}};
1711 : 4 : create(char,file_path);
1712 : :
1713 : : // Validate input path before any filesystem operations
1714 [ + - - + ]: 4 : if(SUCCESS == status && relative_path == NULL)
1715 : : {
1716 : 0 : status = FAILURE;
1717 : : }
1718 : :
1719 : : // Resolve path relative to TMPDIR
1720 [ + - ]: 4 : if(SUCCESS == status)
1721 : : {
1722 : 4 : status = construct_path(relative_path,file_path);
1723 : : }
1724 : :
1725 : : // Open file for in-place read and write operations
1726 [ + - - + ]: 4 : if(SUCCESS == status && (fd = open(getcstring(file_path),O_RDWR)) < 0)
1727 : : {
1728 : 0 : status = FAILURE;
1729 : : }
1730 : :
1731 : : // Read file metadata to preserve timestamps later
1732 [ + - - + ]: 4 : if(SUCCESS == status && fstat(fd,&before) != 0)
1733 : : {
1734 : 0 : status = FAILURE;
1735 : : }
1736 : :
1737 : : // Require at least two bytes because exactly two bytes are modified
1738 [ + - - + ]: 4 : if(SUCCESS == status && before.st_size < (off_t)sizeof(buffer))
1739 : : {
1740 : 0 : status = FAILURE;
1741 : : }
1742 : :
1743 : : // Read first two bytes that will be modified
1744 [ + - - + ]: 4 : if(SUCCESS == status && pread(fd,buffer,sizeof(buffer),0) != (ssize_t)sizeof(buffer))
1745 : : {
1746 : 0 : status = FAILURE;
1747 : : }
1748 : :
1749 : : // Flip both bytes to guarantee content and checksum change
1750 [ + - ]: 4 : if(SUCCESS == status)
1751 : : {
1752 : 4 : buffer[0] = (unsigned char)~buffer[0];
1753 : 4 : buffer[1] = (unsigned char)~buffer[1];
1754 : : }
1755 : :
1756 : : // Write modified bytes back to file start
1757 [ + - - + ]: 4 : if(SUCCESS == status && pwrite(fd,buffer,sizeof(buffer),0) != (ssize_t)sizeof(buffer))
1758 : : {
1759 : 0 : status = FAILURE;
1760 : : }
1761 : :
1762 : : // Restore atime and mtime best effort after content tampering
1763 [ + - ]: 4 : if(SUCCESS == status)
1764 : : {
1765 : : // Best effort restore for atime and mtime while ctime still changes on POSIX
1766 : 4 : times[0] = before.st_atim;
1767 : 4 : times[1] = before.st_mtim;
1768 : :
1769 [ - + ]: 4 : if(futimens(fd,times) != 0)
1770 : : {
1771 : 0 : status = FAILURE;
1772 : : }
1773 : : }
1774 : :
1775 : : // Close descriptor on all paths where open succeeded
1776 [ + - ]: 4 : if(fd >= 0)
1777 : : {
1778 : 4 : (void)close(fd);
1779 : : }
1780 : :
1781 : 4 : del(file_path);
1782 : :
1783 : 4 : return(status);
1784 : : }
|