tuklib_mbstr_nonprint: Preserve the value of errno

A typical use case is like this:

    printf("%s: %s\n", tuklib_mask_nonprint(filename), strerror(errno));

tuklib_mask_nonprint() may call mbrtowc() and malloc() which may modify
errno. If errno isn't preserved, the error message might be wrong if
a compiler decides to call tuklib_mask_nonprint() before strerror().

Fixes: 40e5733055
This commit is contained in:
Lasse Collin 2025-01-05 20:14:49 +02:00
parent 2a9e91d796
commit c405264c03
No known key found for this signature in database
GPG Key ID: 38EE757D69184620
2 changed files with 17 additions and 4 deletions

View File

@ -12,6 +12,7 @@
#include "tuklib_mbstr_nonprint.h" #include "tuklib_mbstr_nonprint.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h>
#ifdef HAVE_MBRTOWC #ifdef HAVE_MBRTOWC
# include <wchar.h> # include <wchar.h>
@ -94,13 +95,18 @@ has_nonprint(const char *str, size_t len)
extern bool extern bool
tuklib_has_nonprint(const char *str) tuklib_has_nonprint(const char *str)
{ {
return has_nonprint(str, strlen(str)); const int saved_errno = errno;
const bool ret = has_nonprint(str, strlen(str));
errno = saved_errno;
return ret;
} }
extern const char * extern const char *
tuklib_mask_nonprint_r(const char *str, char **mem) tuklib_mask_nonprint_r(const char *str, char **mem)
{ {
const int saved_errno = errno;
// Free the old string, if any. // Free the old string, if any.
free(*mem); free(*mem);
*mem = NULL; *mem = NULL;
@ -108,8 +114,10 @@ tuklib_mask_nonprint_r(const char *str, char **mem)
// If the whole input string contains only printable characters, // If the whole input string contains only printable characters,
// return the input string. // return the input string.
const size_t len = strlen(str); const size_t len = strlen(str);
if (!has_nonprint(str, len)) if (!has_nonprint(str, len)) {
errno = saved_errno;
return str; return str;
}
// Allocate memory for the masked string. Since we use the single-byte // Allocate memory for the masked string. Since we use the single-byte
// character '?' to mask non-printable characters, it's possible that // character '?' to mask non-printable characters, it's possible that
@ -119,8 +127,10 @@ tuklib_mask_nonprint_r(const char *str, char **mem)
// If allocation fails, return "???" because it should be safer than // If allocation fails, return "???" because it should be safer than
// returning the unmasked string. // returning the unmasked string.
*mem = malloc(len + 1); *mem = malloc(len + 1);
if (*mem == NULL) if (*mem == NULL) {
errno = saved_errno;
return "???"; return "???";
}
// Replace all non-printable characters with '?'. // Replace all non-printable characters with '?'.
char *dest = *mem; char *dest = *mem;
@ -139,6 +149,7 @@ tuklib_mask_nonprint_r(const char *str, char **mem)
*dest = '\0'; *dest = '\0';
errno = saved_errno;
return *mem; return *mem;
} }

View File

@ -25,7 +25,8 @@ extern bool tuklib_has_nonprint(const char *str);
/// \brief Check if a string contains any non-printable characters /// \brief Check if a string contains any non-printable characters
/// ///
/// \return false if str contains only valid multibyte characters and /// \return false if str contains only valid multibyte characters and
/// iswprint(3) returns non-zero for all of them; true otherwise /// iswprint(3) returns non-zero for all of them; true otherwise.
/// The value of errno is preserved.
/// ///
/// \note In case mbrtowc(3) isn't available, single-byte character set /// \note In case mbrtowc(3) isn't available, single-byte character set
/// is assumed and isprint(3) is used instead of iswprint(3). /// is assumed and isprint(3) is used instead of iswprint(3).
@ -49,6 +50,7 @@ extern const char *tuklib_mask_nonprint_r(const char *str, char **mem);
/// allocated memory is also stored to *mem. A modified string /// allocated memory is also stored to *mem. A modified string
/// has the problematic characters replaced by '?'. If memory /// has the problematic characters replaced by '?'. If memory
/// allocation fails, "???" is returned and *mem is NULL. /// allocation fails, "???" is returned and *mem is NULL.
/// The value of errno is preserved.
#define tuklib_mask_nonprint TUKLIB_SYMBOL(tuklib_mask_nonprint) #define tuklib_mask_nonprint TUKLIB_SYMBOL(tuklib_mask_nonprint)
extern const char *tuklib_mask_nonprint(const char *str); extern const char *tuklib_mask_nonprint(const char *str);