/************************************************************************** * * $Id: str_util.c 1.11 2021/03/22 11:28:52 martin REL_M $ * * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany * * Description: * Meinberg Library module providing portable, safe string functions. * * ----------------------------------------------------------------------- * $Log: str_util.c $ * Revision 1.11 2021/03/22 11:28:52 martin * Updated some comments. * Revision 1.10 2021/03/16 12:20:37 martin * Updated some comments. * Revision 1.9 2021/03/12 12:32:24 martin * Updated some comments. * Revision 1.8 2021/03/12 11:00:26 martin * Updated some doxygen comments. * Revision 1.7 2019/11/27 10:37:33 martin * Tiny code style fixes. * Revision 1.6 2019/07/31 15:42:38 martin * Doxygen changes. * Revision 1.5 2018/08/23 13:07:16 martin * Moved the snprintf() safety checks to new inline functions that * can also be used called from specific kernel mode functions. * Unified variable naming. * More common __attribute__ syntax. * Revision 1.4 2018/06/25 13:22:42 martin * Many functios return int rather than size_t, like standard * library functions. * do_str_copy_safe() now returns the number of chars copied. * Revision 1.3 2016/10/24 08:10:04 thomas-b * Fixed counter var check in mbg_memcpy_reversed * Revision 1.2 2016/08/05 12:31:04 martin * New functions mbg_memcpy() and mbg_memcpy_reversed(). * Moved string trim functions from cfg_util module here. * Fixed some compiler warnings. * Revision 1.1 2015/08/25 15:57:21 martin * Initial revision. * **************************************************************************/ #define _STR_UTIL #include #undef _STR_UTIL #include #include #if defined( MBG_TGT_WIN32 ) && !defined( MBG_TGT_CVI ) #define mbg_vsnprintf _vsnprintf #else #define mbg_vsnprintf vsnprintf #endif #if defined( MBG_TGT_DOS ) static /*HDR*/ // On DOS, we use the Borland C/C++ v3.1 compiler by default, which // doesn't provide a vsnprintf() function, so we use a simple replacement // here. Since we share most of the source code between several target // systems, we assume that if our code works properly for other targets // which really provide a vsnprintf() function, it also works properly // on DOS. ;-) int vsnprintf( char *s, size_t max_len, const char *fmt, va_list args ) { (void) max_len; // Quiet compiler warning "not used". return vsprintf( s, fmt, args ); } // vsnprintf #endif /*HDR*/ /** * @brief A portable, safe implementation of vsnprintf(). * * Unfortunately, the behavior of @a vsnprintf and therefore also that * of @a snprintf differs in detail in different build environments * and runtime libraries. * * If the output exceeds the buffer size and thus is truncated, then:
* * - On Windows, a negative value is returned and maybe ***no*** * terminating 0 is written to the output buffer, so the output string * may not be terminated properly. * * - Some versions of glibc return the number of bytes that ***would*** * have been written to the buffer ***if*** the buffer would have been * large enough, instead of the true number of characters that have * been written to the buffer. * * So subsequent calls like * * @code{.c} n = snprintf( s, max_len, ... ); n += snprintf( &s[n], max_len - n, ... ); * @endcode * * may always work properly, or fail with buffer overruns or stack * corruption depending on the build environment. * This wrapper function takes care that strings are always terminated * properly, and that the returned value always matches the number of * characters really written to the string buffer, excluding the * terminating 0. * * @note The @a size_t type parameter used to specify the buffer size * can be larger (e.g. @a unsigned_long) than the @a int type returned * by mostly all functions of the @a printf family. So if a very large * buffer is specified, and a large number of characters (more than * @a MAXINT) are written to that buffer, how can an @a int type * return the large number of characters written to the buffer? * We also try to workaround this here. * * @param[out] s The string buffer to be filled. * @param[in] max_len Size of the output buffer for a 0-terminated string. * @param[in] fmt Format string according to subsequent parameters. * @param[in] args Variable argument list in @a va_list format. * * @return The number of characters written to the output buffer, * except the terminating 0. * * @see ::snprintf_safe * @see ::strncpy_safe * @see ::sn_cpy_str_safe * @see ::sn_cpy_char_safe */ __attribute__( ( format( printf, 3, 0 ) ) ) int vsnprintf_safe( char *s, size_t max_len, const char *fmt, va_list args ) { size_t n; if ( !mbg_buffer_specs_valid( s, max_len ) ) return 0; // Nothing to do anyway. n = mbg_vsnprintf( s, max_len, fmt, args ); // Do some common checks to avoid subsequent buffer overflows, etc. return mbg_chk_snprint_results( n, s, max_len ); } // vsnprintf_safe /*HDR*/ /** * @brief A portable, safe implementation of snprintf(). * * For a detailed description see ::vsnprintf_safe. * * @param[out] s The string buffer to be filled. * @param[in] max_len Size of the output buffer for a 0-terminated string. * @param[in] fmt Format string according to subsequent parameters. * @param[in] ... Variable argument list according to the format string. * * @return The number of characters written to the output buffer, * except the terminating 0. * * @see ::vsnprintf_safe * @see ::strncpy_safe * @see ::sn_cpy_str_safe * @see ::sn_cpy_char_safe */ __attribute__( ( format( printf, 3, 4 ) ) ) int snprintf_safe( char *s, size_t max_len, const char *fmt, ... ) { va_list args; int len; va_start( args, fmt ); len = vsnprintf_safe( s, max_len, fmt, args ); va_end( args); return len; } // snprintf_safe static __mbg_inline /*HDR*/ /* (explicitly excluded from doxygen) * @brief A portable, safe implementation of a copy function. * * This is the basic function used to implemment ::strncpy_safe and * ::sn_cpy_safe. This function takes care that the copied string * is always terminated by 0, but any remaining buffer space * is ***not*** filled up with '0' characters. * * @param[out] dst Pointer to the output buffer. * @param[in] src Pointer to the input buffer. * @param[in] n Number of characters to copy at most. * @param[in,out] p_i Pointer to a counter variable. * * @return The number of characters written to the output buffer, * except the terminating 0. * * @see ::vsnprintf_safe * @see ::snprintf_safe * @see ::strncpy_safe * @see ::sn_cpy_str_safe * @see ::sn_cpy_char_safe */ size_t do_str_copy_safe( char *dst, const char *src, size_t n ) { size_t i = 0; if ( n > 0 ) { for (;;) { *dst = *src; if ( *dst == 0 ) break; // Just copied the terminating 0, done. if ( --n == 0 ) // No more space left in buffer. { *dst = 0; // Force terminating 0. break; } i++; // Count normal characters. src++; dst++; } } return i; } // do_str_copy_safe /*HDR*/ /** * @brief A portable, safe implementation of strncpy(). * * In the original implementation of @a strncpy, if the length of the * string to be copied into the destination buffer exceeds the specified * buffer length, the string in the output buffer is not 0-terminated. * * Our implementation always forces a proper termination by 0, but unlike * the original implementation of @a strncpy, it does ***not*** fill the whole * remaining buffer space with '0' characters. * * @param[out] dst Pointer to the output buffer. * @param[in] src Pointer to the input buffer. * @param[in] max_len Size of the output buffer for 0-terminated string. * * @return Pointer to the destination buffer. * * @see ::vsnprintf_safe * @see ::snprintf_safe * @see ::sn_cpy_str_safe * @see ::sn_cpy_char_safe */ char *strncpy_safe( char *dst, const char *src, size_t max_len ) { do_str_copy_safe( dst, src, max_len ); return dst; } // strncpy_safe /*HDR*/ /** * @brief A function to copy a string safely, returning the number of characters copied. * * This basically works like ::strncpy_safe but instead of a pointer to * the destination buffer it returns the number of characters copied * to the destination buffer. * * @param[out] dst Pointer to the output buffer. * @param[in] max_len Size of the output buffer for 0-terminated string. * @param[in] src Pointer to the input buffer. * * @return The number of characters written to the output buffer, * except the terminating 0. * * @see ::vsnprintf_safe * @see ::snprintf_safe * @see ::strncpy_safe * @see ::sn_cpy_char_safe */ int sn_cpy_str_safe( char *dst, size_t max_len, const char *src ) { size_t n = do_str_copy_safe( dst, src, max_len ); return _int_from_size_t( n ); } // sn_cpy_str_safe /*HDR*/ /** * @brief A function to copy a character safely to a string buffer. * * This basically works like ::sn_cpy_str_safe but expects a character * to be copied to the destination buffer. Appends a terminating 0 to * the string buffer and returns the number of characters copied to * the destination buffer, usually 0 or 1. * * @param[out] dst Pointer to the output buffer. * @param[in] max_len Size of the output buffer for 0-terminated string. * @param[in] c Character to be copied to the destination buffer. * * @return The number of characters written to the output buffer, * except the terminating 0. * * @see ::vsnprintf_safe * @see ::snprintf_safe * @see ::strncpy_safe * @see ::sn_cpy_str_safe */ int sn_cpy_char_safe( char *dst, size_t max_len, char c ) { size_t n; char tmp_str[2]; tmp_str[0] = c; tmp_str[1] = 0; n = do_str_copy_safe( dst, tmp_str, max_len ); return _int_from_size_t( n ); } // sn_cpy_char_safe /*HDR*/ /** * @brief Trim whitespace at the end of a string. * * @param[in,out] s The string to be trimmed. */ void trim_trailing_whitespace( char *s ) { char *cp; // Set all trailing spaces to 0. for ( cp = &s[strlen( s )]; cp > s; ) { --cp; if ( *cp >= ' ' ) break; *cp = 0; } } // trim_trailing_whitespace /*HDR*/ /** * @brief Trim whitespace at the beginning of a string. * * @param[in,out] s The string to be trimmed. */ void trim_leading_whitespace( char *s ) { char *srcp; char *dstp; // Search the first non-space character. for ( srcp = s; *srcp; srcp++ ) if ( *srcp > ' ' ) break; // If there are leading spaces, srcp now // points behind the beginning of the string, // otherwise there's nothing to do. if ( srcp > s ) { // Copy the remaining string. dstp = s; while ( *srcp ) *dstp++ = *srcp++; *dstp = 0; } } // trim_leading_whitespace /*HDR*/ /** * @brief Trim both leading and trailing whitespace from a string. * * @param[in,out] s The string to be trimmed. */ void trim_whitespace( char *s ) { trim_trailing_whitespace( s ); trim_leading_whitespace( s ); } // trim_whitespace /*HDR*/ /** * @brief Copy array of bytes starting at beginning of buffer. * * Can be used if the destination address is in the same buffer * in front of the source address. Even though you would expect * that @a memcpy would also work for this properly, we have seen * cases where it didn't, and only @a memmove worked correctly. * Anyway, we try to avoid the overhead of @a memmove. * * @param[out] dst Destination address behind the source address. * @param[in] src Source address. * @param[in] n_bytes Number of bytes to copy. * * @see ::mbg_memcpy_reversed */ void mbg_memcpy( void *dst, const void *src, size_t n_bytes ) { uint8_t *dstp = (uint8_t *) dst; uint8_t *srcp = (uint8_t *) src; while ( n_bytes-- ) *dstp++ = *srcp++; } // mbg_memcpy /*HDR*/ /** * @brief Copy an array of bytes in reversed order, starting at end of buffer. * * Can be used if the destination address is in the same buffer * behind the source address, so the source address would be * overwritten by a normal @a memcpy. * * @param[out] dst Destination address behind the source address. * @param[in] src Source address. * @param[in] n_bytes Number of bytes to copy. * * @see ::mbg_memcpy */ void mbg_memcpy_reversed( void *dst, const void *src, size_t n_bytes ) { if ( n_bytes ) // Just to be sure it isn't 0. { uint8_t *dstp = ( (uint8_t *) dst ) + n_bytes; uint8_t *srcp = ( (uint8_t *) src ) + n_bytes; while ( n_bytes-- ) *(--dstp) = *(--srcp); } } // mbg_memcpy_reversed