blob: 1597baa70158f3a10c54b4b5d1fc18fb52f37d80 [file] [log] [blame]
/* libnih
*
* string.c - useful string utility functions
*
* Copyright © 2009 Scott James Remnant <scott@netsplit.com>.
* Copyright © 2009 Canonical Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/logging.h>
#include "string.h"
/**
* nih_sprintf:
* @parent: parent object for new string,
* @format: format string.
*
* Writes a new string according to @format as sprintf(), except that the
* string is allocated using nih_alloc().
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL if insufficient memory.
**/
char *
nih_sprintf (const void *parent,
const char *format,
...)
{
char *str;
va_list args;
nih_assert (format != NULL);
va_start (args, format);
str = nih_vsprintf (parent, format, args);
va_end (args);
return str;
}
/**
* nih_vsprintf:
* @parent: parent object for new string,
* @format: format string,
* @args: arguments to format string.
*
* Writes a new string according to @format as vsprintf(), except that the
* string is allocated using nih_alloc().
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL if insufficient memory.
**/
char *
nih_vsprintf (const void *parent,
const char *format,
va_list args)
{
ssize_t len;
va_list args_copy;
char *str;
nih_assert (format != NULL);
va_copy (args_copy, args);
len = vsnprintf (NULL, 0, format, args_copy);
va_end (args_copy);
nih_assert (len >= 0);
str = nih_alloc (parent, len + 1);
if (! str)
return NULL;
va_copy (args_copy, args);
vsnprintf (str, len + 1, format, args_copy);
va_end (args_copy);
return str;
}
/**
* nih_strdup:
* @parent: parent object for new string,
* @str: string to duplicate.
*
* Allocates enough memory to store a duplicate of @str and writes a
* copy of the string to it.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: duplicated string or NULL if insufficient memory.
**/
char *
nih_strdup (const void *parent,
const char *str)
{
size_t len;
nih_assert (str != NULL);
len = strlen (str);
return nih_strndup (parent, str, len);
}
/**
* nih_strndup:
* @parent: parent object for new string,
* @str: string to duplicate,
* @len: length of string to duplicate.
*
* Allocates enough memory to store up to @len bytes of @str, or if @str
* is shorter, @len bytes. A copy of the string is written to this
* block with a NUL byte appended.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: duplicated string or NULL if insufficient memory.
**/
char *
nih_strndup (const void *parent,
const char *str,
size_t len)
{
char *dup;
nih_assert (str != NULL);
dup = nih_alloc (parent, len + 1);
if (! dup)
return NULL;
memset (dup, '\0', len + 1);
strncpy (dup, str, len);
return dup;
}
/**
* nih_strcat:
* @str: pointer to string to modify,
* @parent: parent object of new string,
* @src: string to append to @str.
*
* Modifies @str, concatenating the contents of @src to it. The new string
* is allocated using nih_alloc(), and @str will be updated to point to the
* new pointer; use the return value simply to check for success.
*
* If the string pointed to by @str is NULL, this is equivalent to
* nih_strdup() and if @parent is not NULL, it should be a pointer to another
* object which will be used as a parent for the returned string. When all
* parents of the returned string are freed, the returned string will also be
* freed.
*
* When the string pointed to by @str is not NULL, @parent is ignored;
* though it usual to pass a parent of @str for style reasons.
*
* Returns: new string pointer or NULL if insufficient memory.
**/
char *
nih_strcat (char **str,
const void *parent,
const char *src)
{
nih_assert (str != NULL);
nih_assert (src != NULL);
return nih_strncat (str, parent, src, strlen (src));
}
/**
* nih_strncat:
* @str: pointer to string to modify,
* @parent: parent object of new string,
* @src: string to append to @str,
* @len: length of @src.
*
* Modifies @str, concatenating up to @len characters of the contents of @src
* to it. The new string is allocated using nih_alloc(), and @str will be
* updated to point to the new pointer; use the return value simply to check
* for success.
*
* If the string pointed to by @str is NULL, this is equivalent to
* nih_strdup() and if @parent is not NULL, it should be a pointer to another
* object which will be used as a parent for the returned string. When all
* parents of the returned string are freed, the returned string will also be
* freed.
*
* When the string pointed to by @str is not NULL, @parent is ignored;
* though it usual to pass a parent of @str for style reasons.
*
* Returns: new string pointer or NULL if insufficient memory.
**/
char *
nih_strncat (char **str,
const void *parent,
const char *src,
size_t len)
{
char *ret;
nih_assert (str != NULL);
nih_assert (src != NULL);
if (! *str) {
*str = nih_strndup (parent, src, len);
return *str;
}
ret = nih_realloc (*str, parent, strlen (*str) + len + 1);
if (! ret)
return NULL;
*str = ret;
strncat (*str, src, len);
return ret;
}
/**
* nih_strcat_sprintf:
* @str: pointer to string to modify,
* @parent: parent object of new string,
* @format: format string to append to @str.
*
* Modifies @str, concatenating according to @format as sprintf(). The new
* string is allocated using nih_alloc(), and @str will be updated to point
* to the new pointer; use the return value simply to check for success.
*
* If the string pointed to by @str is NULL, this is equivalent to
* nih_sprintf() and if @parent is not NULL, it should be a pointer to another
* object which will be used as a parent for the returned string. When all
* parents of the returned string are freed, the returned string will also be
* freed.
*
* When the string pointed to by @str is not NULL, @parent is ignored;
* though it usual to pass a parent of @str for style reasons.
*
* Returns: new string pointer or NULL if insufficient memory.
**/
char *
nih_strcat_sprintf (char **str,
const void *parent,
const char *format,
...)
{
char *ret;
va_list args;
nih_assert (str != NULL);
nih_assert (format != NULL);
va_start (args, format);
ret = nih_strcat_vsprintf (str, parent, format, args);
va_end (args);
return ret;
}
/**
* nih_strcat_vsprintf:
* @str: pointer to string to modify,
* @parent: parent object of new string,
* @format: format string to append to @str,
* @args: arguments to format string.
*
* Modifies @str, concatenating according to @format as vsprintf(). The new
* string is allocated using nih_alloc(), and @str will be updated to point
* to the new pointer; use the return value simply to check for success.
*
* If the string pointed to by @str is NULL, this is equivalent to
* nih_vsprintf() and if @parent is not NULL, it should be a pointer to another
* object which will be used as a parent for the returned string. When all
* parents of the returned string are freed, the returned string will also be
* freed.
*
* When the string pointed to by @str is not NULL, @parent is ignored;
* though it usual to pass a parent of @str for style reasons.
*
* Returns: new string pointer or NULL if insufficient memory.
**/
char *
nih_strcat_vsprintf (char **str,
const void *parent,
const char *format,
va_list args)
{
ssize_t len, str_len;
va_list args_copy;
char *ret;
nih_assert (str != NULL);
nih_assert (format != NULL);
str_len = *str ? strlen (*str) : 0;
va_copy (args_copy, args);
len = vsnprintf (NULL, 0, format, args_copy);
va_end (args_copy);
nih_assert (len >= 0);
ret = nih_realloc (*str, parent, str_len + len + 1);
if (! ret)
return NULL;
*str = ret;
va_copy (args_copy, args);
vsnprintf (*str + str_len, len + 1, format, args_copy);
va_end (args_copy);
return ret;
}
/**
* nih_str_split:
* @parent: parent object of new array,
* @str: string to split,
* @delim: characters to split on,
* @repeat: allow repeated characters.
*
* Splits @str into an array of strings by separating on any character in
* @delim; if @repeat is true then sequences of @delim are ignored, otherwise
* they result in empty strings in the returned array.
*
* The last element in the array is always NULL.
*
* The individual strings are allocated using nih_alloc() so you may just use
* nih_free() on the returned array.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents
* of the returned string are freed, the returned array will also be
* freed.
*
* Returns: allocated array or NULL if insufficient memory.
**/
char **
nih_str_split (const void *parent,
const char *str,
const char *delim,
int repeat)
{
char **array;
size_t len;
nih_assert (str != NULL);
nih_assert (delim != NULL);
len = 0;
array = nih_str_array_new (parent);
if (! array)
return NULL;
while (*str) {
const char *ptr;
/* Skip initial delimiters */
while (repeat && strchr (delim, *str))
str++;
/* Find the end of the token */
ptr = str;
while (*str && (! strchr (delim, *str)))
str++;
if (! nih_str_array_addn (&array, parent, &len,
ptr, str - ptr)) {
nih_free (array);
return NULL;
}
/* Skip over the delimiter */
if (*str)
str++;
}
return array;
}
/**
* nih_str_array_new:
* @parent: parent object of new array.
*
* Allocates a new NULL-terminated array of strings with zero elements;
* use nih_str_array_add() to append new strings to the array. Because
* each array element will be allocated using nih_alloc() as a child of
* the array itself, the entire array can be freed with nih_free().
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents
* of the returned object are freed, the returned array will also be
* freed.
*
* Returns: newly allocated array or NULL if insufficient memory.
**/
char **
nih_str_array_new (const void *parent)
{
char **array;
array = nih_alloc (parent, sizeof (char *));
if (! array)
return NULL;
array[0] = NULL;
return array;
}
/**
* nih_str_array_add:
* @array: array of strings,
* @parent: parent object of new array,
* @len: length of @array,
* @str: string to add.
*
* Extend the NULL-terminated string @array (which has @len elements,
* excluding the final NULL element), appending a copy of @str to it.
* Both the array and the new string are allocated using nih_alloc(),
*
* @len will be updated to contain the new array length and @array will
* be updated to point to the new array pointer; use the return value
* simply to check for success.
*
* If you don't know or care about the length, @len may be set to NULL;
* this is less efficient as it necessates counting the length on each
* invocation.
*
* If the array pointed to by @array is NULL, the array will be allocated
* and @ptr the first element, and if @parent is not NULL, it should be a
* pointer to another object which will be used as a parent for the returned
* array. When all parents of the returned array are freed, the returned
* array will also be freed.
*
* When the array pointed to by @array is not NULL, @parent is ignored;
* though it usual to pass a parent of @array for style reasons.
*
* Returns: new array pointer or NULL if insufficient memory.
**/
char **
nih_str_array_add (char ***array,
const void *parent,
size_t *len,
const char *str)
{
nih_local char *new_str;
nih_assert (array != NULL);
nih_assert (str != NULL);
new_str = nih_strdup (NULL, str);
if (! new_str)
return NULL;
return nih_str_array_addp (array, parent, len, new_str);
}
/**
* nih_str_array_addn:
* @array: array of strings,
* @parent: parent object of new array,
* @len: length of @array,
* @str: string to add,
* @strlen: length of @str.
*
* Extend the NULL-terminated string @array (which has @len elements,
* excluding the final NULL element), appending a copy of the first
* @strlen bytes of @str to it.
*
* Both the array and the new string are allocated using nih_alloc(),
*
* @len will be updated to contain the new array length and @array will
* be updated to point to the new array pointer; use the return value
* simply to check for success.
*
* If you don't know or care about the length, @len may be set to NULL;
* this is less efficient as it necessates counting the length on each
* invocation.
*
* If the array pointed to by @array is NULL, the array will be allocated
* and @ptr the first element, and if @parent is not NULL, it should be a
* pointer to another object which will be used as a parent for the returned
* array. When all parents of the returned array are freed, the returned
* array will also be freed.
*
* When the array pointed to by @array is not NULL, @parent is ignored;
* though it usual to pass a parent of @array for style reasons.
*
* Returns: new array pointer or NULL if insufficient memory.
**/
char **
nih_str_array_addn (char ***array,
const void *parent,
size_t *len,
const char *str,
size_t strlen)
{
nih_local char *new_str;
nih_assert (array != NULL);
nih_assert (str != NULL);
new_str = nih_strndup (NULL, str, strlen);
if (! new_str)
return NULL;
return nih_str_array_addp (array, parent, len, new_str);
}
/**
* nih_str_array_addp:
* @array: array of strings,
* @parent: parent object of new array,
* @len: length of @array,
* @ptr: pointer to add.
*
* Extend the NULL-terminated string @array (which has @len elements,
* excluding the final NULL element), appending the nih_alloc() allocated
* object @ptr to it.
*
* The array is allocated using nih_alloc(), and @ptr will be referenced
* by the new array. After calling this function, you should never use
* nih_free() to free @ptr and instead use nih_unref() or nih_discard()
* if you no longer need to use it.
*
* @len will be updated to contain the new array length and @array will
* be updated to point to the new array pointer; use the return value
* simply to check for success.
*
* If you don't know or care about the length, @len may be set to NULL;
* this is less efficient as it necessates counting the length on each
* invocation.
*
* If the array pointed to by @array is NULL, the array will be allocated
* and @ptr the first element, and if @parent is not NULL, it should be a
* pointer to another object which will be used as a parent for the returned
* array. When all parents of the returned array are freed, the returned
* array will also be freed.
*
* When the array pointed to by @array is not NULL, @parent is ignored;
* though it usual to pass a parent of @array for style reasons.
*
* Returns: new array pointer or NULL if insufficient memory.
**/
char **
nih_str_array_addp (char ***array,
const void *parent,
size_t *len,
void *ptr)
{
char **new_array;
size_t c_len;
nih_assert (array != NULL);
nih_assert (ptr != NULL);
if (! len) {
len = &c_len;
c_len = 0;
for (new_array = *array; new_array && *new_array; new_array++)
c_len++;
}
new_array = nih_realloc (*array, parent, sizeof (char *) * (*len + 2));
if (! new_array)
return NULL;
*array = new_array;
nih_ref (ptr, *array);
(*array)[(*len)++] = ptr;
(*array)[*len] = NULL;
return *array;
}
/**
* nih_str_array_copy:
* @parent: parent object of new array.
* @len: length of new array,
* @array: array of strings to copy.
*
* Allocates a new NULL-terminated array of strings with elements copied
* from the existing @array given.
*
* Because each array element will be allocated using nih_alloc() as a child
* of the array itself, the entire array can be freed with nih_free().
* This will not affect the array copied.
*
* @len will be updated to contain the new array length. If you don't care
* about the length, @len may be set to NULL; this is less efficient as it
* necessates counting the length on each future add operation.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents
* of the returned array are freed, the returned array will also be
* freed.
*
* Returns: newly allocated array or NULL if insufficient memory.
**/
char **
nih_str_array_copy (const void *parent,
size_t *len,
char * const *array)
{
char **new_array;
nih_assert (array != NULL);
new_array = nih_str_array_new (parent);
if (! new_array)
return NULL;
if (! nih_str_array_append (&new_array, parent, len, array)) {
nih_free (new_array);
return NULL;
}
return new_array;
}
/**
* nih_str_array_append:
* @array: array of strings,
* @parent: parent object of new array,
* @len: length of @array,
* @args: array of strings to add.
*
* Extend the NULL-terminated string @array (which has @len elements,
* excluding the final NULL element), appending a copy of each element in
* the additional NULL-terminated string array @args to it.
* Both the array and the new strings are allocated using nih_alloc(),
*
* @len will be updated to contain the new array length and @array will
* be updated to point to the new array pointer; use the return value
* simply to check for success.
*
* If you don't know or care about the length, @len may be set to NULL;
* this is less efficient as it necessates counting the length on each
* operation.
*
* If the array pointed to by @array is NULL, this is equivalent to
* nih_str_array_copy() and if @parent is not NULL, it should be a pointer to
* another object which will be used as a parent for the returned array.
* When all parents of the returned array are freed, the returned array will
* also be freed.
*
* When the array pointed to by @array is not NULL, @parent is ignored;
* though it usual to pass a parent of @array for style reasons.
*
* Returns: new array pointer or NULL if insufficient memory.
**/
char **
nih_str_array_append (char ***array,
const void *parent,
size_t *len,
char * const *args)
{
size_t c_len, o_len;
int free_on_error = FALSE;
char * const *arg;
nih_assert (array != NULL);
nih_assert (args != NULL);
if (! *array)
free_on_error = TRUE;
if (! len) {
c_len = 0;
for (arg = *array; arg && *arg; arg++)
c_len++;
} else {
c_len = *len;
}
o_len = c_len;
for (arg = args; *arg; arg++) {
if (! nih_str_array_add (array, parent, &c_len, *arg)) {
if (*array) {
for (; c_len > o_len; c_len--)
nih_free ((*array)[c_len - 1]);
(*array)[o_len] = NULL;
if (free_on_error) {
nih_free (*array);
*array = NULL;
}
}
return NULL;
}
}
if (len)
*len = c_len;
return *array;
}
/**
* nih_str_wrap:
* @parent: parent object of new string,
* @str: string to be wrapped,
* @len: length of line to fit into,
* @first_indent: indent for first line,
* @indent: indent for subsequent lines.
*
* Returns a newly allocated copy of @str with newlines inserted so no
* line is longer than @len characters (not including the newline). Where
* possible, newlines replace existing whitespace characters so that words
* are not broken.
*
* The first line may be indented by an extra @first_indent characters, and
* subsequent lines may be intended by an extra @indent characters. These
* are added to the string as whitespace characters.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL if insufficient memory.
**/
char *
nih_str_wrap (const void *parent,
const char *str,
size_t len,
size_t first_indent,
size_t indent)
{
char *txt;
size_t txtlen, col, ls, i;
nih_assert (str != NULL);
nih_assert (len > 0);
txtlen = first_indent + strlen (str);
txt = nih_alloc (parent, txtlen + 1);
if (! txt)
return NULL;
memset (txt, ' ', first_indent);
memcpy (txt + first_indent, str, strlen (str) + 1);
col = ls = 0;
for (i = 0; i < txtlen; i++) {
int nl = 0;
if (strchr (" \t\r", txt[i])) {
/* Character is whitespace; convert to an ordinary
* space and remember the position for next time.
*/
txt[i] = ' ';
ls = i;
/* If this doesn't go over the line length,
* continue to the next character
*/
if (++col <= len)
continue;
} else if (txt[i] != '\n') {
/* Character is part of a word. If this doesn't go
* over the line length, continue to the next
* character
*/
if (++col <= len)
continue;
/* Filled a line; if we marked a whitespace character
* on this line, go back to that, otherwise we'll
* need to add a newline to the string after this
* character
*/
if (ls) {
i = ls;
} else {
nl = 1;
}
}
/* We need to insert a line break at this position, and
* any indent that goes along with it
*/
if (indent | nl) {
char *new_txt;
/* Need to increase the size of the string in memory */
new_txt = nih_realloc (txt, parent,
txtlen + indent + nl + 1);
if (! new_txt) {
nih_free (txt);
return NULL;
}
txt = new_txt;
/* Move up the existing characters, then replace
* the gap with the indent
*/
memmove (txt + i + indent + 1, txt + i + 1 - nl,
txtlen - i + nl);
memset (txt + i + 1, ' ', indent);
txtlen += indent + nl;
}
/* Replace the current character with a newline */
txt[i] = '\n';
/* Reset the current column and last seen whitespace index;
* make sure we skip any indent as it's whitespace that doesn't
* count
*/
i += indent;
col = indent;
ls = 0;
}
return txt;
}
/**
* nih_str_screen_width:
*
* Checks the COLUMNS environment variable, standard output if it is a
* terminal or defaults to 80 characters.
*
* Returns: the width of the screen.
**/
size_t
nih_str_screen_width (void)
{
char *columns;
size_t len = 0;
/* Look at the columns environment variable */
columns = getenv ("COLUMNS");
if ((! len) && columns) {
char *endptr;
len = strtoul (columns, &endptr, 10);
if (*endptr)
len = 0;
}
/* Check whether standard output is a tty */
if ((! len) && isatty (STDOUT_FILENO)) {
struct winsize winsize;
if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &winsize) == 0)
len = winsize.ws_col;
}
/* Fallback to 80 columns */
if (! len)
len = 80;
return len;
}
/**
* nih_str_screen_wrap:
* @parent: parent object of new string,
* @str: string to be wrapped,
* @first_indent: indent for first line,
* @indent: indent for subsequent lines.
*
* Returns a newly allocated copy of @str with newlines inserted so no
* line is wider than the screen (not including the newline). Where
* possible, newlines replace existing whitespace characters so that words
* are not broken.
*
* If standard output is not a terminal, then 80 characters is assumed.
* The width can be overriden with the COLUMNS environment variable.
*
* The first line may be indented by an extra @first_indent characters, and
* subsequent lines may be intended by an extra @indent characters. These
* are added to the string as whitespace characters.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL if insufficient memory.
**/
char *
nih_str_screen_wrap (const void *parent,
const char *str,
size_t first_indent,
size_t indent)
{
size_t len;
nih_assert (str != NULL);
len = nih_str_screen_width () - 1;
return nih_str_wrap (parent, str, len, first_indent, indent);
}