LCOV - code coverage report
Current view: top level - libs/rational/src - rational_logger.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 92.7 % 165 153
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 9 9
Branches: 86.1 % 144 124

             Branch data     Line data    Source code
       1                 :             : #include "rational.h"
       2                 :             : #include <limits.h>
       3                 :             : #include <stdint.h>
       4                 :             : #include <stdlib.h>
       5                 :             : #include <stdio.h>
       6                 :             : #include <string.h>
       7                 :             : 
       8                 :             : // Global flag to manage output of all logging messages
       9                 :             : // in an application and its default value
      10                 :             : _Atomic LOGMODES rational_logger_mode = REGULAR;
      11                 :             : _Atomic Return global_return_status = SUCCESS;
      12                 :             : 
      13                 :             : /**
      14                 :             :  * @brief Converts LOGMODES bit flags to their string representation
      15                 :             :  *
      16                 :             :  * @details This function takes a combination of LOGMODES flags and converts them
      17                 :             :  *          into a human-readable string representation where individual flags
      18                 :             :  *          are separated by " | ". For example, (VERBOSE | SILENT) will be
      19                 :             :  *          converted to "VERBOSE | SILENT"
      20                 :             :  *
      21                 :             :  * @param mode Combination of LOGMODES flags
      22                 :             :  * @return char* Pointer to static string containing flag names
      23                 :             :  *
      24                 :             :  * @note The function uses a static buffer which means:
      25                 :             :  *       1. No memory allocation/deallocation is needed
      26                 :             :  *       2. The buffer contents will be overwritten on next function call
      27                 :             :  *       3. The function is not thread-safe
      28                 :             :  *       4. The returned pointer should not be freed
      29                 :             :  *
      30                 :             :  * @warning Maximum resulting string length is limited to 256 characters
      31                 :             :  */
      32                 :        1520 : char *rational_reconvert(LOGMODES mode)
      33                 :             : {
      34                 :             :         /* Static buffer to store the resulting string */
      35                 :             :         static char buffer[MAX_CHARACTERS];
      36                 :        1520 :         buffer[0] = '\0';  /* Initialize buffer as empty string */
      37                 :             : 
      38                 :             :         /* Flag to track if we're adding the first item (for | separator) */
      39                 :        1520 :         int first = 1;
      40                 :             : 
      41                 :             :         /* Define mapping between flag values and their string representations
      42                 :             :          * The array is terminated with {0, NULL} for easy iteration
      43                 :             :          */
      44                 :             :         static const struct {
      45                 :             :                 LOGMODES flag;     /* Flag value from LOGMODES constants */
      46                 :             :                 const char *name;  /* String representation of the flag */
      47                 :             :         } mapping[] = {
      48                 :             :                 {REGULAR,"REGULAR"},
      49                 :             :                 {VERBOSE,"VERBOSE"},
      50                 :             :                 {TESTING,"TESTING"},
      51                 :             :                 {ERROR,"ERROR"},
      52                 :             :                 {SILENT,"SILENT"},
      53                 :             :                 {UNDECOR,"UNDECOR"},
      54                 :             :                 {REMEMBER,"REMEMBER"},
      55                 :             :                 {VISIBLE_IN_SILENT,"VISIBLE_IN_SILENT"},
      56                 :             :                 {0,NULL}   /* Terminator element */
      57                 :             :         };
      58                 :             : 
      59                 :             :         /* Iterate through all possible flags */
      60         [ +  + ]:       13680 :         for(int i = 0; mapping[i].name != NULL; i++)
      61                 :             :         {
      62                 :             :                 /* Check if current flag is set in mode using bitwise AND */
      63         [ +  + ]:       12160 :                 if(mode & mapping[i].flag)
      64                 :             :                 {
      65                 :             :                         /* Add separator before all elements except the first one */
      66         [ +  + ]:        1536 :                         if(!first)
      67                 :             :                         {
      68                 :          17 :                                 strcat(buffer," | ");
      69                 :             :                         }
      70                 :             : 
      71                 :             :                         /* Add flag name to the result string */
      72                 :        1536 :                         strcat(buffer,mapping[i].name);
      73                 :             : 
      74                 :             :                         /* Clear first flag as we've added an element */
      75                 :        1536 :                         first = 0;
      76                 :             :                 }
      77                 :             :         }
      78                 :             : 
      79                 :        1520 :         return buffer;
      80                 :             : }
      81                 :             : 
      82                 :             : /**
      83                 :             :  *
      84                 :             :  * @brief Format current date and time in ISO format
      85                 :             :  * @param time_string Pointer to the destination buffer that receives the timestamp.
      86                 :             :  * @param buffer_size Size of the destination buffer in bytes.
      87                 :             :  * @return Return SUCCESS on success, FAILURE on error (buffer contents will be empty on failure).
      88                 :             :  *
      89                 :             :  */
      90                 :         325 : static Return logger_show_time(
      91                 :             :         char   *time_string,
      92                 :             :         size_t buffer_size)
      93                 :             : {
      94                 :             :         /* Status returned by this function through provide()
      95                 :             :            Default value assumes successful completion */
      96                 :         325 :         Return status = SUCCESS;
      97                 :             : 
      98                 :             :         struct timeval current_time;
      99                 :             :         struct tm local_time;
     100                 :             : 
     101         [ +  + ]:         325 :         if(gettimeofday(&current_time,NULL) != 0)
     102                 :             :         {
     103                 :           1 :                 time_string[0] = '\0';
     104                 :           1 :                 status = FAILURE;
     105                 :             :         }
     106                 :             : 
     107         [ +  + ]:         325 :         if(SUCCESS == status)
     108                 :             :         {
     109         [ +  + ]:         324 :                 if(localtime_r(&current_time.tv_sec,&local_time) == NULL)
     110                 :             :                 {
     111                 :           1 :                         time_string[0] = '\0';
     112                 :           1 :                         status = FAILURE;
     113                 :             :                 }
     114                 :             :         }
     115                 :             : 
     116         [ +  + ]:         325 :         if(SUCCESS == status)
     117                 :             :         {
     118                 :         323 :                 const int milliseconds = (int)(current_time.tv_usec / 1000);
     119                 :             : 
     120                 :         323 :                 if(snprintf(time_string,
     121                 :             :                         buffer_size,
     122                 :             :                         "%04d-%02d-%02d %02d:%02d:%02d:%03d",
     123                 :         323 :                         local_time.tm_year + 1900,
     124         [ +  + ]:         323 :                         local_time.tm_mon + 1,
     125                 :             :                         local_time.tm_mday,
     126                 :             :                         local_time.tm_hour,
     127                 :             :                         local_time.tm_min,
     128                 :             :                         local_time.tm_sec,
     129                 :             :                         milliseconds) < 0)
     130                 :             :                 {
     131                 :           1 :                         time_string[0] = '\0';
     132                 :           1 :                         status = FAILURE;
     133                 :             :                 }
     134                 :             :         }
     135                 :             : 
     136                 :         325 :         return(status);
     137                 :             : }
     138                 :             : 
     139                 :             : __attribute__((format(printf,3,0)))
     140                 :       46907 : static void logger_line_append_va(
     141                 :             :         char       **line,
     142                 :             :         int        *line_len,
     143                 :             :         const char *fmt,
     144                 :             :         va_list    args)
     145                 :             : {
     146                 :             :         va_list args_copy;
     147                 :       46907 :         va_copy(args_copy,args);
     148                 :       46907 :         const int needed = vsnprintf(NULL,0,fmt,args_copy);
     149                 :       46907 :         va_end(args_copy);
     150                 :             : 
     151         [ +  + ]:       46907 :         if(needed < 0)
     152                 :             :         {
     153                 :           3 :                 return;
     154                 :             :         }
     155                 :             : 
     156                 :       46905 :         const size_t new_len = (size_t)(*line_len) + (size_t)needed;
     157                 :       46905 :         char *tmp = realloc(*line,new_len + 1);
     158                 :             : 
     159         [ +  + ]:       46905 :         if(tmp == NULL)
     160                 :             :         {
     161                 :           1 :                 return;
     162                 :             :         }
     163                 :             : 
     164                 :       46904 :         *line = tmp;
     165                 :             : 
     166                 :             :         va_list args_copy2;
     167                 :       46904 :         va_copy(args_copy2,args);
     168                 :       46904 :         vsnprintf(*line + *line_len,(size_t)needed + 1,fmt,args_copy2);
     169                 :       46904 :         va_end(args_copy2);
     170                 :             : 
     171                 :       46904 :         *line_len = (int)new_len;
     172                 :             : }
     173                 :             : 
     174                 :             : __attribute__((format(printf,3,4)))
     175                 :       21002 : static void logger_line_append(
     176                 :             :         char       **line,
     177                 :             :         int        *line_len,
     178                 :             :         const char *fmt,
     179                 :             :         ...)
     180                 :             : {
     181                 :             :         va_list args;
     182                 :       21002 :         va_start(args,fmt);
     183                 :       21002 :         logger_line_append_va(line,line_len,fmt,args);
     184                 :       21002 :         va_end(args);
     185                 :       21002 : }
     186                 :             : 
     187                 :             : /**
     188                 :             :  * @brief Append one byte as a visible hexadecimal escape
     189                 :             :  *
     190                 :             :  * @param[in,out] line Destination buffer with enough free space
     191                 :             :  * @param[in,out] line_len Current destination length in bytes
     192                 :             :  * @param[in] byte Byte to append as \xNN
     193                 :             :  */
     194                 :           8 : static void logger_line_append_hex_escape(
     195                 :             :         char          *line,
     196                 :             :         size_t        *line_len,
     197                 :             :         unsigned char byte)
     198                 :             : {
     199                 :             :         /*
     200                 :             :          * Use an explicit digit table instead of sprintf().
     201                 :             :          * This helper is used from the logger cleanup path, so it should not depend
     202                 :             :          * on formatting functions or temporary buffers just to render one byte
     203                 :             :          */
     204                 :             :         static const char hex_digits[] = "0123456789ABCDEF";
     205                 :             : 
     206                 :             :         /*
     207                 :             :          * Write the escape directly into the caller-owned output buffer.
     208                 :             :          * The sanitizer allocates enough room before calling this helper, and
     209                 :             :          * line_len is advanced after every written character so the next append can
     210                 :             :          * continue from the correct position
     211                 :             :          */
     212                 :           8 :         line[(*line_len)++] = '\\';
     213                 :           8 :         line[(*line_len)++] = 'x';
     214                 :           8 :         line[(*line_len)++] = hex_digits[byte >> 4U];
     215                 :           8 :         line[(*line_len)++] = hex_digits[byte & 0x0FU];
     216                 :           8 : }
     217                 :             : 
     218                 :             : /**
     219                 :             :  * @brief Decode one UTF-8 sequence from a bounded byte range
     220                 :             :  *
     221                 :             :  * @details
     222                 :             :  * The logger uses this small decoder instead of locale-dependent multibyte
     223                 :             :  * conversion because the process locale is not guaranteed to be initialized
     224                 :             :  * before a log line is printed. The function accepts only shortest-form UTF-8,
     225                 :             :  * rejects surrogate code points, and rejects values outside the Unicode range
     226                 :             :  *
     227                 :             :  * @param[in] line Source byte range
     228                 :             :  * @param[in] line_len Number of bytes available at @p line
     229                 :             :  * @param[out] codepoint Decoded Unicode code point
     230                 :             :  * @return Number of bytes consumed, or 0 when the input is not valid UTF-8
     231                 :             :  */
     232                 :        6264 : static size_t logger_line_decode_utf8(
     233                 :             :         const char *line,
     234                 :             :         size_t     line_len,
     235                 :             :         uint32_t   *codepoint)
     236                 :             : {
     237                 :             :         /*
     238                 :             :          * Refuse invalid input before looking at the first byte.
     239                 :             :          * Returning 0 tells the caller to treat the current byte as unsafe and
     240                 :             :          * print it as a visible escape instead of trusting it as text
     241                 :             :          */
     242   [ +  -  +  -  :        6264 :         if(line == NULL || codepoint == NULL || line_len == 0U)
                   -  + ]
     243                 :             :         {
     244                 :           0 :                 return(0U);
     245                 :             :         }
     246                 :             : 
     247                 :        6264 :         const unsigned char first_byte = (unsigned char)line[0];
     248                 :             : 
     249                 :             :         /*
     250                 :             :          * ASCII is a single-byte subset of UTF-8.
     251                 :             :          * Decoding it here keeps the rest of the function focused on multibyte
     252                 :             :          * sequences and lets the caller apply its own ASCII control-character policy
     253                 :             :          */
     254         [ -  + ]:        6264 :         if(first_byte < 0x80U)
     255                 :             :         {
     256                 :           0 :                 *codepoint = (uint32_t)first_byte;
     257                 :           0 :                 return(1U);
     258                 :             :         }
     259                 :             : 
     260                 :             :         /*
     261                 :             :          * Classify the leading byte and prepare the partial code point.
     262                 :             :          * The first byte tells us how many continuation bytes must follow and also
     263                 :             :          * provides the high bits of the decoded Unicode value
     264                 :             :          */
     265                 :        6264 :         size_t expected_len = 0U;
     266                 :        6264 :         uint32_t decoded_codepoint = 0U;
     267                 :        6264 :         uint32_t lowest_codepoint = 0U;
     268                 :             : 
     269   [ +  +  +  + ]:        6264 :         if(first_byte >= 0xC2U && first_byte <= 0xDFU)
     270                 :             :         {
     271                 :        5401 :                 expected_len = 2U;
     272                 :        5401 :                 decoded_codepoint = (uint32_t)(first_byte & 0x1FU);
     273                 :        5401 :                 lowest_codepoint = 0x80U;
     274   [ +  +  +  - ]:         863 :         } else if(first_byte >= 0xE0U && first_byte <= 0xEFU){
     275                 :         861 :                 expected_len = 3U;
     276                 :         861 :                 decoded_codepoint = (uint32_t)(first_byte & 0x0FU);
     277                 :         861 :                 lowest_codepoint = 0x800U;
     278   [ -  +  -  - ]:           2 :         } else if(first_byte >= 0xF0U && first_byte <= 0xF4U){
     279                 :           0 :                 expected_len = 4U;
     280                 :           0 :                 decoded_codepoint = (uint32_t)(first_byte & 0x07U);
     281                 :           0 :                 lowest_codepoint = 0x10000U;
     282                 :             :         } else {
     283                 :             :                 /*
     284                 :             :                  * Bytes outside these leading-byte ranges cannot start a valid UTF-8
     285                 :             :                  * sequence. This includes continuation bytes seen without a starter,
     286                 :             :                  * obsolete overlong starters, and values beyond the Unicode limit
     287                 :             :                  */
     288                 :           2 :                 return(0U);
     289                 :             :         }
     290                 :             : 
     291                 :             :         /*
     292                 :             :          * A valid sequence must be complete inside the provided byte range.
     293                 :             :          * The logger works with bounded buffers, so an incomplete trailing sequence
     294                 :             :          * is escaped byte by byte rather than reading past the formatted line
     295                 :             :          */
     296         [ -  + ]:        6262 :         if(line_len < expected_len)
     297                 :             :         {
     298                 :           0 :                 return(0U);
     299                 :             :         }
     300                 :             : 
     301                 :             :         /*
     302                 :             :          * Every byte after the leading byte must have the UTF-8 continuation shape
     303                 :             :          * 10xxxxxx. While checking that shape, assemble the final code point by
     304                 :             :          * shifting in the six payload bits carried by each continuation byte
     305                 :             :          */
     306         [ +  + ]:       13383 :         for(size_t i = 1U; i < expected_len; i++)
     307                 :             :         {
     308                 :        7123 :                 const unsigned char continuation_byte = (unsigned char)line[i];
     309                 :             : 
     310         [ +  + ]:        7123 :                 if((continuation_byte & 0xC0U) != 0x80U)
     311                 :             :                 {
     312                 :           2 :                         return(0U);
     313                 :             :                 }
     314                 :             : 
     315                 :        7121 :                 decoded_codepoint = (decoded_codepoint << 6U) | (uint32_t)(continuation_byte & 0x3FU);
     316                 :             :         }
     317                 :             : 
     318                 :             :         /*
     319                 :             :          * Reject overlong encodings.
     320                 :             :          * UTF-8 has exactly one shortest byte representation for each code point,
     321                 :             :          * and accepting longer aliases would let unsafe bytes hide behind another
     322                 :             :          * spelling of the same character
     323                 :             :          */
     324         [ -  + ]:        6260 :         if(decoded_codepoint < lowest_codepoint)
     325                 :             :         {
     326                 :           0 :                 return(0U);
     327                 :             :         }
     328                 :             : 
     329                 :             :         /*
     330                 :             :          * UTF-16 surrogate values are not Unicode scalar values.
     331                 :             :          * They are invalid in UTF-8 text, so the logger escapes their original bytes
     332                 :             :          * instead of copying them into terminal output
     333                 :             :          */
     334   [ -  +  -  - ]:        6260 :         if(decoded_codepoint >= 0xD800U && decoded_codepoint <= 0xDFFFU)
     335                 :             :         {
     336                 :           0 :                 return(0U);
     337                 :             :         }
     338                 :             : 
     339                 :             :         /*
     340                 :             :          * Unicode ends at U+10FFFF.
     341                 :             :          * Anything above that value is invalid input and must be shown as escaped
     342                 :             :          * bytes, not as trusted text
     343                 :             :          */
     344         [ -  + ]:        6260 :         if(decoded_codepoint > 0x10FFFFU)
     345                 :             :         {
     346                 :           0 :                 return(0U);
     347                 :             :         }
     348                 :             : 
     349                 :             :         /*
     350                 :             :          * At this point the byte sequence is well-formed UTF-8.
     351                 :             :          * Return both the decoded code point and the number of bytes consumed so the
     352                 :             :          * sanitizer can decide whether the character is safe to copy
     353                 :             :          */
     354                 :        6260 :         *codepoint = decoded_codepoint;
     355                 :        6260 :         return(expected_len);
     356                 :             : }
     357                 :             : 
     358                 :             : /**
     359                 :             :  * @brief Escape bytes that can disrupt terminal output
     360                 :             :  *
     361                 :             :  * @details
     362                 :             :  * The logger receives an already formatted line, so this layer cannot tell
     363                 :             :  * whether bytes came from a file name, a database path, an error message, or
     364                 :             :  * fixed application text. The filter therefore treats the complete log line as
     365                 :             :  * terminal output and escapes only byte patterns that are unsafe to write as
     366                 :             :  * text. Plain printable ASCII, newline, carriage return, tab, raw ESC, and
     367                 :             :  * valid non-C1 UTF-8 multibyte sequences are preserved. Invalid multibyte input
     368                 :             :  * is escaped byte by byte so malformed file names remain visible without being
     369                 :             :  * interpreted by the terminal. Unicode C1 control characters such as U+0090
     370                 :             :  * are also escaped, even though their UTF-8 byte sequence is formally valid,
     371                 :             :  * because terminals may interpret them as control strings and hide later output.
     372                 :             :  *
     373                 :             :  * This first pass deliberately leaves raw ESC bytes unchanged. The project uses
     374                 :             :  * ESC-based decorations such as bold and colors, and distinguishing those
     375                 :             :  * trusted decorations from path bytes requires a separate whitelist policy. That
     376                 :             :  * whitelist can be added later without changing slog() call sites
     377                 :             :  *
     378                 :             :  * @param[in,out] line Pointer to the allocated line buffer
     379                 :             :  * @param[in,out] line_len Current line length in bytes, updated on success
     380                 :             :  */
     381                 :       37114 : static void logger_line_sanitize_for_terminal(
     382                 :             :         char **line,
     383                 :             :         int  *line_len)
     384                 :             : {
     385                 :             :         /*
     386                 :             :          * Nothing useful can be sanitized without an existing allocated buffer and a
     387                 :             :          * positive byte count. Returning quietly preserves the logger's current
     388                 :             :          * best-effort behavior for allocation and formatting failure paths
     389                 :             :          */
     390   [ +  -  +  +  :       37114 :         if(line == NULL || *line == NULL || line_len == NULL || *line_len <= 0)
             +  -  +  + ]
     391                 :             :         {
     392                 :       11213 :                 return;
     393                 :             :         }
     394                 :             : 
     395                 :       25901 :         const size_t input_len = (size_t)*line_len;
     396                 :             : 
     397                 :             :         /*
     398                 :             :          * Escaping one input byte as \xNN needs four output bytes.
     399                 :             :          * This guard keeps the worst-case allocation and the final int length update
     400                 :             :          * inside representable bounds
     401                 :             :          */
     402         [ -  + ]:       25901 :         if(input_len > (size_t)INT_MAX / 4U)
     403                 :             :         {
     404                 :           0 :                 return;
     405                 :             :         }
     406                 :             : 
     407                 :             :         /*
     408                 :             :          * Allocate for the worst case where every input byte becomes \xNN.
     409                 :             :          * The extra byte is for a terminator because the logger stores text in a C
     410                 :             :          * string buffer even though fwrite() uses the explicit byte length
     411                 :             :          */
     412                 :       25901 :         char *sanitized_line = malloc((input_len * 4U) + 1U);
     413                 :             : 
     414         [ -  + ]:       25901 :         if(sanitized_line == NULL)
     415                 :             :         {
     416                 :           0 :                 return;
     417                 :             :         }
     418                 :             : 
     419                 :             :         /*
     420                 :             :          * input_position walks through the original formatted line.
     421                 :             :          * output_len tracks the next free position in the sanitized replacement
     422                 :             :          * buffer, which may grow faster than the input when bytes are escaped
     423                 :             :          */
     424                 :       25901 :         size_t input_position = 0U;
     425                 :       25901 :         size_t output_len = 0U;
     426                 :             : 
     427                 :             :         /*
     428                 :             :          * Consume one ASCII byte or one complete UTF-8 sequence per loop.
     429                 :             :          * The loop never trusts a multibyte sequence until it has been decoded and
     430                 :             :          * checked, so damaged input cannot leak raw terminal controls into output
     431                 :             :          */
     432         [ +  + ]:     1331908 :         while(input_position < input_len)
     433                 :             :         {
     434                 :     1306007 :                 const unsigned char byte = (unsigned char)(*line)[input_position];
     435                 :             : 
     436         [ +  + ]:     1306007 :                 if(byte < 0x80U)
     437                 :             :                 {
     438                 :             :                         /*
     439                 :             :                          * ASCII printable characters are safe to copy.
     440                 :             :                          * Newline, carriage return, and tab are preserved because log lines
     441                 :             :                          * legitimately use them for layout. ESC is also preserved for now so
     442                 :             :                          * existing color and bold decorations keep working until an explicit
     443                 :             :                          * decoration whitelist is added
     444                 :             :                          */
     445   [ +  +  +  +  :     1299743 :                         if(byte == '\n' || byte == '\r' || byte == '\t' || byte == '\033' || (byte >= 0x20U && byte != 0x7FU))
          +  +  +  +  +  
                +  +  + ]
     446                 :             :                         {
     447                 :     1299741 :                                 sanitized_line[output_len++] = (char)byte;
     448                 :             :                         } else {
     449                 :             :                                 /*
     450                 :             :                                  * Other ASCII control bytes and DEL are not safe terminal text.
     451                 :             :                                  * Showing them as \xNN makes the byte visible to the user while
     452                 :             :                                  * preventing the terminal from treating it as an action
     453                 :             :                                  */
     454                 :           2 :                                 logger_line_append_hex_escape(sanitized_line,&output_len,byte);
     455                 :             :                         }
     456                 :             : 
     457                 :     1299743 :                         input_position++;
     458                 :     1299747 :                         continue;
     459                 :             :                 }
     460                 :             : 
     461                 :             :                 /*
     462                 :             :                  * Non-ASCII input must first prove that it is valid UTF-8.
     463                 :             :                  * The decoder is intentionally local and locale-independent so logger
     464                 :             :                  * safety does not depend on whether setlocale() has already run
     465                 :             :                  */
     466                 :        6264 :                 uint32_t codepoint = 0U;
     467                 :        6264 :                 const size_t decoded_len = logger_line_decode_utf8(*line + input_position,
     468                 :             :                         input_len - input_position,
     469                 :             :                         &codepoint);
     470                 :             : 
     471                 :             :                 /*
     472                 :             :                  * Invalid UTF-8 is escaped one byte at a time.
     473                 :             :                  * This preserves every original byte in a readable form and then retries
     474                 :             :                  * from the next byte, which helps recover cleanly after a malformed prefix
     475                 :             :                  */
     476         [ +  + ]:        6264 :                 if(decoded_len == 0U)
     477                 :             :                 {
     478                 :           4 :                         logger_line_append_hex_escape(sanitized_line,&output_len,byte);
     479                 :           4 :                         input_position++;
     480                 :           4 :                         continue;
     481                 :             :                 }
     482                 :             : 
     483                 :             :                 /*
     484                 :             :                  * C1 controls are dangerous even when encoded as valid UTF-8.
     485                 :             :                  * U+0090, for example, is a terminal control-string introducer on some
     486                 :             :                  * terminals, so the original bytes are escaped instead of being copied
     487                 :             :                  */
     488   [ +  -  +  + ]:        6260 :                 if(codepoint >= 0x80U && codepoint <= 0x9FU)
     489                 :             :                 {
     490         [ +  + ]:           3 :                         for(size_t i = 0U; i < decoded_len; i++)
     491                 :             :                         {
     492                 :           2 :                                 const unsigned char control_byte = (unsigned char)(*line)[input_position + i];
     493                 :           2 :                                 logger_line_append_hex_escape(sanitized_line,&output_len,control_byte);
     494                 :             :                         }
     495                 :             :                 } else {
     496                 :             :                         /*
     497                 :             :                          * Valid non-C1 UTF-8 is copied unchanged.
     498                 :             :                          * This keeps ordinary international file names and messages readable
     499                 :             :                          * instead of turning every non-ASCII character into escapes
     500                 :             :                          */
     501                 :        6259 :                         memcpy(sanitized_line + output_len,*line + input_position,decoded_len);
     502                 :        6259 :                         output_len += decoded_len;
     503                 :             :                 }
     504                 :             : 
     505                 :        6260 :                 input_position += decoded_len;
     506                 :             :         }
     507                 :             : 
     508                 :             :         /*
     509                 :             :          * Replace the original formatted line with the sanitized version.
     510                 :             :          * The caller will pass the same buffer to REMEMBER and fwrite(), so both
     511                 :             :          * delayed warnings and immediate terminal output see identical safe text
     512                 :             :          */
     513                 :       25901 :         sanitized_line[output_len] = '\0';
     514                 :       25901 :         free(*line);
     515                 :       25901 :         *line = sanitized_line;
     516                 :       25901 :         *line_len = (int)output_len;
     517                 :             : }
     518                 :             : 
     519                 :             : __attribute__((format(printf,7,0)))
     520                 :       37114 : static void logger_line(
     521                 :             :         char              **line,
     522                 :             :         int               *line_len,
     523                 :             :         const LOGMODES    level,
     524                 :             :         const char *const filename,
     525                 :             :         size_t            line_number,
     526                 :             :         const char *const funcname,
     527                 :             :         const char        *fmt,
     528                 :             :         va_list           args)
     529                 :             : {
     530         [ +  + ]:       37114 :         if(rational_logger_mode & SILENT)
     531                 :             :         {
     532         [ +  + ]:        4408 :                 if(level & VISIBLE_IN_SILENT)
     533                 :             :                 {
     534                 :          28 :                         logger_line_append_va(line,line_len,fmt,args);
     535                 :             :                 }
     536                 :             : 
     537                 :        4408 :                 return;
     538                 :             :         }
     539                 :             : 
     540   [ +  +  +  +  :       32706 :         if(!(level & UNDECOR) && (level & TESTING) && (rational_logger_mode & TESTING))
                   +  + ]
     541                 :             :         {
     542                 :             :                 // Print out the word "TESTING:"
     543                 :       19441 :                 logger_line_append(line,line_len,"TESTING:");
     544                 :             :         }
     545                 :             : 
     546   [ +  +  +  +  :       32706 :         if(!(level & UNDECOR) && (level & (VERBOSE|ERROR)) && (rational_logger_mode & VERBOSE))
                   +  + ]
     547                 :             :         {
     548                 :             :                 char time_string[sizeof "2011-10-18 07:07:09:000"];
     549                 :         325 :                 (void)logger_show_time(time_string,sizeof(time_string));
     550                 :             : 
     551                 :             :                 // Print out current time
     552                 :         325 :                 logger_line_append(line,line_len,"%s ",time_string);
     553                 :             : 
     554                 :             :                 // Print out the source file name
     555                 :         325 :                 logger_line_append(line,line_len,"%s:",filename);
     556                 :             : 
     557                 :             :                 // Print out line number in source file
     558                 :         325 :                 logger_line_append(line,line_len,"%03zu:",line_number);
     559                 :             : 
     560                 :             :                 // Print out name of the function itself
     561                 :         325 :                 logger_line_append(line,line_len,"%s:",funcname);
     562                 :             :         }
     563                 :             : 
     564   [ +  +  +  +  :       32706 :         if(!(level & UNDECOR) && (level & ERROR) && (rational_logger_mode & (REGULAR | ERROR)))
                   +  + ]
     565                 :             :         {
     566                 :             :                 // Print out error prefix
     567                 :          26 :                 logger_line_append(line,line_len,"ERROR: ");
     568                 :             : 
     569   [ +  +  +  +  :       32680 :         } else if(!(level & UNDECOR) && (level & ERROR) && (rational_logger_mode & (TESTING | VERBOSE))){
                   +  + ]
     570                 :             :                 // Print out the word "ERROR:"
     571                 :         235 :                 logger_line_append(line,line_len,"ERROR:");
     572                 :             :         }
     573                 :             : 
     574   [ +  +  +  + ]:       32706 :         if(level & ERROR && rational_logger_mode & ERROR)
     575                 :             :         {
     576                 :             :                 // Print out other arguments
     577                 :           2 :                 logger_line_append_va(line,line_len,fmt,args);
     578                 :             : 
     579   [ +  +  +  + ]:       32704 :         } else if(level & (REGULAR|ERROR) && rational_logger_mode & REGULAR){
     580                 :             :                 // Print out other arguments
     581                 :        1132 :                 logger_line_append_va(line,line_len,fmt,args);
     582                 :             : 
     583   [ +  +  +  + ]:       31572 :         } else if(level & (VERBOSE|ERROR) && rational_logger_mode & VERBOSE){
     584                 :             :                 // Print out other arguments
     585                 :         640 :                 logger_line_append_va(line,line_len,fmt,args);
     586                 :             : 
     587   [ +  +  +  + ]:       30932 :         } else if(level & (TESTING|ERROR) && rational_logger_mode & TESTING){
     588                 :             :                 // Print out other arguments
     589                 :       24103 :                 logger_line_append_va(line,line_len,fmt,args);
     590                 :             :         }
     591                 :             : }
     592                 :             : 
     593                 :             : /**
     594                 :             :  *
     595                 :             :  * @brief Build and print a formatted log line with file, line, and function metadata
     596                 :             :  *
     597                 :             :  * @details When REMEMBER is set and the weak rational_remember() symbol is defined,
     598                 :             :  *          the formatted line (without a trailing newline) and its length are
     599                 :             :  *          passed to that callback.
     600                 :             :  *
     601                 :             :  */
     602                 :             : __attribute__((format(printf,5,6))) // Without this we will get warning
     603                 :       37114 : void rational_logger(
     604                 :             :         const LOGMODES    level,
     605                 :             :         const char *const filename,
     606                 :             :         size_t            line,
     607                 :             :         const char *const funcname,
     608                 :             :         const char        *fmt,
     609                 :             :         ...)
     610                 :             : {
     611                 :             : 
     612                 :       37114 :         char *logger_line_text = NULL;
     613                 :       37114 :         int line_len = 0;
     614                 :             : 
     615                 :             :         va_list args;
     616                 :       37114 :         va_start(args,fmt);
     617                 :       37114 :         logger_line(&logger_line_text,&line_len,level,filename,line,funcname,fmt,args);
     618                 :       37114 :         va_end(args);
     619                 :             : 
     620                 :             :         /*
     621                 :             :          * Sanitize the final formatted line before any consumer sees it.
     622                 :             :          * This keeps immediate terminal output and delayed REMEMBER output identical,
     623                 :             :          * and prevents path bytes from being interpreted as terminal controls
     624                 :             :          */
     625                 :       37114 :         logger_line_sanitize_for_terminal(&logger_line_text,&line_len);
     626                 :             : 
     627   [ +  +  +  -  :       37114 :         if((level & REMEMBER) && rational_remember && logger_line_text != NULL && line_len > 0)
             +  +  +  + ]
     628                 :             :         {
     629                 :         143 :                 rational_remember(logger_line_text,line_len);
     630                 :             :         }
     631                 :             : 
     632         [ +  + ]:       37114 :         if(logger_line_text != NULL)
     633                 :             :         {
     634                 :       25902 :                 fwrite(logger_line_text,sizeof(char),(size_t)line_len,stdout);
     635                 :             :         }
     636                 :             : 
     637                 :       37114 :         free(logger_line_text);
     638                 :       37114 : }
     639                 :             : 
     640                 :             : #ifdef TEST
     641                 :             : /**
     642                 :             :  * @file test_slog.c
     643                 :             :  * @brief Complete test suite for log functionality
     644                 :             :  */
     645                 :             : int main(void)
     646                 :             : {
     647                 :             :         printf("All available combinations:\n");
     648                 :             :         printf("%s\n",rational_convert(REGULAR));
     649                 :             :         printf("%s\n",rational_convert(VERBOSE));
     650                 :             :         printf("%s\n",rational_convert(TESTING));
     651                 :             :         printf("%s\n",rational_convert(SILENT));
     652                 :             :         printf("%s\n",rational_convert(REGULAR|VERBOSE));
     653                 :             :         printf("%s\n",rational_convert(REGULAR|TESTING));
     654                 :             :         printf("%s\n",rational_convert(VERBOSE|TESTING));
     655                 :             :         printf("%s\n",rational_convert(REGULAR|VERBOSE|TESTING));
     656                 :             :         printf("%s\n",rational_convert(ERROR));
     657                 :             :         printf("%s\n",rational_convert(UNDECOR));
     658                 :             :         printf("%s\n",rational_convert(EVERY|UNDECOR));
     659                 :             :         printf("%s\n",rational_convert(ERROR|UNDECOR));
     660                 :             :         printf("%s\n",rational_convert(VISIBLE_IN_SILENT));
     661                 :             : 
     662                 :             :         /* Test REGULAR mode combinations */
     663                 :             :         rational_logger_mode = REGULAR;
     664                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     665                 :             :         printf("1.  Must print:"); slog(REGULAR,"true"); printf("\n");
     666                 :             :         printf("2. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
     667                 :             :         printf("3. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
     668                 :             :         printf("4.  Must print:");   slog(ERROR,"true"); printf("\n");
     669                 :             : 
     670                 :             :         /* Test VERBOSE mode combinations */
     671                 :             :         rational_logger_mode = VERBOSE;
     672                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     673                 :             :         printf("5. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
     674                 :             :         printf("6.  Must print:"); slog(VERBOSE,"true"); printf("\n");
     675                 :             :         printf("7. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
     676                 :             :         printf("8.  Must print:");   slog(ERROR,"true"); printf("\n");
     677                 :             : 
     678                 :             :         /* Test TESTING mode combinations */
     679                 :             :         rational_logger_mode = TESTING;
     680                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     681                 :             :         printf("9.  Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
     682                 :             :         printf("10. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
     683                 :             :         printf("11.  Must print:"); slog(TESTING,"true"); printf("\n");
     684                 :             :         printf("12.  Must print:");   slog(ERROR,"true"); printf("\n");
     685                 :             : 
     686                 :             :         /* Test SILENT mode combinations */
     687                 :             :         rational_logger_mode = SILENT;
     688                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     689                 :             :         printf("13. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
     690                 :             :         printf("14. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
     691                 :             :         printf("15. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
     692                 :             :         printf("16. Won't print:");   slog(ERROR,"but printed!"); printf("\n");
     693                 :             : 
     694                 :             :         /* Test REGULAR|VERBOSE combinations */
     695                 :             :         rational_logger_mode = REGULAR|VERBOSE;
     696                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     697                 :             :         printf("17.  Must print:"); slog(REGULAR,"true"); printf("\n");
     698                 :             :         printf("18.  Must print:"); slog(VERBOSE,"true"); printf("\n");
     699                 :             :         printf("19. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
     700                 :             :         printf("20.  Must print:");   slog(ERROR,"true"); printf("\n");
     701                 :             : 
     702                 :             :         /* Test REGULAR|TESTING combinations */
     703                 :             :         rational_logger_mode = REGULAR|TESTING;
     704                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     705                 :             :         printf("21.  Must print:"); slog(REGULAR,"true"); printf("\n");
     706                 :             :         printf("22. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
     707                 :             :         printf("23.  Must print:"); slog(TESTING,"true"); printf("\n");
     708                 :             :         printf("24.  Must print:"); slog(ERROR,"true"); printf("\n");
     709                 :             : 
     710                 :             :         /* Test VERBOSE|TESTING combinations */
     711                 :             :         rational_logger_mode = VERBOSE|TESTING;
     712                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     713                 :             :         printf("25. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
     714                 :             :         printf("26.  Must print:"); slog(VERBOSE,"true"); printf("\n");
     715                 :             :         printf("27.  Must print:"); slog(TESTING,"true"); printf("\n");
     716                 :             :         printf("28.  Must print:");   slog(ERROR,"true"); printf("\n");
     717                 :             : 
     718                 :             :         /* Test REGULAR|VERBOSE|TESTING combinations */
     719                 :             :         rational_logger_mode = REGULAR|VERBOSE|TESTING;
     720                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     721                 :             :         printf("29. Must print:"); slog(REGULAR,"true"); printf("\n");
     722                 :             :         printf("30. Must print:"); slog(VERBOSE,"true"); printf("\n");
     723                 :             :         printf("31. Must print:"); slog(TESTING,"true"); printf("\n");
     724                 :             :         printf("32. Must print:");   slog(ERROR,"true"); printf("\n");
     725                 :             : 
     726                 :             :         /* Test ERROR mode combinations */
     727                 :             :         rational_logger_mode = ERROR;
     728                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     729                 :             :         printf("33. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
     730                 :             :         printf("34. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
     731                 :             :         printf("35. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
     732                 :             :         printf("36.  Must print:");   slog(ERROR,"true"); printf("\n");
     733                 :             : 
     734                 :             :         /*
     735                 :             :          * Test UNDECOR flag: suppress logger prefixes (TESTING:, time/file/line/func, ERROR:)
     736                 :             :          * The output between the '|' markers should contain only the message payload.
     737                 :             :          */
     738                 :             : 
     739                 :             :         rational_logger_mode = EVERY|ERROR;
     740                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     741                 :             :         printf("37. Must print no prefixes:|"); slog(EVERY|UNDECOR,"true"); printf("|\n");
     742                 :             :         printf("38. Must print no ERROR prefix:|"); slog(ERROR|UNDECOR,"true"); printf("|\n");
     743                 :             : 
     744                 :             :         rational_logger_mode = VERBOSE;
     745                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     746                 :             :         printf("39. Must print no time/file/line/func:|"); slog(VERBOSE|UNDECOR,"true"); printf("|\n");
     747                 :             : 
     748                 :             :         rational_logger_mode = TESTING;
     749                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     750                 :             :         printf("40. Must print no TESTING prefix:|"); slog(TESTING|UNDECOR,"true"); printf("|\n");
     751                 :             : 
     752                 :             :         rational_logger_mode = REGULAR;
     753                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     754                 :             :         printf("41. Must not print (VERBOSE not enabled):|"); slog(VERBOSE|UNDECOR,"but printed!"); printf("|\n");
     755                 :             : 
     756                 :             :         rational_logger_mode = SILENT;
     757                 :             :         printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
     758                 :             :         printf("42. Must print in SILENT without prefixes:|"); slog(EVERY|VISIBLE_IN_SILENT,"true"); printf("|\n");
     759                 :             :         printf("43. Must print no ERROR prefix in SILENT:|"); slog(ERROR|VISIBLE_IN_SILENT,"true"); printf("|\n");
     760                 :             : 
     761                 :             :         return 0;
     762                 :             : }
     763                 :             : #endif
        

Generated by: LCOV version 2.0-1