]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - string.c
build: Rename all_objs -> dep_objs.
[paraslash.git] / string.c
index f8b64b77c08d0c109c62129bdfeac04b775b3d6b..d8bd027b7010a149be59b8883b3e5ee685fb3c01 100644 (file)
--- a/string.c
+++ b/string.c
-/*
- * Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file string.c Memory allocation and string handling functions. */
 
-#define _GNU_SOURCE
+#include "para.h"
 
 #include <pwd.h>
 #include <sys/utsname.h> /* uname() */
-
-#include <string.h>
 #include <regex.h>
-
 #include <langinfo.h>
 #include <wchar.h>
 #include <wctype.h>
 
-#include "para.h"
 #include "string.h"
 #include "error.h"
 
 /**
- * Paraslash's version of realloc().
+ * Reallocate an array, abort on failure or bugs.
  *
- * \param p Pointer to the memory block, may be \p NULL.
- * \param size The desired new size.
+ * \param ptr Pointer to the memory block, may be NULL.
+ * \param nmemb Number of elements.
+ * \param size The size of one element in bytes.
  *
- * 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.
+ * A wrapper for realloc(3) which aborts on invalid arguments or integer
+ * overflow. The wrapper also terminates the current process on allocation
+ * errors, so the caller does not need to check for failure.
  *
  * \return A pointer to newly allocated memory which is suitably aligned for
- * any kind of variable and may be different from \a p.
+ * any kind of variable and may be different from ptr.
  *
  * \sa realloc(3).
  */
-__must_check void *para_realloc(void *p, size_t size)
+__must_check void *arr_realloc(void *ptr, size_t nmemb, size_t size)
 {
+       size_t pr;
+
+       assert(size > 0);
+       assert(nmemb > 0);
+       assert(!__builtin_mul_overflow(nmemb, size, &pr));
+       assert(pr != 0);
+       ptr = realloc(ptr, pr);
+       assert(ptr);
+       return ptr;
+}
+
+/**
+ * Allocate an array, abort on failure or bugs.
+ *
+ * \param nmemb See \ref arr_realloc().
+ * \param size See \ref arr_realloc().
+ *
+ * Like \ref arr_realloc(), this aborts on invalid arguments, integer overflow
+ * and allocation errors.
+ *
+ * \return A pointer to newly allocated memory which is suitably aligned for
+ * any kind of variable.
+ *
+ * \sa See \ref arr_realloc().
+ */
+__must_check __malloc void *arr_alloc(size_t nmemb, size_t size)
+{
+       return arr_realloc(NULL, nmemb, size);
+}
+
+/**
+ * Allocate and initialize an array, abort on failure or bugs.
+ *
+ * \param nmemb See \ref arr_realloc().
+ * \param size See \ref arr_realloc().
+ *
+ * This calls \ref arr_alloc() and zeroes-out the array.
+ *
+ * \return See \ref arr_alloc().
+ */
+__must_check __malloc void *arr_zalloc(size_t nmemb, size_t size)
+{
+       void *ptr = arr_alloc(nmemb, size);
+
        /*
-        * No need to check for NULL pointers: If p is NULL, the call
-        * to realloc is equivalent to malloc(size)
+        * This multiplication can not overflow because the above call to \ref
+        * arr_alloc() aborts on overflow.
         */
-       assert(size);
-       if (!(p = realloc(p, size))) {
-               PARA_EMERG_LOG("realloc failed (size = %zu), aborting\n",
-                       size);
-               exit(EXIT_FAILURE);
-       }
-       return p;
+       memset(ptr, 0, nmemb * size);
+       return ptr;
 }
 
 /**
- * Paraslash's version of malloc().
+ * Allocate and initialize memory.
  *
  * \param size The desired new size.
  *
- * A wrapper for malloc(3) which exits on errors.
- *
- * \return A pointer to the allocated memory, which is suitably aligned for any
- * kind of variable.
+ * \return A pointer to the allocated and zeroed-out memory, which is suitably
+ * aligned for any kind of variable.
  *
- * \sa malloc(3).
+ * \sa \ref alloc(), calloc(3).
  */
-__must_check __malloc void *para_malloc(size_t size)
+__must_check void *zalloc(size_t size)
 {
-       void *p;
-
-       assert(size);
-       p = malloc(size);
-       if (!p) {
-               PARA_EMERG_LOG("malloc failed (size = %zu), aborting\n",
-                       size);
-               exit(EXIT_FAILURE);
-       }
-       return p;
+       return arr_zalloc(1, size);
 }
 
 /**
- * Paraslash's version of calloc().
+ * Paraslash's version of realloc().
  *
+ * \param p Pointer to the memory block, may be \p NULL.
  * \param size The desired new size.
  *
- * A wrapper for calloc(3) which exits on errors.
+ * 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 allocated and zeroed-out memory, which is suitably
- * aligned for any kind of variable.
+ * \return A pointer to newly allocated memory which is suitably aligned for
+ * any kind of variable and may be different from \a p.
  *
- * \sa calloc(3)
+ * \sa realloc(3).
  */
-__must_check __malloc void *para_calloc(size_t size)
+__must_check void *para_realloc(void *p, size_t size)
 {
-       void *ret = para_malloc(size);
+       return arr_realloc(p, 1, size);
+}
 
-       memset(ret, 0, size);
-       return ret;
+/**
+ * Paraslash's version of malloc().
+ *
+ * \param size The desired new size.
+ *
+ * A wrapper for malloc(3) which exits on errors.
+ *
+ * \return A pointer to the allocated memory, which is suitably aligned for any
+ * kind of variable.
+ *
+ * \sa malloc(3).
+ */
+__must_check __malloc void *alloc(size_t size)
+{
+       return arr_alloc(1, size);
 }
 
 /**
@@ -102,28 +140,29 @@ __must_check __malloc void *para_calloc(size_t size)
  *
  * \param s The string to be duplicated.
  *
- * A wrapper for strdup(3). It calls \p exit(EXIT_FAILURE) on errors, i.e.
- * there is no need to check the return value in the caller.
+ * A strdup(3)-like function which aborts if insufficient memory was available
+ * to allocate the duplicated string, absolving the caller from the
+ * responsibility to check for failure.
  *
- * \return A pointer to the duplicated string. If \a s was the \p NULL pointer,
- * an pointer to an empty string is returned.
+ * \return A pointer to the duplicated string. Unlike strdup(3), the caller may
+ * pass NULL, in which case the function returns a pointer to an empty string.
+ * Regardless of whether or not NULL was passed, the returned string is
+ * allocated on the heap and has to be freed by the caller.
  *
- * \sa strdup(3)
+ * \sa strdup(3).
  */
 __must_check __malloc char *para_strdup(const char *s)
 {
-       char *ret;
+       char *dupped_string = strdup(s? s: "");
 
-       if ((ret = strdup(s? s: "")))
-               return ret;
-       PARA_EMERG_LOG("strdup failed, aborting\n");
-       exit(EXIT_FAILURE);
+       assert(dupped_string);
+       return dupped_string;
 }
 
 /**
- * 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.
  *
@@ -145,7 +184,7 @@ __printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap)
        size_t size = 150;
        va_list aq;
 
-       *result = para_malloc(size + 1);
+       *result = alloc(size + 1);
        va_copy(aq, ap);
        ret = vsnprintf(*result, size, fmt, aq);
        va_end(aq);
@@ -193,7 +232,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, ...)
 {
@@ -207,17 +246,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;
+       }
 }
 
 /**
@@ -233,7 +277,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)
 {
@@ -248,56 +292,6 @@ __must_check __malloc char *para_strcat(char *a, const char *b)
        return tmp;
 }
 
-/**
- * Paraslash's version of dirname().
- *
- * \param name Pointer to the full path.
- *
- * Compute the directory component of \p name.
- *
- * \return If \a name is \p NULL or the empty string, return \p NULL.
- * Otherwise, Make a copy of \a name and return its directory component. Caller
- * is responsible to free the result.
- */
-__must_check __malloc char *para_dirname(const char *name)
-{
-       char *p, *ret;
-
-       if (!name || !*name)
-               return NULL;
-       ret = para_strdup(name);
-       p = strrchr(ret, '/');
-       if (!p)
-               *ret = '\0';
-       else
-               *p = '\0';
-       return ret;
-}
-
-/**
- * Paraslash's version of basename().
- *
- * \param name Pointer to the full path.
- *
- * Compute the filename component of \a name.
- *
- * \return \p NULL if (a) \a name is the empty string or \p NULL, or (b) name
- * ends with a slash.  Otherwise, a pointer within \a name is returned.  Caller
- * must not free the result.
- */
-__must_check char *para_basename(const char *name)
-{
-       char *ret;
-
-       if (!name || !*name)
-               return NULL;
-       ret = strrchr(name, '/');
-       if (!ret)
-               return (char *)name;
-       ret++;
-       return ret;
-}
-
 /**
  * Get the logname of the current user.
  *
@@ -314,15 +308,32 @@ __must_check __malloc char *para_logname(void)
 }
 
 /**
- * Get the home directory of the current user.
+ * Get the home directory of the calling user.
  *
  * \return A dynamically allocated string that must be freed by the caller. If
- * the home directory could not be found, this function returns "/tmp".
+ * no entry is found which matches the UID of the calling process, or any other
+ * error occurs, the function prints an error message and aborts.
+ *
+ * \sa getpwuid(3), getuid(2).
  */
 __must_check __malloc char *para_homedir(void)
 {
-       struct passwd *pw = getpwuid(getuid());
-       return para_strdup(pw? pw->pw_dir : "/tmp");
+       struct passwd *pw;
+
+       /*
+        * To distinguish between the error case and the "not found" case we
+        * have to check errno after getpwuid(3). The manual page recommends to
+        * set it to zero before the call.
+        */
+       errno = 0;
+       pw = getpwuid(getuid());
+       if (pw)
+               return para_strdup(pw->pw_dir);
+       if (errno != 0)
+               PARA_EMERG_LOG("getpwuid error: %s\n", strerror(errno));
+       else
+               PARA_EMERG_LOG("no pw entry for uid %u\n", (unsigned)getuid());
+       exit(EXIT_FAILURE);
 }
 
 /**
@@ -392,7 +403,7 @@ int for_each_line(unsigned flags, char *buf, size_t size,
                if (!(flags & FELF_DISCARD_FIRST) || start != buf) {
                        if (flags & FELF_READ_ONLY) {
                                size_t s = end - start;
-                               char *b = para_malloc(s + 1);
+                               char *b = alloc(s + 1);
                                memcpy(b, start, s);
                                b[s] = '\0';
                                ret = line_handler(b, private_data);
@@ -496,7 +507,7 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...)
        int ret, sz_off = (b->flags & PBF_SIZE_PREFIX)? 5 : 0;
 
        if (!b->buf) {
-               b->buf = para_malloc(128);
+               b->buf = alloc(128);
                b->size = 128;
                b->offset = 0;
        }
@@ -551,7 +562,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)
 {
@@ -562,10 +573,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;
@@ -580,7 +599,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)
 {
@@ -597,37 +616,6 @@ int para_atoi32(const char *str, int32_t *value)
        return 1;
 }
 
-static inline int loglevel_equal(const char *arg, const char * const ll)
-{
-       return !strncasecmp(arg, ll, strlen(ll));
-}
-
-/**
- * Compute the loglevel number from its name.
- *
- * \param txt The name of the loglevel (debug, info, ...).
- *
- * \return The numeric representation of the loglevel name.
- */
-int get_loglevel_by_name(const char *txt)
-{
-       if (loglevel_equal(txt, "debug"))
-               return LL_DEBUG;
-       if (loglevel_equal(txt, "info"))
-               return LL_INFO;
-       if (loglevel_equal(txt, "notice"))
-               return LL_NOTICE;
-       if (loglevel_equal(txt, "warning"))
-               return LL_WARNING;
-       if (loglevel_equal(txt, "error"))
-               return LL_ERROR;
-       if (loglevel_equal(txt, "crit"))
-               return LL_CRIT;
-       if (loglevel_equal(txt, "emerg"))
-               return LL_EMERG;
-       return -1;
-}
-
 static int get_next_word(const char *buf, const char *delim, char **word)
 {
        enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2,
@@ -636,7 +624,7 @@ static int get_next_word(const char *buf, const char *delim, char **word)
        char *out;
        int ret, state = 0;
 
-       out = para_malloc(strlen(buf) + 1);
+       out = alloc(strlen(buf) + 1);
        *out = '\0';
        *word = out;
        for (in = buf; *in; in++) {
@@ -768,19 +756,17 @@ void free_argv(char **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 *));
+       char *word, **argv = arr_zalloc(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++) {
+       for (p = buf, i = offset; 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 = arr_realloc(argv, i + 2, sizeof(char*));
                argv[i] = word;
        }
        argv[i] = NULL;
@@ -798,15 +784,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)
 {
@@ -832,27 +821,6 @@ 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;
-}
-
 /**
  * Compile a regular expression.
  *
@@ -873,7 +841,7 @@ int para_regcomp(regex_t *preg, const char *regex, int cflags)
        if (ret == 0)
                return 1;
        size = regerror(ret, preg, NULL, 0);
-       buf = para_malloc(size);
+       buf = alloc(size);
        regerror(ret, preg, buf, size);
        PARA_ERROR_LOG("%s\n", buf);
        free(buf);
@@ -899,7 +867,7 @@ char *safe_strdup(const char *src, size_t len)
        char *p;
 
        assert(len < (size_t)-1);
-       p = para_malloc(len + 1);
+       p = alloc(len + 1);
        if (len > 0)
                memcpy(p, src, len);
        p[len] = '\0';
@@ -947,36 +915,24 @@ static bool utf8_mode(void)
        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)
+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);
-       if (IsWPrint(wc) && n > 0)
-               return n;
-       if (!(wc & ~0x7f))
-               return 2;
-       if (!(wc & ~0xffff))
-               return 6;
-       return 10;
+       /* wcswidth() returns -1 for non-printable characters */
+       return n >= 0? n : 1;
 }
 
-static size_t mutt_wcswidth(const wchar_t *s, size_t n)
+static size_t xwcswidth(const wchar_t *s, size_t n)
 {
        size_t w = 0;
 
        while (n--)
-               w += mutt_wcwidth(*s++, w);
+               w += xwcwidth(*s++, w);
        return w;
 }
 
@@ -1021,7 +977,7 @@ int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip)
                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);
+               cells_skipped += xwcwidth(wc, cells_skipped);
        }
        *bytes_to_skip = bytes_parsed;
        return 1;
@@ -1065,12 +1021,75 @@ __must_check int strwidth(const char *s, size_t *result)
                return -ERRNO_TO_PARA_ERROR(errno);
        if (num_wchars == 0)
                return 0;
-       dest = para_malloc(num_wchars * sizeof(*dest));
+       dest = arr_alloc(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 = mutt_wcswidth(dest, num_wchars);
+       *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 = arr_alloc(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 = alloc(n);
+       num_wchars = wcstombs(*result, wcs, n);
+       assert(num_wchars != (size_t)-1);
+       free(wcs);
+       return 1;
+}