LCOV - code coverage report
Current view: top level - libs/testitall/src - external_call.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 60.0 % 115 69
Test Date: 2026-01-12 05:34:38 Functions: 100.0 % 1 1

            Line data    Source code
       1              : #include "testitall.h"
       2              : #include <sysexits.h>
       3              : 
       4              : // Global buffers for captured output streams
       5              : memory _STDOUT = {sizeof(char),0,0,NULL};
       6              : memory *STDOUT = &_STDOUT;
       7              : memory _STDERR = {sizeof(char),0,0,NULL};
       8              : memory *STDERR = &_STDERR;
       9              : memory _EXTEND = {sizeof(char),0,0,NULL};
      10              : memory *EXTEND = &_EXTEND;
      11              : 
      12              : extern char **environ; // Environment variables used by posix_spawnp
      13              : 
      14              : /**
      15              :  * Executes an external command, capturing stdout/stderr into shared buffers.
      16              :  * @param command              Shell command to execute.
      17              :  * @param expected_return_code Exit code the command must produce for SUCCESS.
      18              :  * @param buffer_policy        Bitmask controlling stdout/stderr handling.
      19              :  *                             Use STDOUT_SUPPRESS/STDERR_SUPPRESS to drop buffers,
      20              :  *                             STDOUT_ENABLE/STDERR_ENABLE to document enabled streams,
      21              :  *                             and their combinations like ALLOW_BOTH.
      22              :  * @return SUCCESS if execution, capture, and exit code checks succeed; FAILURE otherwise.
      23              :  *
      24              :  * Side effects:
      25              :  *  - Clears global STDOUT/STDERR buffers before running.
      26              :  *  - Formats diagnostics into STDERR on unexpected exit codes or unsuppressed stderr.
      27              :  */
      28          285 : Return external_call(
      29              :         const char   *command,
      30              :         const int    expected_return_code,
      31              :         unsigned int buffer_policy)
      32              : {
      33              :         /// The status that will be passed to return() before exiting.
      34              :         /// By default, the function worked without errors.
      35          285 :         Return status = SUCCESS;
      36          285 :         const bool suppress_stdout = (buffer_policy & STDOUT_SUPPRESS) != 0U;
      37          285 :         const bool suppress_stderr = (buffer_policy & STDERR_SUPPRESS) != 0U;
      38              : 
      39              :         // Create pipes to capture stdout and stderr
      40              :         int stdout_pipe[2],stderr_pipe[2];
      41              : 
      42          285 :         if(pipe(stdout_pipe) == -1 || pipe(stderr_pipe) == -1)
      43              :         {
      44            0 :                 serp("Error creating pipe");
      45            0 :                 return(FAILURE);
      46              :         }
      47              : 
      48              :         // Clear data from previous usage
      49          285 :         del(STDERR);
      50              :         // Clear data from previous usage
      51          285 :         del(STDOUT);
      52              : 
      53              :         // Initialize spawn file actions and attributes
      54              :         posix_spawn_file_actions_t actions;
      55          285 :         posix_spawn_file_actions_init(&actions);
      56              : 
      57              :         // Redirect child process stdout and stderr to the pipes
      58          285 :         posix_spawn_file_actions_adddup2(&actions,stdout_pipe[1],STDOUT_FILENO);
      59          285 :         posix_spawn_file_actions_adddup2(&actions,stderr_pipe[1],STDERR_FILENO);
      60              : 
      61              :         // Close unused ends of the pipes in the child process
      62          285 :         posix_spawn_file_actions_addclose(&actions,stdout_pipe[0]);
      63          285 :         posix_spawn_file_actions_addclose(&actions,stderr_pipe[0]);
      64          285 :         posix_spawn_file_actions_addclose(&actions,stdout_pipe[1]);
      65          285 :         posix_spawn_file_actions_addclose(&actions,stderr_pipe[1]);
      66              : 
      67              :         pid_t pid;
      68              : 
      69              :         // Prepare command arguments
      70          285 :         char *const arguments[] = {
      71              :                 (char *)(uintptr_t)"sh",
      72              :                 (char *)(uintptr_t)"-c",
      73              :                 (char *)(uintptr_t)command,
      74              :                 NULL
      75              :         };
      76              : 
      77              :         // Execute the command while inheriting current environment variables
      78          285 :         if(posix_spawnp(&pid,(char *)(uintptr_t)"sh",&actions,NULL,arguments,environ) != 0)
      79              :         {
      80            0 :                 serp("Error executing posix_spawnp"); // Handle command execution error
      81            0 :                 posix_spawn_file_actions_destroy(&actions);
      82            0 :                 return(FAILURE);
      83              :         }
      84              : 
      85              :         // Clean up spawn resources
      86          285 :         posix_spawn_file_actions_destroy(&actions);
      87              : 
      88              :         // Close the write ends of the pipes
      89          285 :         close(stdout_pipe[1]);
      90          285 :         close(stderr_pipe[1]);
      91              : 
      92              :         // Variables for reading from the pipe
      93          285 :         char *tmp_stdout_buffer = NULL; // Pointer to the buffer
      94          285 :         size_t total_read = 0;      // Total bytes read so far
      95          285 :         ssize_t count = 0;          // Bytes read during each iteration
      96              : 
      97              :         // Read data from the pipe (chunk size matches libmem's MEMORY_BLOCK_BYTES)
      98              :         char temp_buffer[MEMORY_BLOCK_BYTES];
      99              : 
     100          389 :         while((count = read(stdout_pipe[0],temp_buffer,MEMORY_BLOCK_BYTES)) > 0)
     101              :         {
     102          104 :                 if(count == -1)
     103              :                 {
     104            0 :                         serp("Error reading from pipe"); // Handle read error
     105            0 :                         free(tmp_stdout_buffer);
     106            0 :                         return(FAILURE);
     107              :                 }
     108              : 
     109              :                 // Reallocate memory to accommodate new data
     110          104 :                 char *new_buffer = realloc(tmp_stdout_buffer,total_read + (size_t)count + 1); // +1 for null terminator
     111              : 
     112          104 :                 if(!new_buffer)
     113              :                 {
     114            0 :                         report("Memory allocation failed, requested size: %zu bytes",total_read + (size_t)count + 1);
     115            0 :                         free(tmp_stdout_buffer);
     116            0 :                         return(FAILURE);
     117              :                 }
     118          104 :                 tmp_stdout_buffer = new_buffer;
     119              : 
     120              :                 // Copy the read data into the buffer
     121          104 :                 memcpy(tmp_stdout_buffer + total_read,temp_buffer,(size_t)count);
     122          104 :                 total_read += (size_t)count;
     123              :         }
     124              : 
     125          285 :         if(total_read > 0)
     126              :         {
     127           94 :                 if(resize(STDOUT,total_read + 1) != SUCCESS)
     128              :                 {
     129            0 :                         free(tmp_stdout_buffer);
     130            0 :                         return(FAILURE);
     131              :                 }
     132              : 
     133           94 :                 char *stdout_mem = data(char,STDOUT);
     134              : 
     135           94 :                 if(stdout_mem == NULL)
     136              :                 {
     137            0 :                         free(tmp_stdout_buffer);
     138            0 :                         return(FAILURE);
     139              :                 }
     140              : 
     141           94 :                 memcpy(stdout_mem,tmp_stdout_buffer,(size_t)total_read);
     142           94 :                 stdout_mem[STDOUT->length - 1] = '\0';
     143              :         }
     144              : 
     145          285 :         free(tmp_stdout_buffer); // Free allocated memory
     146              : 
     147              :         // Variables for reading stderr
     148          285 :         char *tmp_stderr_buffer = NULL; // Pointer to the buffer
     149          285 :         total_read = 0;             // Total bytes read so far
     150              : 
     151              :         /* Read data from the pipe */
     152              : 
     153              :         // Clear the temporary buffer
     154          285 :         memset(temp_buffer,0,MEMORY_BLOCK_BYTES);
     155              : 
     156          287 :         while((count = read(stderr_pipe[0],temp_buffer,MEMORY_BLOCK_BYTES)) > 0)
     157              :         {
     158            2 :                 if(count == -1)
     159              :                 {
     160            0 :                         serp("Error reading from pipe"); // Handle read error
     161            0 :                         free(tmp_stderr_buffer);
     162            0 :                         return(FAILURE);
     163              :                 }
     164              : 
     165              :                 // Reallocate memory to accommodate new data
     166            2 :                 char *new_buffer = realloc(tmp_stderr_buffer,total_read + (size_t)count + 1); // +1 for null terminator
     167              : 
     168            2 :                 if(!new_buffer)
     169              :                 {
     170            0 :                         report("Memory allocation failed, requested size: %zu bytes",total_read + (size_t)count + 1);
     171            0 :                         free(tmp_stderr_buffer);
     172            0 :                         return(FAILURE);
     173              :                 }
     174            2 :                 tmp_stderr_buffer = new_buffer;
     175              : 
     176              :                 // Copy the read data into the buffer
     177            2 :                 memcpy(tmp_stderr_buffer + total_read,temp_buffer,(size_t)count);
     178            2 :                 total_read += (size_t)count;
     179              :         }
     180              : 
     181          285 :         if(total_read > 0)
     182              :         {
     183            2 :                 if(resize(STDERR,total_read + 1) != SUCCESS)
     184              :                 {
     185            0 :                         free(tmp_stderr_buffer);
     186            0 :                         return(FAILURE);
     187              :                 }
     188              : 
     189            2 :                 char *stderr_mem = data(char,STDERR);
     190              : 
     191            2 :                 if(stderr_mem == NULL)
     192              :                 {
     193            0 :                         free(tmp_stderr_buffer);
     194            0 :                         return(FAILURE);
     195              :                 }
     196              : 
     197            2 :                 memcpy(stderr_mem,tmp_stderr_buffer,(size_t)total_read);
     198            2 :                 stderr_mem[STDERR->length - 1] = '\0';
     199              :         }
     200              : 
     201          285 :         free(tmp_stderr_buffer);
     202              : 
     203              :         // Wait for the child process to finish and capture its exit status
     204              :         int return_code;
     205          285 :         bool allow_stderr = (expected_return_code == EX_USAGE);
     206              : 
     207          285 :         if(waitpid(pid,&return_code,0) == -1)
     208              :         {
     209            0 :                 serp("Error waiting for child process");
     210            0 :                 return(FAILURE);
     211              :         }
     212          285 :         int exit_code = WEXITSTATUS(return_code);
     213              : 
     214          285 :         close(stdout_pipe[0]); // Close the read end of the pipe
     215          285 :         close(stderr_pipe[0]);
     216              : 
     217          285 :         if(STDERR->length > 0)
     218              :         {
     219            2 :                 if(allow_stderr == true)
     220              :                 {
     221            1 :                         if(STDOUT->length == 0 && STDOUT->data == NULL)
     222              :                         {
     223            0 :                                 run(copy(STDOUT,STDERR));
     224              : 
     225              :                         } else {
     226            1 :                                 run(concat_strings(STDOUT,STDERR));
     227              :                         }
     228              : 
     229              :                         // Clear STDERR to avoid warning reports
     230            1 :                         del(STDERR);
     231              : 
     232              :                 } else {
     233              :                         // Suppress the output from the STDERR buffer if needed
     234            1 :                         if(suppress_stderr == true)
     235              :                         {
     236              :                                 // Suppress the output from the STDERR buffer
     237            1 :                                 del(STDERR);
     238              : 
     239              :                         } else {
     240              :                                 // Format stderr output
     241              :                                 char *str;
     242            0 :                                 const char *stderr_view = getcstring(STDERR);
     243            0 :                                 int rt = asprintf(&str, \
     244              :                                         YELLOW "Warning! STDERR buffer is not empty!\n"
     245              :                                         "External command call:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n"
     246              :                                         "Stderr output:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n",
     247              :                                         command,stderr_view);
     248              : 
     249            0 :                                 if(rt > -1)
     250              :                                 {
     251              :                                         // Copy str into STDERR buffer
     252            0 :                                         run(resize(STDERR,(size_t)rt + 1));
     253              : 
     254            0 :                                         run(copy_literal(STDERR,str));
     255              : 
     256              :                                 } else {
     257            0 :                                         report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1);
     258              :                                 }
     259              : 
     260            0 :                                 free(str);
     261              : 
     262            0 :                                 return(FAILURE);
     263              :                         }
     264              :                 }
     265              :         }
     266              : 
     267          285 :         if(STDOUT->length > 0)
     268              :         {
     269              :                 // Suppress the output from the STDOUT buffer if needed
     270           94 :                 if(suppress_stdout == true)
     271              :                 {
     272              :                         // Suppress the output from the STDOUT buffer
     273            0 :                         del(STDOUT);
     274              : 
     275              :                 }
     276              :         }
     277              : 
     278              :         // Check the exit status of the child process
     279          285 :         if(expected_return_code != exit_code)
     280              :         {
     281              :                 // Format stderr output
     282              :                 char *str;
     283            0 :                 const char *stderr_view = getcstring(STDERR);
     284            0 :                 const char *stdout_view = getcstring(STDOUT);
     285            0 :                 int rt = asprintf(&str,YELLOW "ERROR: Unexpected exit code!" RESET "\n"
     286              :                         YELLOW "External command call:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n"
     287              :                         YELLOW "Exited with code " RESET "%d " YELLOW "but expected " RESET "%d\n"
     288              :                         YELLOW "Process terminated signal" RESET " %d\n"
     289              :                         YELLOW "Stderr output:\n>>" RESET "%s" YELLOW "<<" RESET "\n"
     290              :                         YELLOW "Stdout output:\n>>" RESET "%s" YELLOW "<<" RESET "\n",
     291              :                         command,
     292              :                         exit_code,
     293              :                         expected_return_code,
     294              :                         WTERMSIG(return_code),
     295              :                         stderr_view,
     296              :                         stdout_view);
     297              : 
     298            0 :                 if(rt > -1)
     299              :                 {
     300              :                         // Copy str into STDERR buffer
     301            0 :                         run(resize(STDERR,(size_t)rt + 1));
     302              : 
     303            0 :                         run(copy_literal(STDERR,str));
     304              : 
     305              :                 } else {
     306            0 :                         report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1);
     307              :                 }
     308              : 
     309            0 :                 free(str);
     310              : 
     311            0 :                 return(FAILURE);
     312              :         }
     313              : 
     314          285 :         return(SUCCESS);
     315              : }
        

Generated by: LCOV version 2.0-1