1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2017 Jason King
14  */
15 #include <sys/debug.h>
16 #include <sys/sysmacros.h>
17 #include <string.h>
18 #include "str.h"
19 #include "demangle_int.h"
20 
21 #define	STR_CHUNK_SZ	(64U)
22 
23 /* are we storing a reference vs. a dynamically allocated copy? */
24 #define	IS_REF(s) ((s)->str_s != NULL && (s)->str_size == 0)
25 
26 /*
27  * Dynamically resizeable strings, with lazy allocation when initialized
28  * with a constant string value
29  *
30  * NOTE: these are not necessairly 0-terminated
31  *
32  * Additionally, these can store references instead of copies of strings
33  * (as indicated by the IS_REF() macro.  However mutation may cause a
34  * string to convert from a refence to a dynamically allocated copy.
35  */
36 
37 void
38 str_init(str_t *restrict s, sysdem_ops_t *restrict ops)
39 {
40 	(void) memset(s, 0, sizeof (*s));
41 	s->str_ops = (ops != NULL) ? ops : sysdem_ops_default;
42 }
43 
44 void
45 str_fini(str_t *s)
46 {
47 	if (s == NULL)
48 		return;
49 	if (!IS_REF(s))
50 		xfree(s->str_ops, s->str_s, s->str_size);
51 	(void) memset(s, 0, sizeof (*s));
52 }
53 
54 size_t
55 str_length(const str_t *s)
56 {
57 	return (s->str_len);
58 }
59 
60 /*
61  * store as a reference instead of a copy
62  * if len == 0, means store entire copy of 0 terminated string
63  */
64 void
65 str_set(str_t *s, const char *cstr, size_t len)
66 {
67 	sysdem_ops_t *ops = s->str_ops;
68 
69 	str_fini(s);
70 	s->str_ops = ops;
71 	s->str_s = (char *)cstr;
72 	s->str_len = (len == 0 && cstr != NULL) ? strlen(cstr) : len;
73 }
74 
75 boolean_t
76 str_copy(const str_t *src, str_t *dest)
77 {
78 	str_fini(dest);
79 	str_init(dest, src->str_ops);
80 
81 	if (src->str_len == 0)
82 		return (B_TRUE);
83 
84 	size_t len = roundup(src->str_len, STR_CHUNK_SZ);
85 	dest->str_s = zalloc(src->str_ops, len);
86 	if (dest->str_s == NULL)
87 		return (B_FALSE);
88 
89 	(void) memcpy(dest->str_s, src->str_s, src->str_len);
90 	dest->str_len = src->str_len;
91 	dest->str_size = len;
92 
93 	return (B_TRUE);
94 }
95 
96 /*
97  * ensure s has at least amt bytes free, resizing if necessary
98  */
99 static boolean_t
100 str_reserve(str_t *s, size_t amt)
101 {
102 	size_t newlen = s->str_len + amt;
103 
104 	/* overflow check */
105 	if (newlen < s->str_len || newlen < amt)
106 		return (B_FALSE);
107 
108 	if ((amt > 0) && (s->str_len + amt <= s->str_size))
109 		return (B_TRUE);
110 
111 	size_t newsize = roundup(newlen, STR_CHUNK_SZ);
112 	void *temp;
113 
114 	if (IS_REF(s)) {
115 		temp = zalloc(s->str_ops, newsize);
116 		if (temp == NULL)
117 			return (B_FALSE);
118 
119 		(void) memcpy(temp, s->str_s, s->str_len);
120 	} else {
121 		temp = xrealloc(s->str_ops, s->str_s, s->str_size, newsize);
122 		if (temp == NULL)
123 			return (B_FALSE);
124 	}
125 
126 	s->str_s = temp;
127 	s->str_size = newsize;
128 
129 	return (B_TRUE);
130 }
131 
132 /* append to s, cstrlen == 0 means entire length of string */
133 boolean_t
134 str_append(str_t *s, const char *cstr, size_t cstrlen)
135 {
136 	if (cstr != NULL && cstrlen == 0)
137 		cstrlen = strlen(cstr);
138 
139 	const str_t src = {
140 		.str_s = (char *)cstr,
141 		.str_len = cstrlen,
142 		.str_ops = s->str_ops
143 	};
144 
145 	return (str_append_str(s, &src));
146 }
147 
148 boolean_t
149 str_append_str(str_t *dest, const str_t *src)
150 {
151 	/* empty string is a noop */
152 	if (src->str_s == NULL || src->str_len == 0)
153 		return (B_TRUE);
154 
155 	/* if src is a reference, we can just copy that */
156 	if (dest->str_s == NULL && IS_REF(src)) {
157 		*dest = *src;
158 		return (B_TRUE);
159 	}
160 
161 	if (!str_reserve(dest, src->str_len))
162 		return (B_FALSE);
163 
164 	(void) memcpy(dest->str_s + dest->str_len, src->str_s, src->str_len);
165 	dest->str_len += src->str_len;
166 	return (B_TRUE);
167 }
168 
169 boolean_t
170 str_append_c(str_t *s, char c)
171 {
172 	if (!str_reserve(s, 1))
173 		return (B_FALSE);
174 
175 	s->str_s[s->str_len++] = c;
176 	return (B_TRUE);
177 }
178 
179 boolean_t
180 str_insert(str_t *s, size_t idx, const char *cstr, size_t cstrlen)
181 {
182 	if (cstr == NULL)
183 		return (B_TRUE);
184 
185 	if (cstrlen == 0)
186 		cstrlen = strlen(cstr);
187 
188 	str_t src = {
189 		.str_s = (char *)cstr,
190 		.str_len = cstrlen,
191 		.str_ops = s->str_ops,
192 		.str_size = 0
193 	};
194 
195 	return (str_insert_str(s, idx, &src));
196 }
197 
198 boolean_t
199 str_insert_str(str_t *dest, size_t idx, const str_t *src)
200 {
201 	ASSERT3U(idx, <=, dest->str_len);
202 
203 	if (idx == dest->str_len)
204 		return (str_append_str(dest, src));
205 
206 	if (idx == 0 && dest->str_s == NULL && IS_REF(src)) {
207 		sysdem_ops_t *ops = dest->str_ops;
208 		*dest = *src;
209 		dest->str_ops = ops;
210 		return (B_TRUE);
211 	}
212 
213 	if (!str_reserve(dest, src->str_len))
214 		return (B_FALSE);
215 
216 	/*
217 	 * Shift the contents of dest over at the insertion point.  Since
218 	 * src and dest ranges will overlap, and unlike some programmers,
219 	 * *I* can read man pages - memmove() is the appropriate function
220 	 * to this.
221 	 */
222 	(void) memmove(dest->str_s + idx + src->str_len, dest->str_s + idx,
223 	    dest->str_len - idx);
224 
225 	/*
226 	 * However the content to insert does not overlap with the destination
227 	 * so memcpy() is fine here.
228 	 */
229 	(void) memcpy(dest->str_s + idx, src->str_s, src->str_len);
230 	dest->str_len += src->str_len;
231 
232 	return (B_TRUE);
233 }
234 
235 boolean_t
236 str_erase(str_t *s, size_t pos, size_t len)
237 {
238 	ASSERT3U(pos, <, s->str_len);
239 	ASSERT3U(pos + len, <=, s->str_len);
240 
241 	if (IS_REF(s)) {
242 		if (!str_reserve(s, 0))
243 			return (B_FALSE);
244 	}
245 
246 	(void) memmove(s->str_s + pos, s->str_s + pos + len, s->str_len - len);
247 	s->str_len -= len;
248 	return (B_TRUE);
249 }
250 
251 str_pair_t *
252 str_pair_init(str_pair_t *sp, sysdem_ops_t *ops)
253 {
254 	(void) memset(sp, 0, sizeof (*sp));
255 	str_init(&sp->strp_l, ops);
256 	str_init(&sp->strp_r, ops);
257 	return (sp);
258 }
259 
260 void
261 str_pair_fini(str_pair_t *sp)
262 {
263 	str_fini(&sp->strp_l);
264 	str_fini(&sp->strp_r);
265 }
266 
267 /* combine left and right parts and put result into left part */
268 boolean_t
269 str_pair_merge(str_pair_t *sp)
270 {
271 	/* if right side is empty, don't need to do anything */
272 	if (str_length(&sp->strp_r) == 0)
273 		return (B_TRUE);
274 
275 	/* if left side is empty, just move right to left */
276 	if (str_length(&sp->strp_l) == 0) {
277 		str_fini(&sp->strp_l);
278 		sp->strp_l = sp->strp_r;
279 		sp->strp_r.str_s = NULL;
280 		sp->strp_r.str_len = sp->strp_r.str_size = 0;
281 		return (B_TRUE);
282 	}
283 
284 	if (!str_append_str(&sp->strp_l, &sp->strp_r))
285 		return (B_FALSE);
286 
287 	str_fini(&sp->strp_r);
288 	str_init(&sp->strp_r, sp->strp_l.str_ops);
289 	return (B_TRUE);
290 }
291 
292 boolean_t
293 str_pair_copy(const str_pair_t *src, str_pair_t *dest)
294 {
295 	boolean_t ok = B_TRUE;
296 
297 	ok &= str_copy(&src->strp_l, &dest->strp_l);
298 	ok &= str_copy(&src->strp_r, &dest->strp_r);
299 
300 	if (!ok) {
301 		str_fini(&dest->strp_l);
302 		str_fini(&dest->strp_r);
303 		return (B_FALSE);
304 	}
305 
306 	return (B_TRUE);
307 }
308 
309 size_t
310 str_pair_len(const str_pair_t *sp)
311 {
312 	return (str_length(&sp->strp_l) + str_length(&sp->strp_r));
313 }
314