LCOV - code coverage report
Current view: top level - tests/src - helpers_file_utils.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 77.5 % 630 488
Test Date: 2026-03-31 13:51:38 Functions: 100.0 % 27 27
Branches: 53.2 % 602 320

             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                 :             : }
        

Generated by: LCOV version 2.0-1