X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=string.c;h=38e25b09c5f3ff2b289d6e943a0f7bde3bfc23ad;hp=c228c24c4f27eee753349e361afd1d70708aa2b4;hb=b3644a3f5de245609dc608a947ed71e2f75b2fd5;hpb=ffb2eaa90429f6d5c3d369509efcdf91c5463dad diff --git a/string.c b/string.c index c228c24c..38e25b09 100644 --- a/string.c +++ b/string.c @@ -1,17 +1,23 @@ /* - * Copyright (C) 2004-2010 Andre Noll + * Copyright (C) 2004-2013 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file string.c Memory allocation and string handling functions. */ -#include /* gettimeofday */ +#define _GNU_SOURCE + #include #include /* uname() */ + #include #include +#include +#include +#include + #include "para.h" #include "string.h" #include "error.h" @@ -114,6 +120,68 @@ __must_check __malloc char *para_strdup(const char *s) exit(EXIT_FAILURE); } +/** + * Print a formated message to a dynamically allocated string. + * + * \param result The formated string is returned here. + * \param fmt The format string. + * \param ap Initialized list of arguments. + * + * This function is similar to vasprintf(), a GNU extension which is not in C + * or POSIX. It allocates a string large enough to hold the output including + * the terminating null byte. The allocated string is returned via the first + * argument and must be freed by the caller. However, unlike vasprintf(), this + * function calls exit() if insufficient memory is available, while vasprintf() + * returns -1 in this case. + * + * \return Number of bytes written, not including the terminating \p NULL + * character. + * + * \sa printf(3), vsnprintf(3), va_start(3), vasprintf(3), \ref xasprintf(). + */ +__printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap) +{ + int ret; + size_t size = 150; + va_list aq; + + *result = para_malloc(size + 1); + va_copy(aq, ap); + ret = vsnprintf(*result, size, fmt, aq); + va_end(aq); + assert(ret >= 0); + if (ret < size) /* OK */ + return ret; + size = ret + 1; + *result = para_realloc(*result, size); + va_copy(aq, ap); + ret = vsnprintf(*result, size, fmt, aq); + va_end(aq); + assert(ret >= 0 && ret < size); + return ret; +} + +/** + * Print to a dynamically allocated string, variable number of arguments. + * + * \param result See \ref xvasprintf(). + * \param fmt Usual format string. + * + * \return The return value of the underlying call to \ref xvasprintf(). + * + * \sa \ref xvasprintf() and the references mentioned there. + */ +__printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...) +{ + va_list ap; + unsigned ret; + + va_start(ap, fmt); + ret = xvasprintf(result, fmt, ap); + va_end(ap); + return ret; +} + /** * Allocate a sufficiently large string and print into it. * @@ -125,13 +193,16 @@ __must_check __malloc char *para_strdup(const char *s) * \return This function either returns a pointer to a string that must be * freed by the caller or aborts without returning. * - * \sa printf(3). + * \sa printf(3), xasprintf(). */ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) { char *msg; + va_list ap; - PARA_VSPRINTF(fmt, msg); + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); return msg; } @@ -288,19 +359,35 @@ __malloc char *para_hostname(void) } /** - * Used to distinguish between read-only and read-write mode. + * Call a custom function for each complete line. * - * \sa for_each_line(), for_each_line_ro(). + * \param flags Any combination of flags defined in \ref for_each_line_flags. + * \param buf The buffer containing data separated by newlines. + * \param size The number of bytes in \a buf. + * \param line_handler The custom function. + * \param private_data Pointer passed to \a line_handler. + * + * For each complete line in \p buf, \p line_handler is called. The first + * argument to \p line_handler is (a copy of) the current line, and \p + * private_data is passed as the second argument. If the \p FELF_READ_ONLY + * flag is unset, a pointer into \a buf is passed to the line handler, + * otherwise a pointer to a copy of the current line is passed instead. This + * copy is freed immediately after the line handler returns. + * + * The function returns if \p line_handler returns a negative value or no more + * lines are in the buffer. The rest of the buffer (last chunk containing an + * incomplete line) is moved to the beginning of the buffer if FELF_READ_ONLY is + * unset. + * + * \return On success this function returns the number of bytes not handled to + * \p line_handler. The only possible error is a negative return value from the + * line handler. In this case processing stops and the return value of the line + * handler is returned to indicate failure. + * + * \sa \ref for_each_line_flags. */ -enum for_each_line_modes{ - /** Activate read-only mode. */ - LINE_MODE_RO, - /** Activate read-write mode. */ - LINE_MODE_RW -}; - -static int for_each_complete_line(enum for_each_line_modes mode, char *buf, - size_t size, line_handler_t *line_handler, void *private_data) +int for_each_line(unsigned flags, char *buf, size_t size, + line_handler_t *line_handler, void *private_data) { char *start = buf, *end; int ret, i, num_lines = 0; @@ -321,85 +408,29 @@ static int for_each_complete_line(enum for_each_line_modes mode, char *buf, } else end = next_cr; num_lines++; - if (!line_handler) { - start = ++end; - continue; - } - if (mode == LINE_MODE_RO) { - 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; } - if (!line_handler || mode == LINE_MODE_RO) - return num_lines; i = buf + size - start; - if (i && i != size) + if (i && i != size && !(flags & FELF_READ_ONLY)) memmove(buf, start, i); return i; } -/** - * Call a custom function for each complete line. - * - * \param buf The buffer containing data separated by newlines. - * \param size The number of bytes in \a buf. - * \param line_handler The custom function. - * \param private_data Pointer passed to \a line_handler. - * - * If \p line_handler is \p NULL, the function returns the number of complete - * lines in \p buf. Otherwise, \p line_handler is called for each complete - * line in \p buf. The first argument to \p line_handler is the current line, - * and \p private_data is passed as the second argument. The function returns - * if \p line_handler returns a negative value or no more lines are in the - * buffer. The rest of the buffer (last chunk containing an incomplete line) - * is moved to the beginning of the buffer. - * - * \return If \p line_handler is not \p NULL, this function returns the number - * of bytes not handled to \p line_handler on success, or the negative return - * value of the \p line_handler on errors. - * - * \sa for_each_line_ro(). - */ -int for_each_line(char *buf, size_t size, line_handler_t *line_handler, - void *private_data) -{ - return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler, - private_data); -} - -/** - * Call a custom function for each complete line. - * - * \param buf Same meaning as in \p for_each_line(). - * \param size Same meaning as in \p for_each_line(). - * \param line_handler Same meaning as in \p for_each_line(). - * \param private_data Same meaning as in \p for_each_line(). - * - * This function behaves like \p for_each_line(), but \a buf is left unchanged. - * - * \return On success, the function returns the number of complete lines in \p - * buf, otherwise the (negative) return value of \p line_handler is returned. - * - * \sa for_each_line(). - */ -int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, - void *private_data) -{ - return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler, - private_data); -} - /** Return the hex characters of the lower 4 bits. */ #define hex(a) (hexchar[(a) & 15]) @@ -521,14 +552,15 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) } } -/** \cond LLONG_MAX and LLONG_MIN might not be defined. */ +/** \cond llong_minmax */ +/* LLONG_MAX and LLONG_MIN might not be defined. */ #ifndef LLONG_MAX #define LLONG_MAX 9223372036854775807LL #endif #ifndef LLONG_MIN #define LLONG_MIN (-LLONG_MAX - 1LL) #endif -/** \endcond */ +/** \endcond llong_minmax */ /** * Convert a string to a 64-bit signed integer value. @@ -615,7 +647,7 @@ int get_loglevel_by_name(const char *txt) return -1; } -static int get_next_word(const char *buf, const char *delim, char **word) +static int get_next_word(const char *buf, const char *delim, char **word) { enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2, LSF_SINGLE_QUOTE = 4, LSF_DOUBLE_QUOTE = 8}; @@ -711,7 +743,33 @@ out: } /** - * Free an array of words created by create_argv(). + * Get the number of the word the cursor is on. + * + * \param buf The zero-terminated line buffer. + * \param delim Characters that separate words. + * \param point The cursor position. + * + * \return Zero-based word number. + */ +int compute_word_num(const char *buf, const char *delim, int point) +{ + int ret, num_words; + const char *p; + char *word; + + for (p = buf, num_words = 0; ; p += ret, num_words++) { + ret = get_next_word(p, delim, &word); + if (ret <= 0) + break; + free(word); + if (p + ret >= buf + point) + break; + } + return num_words; +} + +/** + * Free an array of words created by create_argv() or create_shifted_argv(). * * \param argv A pointer previously obtained by \ref create_argv(). */ @@ -719,11 +777,42 @@ void free_argv(char **argv) { int i; + if (!argv) + return; for (i = 0; argv[i]; i++) free(argv[i]); free(argv); } +static int create_argv_offset(int offset, const char *buf, const char *delim, + char ***result) +{ + char *word, **argv = para_malloc((offset + 1) * sizeof(char *)); + const char *p; + int i, ret; + + for (i = 0; i < offset; i++) + argv[i] = NULL; + for (p = buf; p && *p; p += ret, i++) { + ret = get_next_word(p, delim, &word); + if (ret < 0) + goto err; + if (!ret) + break; + argv = para_realloc(argv, (i + 2) * sizeof(char*)); + argv[i] = word; + } + argv[i] = NULL; + *result = argv; + return i; +err: + while (i > 0) + free(argv[--i]); + free(argv); + *result = NULL; + return ret; +} + /** * Split a buffer into words. * @@ -740,27 +829,47 @@ void free_argv(char **argv) */ int create_argv(const char *buf, const char *delim, char ***result) { - char *word, **argv = para_malloc(2 * sizeof(char *)); - const char *p; - int ret, num_words; + return create_argv_offset(0, buf, delim, result); +} - for (p = buf, num_words = 0; ; p += ret, num_words++) { - ret = get_next_word(p, delim, &word); - if (ret < 0) - goto err; - if (!ret) - break; - argv = para_realloc(argv, (num_words + 2) * sizeof(char*)); - argv[num_words] = word; - } - argv[num_words] = NULL; - *result = argv; - return num_words; -err: - while (num_words > 0) - free(argv[--num_words]); - free(argv); - return ret; +/** + * Split a buffer into words, offset one. + * + * This is similar to \ref create_argv() but the returned array is one element + * larger, words start at index one and element zero is initialized to \p NULL. + * Callers must set element zero to a non-NULL value before calling free_argv() + * on the returned array to avoid a memory leak. + * + * \param buf See \ref create_argv(). + * \param delim See \ref create_argv(). + * \param result See \ref create_argv(). + * + * \return Number of words plus one on success, negative on errors. + */ +int create_shifted_argv(const char *buf, const char *delim, char ***result) +{ + return create_argv_offset(1, buf, delim, result); +} + +/** + * Find out if the given string is contained in the arg vector. + * + * \param arg The string to look for. + * \param argv The array to search. + * + * \return The first index whose value equals \a arg, or \p -E_ARG_NOT_FOUND if + * arg was not found in \a argv. + */ +int find_arg(const char *arg, char **argv) +{ + int i; + + if (!argv) + return -E_ARG_NOT_FOUND; + for (i = 0; argv[i]; i++) + if (strcmp(arg, argv[i]) == 0) + return i; + return -E_ARG_NOT_FOUND; } /** @@ -789,3 +898,198 @@ int para_regcomp(regex_t *preg, const char *regex, int cflags) free(buf); return -E_REGEX; } + +/** + * strdup() for not necessarily zero-terminated strings. + * + * \param src The source buffer. + * \param len The number of bytes to be copied. + * + * \return A 0-terminated buffer of length \a len + 1. + * + * This is similar to strndup(), which is a GNU extension. However, one + * difference is that strndup() returns \p NULL if insufficient memory was + * available while this function aborts in this case. + * + * \sa strdup(), \ref para_strdup(). + */ +char *safe_strdup(const char *src, size_t len) +{ + char *p; + + assert(len < (size_t)-1); + p = para_malloc(len + 1); + if (len > 0) + memcpy(p, src, len); + p[len] = '\0'; + return p; +} + +/** + * Copy the value of a key=value pair. + * + * This checks whether the given buffer starts with "key=", ignoring case. If + * yes, a copy of the value is returned. The source buffer may not be + * zero-terminated. + * + * \param src The source buffer. + * \param len The number of bytes of the tag. + * \param key Only copy if it is the value of this key. + * + * \return A zero-terminated buffer, or \p NULL if the key was + * not of the given type. + */ +char *key_value_copy(const char *src, size_t len, const char *key) +{ + int keylen = strlen(key); + + if (len <= keylen) + return NULL; + if (strncasecmp(src, key, keylen)) + return NULL; + if (src[keylen] != '=') + 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; +} + +/* + * glibc's wcswidth returns -1 if the string contains a tab character, which + * makes the function next to useless. The two functions below are taken from + * mutt. + */ + +#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0) + +static int mutt_wcwidth(wchar_t wc, size_t pos) +{ + int n; + + if (wc == 0x09) /* tab */ + return (pos | 7) + 1 - pos; + n = wcwidth(wc); + if (IsWPrint(wc) && n > 0) + return n; + if (!(wc & ~0x7f)) + return 2; + if (!(wc & ~0xffff)) + return 6; + return 10; +} + +static size_t mutt_wcswidth(const wchar_t *s, size_t n) +{ + size_t w = 0; + + while (n--) + w += mutt_wcwidth(*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 += mutt_wcwidth(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 * 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 = mutt_wcswidth(dest, num_wchars); + free(dest); + return 1; +}