/* SPDX-License-Identifier: GPL-2.0 */ /** \file string.c Memory allocation and string handling functions. */ #include "para.h" #include #include /* uname() */ #include #include #include "string.h" #include "error.h" /** * Reallocate an array, abort on failure or bugs. * * \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) 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 ptr. * * \sa realloc(3). */ __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); /* * This multiplication can not overflow because the above call to \ref * arr_alloc() aborts on overflow. */ memset(ptr, 0, nmemb * size); return ptr; } /** * Allocate and initialize memory. * * \param size The desired new size. * * \return A pointer to the allocated and zeroed-out memory, which is suitably * aligned for any kind of variable. * * \sa \ref alloc(), calloc(3). */ __must_check __malloc void *zalloc(size_t size) { return arr_zalloc(1, size); } /** * 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 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 newly allocated memory which is suitably aligned for * any kind of variable and may be different from \a p. * * \sa realloc(3). */ __must_check void *para_realloc(void *p, size_t size) { return arr_realloc(p, 1, size); } /** * Paraslash's version of malloc(). * * A wrapper for malloc(3) which exits on errors. * * \param size The number of bytes to allocate. * * \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); } /** * Paraslash's version of strdup(). * * \param s The string to be duplicated. * * 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. 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). */ __must_check __malloc char *para_strdup(const char *s) { char *dupped_string = strdup(s? s: ""); assert(dupped_string); return dupped_string; } /** * Print a formatted message to a dynamically allocated string. * * \param result The formatted 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 = alloc(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 A usual format string. * * Produce output according to \p fmt. No artificial bound on the length of the * resulting string is imposed. * * \return This function either returns a pointer to a string that must be * freed by the caller or aborts without returning. * * \sa printf(3), \ref xasprintf(). */ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) { char *msg; va_list ap; va_start(ap, fmt); xvasprintf(&msg, fmt, ap); va_end(ap); return msg; } /** * Free the content of a pointer and set it to NULL. * * \param arg A pointer to 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) { if (arg) { void **ptr = arg; free(*ptr); *ptr = NULL; } } /** * Get the logname of the current user. * * \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). */ __must_check __malloc char *para_logname(void) { struct passwd *pw = getpwuid(getuid()); return para_strdup(pw? pw->pw_name : "unknown_user"); } /** * Get the home directory of the calling user. * * \return A dynamically allocated string that must be freed by the caller. If * 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; /* * 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); } /** * Get the own hostname. * * \return A static string containing the hostname. Do not free! * * \sa uname(2). */ const char *para_hostname(void) { static struct utsname u; uname(&u); return u.nodename; } /** * Call a function for each complete line in a buffer. * * \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 the buffer. * \param line_handler The callback function. * \param private_data Pointer passed to the line handler. * * If the FELF_READ_ONLY flag is unset, line breaks in the buffer are replaced * by NUL characters and pointers into the thusly modified buffer are passed to * the line handler. Otherwise, at each iteration a temporary NUL-terminated * copy of the current line is made and a pointer to the copy is passed * instead. * * Processing stops if the line handler returns negative or when there are no * more complete lines in the buffer. In the latter case, if FELF_READ_ONLY is * unset, the last chunk containing an incomplete line is moved to the * beginning of the buffer. * * \return The only possible error is a negative return value from the line * handler. The function then returns this negative value to indicate failure. * Otherwise it returns the size of the last incomplete line in bytes. * * \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; while (start < buf + size) { char *next_null; char *next_cr; next_cr = memchr(start, '\n', buf + size - start); next_null = memchr(start, '\0', next_cr? next_cr - start : buf + size - start); if (!next_cr && !next_null) break; if (next_null) end = next_null; else end = next_cr; if (!(flags & FELF_DISCARD_FIRST) || start != buf) { if (flags & FELF_READ_ONLY) { size_t s = end - start; char *b = alloc(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; } start = ++end; } i = buf + size - start; if (i && i != size && !(flags & FELF_READ_ONLY)) memmove(buf, start, i); return i; } /** Return the hex characters of the lower 4 bits. */ #define hex(a) (hexchar[(a) & 15]) static void write_size_header(char *buf, int n) { 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] = ' '; } /** * Append to the contents of a para buffer. * * \param b Determines the buffer, its size, and the offset. * \param fmt A printf-like format string. * * This function prints into the given para buffer at the current offset, * advancing the offset so that subsequent calls append to existing buffer * contents. * * If there is not enough space for the result, the buffer size is doubled * until it exceeds its maximal size. If the buffer is already at the maximum * size and is still too small for the input, the max_size handler is called, * passing the (unmodified) buffer as an argument. If the handler indicates * success by returning non-negative, the offset is reset to zero and the * given data is written to the beginning of the now empty buffer. If the * max_size handler returns a negative error code, this value is returned. * * It's OK to call this with b->buf == NULL. In this case, a small initial * buffer will be allocated. * * \return The number of bytes printed into the buffer (not including the * terminating NUL byte) on success, negative on errors. If there is no * size-bound (i.e., if b->max_size is zero), this function never fails. * * \sa make_message(), vsnprintf(3), stdarg(3) */ __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 = alloc(128); b->size = 128; b->offset = 0; } while (1) { char *p = b->buf + b->offset; size_t size = b->size - b->offset; va_list ap; 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; } } /* 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; } } /** \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 llong_minmax */ /** * Convert a string to a 64-bit signed integer value. * * \param str The string to be converted. * \param value Result pointer. * * \return Standard. * * \sa \ref para_atoi32(), strtol(3), atoi(3). */ int para_atoi64(const char *str, int64_t *value) { char *endptr; long long tmp; errno = 0; /* To distinguish success/failure after call */ tmp = strtoll(str, &endptr, 10); if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) return -E_ATOI_OVERFLOW; /* * 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; return 1; } /** * Convert a string to a 32-bit signed integer value. * * \param str The string to be converted. * \param value Result pointer. * * \return Standard. * * \sa \ref para_atoi64(). */ int para_atoi32(const char *str, int32_t *value) { int64_t tmp; int ret; const int32_t max = 2147483647; ret = para_atoi64(str, &tmp); if (ret < 0) return ret; if (tmp > max || tmp < -max - 1) return -E_ATOI_OVERFLOW; *value = tmp; 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 = alloc(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 = arr_zalloc(offset + 1, sizeof(char *)); const char *p; int i, ret; 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 = arr_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 \\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. * * 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) { 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); } /** * 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 = alloc(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 = alloc(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 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; 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. */ 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 = 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 = 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; *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; } /** * Get the version string for an executable. * * \param pfx The program name (without the leading "para_"). * * \return A statically allocated string which contains the program name and * the git version. It must not be freed by the caller. */ const char *version_single_line(const char *pfx) { static char buf[100]; snprintf(buf, sizeof(buf) - 1, "para_%s %s", pfx, paraslash_version()); return buf; } /** * Get the full version text. * * \param pfx See \ref version_single_line(). * * \return A string containing the same text as returned by \ref * version_single_line(), augmented by additional build information, a * copyright text and the email address of the author. * * Like \ref version_single_line(), this string is stored in a statically * allocated buffer and must not be freed. */ const char *version_text(const char *pfx) { static char buf[1024]; snprintf(buf, sizeof(buf) - 1, "%s\n%s\n", version_single_line(pfx), paraslash_info()); return buf; } /** * Print the version text and exit successfully. * * \param pfx See \ref version_single_line(). * \param flag Whether --version was given. * * If \a flag is false, this function does nothing. Otherwise it prints the * full version text as returned by \ref version_text() and exits successfully. */ void version_handle_flag(const char *pfx, bool flag) { if (!flag) return; printf("%s", version_text(pfx)); exit(EXIT_SUCCESS); }