Commit d26475dc authored by Dan Good's avatar Dan Good Committed by Rusty Russell

Add xstring module - bounded string builder with three valued comparator

parent 207e7b79
......@@ -122,7 +122,8 @@ MODS_WITH_SRC := aga \
time \
timer \
ttxml \
wwviaudio
wwviaudio \
xstring
MODS:=$(MODS_WITH_SRC) $(MODS_NO_SRC)
......
../../licenses/APACHE-2
\ No newline at end of file
#include "config.h"
#include <stdio.h>
#include <string.h>
/**
* xstring - bounded string builder with three valued comparator
*
* xstring handles string concatenation to a buffer, flagging truncation.
* Additions can be transactional, where the addition wholly succeeds or the buffer is reterminated at its prior length.
* Once flagged, no further additions are allowed.
* Clearing reuses the buffer from the beginning.
*
* When comparing two xstrings, the truncation state is taken into account.
* If either is truncated, an equality test returns unknown (-1).
* Testing if x contains y returns unknown (-1) if y is truncated.
* If x is whole, the test returns true (1) or false (0) appropriately.
* If x is truncated, the test returns true (1) if the known portion of x contains y, and unknown (-1) otherwise.
*
* The structure members are intended for direct access.
*
* Example:
* // demo - break long lines
* #include <string.h>
* #include <stdio.h>
* #include <unistd.h>
* #include <err.h>
* #include <fcntl.h>
* #include <sys/types.h>
* #include <sys/stat.h>
* #include <sys/mman.h>
*
* #include "ccan/xstring/xstring.h"
*
* #define MAXLEN 80
* #define MARGIN 10
* int main(int argc, char *argv[])
* {
* char buf[BUFSIZ];
* xstring _x, *x = &_x;
* struct stat sb;
* int fd, span, lnlen;
* char *map, *p, *q;
*
* xstrInit(x, buf, sizeof(buf), 0);
* assert(sizeof(buf) > MAXLEN);
*
* if (argc != 2) return 1;
*
* if ((fd = open(argv[1], O_RDONLY)) == -1)
* err(1, "open");
* if (fstat(fd, &sb) == -1)
* err(1, "stat");
* if ((map = mmap(NULL, sb.st_size + 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
* err(1, "mmap");
* map[sb.st_size] = '\n';
*
* for (p = map, lnlen = 0; p < map + sb.st_size; ) {
* span = strcspn(p, " \t\n") + 1;
*
* if (lnlen + span > MAXLEN) {
* span = MAXLEN - lnlen;
* xstrAddSubsT(x, p, span, "\n", 1, NULL);
* }
* else
* xstrAddSubT(x, p, span);
*
* if (x->truncated) {
* fputs(x->str, stdout);
* xstrClear(x);
* continue; // p unchanged
* }
*
* q = x->str + x->len - 1;
* if (lnlen + MARGIN > MAXLEN)
* *q = '\n';
*
* lnlen = *q == '\n' ? 0 : lnlen + span;
* p += span;
* }
*
* if (x->len)
* fputs(x->str, stdout);
*
* return 0;
* }
*
* License: APACHE-2
* Author: Dan Good <dan@dancancode.com>
*/
int main(int argc, char *argv[])
{
/* Expect exactly one argument */
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
printf("ccan/failtest\n");
return 0;
}
return 1;
}
#include <ccan/xstring/xstring.h>
/* Include the C files directly. */
#include <ccan/xstring/xstring.c>
#include <ccan/tap/tap.h>
int main(void)
{
plan_tests(70);
{ //12
char buf[4] = { 'a', 'a', 'a', 'a' };
xstring a;
ok1(xstrInit(&a, buf, sizeof(buf), 0) == 0);
ok1(a.str == buf && a.len == 0 && a.cap == sizeof(buf) && a.truncated == 0 && a.str[0] == '\0');
buf[0] = 'a';
ok1(xstrInit(&a, buf, sizeof(buf), 1) == -1);
ok1(a.str == buf && a.len == 3 && a.cap == sizeof(buf) && a.truncated == -1 && strcmp(a.str, "aaa") == 0);
ok1(xstrInit(&a, buf, sizeof(buf), 1) == 0);
ok1(a.str == buf && a.len == 3 && a.cap == sizeof(buf) && a.truncated == 0 && strcmp(a.str, "aaa") == 0);
xstrClear(&a);
ok1(xstrAddChar(&a, '1') == 0 && a.truncated == 0 && strcmp(a.str, "1") == 0);
ok1(xstrAddChar(&a, '2') == 0 && a.truncated == 0 && strcmp(a.str, "12") == 0);
ok1(xstrAddChar(&a, '3') == 0 && a.truncated == 0 && strcmp(a.str, "123") == 0);
ok1(xstrAddChar(&a, '\0') == 0 && a.truncated == 0 && strcmp(a.str, "123") == 0);
ok1(xstrAddChar(&a, '4') == -1 && a.truncated == -1 && strcmp(a.str, "123") == 0);
ok1(xstrAddChar(&a, '5') == -1 && a.truncated == -1 && strcmp(a.str, "123") == 0);
}
{ //21
char buf[10];
xstring _x, *x = &_x;
ok1(xstrInit(x, buf, sizeof(buf), 0) == 0);
ok1(x->str == buf && x->len == 0 && x->cap == sizeof(buf) && x->truncated == 0 && *x->str == '\0');
ok1(xstrAdd(x, "") == 0 && x->len == 0 && x->truncated == 0 && *x->str == '\0');
ok1(xstrAdd(x, NULL) == 0 && x->len == 0 && x->truncated == 0 && *x->str == '\0');
ok1(xstrAdd(x, "foo") == 0 && x->len == 3 && x->truncated == 0 && strcmp(x->str, "foo") == 0);
ok1(xstrAdd(x, "bar") == 0 && x->len == 6 && x->truncated == 0 && strcmp(x->str, "foobar") == 0);
ok1(xstrAdd(x, "baz") == 0 && x->len == 9 && x->truncated == 0 && strcmp(x->str, "foobarbaz") == 0);
ok1(xstrAdd(x, "oof") == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarbaz") == 0);
ok1(xstrAdd(x, "rab") == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarbaz") == 0);
xstrClear(x);
ok1(x->str == buf && x->len == 0 && x->cap == sizeof(buf) && x->truncated == 0 && *x->str == '\0');
ok1(xstrAdd(x, "foobarbazoof") == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarbaz") == 0);
xstrClear(x);
ok1(xstrAdd(x, "foo") == 0 && x->len == 3 && x->truncated == 0 && strcmp(x->str, "foo") == 0);
ok1(xstrAddT(x, "foobarbazoof") == -1 && x->len == 3 && x->truncated == -1 && strcmp(x->str, "foo") == 0);
xstrClear(x);
ok1(xstrAddT(&_x, "foobarbazoof") == -1 && x->len == 0 && x->truncated == -1 && *x->str == '\0');
xstrClear(x);
ok1(xstrCat(x, "foo", "bar", "baz", NULL) == 0 && x->len == 9 && x->truncated == 0 && strcmp(x->str, "foobarbaz") == 0);
xstrClear(x);
ok1(xstrCat(x, "foo", "bar", "baz", "oof", NULL) == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarbaz") == 0);
xstrClear(x);
ok1(xstrCatT(x, "foo", "bar", "baz", "oof", NULL) == -1 && x->len == 0 && x->truncated == -1 && *x->str == '\0');
xstrClear(x);
ok1(xstrCatT(&_x, "foo", "bar", "baz", "oof", NULL) == -1 && x->len == 0 && x->truncated == -1 && *x->str == '\0');
xstrClear(x);
ok1(xstrJoin(x, ",", "fo", "ba", "ba", NULL) == 0 && x->len == 8 && x->truncated == 0 && strcmp(x->str, "fo,ba,ba") == 0);
xstrClear(x);
ok1(xstrJoin(x, ",", "foobarbaz", "oof", NULL) == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarbaz") == 0);
xstrClear(x);
ok1(xstrJoin(x, ",", "foobarba", "oof", NULL) == -1 && x->len == 9 && x->truncated == -1 && strcmp(x->str, "foobarba,") == 0);
}
{ //9
char buf[10];
xstring _x, *x = &_x;
ok1(xstrInit(x, buf, sizeof(buf), 0) == 0);
ok1(xstrAddSub(x, NULL, 0) == 0 && x->len == 0 && x->truncated == 0 && *x->str == '\0');
ok1(xstrAddSub(x, "foo", 0) == 0 && x->len == 0 && x->truncated == 0 && *x->str == '\0');
ok1(xstrAddSub(x, "foobar", 3) == 0 && x->len == 3 && x->truncated == 0 && strcmp(x->str, "foo") == 0);
ok1(xstrAddSub(x, "foobarbaz", 7) == -1 && x->len == 3 && x->truncated == -1 && strcmp(x->str, "foo") == 0);
xstrClear(x);
ok1(xstrAddSubs(x, "aa", 1, "bbb", 2, NULL) == 0 && x->len == 3 && x->truncated == 0 && strcmp(x->str, "abb") == 0);
ok1(xstrAddSubs(x, "cccccccc", 7, NULL) == -1 && x->len == 3 && x->truncated == -1 && strcmp(x->str, "abb") == 0);
xstrClear(x);
ok1(xstrAddSubsT(x, "aa", 1, "bbb", 2, NULL) == 0 && x->len == 3 && x->truncated == 0 && strcmp(x->str, "abb") == 0);
ok1(xstrAddSubsT(x, "cccccccc", 7, NULL) == -1 && x->len == 3 && x->truncated == -1 && strcmp(x->str, "abb") == 0);
}
{ //28
char a[10], b[10];
xstring _x, *x = &_x;
xstring _y, *y = &_y;
xstrInit(x, a, sizeof(a), 0);
xstrInit(y, b, sizeof(b), 0);
xstrAdd(x, "foobarbaz");
xstrAdd(y, "foobarbaz");
ok1(xstrEq3(x, y) == 1);
ok1(xstrEq(x, y) == 1);
ok1(xstrContains3(x, y, 1) == 1);
ok1(xstrContains(x, y, 1) == 1);
xstrAddChar(x, 'x');
ok1(xstrEq3(x, y) == -1);
ok1(xstrEq(x, y) == 0);
ok1(xstrContains3(x, y, 1) == 1);
ok1(xstrContains(x, y, 1) == 1);
xstrClear(x);
xstrAdd(x, "foobarbaz");
xstrAddChar(y, 'x');
ok1(xstrEq3(x, y) == -1);
ok1(xstrEq(x, y) == 0);
ok1(xstrContains3(x, y, 1) == -1);
ok1(xstrContains(x, y, 1) == 0);
xstrClear(y);
xstrAdd(y, "Foobarbaz");
ok1(xstrEq3(x, y) == 0);
ok1(xstrEq(x, y) == 0);
xstrClear(x);
xstrClear(y);
xstrAdd(x, "foo");
xstrAdd(y, "foobarbaz");
ok1(xstrContains3(x, y, 1) == 0);
ok1(xstrContains(x, y, 1) == 0);
ok1(xstrContains3(y, x, 1) == 1);
ok1(xstrContains(y, x, 1) == 1);
ok1(xstrContains3(y, x, 0) == 1);
ok1(xstrContains(y, x, 0) == 1);
ok1(xstrContains3(y, x, -1) == 0);
ok1(xstrContains(y, x, -1) == 0);
xstrClear(x);
xstrAdd(x, "baz");
ok1(xstrContains3(y, x, -1) == 1);
ok1(xstrContains(y, x, -1) == 1);
ok1(xstrContains3(y, x, 0) == 1);
ok1(xstrContains(y, x, 0) == 1);
ok1(xstrContains3(y, x, 1) == 0);
ok1(xstrContains(y, x, 1) == 0);
}
return exit_status();
}
#include <ccan/failtest/failtest_override.h>
#include <ccan/failtest/failtest.h>
#include <ccan/xstring/xstring.h>
/* Include the C files directly. */
#include <ccan/xstring/xstring.c>
#include <ccan/tap/tap.h>
unsigned last_fail_line;
enum failtest_result once_only(struct tlist_calls *history)
{
const struct failtest_call *tail = tlist_tail(history, list);
if (tail->line == last_fail_line)
return FAIL_DONT_FAIL;
last_fail_line = tail->line;
return FAIL_OK;
}
int main(int argc, char *argv[])
{
failtest_init(argc, argv);
failtest_hook = once_only;
plan_tests(3);
xstring *x;
ok1((x = xstrNew(100)) != NULL); // fail first malloc
if (x) xstrFree(x);
ok1((x = xstrNew(100)) != NULL); // fail second malloc
if (x) xstrFree(x);
ok1((x = xstrNew(0)) == NULL && errno == EINVAL);
failtest_exit(exit_status());
}
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#define _XOPEN_SOURCE 700
#include <string.h>
#include "xstring.h"
xstring *xstrNew(const size_t size)
{
char *str;
xstring *x;
if (size < 1) {
errno = EINVAL;
return NULL;
}
str = malloc(size);
if (!str)
return NULL;
x = malloc(sizeof(struct xstring));
if (!x) {
free(str);
return NULL;
}
xstrInit(x, str, size, 0);
return x;
}
int xstrInit(xstring *x, char *str, const size_t size, int keep)
{
assert(x && str && size > 0);
memset(x, 0, sizeof(*x));
x->str = str;
x->cap = size;
if (keep) {
x->len = strnlen(str, x->cap);
if (x->cap == x->len) {
x->truncated = -1;
x->len--;
}
}
*(x->str + x->len) = '\0';
return x->truncated;
}
int xstrAddChar(xstring *x, const char c)
{
assert(x);
if (x->truncated || c == '\0')
return x->truncated;
if (x->len + 1 < x->cap) {
char *p = x->str + x->len;
*p++ = c;
*p = '\0';
x->len++;
}
else
x->truncated = -1;
return x->truncated;
}
int xstrAdd(xstring *x, const char *src)
{
char *p, *q, *s;
assert(x);
if (x->truncated || !src || *src == '\0')
return x->truncated;
for (s = (char *)src,
p = x->str + x->len,
q = x->str + x->cap - 1;
*s != '\0' && p < q; p++, s++) {
*p = *s;
}
*p = '\0';
x->len = (size_t) (p - x->str);
if (*s != '\0')
x->truncated = -1;
return x->truncated;
}
int xstrAddSub(xstring *x, const char *src, size_t len)
{
assert(x);
if (x->truncated || !src || len == 0)
return x->truncated;
if (x->len + len + 1 > x->cap)
return x->truncated = -1;
memcpy(x->str + x->len, src, len);
x->len += len;
*(x->str + x->len) = '\0';
return x->truncated;
}
int xstrCat(xstring *x, ...)
{
va_list ap;
char *s;
assert(x);
va_start(ap, x);
while ((s = va_arg(ap, char *)) != NULL) {
if (xstrAdd(x, s) == -1)
break;
}
va_end(ap);
return x->truncated;
}
int xstrJoin(xstring *x, const char *sep, ...)
{
va_list ap;
char *s;
int i;
assert(x && sep);
va_start(ap, sep);
for (i = 0; (s = va_arg(ap, char *)) != NULL; i++) {
if (i && xstrAdd(x, sep) == -1)
break;
if (xstrAdd(x, s) == -1)
break;
}
va_end(ap);
return x->truncated;
}
int xstrAddSubs(xstring *x, ...)
{
va_list ap;
char *s;
assert(x);
va_start(ap, x);
while ((s = va_arg(ap, char *)) != NULL) {
size_t n = va_arg(ap, size_t);
if (xstrAddSub(x, s, n) == -1)
break;
}
va_end(ap);
return x->truncated;
}
int xstrEq3(xstring *x, xstring *y)
{
assert(x && y);
if (x->truncated || y->truncated)
return unknown;
return x->len == y->len && xstrContains3(x, y, 1);
}
/* Does the first string contain the second */
int xstrContains3(xstring *x, xstring *y, int where)
{
int b;
assert(x && y && where >= -1 && where <= 1);
if (y->truncated)
return unknown;
if (x->len < y->len)
return 0;
switch (where) {
case -1:
b = strncmp(x->str + x->len - y->len, y->str, y->len) == 0;
break;
case 0:
b = strstr(x->str, y->str) != NULL;
break;
case 1:
b = strncmp(x->str, y->str, y->len) == 0;
break;
}
return b ? 1 : x->truncated ? unknown : 0;
}
void xstrFree(xstring *x)
{
assert(x);
free(x->str);
free(x);
}
#ifndef _XSTRING_H
#define _XSTRING_H 1
#include <assert.h>
#include <sys/types.h>
/**
* struct xstring - string metadata
* @str: pointer to buf
* @len: current length of buf contents
* @cap: maximum capacity of buf
* @truncated: -1 indicates truncation
*/
typedef struct xstring {
char *str;
size_t len;
size_t cap;
int truncated;
} xstring;
/**
* xstrNew - mallocs and inits
* @size: size of buffer to allocate
*
* mallocs both buffer and struct, calls xstrInit
*
* Return: ptr to xstring or NULL
*/
xstring *xstrNew(const size_t size);
/**
* xstrInit - initialize an xstring struct
* @x: pointer to xstring
* @str: buffer to manage
* @size: size of str
* @keep: if !0, keep existing contents of str;
* otherwise, str[0] = '\0'
*
* Return: x->truncated
*/
int xstrInit(xstring *x, char *str, const size_t size, int keep);
/**
* xstrClear - clear xstring
* @x: pointer to xstring
*
* This sets x->len and x->truncated to 0 and str[0] to '\0';
*
* Return: x->truncated (always 0)
*/
#define xstrClear(x) (xstrInit((x), (x)->str, (x)->cap, 0))
/**
* xstrAddChar - add a single character
* @x: pointer to xstring
* @c: character to append
*
* Return: x->truncated
*/
int xstrAddChar(xstring *x, const char c);
/**
* xstrAdd - append a string
* @x: pointer to xstring
* @src: string to append
*
* Append as much from src as fits - if not all, flag truncation.
*
* Return: x->truncated
*/
int xstrAdd(xstring *x, const char *src);
/**
* xstrAddSub - append a substring
* @x: pointer to xstring
* @src: string to append
* @len: length of substring
*
* Append substring and '\0' if len fits, otherwise flag truncation.
*
* Return: x->truncated
*/
int xstrAddSub(xstring *x, const char *src, size_t len);
/** xstrCat - append multiple strings
* @x: pointer to xstring
* @...: one or more strings followed by NULL
*
* Run xstrAdd for each string.
*
* Return: x->truncated
*/
int xstrCat(xstring *x, ...);
/** xstrJoin - append multiple strings joined by sep
* @x: pointer to xstring
* @sep: separator string
* @...: one or more strings followed by NULL
*
* Run xstrAdd for each string and append sep between each pair.
*
* Return: x->truncated
*/
int xstrJoin(xstring *x, const char *sep, ...);
/**
* xstrAddSubs - append multiple substrings
* @x: pointer to xstring
* @...: one or more pairs of string and length followed by NULL
*
* Run xstrAddSub for each pair of string and length.
*
* Return: x->truncated
*/
int xstrAddSubs(xstring *x, ...);
#define transact(x, y) ({ \
size_t last; \
int ret; \
assert((x)); \
last = (x)->len; \
if ((ret = (y)) == -1) { \
(x)->len = last; \
*((x)->str + (x)->len) = '\0'; \
} \
ret; \
})
/**
* xstrAddT - append a string as a transaction
* @x: pointer to xstring
* @src: string to append
*
* Run xstrAdd. Reterminate at inital length if truncation occurs.
*
* Return: x->truncated
*/
#define xstrAddT(x, y) transact(x, xstrAdd((x), y))
/** xstrCatT - append multiple strings as one transaction
* @x: pointer to xstring
* @...: one or more strings followed by NULL
*
* Run xstrCat. Reterminate at inital length if truncation occurs.
*
* Return: x->truncated
*/
#define xstrCatT(x, y...) transact(x, xstrCat((x) , ## y))
/** xstrJoinT - append multiple strings joined by sep as one transaction
* @x: pointer to xstring
* @sep: separator string
* @...: one or more strings followed by NULL
*
* Run xstrJoin. Reterminate at inital length if truncation occurs.
*
* Return: x->truncated
*/
#define xstrJoinT(x, y...) transact(x, xstrJoin((x) , ## y))
/**
* xstrAddSubsT - append multiple substrings as one transaction
* @x: pointer to xstring
* @...: one or more pairs of string and length followed by NULL
*
* Run xstrAddSubs. Reterminate at inital length if truncation occurs.
*
* Return: x->truncated
*/
#define xstrAddSubsT(x, y...) transact(x, xstrAddSubs((x) , ## y))
/**
* xstrAddSubT - same as xstrAddSub
*/
// addsub is already transactional
#define xstrAddSubT xstrAddSub
#define unknown -1
/**
* xstrEq3 - test if two strings are equal
* @x: pointer to xstring
* @y: pointer to xstring
*
* Return true (1) if the strings held by x and y match and no truncation has occurred.
* Return unknown (-1) if either is flagged as truncated.
* Return false (0) otherwise.
*
* Return: -1, 0, or 1
*/
int xstrEq3(xstring *x, xstring *y);
/**
* xstrEq - test if two strings are equal
* @x: pointer to xstring
* @y: pointer to xstring
*
* Same as xstrEq3, but return false (0) for unknown (-1).
*
* Return: 0 or 1
*/
#define xstrEq(x, y) (xstrEq3((x), (y)) == 1)
/**
* xstrContains3 - test if first string contains second
* @x: pointer to xstring
* @y: pointer to xstring
* @where: -1 (ends), 0 (anywhere), 1 (begins)
*
* If neither x nor y are truncated, returns true (1) or false (0).
* If y is truncated, return unknown (-1).
* If x is truncated, return true (1) if known portion of x contains y, unknown (-1) otherwise.
*
* Return: -1, 0, or 1
*/
int xstrContains3(xstring *x, xstring *y, int where);
/**
* xstrContains3 - test if first string contains second
* @x: pointer to xstring
* @y: pointer to xstring