| /* |
| * This file and its contents are supplied under the terms of the |
| * Common Development and Distribution License ("CDDL"), version 1.0. |
| * You may only use this file in accordance with the terms of version |
| * 1.0 of the CDDL. |
| * |
| * A full copy of the text of the CDDL should have accompanied this |
| * source. A copy of the CDDL is also available via the Internet at |
| * http://www.illumos.org/license/CDDL. |
| */ |
| |
| /* |
| * Copyright 2017 Jason King |
| */ |
| #include <sys/debug.h> |
| #include <sys/sysmacros.h> |
| #include <string.h> |
| #include "str.h" |
| #include "demangle_int.h" |
| |
| #define STR_CHUNK_SZ (64U) |
| |
| /* are we storing a reference vs. a dynamically allocated copy? */ |
| #define IS_REF(s) ((s)->str_s != NULL && (s)->str_size == 0) |
| |
| /* |
| * Dynamically resizeable strings, with lazy allocation when initialized |
| * with a constant string value |
| * |
| * NOTE: these are not necessairly 0-terminated |
| * |
| * Additionally, these can store references instead of copies of strings |
| * (as indicated by the IS_REF() macro. However mutation may cause a |
| * string to convert from a refence to a dynamically allocated copy. |
| */ |
| |
| void |
| str_init(str_t *restrict s, sysdem_ops_t *restrict ops) |
| { |
| (void) memset(s, 0, sizeof (*s)); |
| s->str_ops = (ops != NULL) ? ops : sysdem_ops_default; |
| } |
| |
| void |
| str_fini(str_t *s) |
| { |
| if (s == NULL) |
| return; |
| if (!IS_REF(s)) |
| xfree(s->str_ops, s->str_s, s->str_size); |
| (void) memset(s, 0, sizeof (*s)); |
| } |
| |
| size_t |
| str_length(const str_t *s) |
| { |
| return (s->str_len); |
| } |
| |
| /* |
| * store as a reference instead of a copy |
| * if len == 0, means store entire copy of 0 terminated string |
| */ |
| void |
| str_set(str_t *s, const char *cstr, size_t len) |
| { |
| sysdem_ops_t *ops = s->str_ops; |
| |
| str_fini(s); |
| s->str_ops = ops; |
| s->str_s = (char *)cstr; |
| s->str_len = (len == 0 && cstr != NULL) ? strlen(cstr) : len; |
| } |
| |
| boolean_t |
| str_copy(const str_t *src, str_t *dest) |
| { |
| str_fini(dest); |
| str_init(dest, src->str_ops); |
| |
| if (src->str_len == 0) |
| return (B_TRUE); |
| |
| size_t len = roundup(src->str_len, STR_CHUNK_SZ); |
| dest->str_s = zalloc(src->str_ops, len); |
| if (dest->str_s == NULL) |
| return (B_FALSE); |
| |
| (void) memcpy(dest->str_s, src->str_s, src->str_len); |
| dest->str_len = src->str_len; |
| dest->str_size = len; |
| |
| return (B_TRUE); |
| } |
| |
| /* |
| * ensure s has at least amt bytes free, resizing if necessary |
| */ |
| static boolean_t |
| str_reserve(str_t *s, size_t amt) |
| { |
| size_t newlen = s->str_len + amt; |
| |
| /* overflow check */ |
| if (newlen < s->str_len || newlen < amt) |
| return (B_FALSE); |
| |
| if ((amt > 0) && (s->str_len + amt <= s->str_size)) |
| return (B_TRUE); |
| |
| size_t newsize = roundup(newlen, STR_CHUNK_SZ); |
| void *temp; |
| |
| if (IS_REF(s)) { |
| temp = zalloc(s->str_ops, newsize); |
| if (temp == NULL) |
| return (B_FALSE); |
| |
| (void) memcpy(temp, s->str_s, s->str_len); |
| } else { |
| temp = xrealloc(s->str_ops, s->str_s, s->str_size, newsize); |
| if (temp == NULL) |
| return (B_FALSE); |
| } |
| |
| s->str_s = temp; |
| s->str_size = newsize; |
| |
| return (B_TRUE); |
| } |
| |
| /* append to s, cstrlen == 0 means entire length of string */ |
| boolean_t |
| str_append(str_t *s, const char *cstr, size_t cstrlen) |
| { |
| if (cstr != NULL && cstrlen == 0) |
| cstrlen = strlen(cstr); |
| |
| const str_t src = { |
| .str_s = (char *)cstr, |
| .str_len = cstrlen, |
| .str_ops = s->str_ops |
| }; |
| |
| return (str_append_str(s, &src)); |
| } |
| |
| boolean_t |
| str_append_str(str_t *dest, const str_t *src) |
| { |
| /* empty string is a noop */ |
| if (src->str_s == NULL || src->str_len == 0) |
| return (B_TRUE); |
| |
| /* if src is a reference, we can just copy that */ |
| if (dest->str_s == NULL && IS_REF(src)) { |
| *dest = *src; |
| return (B_TRUE); |
| } |
| |
| if (!str_reserve(dest, src->str_len)) |
| return (B_FALSE); |
| |
| (void) memcpy(dest->str_s + dest->str_len, src->str_s, src->str_len); |
| dest->str_len += src->str_len; |
| return (B_TRUE); |
| } |
| |
| boolean_t |
| str_append_c(str_t *s, char c) |
| { |
| if (!str_reserve(s, 1)) |
| return (B_FALSE); |
| |
| s->str_s[s->str_len++] = c; |
| return (B_TRUE); |
| } |
| |
| boolean_t |
| str_insert(str_t *s, size_t idx, const char *cstr, size_t cstrlen) |
| { |
| if (cstr == NULL) |
| return (B_TRUE); |
| |
| if (cstrlen == 0) |
| cstrlen = strlen(cstr); |
| |
| str_t src = { |
| .str_s = (char *)cstr, |
| .str_len = cstrlen, |
| .str_ops = s->str_ops, |
| .str_size = 0 |
| }; |
| |
| return (str_insert_str(s, idx, &src)); |
| } |
| |
| boolean_t |
| str_insert_str(str_t *dest, size_t idx, const str_t *src) |
| { |
| ASSERT3U(idx, <=, dest->str_len); |
| |
| if (idx == dest->str_len) |
| return (str_append_str(dest, src)); |
| |
| if (idx == 0 && dest->str_s == NULL && IS_REF(src)) { |
| sysdem_ops_t *ops = dest->str_ops; |
| *dest = *src; |
| dest->str_ops = ops; |
| return (B_TRUE); |
| } |
| |
| if (!str_reserve(dest, src->str_len)) |
| return (B_FALSE); |
| |
| /* |
| * Shift the contents of dest over at the insertion point. Since |
| * src and dest ranges will overlap, and unlike some programmers, |
| * *I* can read man pages - memmove() is the appropriate function |
| * to this. |
| */ |
| (void) memmove(dest->str_s + idx + src->str_len, dest->str_s + idx, |
| dest->str_len - idx); |
| |
| /* |
| * However the content to insert does not overlap with the destination |
| * so memcpy() is fine here. |
| */ |
| (void) memcpy(dest->str_s + idx, src->str_s, src->str_len); |
| dest->str_len += src->str_len; |
| |
| return (B_TRUE); |
| } |
| |
| boolean_t |
| str_erase(str_t *s, size_t pos, size_t len) |
| { |
| ASSERT3U(pos, <, s->str_len); |
| ASSERT3U(pos + len, <=, s->str_len); |
| |
| if (IS_REF(s)) { |
| if (!str_reserve(s, 0)) |
| return (B_FALSE); |
| } |
| |
| (void) memmove(s->str_s + pos, s->str_s + pos + len, s->str_len - len); |
| s->str_len -= len; |
| return (B_TRUE); |
| } |
| |
| str_pair_t * |
| str_pair_init(str_pair_t *sp, sysdem_ops_t *ops) |
| { |
| (void) memset(sp, 0, sizeof (*sp)); |
| str_init(&sp->strp_l, ops); |
| str_init(&sp->strp_r, ops); |
| return (sp); |
| } |
| |
| void |
| str_pair_fini(str_pair_t *sp) |
| { |
| str_fini(&sp->strp_l); |
| str_fini(&sp->strp_r); |
| } |
| |
| /* combine left and right parts and put result into left part */ |
| boolean_t |
| str_pair_merge(str_pair_t *sp) |
| { |
| /* if right side is empty, don't need to do anything */ |
| if (str_length(&sp->strp_r) == 0) |
| return (B_TRUE); |
| |
| /* if left side is empty, just move right to left */ |
| if (str_length(&sp->strp_l) == 0) { |
| str_fini(&sp->strp_l); |
| sp->strp_l = sp->strp_r; |
| sp->strp_r.str_s = NULL; |
| sp->strp_r.str_len = sp->strp_r.str_size = 0; |
| return (B_TRUE); |
| } |
| |
| if (!str_append_str(&sp->strp_l, &sp->strp_r)) |
| return (B_FALSE); |
| |
| str_fini(&sp->strp_r); |
| str_init(&sp->strp_r, sp->strp_l.str_ops); |
| return (B_TRUE); |
| } |
| |
| boolean_t |
| str_pair_copy(const str_pair_t *src, str_pair_t *dest) |
| { |
| boolean_t ok = B_TRUE; |
| |
| ok &= str_copy(&src->strp_l, &dest->strp_l); |
| ok &= str_copy(&src->strp_r, &dest->strp_r); |
| |
| if (!ok) { |
| str_fini(&dest->strp_l); |
| str_fini(&dest->strp_r); |
| return (B_FALSE); |
| } |
| |
| return (B_TRUE); |
| } |
| |
| size_t |
| str_pair_len(const str_pair_t *sp) |
| { |
| return (str_length(&sp->strp_l) + str_length(&sp->strp_r)); |
| } |