X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=string.c;h=77f2f2a17d27002b0fcf608a4b615f6beaa83b01;hp=78d54f67fd992f099f287233184343f912a05941;hb=9a67f9e1e37589b548fc1823a21ffdf0b6faf4ea;hpb=1a4b98e797ab5ebb1ee1daa707aa0d520379ccc9 diff --git a/string.c b/string.c index 78d54f67..f8b64b77 100644 --- a/string.c +++ b/string.c @@ -1,39 +1,45 @@ /* - * Copyright (C) 2004-2007 Andre Noll + * Copyright (C) 2004 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file string.c memory allocation and string handling functions */ +/** \file string.c Memory allocation and string handling functions. */ -#include "para.h" -#include "string.h" +#define _GNU_SOURCE -#include /* gettimeofday */ #include #include /* uname() */ + #include +#include +#include +#include +#include + +#include "para.h" +#include "string.h" #include "error.h" /** - * paraslash's version of realloc() + * Paraslash's version of realloc(). * - * \param p pointer to the memory block, may be NULL - * \param size desired new size + * \param p Pointer to the memory block, may be \p NULL. + * \param size The desired new size. * * 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 \p 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) + * \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 + * No need to check for NULL pointers: If p is NULL, the call * to realloc is equivalent to malloc(size) */ assert(size); @@ -46,38 +52,40 @@ __must_check __malloc void *para_realloc(void *p, size_t size) } /** - * paraslash's version of malloc() + * Paraslash's version of malloc(). * - * \param size desired new size + * \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. + * kind of variable. * - * \sa malloc(3) + * \sa malloc(3). */ __must_check __malloc void *para_malloc(size_t size) { - assert(size); - void *p = malloc(size); + void *p; + assert(size); + p = malloc(size); if (!p) { - PARA_EMERG_LOG("%s", "malloc failed, aborting\n"); + PARA_EMERG_LOG("malloc failed (size = %zu), aborting\n", + size); exit(EXIT_FAILURE); } return p; } /** - * paraslash's version of calloc() + * Paraslash's version of calloc(). * - * \param size desired new size + * \param size The desired new size. * * A wrapper for calloc(3) which exits on errors. * * \return A pointer to the allocated and zeroed-out memory, which is suitably - * aligned for any kind of variable. + * aligned for any kind of variable. * * \sa calloc(3) */ @@ -90,14 +98,14 @@ __must_check __malloc void *para_calloc(size_t size) } /** - * paraslash's version of strdup() + * Paraslash's version of strdup(). * - * \param s string to be duplicated + * \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. * - * \return A pointer to the duplicated string. If \p s was the NULL pointer, + * \return A pointer to the duplicated string. If \a s was the \p NULL pointer, * an pointer to an empty string is returned. * * \sa strdup(3) @@ -108,14 +116,76 @@ __must_check __malloc char *para_strdup(const char *s) if ((ret = strdup(s? s: ""))) return ret; - PARA_EMERG_LOG("%s", "strdup failed, aborting\n"); + PARA_EMERG_LOG("strdup failed, aborting\n"); exit(EXIT_FAILURE); } /** - * allocate a sufficiently large string and print into it + * 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. * - * \param fmt usual format string + * \param fmt A usual format string. * * Produce output according to \p fmt. No artificial bound on the length of the * resulting string is imposed. @@ -123,28 +193,45 @@ __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; } /** - * paraslash's version of strcat() + * Free the content of a pointer and set it to \p NULL. * - * \param a string to be appended to - * \param b string to append + * This is equivalent to "free(*arg); *arg = NULL;". + * + * \param arg The pointer whose content should be freed. + */ +void freep(void *arg) +{ + void **ptr = (void **)arg; + free(*ptr); + *ptr = NULL; +} + +/** + * Paraslash's version of strcat(). + * + * \param a String to be appended to. + * \param b String to append. * * Append \p b to \p a. * - * \return If \p a is NULL, return a pointer to a copy of \p b, i.e. - * para_strcat(NULL, b) is equivalent to para_strdup(b). If \p b is NULL, - * return \p a without making a copy of \p a. Otherwise, construct the - * concatenation \p c, free \p a (but not \p b) and return \p c. + * \return If \a a is \p NULL, return a pointer to a copy of \a b, i.e. + * para_strcat(NULL, b) is equivalent to para_strdup(b). If \a b is \p NULL, + * 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) */ @@ -162,14 +249,14 @@ __must_check __malloc char *para_strcat(char *a, const char *b) } /** - * paraslash's version of dirname() + * Paraslash's version of dirname(). * - * \param name pointer to the full path + * \param name Pointer to the full path. * - * Compute the directory component of \p name + * Compute the directory component of \p name. * - * \return If \p name is \รพ NULL or the empty string, return \p NULL. - * Otherwise, Make a copy of \p name and return its directory component. Caller + * \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) @@ -188,103 +275,37 @@ __must_check __malloc char *para_dirname(const char *name) } /** - * paraslash's version of basename() + * Paraslash's version of basename(). * - * \param name Pointer to the full path + * \param name Pointer to the full path. * - * Compute the filename component of \p name + * Compute the filename component of \a name. * - * \return If \p name is \p NULL or the empty string, return \p NULL, - * Otherwise, make a copy of \p name and return its filename component. Caller - * is responsible to free the result. + * \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 __malloc char *para_basename(const char *name) +__must_check char *para_basename(const char *name) { - char *p; + char *ret; if (!name || !*name) return NULL; - p = strrchr(name, '/'); - if (!p) - return para_strdup(name); - p++; - if (!*p) - return NULL; - return para_strdup(p); -} - -/** - * cut trailing newline - * - * \param buf the string to be chopped. - * - * Replace the last character in \p buf by zero if it is euqal 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 a random filename - * - * This is by no means a secure way to create temporary files in a hostile - * direcory like \p /tmp. However, it is OK to use for temp files, fifos, - * sockets that are created in ~/.paraslash. Result must be freed by the - * caller. - * - * \return a pointer to a random filename. - */ -__must_check __malloc char *para_tmpname(void) -{ - struct timeval now; - unsigned int seed; - - gettimeofday(&now, NULL); - seed = now.tv_usec; - srand(seed); - return make_message("%08i", rand()); -} - -/** - * create unique temporary file - * - * \param template the template to be passed to mkstemp() - * \param mode the desired mode of the tempfile - * - * This wrapper for mkstemp additionally uses fchmod() to - * set the given mode of the tempfile if mkstemp() returned success. - * - * \return The file descriptor of the temp file just created on success. - * On errors, -E_MKSTEMP or -E_FCHMOD is returned. - */ -__must_check int para_mkstemp(char *template, mode_t mode) -{ - int tmp, fd = mkstemp(template); - - if (fd < 0) - return -E_MKSTEMP; - tmp = fchmod(fd, mode); - if (tmp >= 0) - return fd; - close(fd); - unlink(template); - return -E_FCHMOD; + ret = strrchr(name, '/'); + if (!ret) + return (char *)name; + ret++; + return ret; } /** - * get the logname of the current user + * Get the logname of the current user. * - * \return A dynammically allocated string that must be freed by the caller. On - * errors, the string "unknown user" is returned, i.e. this function never - * returns NULL. + * \return A dynamically allocated string that must be freed by the caller. On + * errors, the string "unknown_user" is returned, i.e. this function never + * returns \p NULL. * - * \sa getpwuid(3) + * \sa getpwuid(3). */ __must_check __malloc char *para_logname(void) { @@ -293,9 +314,9 @@ __must_check __malloc char *para_logname(void) } /** - * get the home directory of the current user + * Get the home directory of the current user. * - * \return A dynammically allocated string that must be freed by the caller. If + * \return A dynamically allocated string that must be freed by the caller. If * the home directory could not be found, this function returns "/tmp". */ __must_check __malloc char *para_homedir(void) @@ -305,85 +326,11 @@ __must_check __malloc char *para_homedir(void) } /** - * split string and return pointers to its parts. + * Get the own hostname. * - * \param args the string to be split - * \param argv_ptr pointer to the list of substrings - * \param delim delimiter + * \return A dynamically allocated string containing the hostname. * - * This function modifies \a args by replacing each occurance of \a delim by - * zero. A \p NULL-terminated array of pointers to char* is allocated dynamically - * and these pointers are initialized to point to the broken-up substrings - * within \a args. A pointer to this array is returned via \a argv_ptr. It's OK - * to call this function with \a args \a == \p NULL. - * - * \return The number of substrings found in \a args. - */ -__must_check unsigned split_args(char *args, char *** const argv_ptr, const char *delim) -{ - char *p = args; - char **argv; - size_t n = 0, i, j; - - p = args + strspn(args, delim); - for (;;) { - i = strcspn(p, delim); - if (!i) - break; - p += i; - n++; - p += strspn(p, delim); - } - *argv_ptr = para_malloc((n + 1) * sizeof(char *)); - argv = *argv_ptr; - i = 0; - p = args + strspn(args, delim); - while (p) { - argv[i] = p; - j = strcspn(p, delim); - if (!j) - break; - p += strcspn(p, delim); - if (*p) { - *p = '\0'; - p++; - p += strspn(p, delim); - } - i++; - } - argv[n] = NULL; - return n; -} - -/** - * ensure that file descriptors 0, 1, and 2 are valid - * - * Common approach that opens /dev/null until it gets a file descriptor greater - * than two. - * - * \sa okir's Black Hats Manual. - */ -void valid_fd_012(void) -{ - while (1) { - int fd; - - fd = open("/dev/null", O_RDWR); - if (fd < 0) - exit(EXIT_FAILURE); - if (fd > 2) { - close(fd); - break; - } - } -} - -/** - * get the own hostname - * - * \return A dynammically allocated string containing the hostname. - * - * \sa uname(2) + * \sa uname(2). */ __malloc char *para_hostname(void) { @@ -393,10 +340,36 @@ __malloc char *para_hostname(void) return para_strdup(u.nodename); } -enum for_each_line_modes{LINE_MODE_RO, 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) +/** + * Call a custom function for each complete line. + * + * \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. + */ +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; @@ -407,106 +380,103 @@ static int for_each_complete_line(enum for_each_line_modes mode, char *buf, 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 (!line_handler) { - start = ++end; - continue; + 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 (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 (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 seperated by newlines - * \param size the number of bytes in \a buf - * \param line_handler the custom function - * \param private_data pointer to data which is 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 the hex characters of the lower 4 bits. */ +#define hex(a) (hexchar[(a) & 15]) + +static void write_size_header(char *buf, int n) { - return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler, - private_data); + static char hexchar[] = "0123456789abcdef"; + + buf[0] = hex(n >> 12); + buf[1] = hex(n >> 8); + buf[2] = hex(n >> 4); + buf[3] = hex(n); + buf[4] = ' '; } /** - * 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() with the following differences: + * Read a four-byte hex-number and return its value. * - * - buf is left unchanged + * Each status item sent by para_server is prefixed with such a hex number in + * ASCII which describes the size of the status item. * - * \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. + * \param buf The buffer which must be at least four bytes long. * - * \sa for_each_line() + * \return The value of the hex number on success, \p -E_SIZE_PREFIX if the + * buffer did not contain only hex digits. */ -int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, - void *private_data) +int read_size_header(const char *buf) { - return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler, - private_data); + int i, len = 0; + + for (i = 0; i < 4; i++) { + unsigned char c = buf[i]; + len <<= 4; + if (c >= '0' && c <= '9') { + len += c - '0'; + continue; + } + if (c >= 'a' && c <= 'f') { + len += c - 'a' + 10; + continue; + } + return -E_SIZE_PREFIX; + } + if (buf[4] != ' ') + return -E_SIZE_PREFIX; + return len; } /** - * Safely print into a buffer at a given offset + * Safely print into a buffer at a given offset. * * \param b Determines the buffer, its size, and the offset. * \param fmt The format string. * * This function prints into the buffer given by \a b at the offset which is * also given by \a b. If there is not enough space to hold the result, the - * buffer size is doubled until the underlying call to vsnprintf() succeeds. + * buffer size is doubled until the underlying call to vsnprintf() succeeds + * or the size of the buffer exceeds the maximal size specified in \a b. + * + * In the latter case the unmodified \a buf and \a offset values as well as the + * private_data pointer of \a b are passed to the \a max_size_handler of \a b. + * If this function succeeds, i.e. returns a non-negative value, the offset of + * \a b is reset to zero and the given data is written to the beginning of the + * buffer. If \a max_size_handler() returns a negative value, this value is + * returned by \a para_printf(). + * * Upon return, the offset of \a b is adjusted accordingly so that subsequent * calls to this function append data to what is already contained in the * buffer. @@ -515,46 +485,63 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, * initial buffer is allocated. * * \return The number of bytes printed into the buffer (not including the - * therminating \p NULL byte). + * terminating \p NULL byte) on success, negative on errors. If there is no + * size-bound on \a b, i.e. if \p b->max_size is zero, this function never + * fails. * * \sa make_message(), vsnprintf(3). */ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) { - int ret; + int ret, sz_off = (b->flags & PBF_SIZE_PREFIX)? 5 : 0; if (!b->buf) { b->buf = para_malloc(128); b->size = 128; b->offset = 0; - } else if (b->size <= b->offset + 1) { - b->size *= 2; - b->buf = para_realloc(b->buf, b->size); } while (1) { char *p = b->buf + b->offset; size_t size = b->size - b->offset; va_list ap; - va_start(ap, fmt); - ret = vsnprintf(p, size, fmt, ap); - va_end(ap); - if (ret > -1 && ret < size) { /* success */ - b->offset += ret; - break; + + if (size > sz_off) { + va_start(ap, fmt); + ret = vsnprintf(p + sz_off, size - sz_off, fmt, ap); + va_end(ap); + if (ret > -1 && ret < size - sz_off) { /* success */ + b->offset += ret + sz_off; + if (sz_off) + write_size_header(p, ret); + return ret + sz_off; + } } - /* try again with more space */ - b->size *= 2; - b->buf = para_realloc(b->buf, b->size); + /* check if we may grow the buffer */ + if (!b->max_size || 2 * b->size < b->max_size) { /* yes */ + /* try again with more space */ + b->size *= 2; + b->buf = para_realloc(b->buf, b->size); + continue; + } + /* can't grow buffer */ + if (!b->offset || !b->max_size_handler) /* message too large */ + return -ERRNO_TO_PARA_ERROR(ENOSPC); + ret = b->max_size_handler(b->buf, b->offset, b->private_data); + if (ret < 0) + return ret; + b->offset = 0; } - return ret; } +/** \cond llong_minmax */ +/* LLONG_MAX and LLONG_MIN might not be defined. */ #ifndef LLONG_MAX -#define LLONG_MAX (1 << (sizeof(long) - 1)) +#define LLONG_MAX 9223372036854775807LL #endif #ifndef LLONG_MIN #define LLONG_MIN (-LLONG_MAX - 1LL) #endif +/** \endcond llong_minmax */ /** * Convert a string to a 64-bit signed integer value. @@ -562,7 +549,7 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) * \param str The string to be converted. * \param value Result pointer. * - * \return Positive on success, negative on errors. + * \return Standard. * * \sa para_atoi32(), strtol(3), atoi(3). */ @@ -591,7 +578,7 @@ int para_atoi64(const char *str, int64_t *value) * \param str The string to be converted. * \param value Result pointer. * - * \return Positive on success, negative on errors. + * \return Standard. * * \sa para_atoi64(). */ @@ -609,3 +596,481 @@ int para_atoi32(const char *str, int32_t *value) *value = tmp; 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, + LSF_SINGLE_QUOTE = 4, LSF_DOUBLE_QUOTE = 8}; + const char *in; + char *out; + int ret, state = 0; + + out = para_malloc(strlen(buf) + 1); + *out = '\0'; + *word = out; + for (in = buf; *in; in++) { + const char *p; + + switch (*in) { + case '\\': + if (state & LSF_BACKSLASH) /* \\ */ + goto copy_char; + state |= LSF_BACKSLASH; + state |= LSF_HAVE_WORD; + continue; + case 'n': + case 't': + if (state & LSF_BACKSLASH) { /* \n or \t */ + *out++ = (*in == 'n')? '\n' : '\t'; + state &= ~LSF_BACKSLASH; + continue; + } + goto copy_char; + case '"': + if (state & LSF_BACKSLASH) /* \" */ + goto copy_char; + if (state & LSF_SINGLE_QUOTE) /* '" */ + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) { + state &= ~LSF_DOUBLE_QUOTE; + continue; + } + state |= LSF_HAVE_WORD; + state |= LSF_DOUBLE_QUOTE; + continue; + case '\'': + if (state & LSF_BACKSLASH) /* \' */ + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) /* "' */ + goto copy_char; + if (state & LSF_SINGLE_QUOTE) { + state &= ~LSF_SINGLE_QUOTE; + continue; + } + state |= LSF_HAVE_WORD; + state |= LSF_SINGLE_QUOTE; + continue; + } + for (p = delim; *p; p++) { + if (*in != *p) + continue; + if (state & LSF_BACKSLASH) + goto copy_char; + if (state & LSF_SINGLE_QUOTE) + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) + goto copy_char; + if (state & LSF_HAVE_WORD) + goto success; + break; + } + if (*p) /* ignore delimiter at the beginning */ + continue; +copy_char: + state |= LSF_HAVE_WORD; + *out++ = *in; + state &= ~LSF_BACKSLASH; + } + ret = 0; + if (!(state & LSF_HAVE_WORD)) + goto out; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (state & LSF_BACKSLASH) { + PARA_ERROR_LOG("trailing backslash\n"); + goto out; + } + if ((state & LSF_SINGLE_QUOTE) || (state & LSF_DOUBLE_QUOTE)) { + PARA_ERROR_LOG("unmatched quote character\n"); + goto out; + } +success: + *out = '\0'; + return in - buf; +out: + free(*word); + *word = NULL; + return ret; +} + +/** + * 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(). + */ +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. + * + * 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(). + * + * \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. + */ +int create_argv(const char *buf, const char *delim, char ***result) +{ + return create_argv_offset(0, buf, delim, result); +} + +/** + * 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; +} + +/** + * Compile a regular expression. + * + * This simple wrapper calls regcomp() and logs a message on errors. + * + * \param preg See regcomp(3). + * \param regex See regcomp(3). + * \param cflags See regcomp(3). + * + * \return Standard. + */ +int para_regcomp(regex_t *preg, const char *regex, int cflags) +{ + char *buf; + size_t size; + int ret = regcomp(preg, regex, cflags); + + if (ret == 0) + return 1; + size = regerror(ret, preg, NULL, 0); + buf = para_malloc(size); + regerror(ret, preg, buf, size); + PARA_ERROR_LOG("%s\n", buf); + 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; +}