LCOV - code coverage report
Current view: top level - tests/src - helpers_file_utils.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 79.4 % 773 614
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 36 36
Branches: 52.7 % 698 368

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

Generated by: LCOV version 2.0-1