X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=string.c;h=6033a008dbf154953de673f85048eb78812d1722;hp=c0a1951d83fd934c019413dc985e3d70247b3566;hb=refs%2Fheads%2Fmaint;hpb=e3868dab3db27242f066bf52d95d0b3f2c0df7e3 diff --git a/string.c b/string.c index c0a1951d..198e9f1d 100644 --- a/string.c +++ b/string.c @@ -1,18 +1,16 @@ -/* - * Copyright (C) 2004-2013 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2004 Andre Noll , see file COPYING. */ /** \file string.c Memory allocation and string handling functions. */ -#include /* gettimeofday */ +#include "para.h" + #include #include /* uname() */ -#include #include +#include +#include +#include -#include "para.h" #include "string.h" #include "error.h" @@ -25,12 +23,12 @@ * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors, * i.e. there is no need to check the return value in the caller. * - * \return A pointer to the newly allocated memory, which is suitably aligned - * for any kind of variable and may be different from \a p. + * \return A pointer to newly allocated memory which is suitably aligned for + * any kind of variable and may be different from \a p. * * \sa realloc(3). */ -__must_check __malloc void *para_realloc(void *p, size_t size) +__must_check void *para_realloc(void *p, size_t size) { /* * No need to check for NULL pointers: If p is NULL, the call @@ -115,9 +113,9 @@ __must_check __malloc char *para_strdup(const char *s) } /** - * Print a formated message to a dynamically allocated string. + * Print a formatted message to a dynamically allocated string. * - * \param result The formated string is returned here. + * \param result The formatted string is returned here. * \param fmt The format string. * \param ap Initialized list of arguments. * @@ -136,15 +134,18 @@ __must_check __malloc char *para_strdup(const char *s) __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap) { int ret; - size_t size; + size_t size = 150; va_list aq; + *result = para_malloc(size + 1); va_copy(aq, ap); - ret = vsnprintf(NULL, 0, fmt, aq); + ret = vsnprintf(*result, size, fmt, aq); va_end(aq); assert(ret >= 0); + if (ret < size) /* OK */ + return ret; size = ret + 1; - *result = para_malloc(size); + *result = para_realloc(*result, size); va_copy(aq, ap); ret = vsnprintf(*result, size, fmt, aq); va_end(aq); @@ -184,7 +185,7 @@ __printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...) * \return This function either returns a pointer to a string that must be * freed by the caller or aborts without returning. * - * \sa printf(3), xasprintf(). + * \sa printf(3), \ref xasprintf(). */ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) { @@ -198,17 +199,22 @@ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) } /** - * Free the content of a pointer and set it to \p NULL. + * Free the content of a pointer and set it to NULL. * - * This is equivalent to "free(*arg); *arg = NULL;". + * \param arg A pointer to the pointer whose content should be freed. * - * \param arg The pointer whose content should be freed. + * If arg is NULL, the function returns immediately. Otherwise it frees the + * memory pointed to by *arg and sets *arg to NULL. Hence callers have to pass + * the *address* of the pointer variable that points to the memory which should + * be freed. */ void freep(void *arg) { - void **ptr = (void **)arg; - free(*ptr); - *ptr = NULL; + if (arg) { + void **ptr = arg; + free(*ptr); + *ptr = NULL; + } } /** @@ -224,7 +230,7 @@ void freep(void *arg) * return \a a without making a copy of \a a. Otherwise, construct the * concatenation \a c, free \a a (but not \a b) and return \a c. * - * \sa strcat(3) + * \sa strcat(3). */ __must_check __malloc char *para_strcat(char *a, const char *b) { @@ -289,24 +295,6 @@ __must_check char *para_basename(const char *name) return ret; } -/** - * Cut trailing newline. - * - * \param buf The string to be chopped. - * - * Replace the last character in \p buf by zero if it is equal to - * the newline character. - */ -void chop(char *buf) -{ - int n = strlen(buf); - - if (!n) - return; - if (buf[n - 1] == '\n') - buf[n - 1] = '\0'; -} - /** * Get the logname of the current user. * @@ -389,30 +377,30 @@ int for_each_line(unsigned flags, char *buf, size_t size, char *next_cr; next_cr = memchr(start, '\n', buf + size - start); - next_null = memchr(start, '\0', buf + size - start); + next_null = memchr(start, '\0', next_cr? + next_cr - start : buf + size - start); if (!next_cr && !next_null) break; - if (next_cr && next_null) { - end = next_cr < next_null? next_cr : next_null; - } else if (next_null) { + if (next_null) end = next_null; - } else + else end = next_cr; num_lines++; - if (flags & FELF_READ_ONLY) { - size_t s = end - start; - char *b = para_malloc(s + 1); - memcpy(b, start, s); - b[s] = '\0'; -// PARA_NOTICE_LOG("b: %s, start: %s\n", b, start); - ret = line_handler(b, private_data); - free(b); - } else { - *end = '\0'; - ret = line_handler(start, private_data); + if (!(flags & FELF_DISCARD_FIRST) || start != buf) { + if (flags & FELF_READ_ONLY) { + size_t s = end - start; + char *b = para_malloc(s + 1); + memcpy(b, start, s); + b[s] = '\0'; + ret = line_handler(b, private_data); + free(b); + } else { + *end = '\0'; + ret = line_handler(start, private_data); + } + if (ret < 0) + return ret; } - if (ret < 0) - return ret; start = ++end; } i = buf + size - start; @@ -560,7 +548,7 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) * * \return Standard. * - * \sa para_atoi32(), strtol(3), atoi(3). + * \sa \ref para_atoi32(), strtol(3), atoi(3). */ int para_atoi64(const char *str, int64_t *value) { @@ -571,10 +559,18 @@ int para_atoi64(const char *str, int64_t *value) tmp = strtoll(str, &endptr, 10); if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) return -E_ATOI_OVERFLOW; - if (errno != 0 && tmp == 0) /* other error */ - return -E_STRTOLL; + /* + * If there were no digits at all, strtoll() stores the original value + * of str in *endptr. + */ if (endptr == str) return -E_ATOI_NO_DIGITS; + /* + * The implementation may also set errno and return 0 in case no + * conversion was performed. + */ + if (errno != 0 && tmp == 0) + return -E_ATOI_NO_DIGITS; if (*endptr != '\0') /* Further characters after number */ return -E_ATOI_JUNK_AT_END; *value = tmp; @@ -589,7 +585,7 @@ int para_atoi64(const char *str, int64_t *value) * * \return Standard. * - * \sa para_atoi64(). + * \sa \ref para_atoi64(). */ int para_atoi32(const char *str, int32_t *value) { @@ -634,7 +630,7 @@ int get_loglevel_by_name(const char *txt) return LL_CRIT; if (loglevel_equal(txt, "emerg")) return LL_EMERG; - return -1; + return -E_BAD_LL; } static int get_next_word(const char *buf, const char *delim, char **word) @@ -807,15 +803,18 @@ err: * Split a buffer into words. * * This parser honors single and double quotes, backslash-escaped characters - * and special characters like \p \\n. The result contains pointers to copies - * of the words contained in \a buf and has to be freed by using \ref - * free_argv(). + * and special characters like \\n. The result contains pointers to copies of + * the words contained in buf and has to be freed by using \ref free_argv(). * * \param buf The buffer to be split. * \param delim Each character in this string is treated as a separator. * \param result The array of words is returned here. * - * \return Number of words in \a buf, negative on errors. + * It's OK to pass NULL as the buffer argument. This is equivalent to passing + * the empty string. + * + * \return Number of words in buf, negative on errors. The array returned + * through the result pointer is NULL terminated. */ int create_argv(const char *buf, const char *delim, char ***result) { @@ -941,3 +940,196 @@ char *key_value_copy(const char *src, size_t len, const char *key) return NULL; return safe_strdup(src + keylen + 1, len - keylen - 1); } + +static bool utf8_mode(void) +{ + static bool initialized, have_utf8; + + if (!initialized) { + char *info = nl_langinfo(CODESET); + have_utf8 = (info && strcmp(info, "UTF-8") == 0); + initialized = true; + PARA_INFO_LOG("%susing UTF-8 character encoding\n", + have_utf8? "" : "not "); + } + return have_utf8; +} + +static int xwcwidth(wchar_t wc, size_t pos) +{ + int n; + + /* special-case for tab */ + if (wc == 0x09) /* tab */ + return (pos | 7) + 1 - pos; + n = wcwidth(wc); + /* wcswidth() returns -1 for non-printable characters */ + return n >= 0? n : 1; +} + +static size_t xwcswidth(const wchar_t *s, size_t n) +{ + size_t w = 0; + + while (n--) + w += xwcwidth(*s++, w); + return w; +} + +/** + * Skip a given number of cells at the beginning of a string. + * + * \param s The input string. + * \param cells_to_skip Desired number of cells that should be skipped. + * \param bytes_to_skip Result. + * + * This function computes how many input bytes must be skipped to advance a + * string by the given width. If the current character encoding is not UTF-8, + * this is simply the given number of cells, i.e. \a cells_to_skip. Otherwise, + * \a s is treated as a multibyte string and on successful return, \a s + + * bytes_to_skip points to the start of a multibyte string such that the total + * width of the multibyte characters that are skipped by advancing \a s that + * many bytes equals at least \a cells_to_skip. + * + * \return Standard. + */ +int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip) +{ + wchar_t wc; + mbstate_t ps; + size_t n, bytes_parsed, cells_skipped; + + *bytes_to_skip = 0; + if (cells_to_skip == 0) + return 0; + if (!utf8_mode()) { + *bytes_to_skip = cells_to_skip; + return 0; + } + bytes_parsed = cells_skipped = 0; + memset(&ps, 0, sizeof(ps)); + n = strlen(s); + while (cells_to_skip > cells_skipped) { + size_t mbret; + + mbret = mbrtowc(&wc, s + bytes_parsed, n - bytes_parsed, &ps); + assert(mbret != 0); + if (mbret == (size_t)-1 || mbret == (size_t)-2) + return -ERRNO_TO_PARA_ERROR(EILSEQ); + bytes_parsed += mbret; + cells_skipped += xwcwidth(wc, cells_skipped); + } + *bytes_to_skip = bytes_parsed; + return 1; +} + +/** + * Compute the width of an UTF-8 string. + * + * \param s The string. + * \param result The width of \a s is returned here. + * + * If not in UTF8-mode. this function is just a wrapper for strlen(3). + * Otherwise \a s is treated as an UTF-8 string and its display width is + * computed. Note that this function may fail if the underlying call to + * mbsrtowcs(3) fails, so the caller must check the return value. + * + * \sa nl_langinfo(3), wcswidth(3). + * + * \return Standard. + */ +__must_check int strwidth(const char *s, size_t *result) +{ + const char *src = s; + mbstate_t state; + static wchar_t *dest; + size_t num_wchars; + + /* + * Never call any log function here. This may result in an endless loop + * as para_gui's para_log() calls this function. + */ + + if (!utf8_mode()) { + *result = strlen(s); + return 0; + } + memset(&state, 0, sizeof(state)); + *result = 0; + num_wchars = mbsrtowcs(NULL, &src, 0, &state); + if (num_wchars == (size_t)-1) + return -ERRNO_TO_PARA_ERROR(errno); + if (num_wchars == 0) + return 0; + dest = para_malloc((num_wchars + 1) * sizeof(*dest)); + src = s; + memset(&state, 0, sizeof(state)); + num_wchars = mbsrtowcs(dest, &src, num_wchars, &state); + assert(num_wchars > 0 && num_wchars != (size_t)-1); + *result = xwcswidth(dest, num_wchars); + free(dest); + return 1; +} + +/** + * Truncate and sanitize a (wide character) string. + * + * This replaces all non-printable characters by spaces and makes sure that the + * modified string does not exceed the given maximal width. + * + * \param src The source string in multi-byte form. + * \param max_width The maximal number of cells the result may occupy. + * \param result Sanitized multi-byte string, must be freed by caller. + * \param width The width of the sanitized string, always <= max_width. + * + * The function is wide-character aware but falls back to C strings for + * non-UTF-8 locales. + * + * \return Standard. On success, *result points to a sanitized copy of the + * given string. This copy was allocated with malloc() and should hence be + * freed when the caller is no longer interested in the result. + * + * The function fails if the given string contains an invalid multibyte + * sequence. In this case, *result is set to NULL, and *width to zero. + */ +__must_check int sanitize_str(const char *src, size_t max_width, + char **result, size_t *width) +{ + mbstate_t state; + static wchar_t *wcs; + size_t num_wchars, n; + + if (!utf8_mode()) { + *result = para_strdup(src); + /* replace non-printable characters by spaces */ + for (n = 0; n < max_width && src[n]; n++) { + if (!isprint((unsigned char)src[n])) + (*result)[n] = ' '; + } + (*result)[n] = '\0'; + *width = n; + return 0; + } + *result = NULL; + *width = 0; + memset(&state, 0, sizeof(state)); + num_wchars = mbsrtowcs(NULL, &src, 0, &state); + if (num_wchars == (size_t)-1) + return -ERRNO_TO_PARA_ERROR(errno); + wcs = para_malloc((num_wchars + 1) * sizeof(*wcs)); + memset(&state, 0, sizeof(state)); + num_wchars = mbsrtowcs(wcs, &src, num_wchars + 1, &state); + assert(num_wchars != (size_t)-1); + for (n = 0; n < num_wchars && *width < max_width; n++) { + if (!iswprint(wcs[n])) + wcs[n] = L' '; + *width += xwcwidth(wcs[n], *width); + } + wcs[n] = L'\0'; + n = wcstombs(NULL, wcs, 0) + 1; + *result = para_malloc(n); + num_wchars = wcstombs(*result, wcs, n); + assert(num_wchars != (size_t)-1); + free(wcs); + return 1; +}