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 * String utility functions with dynamic memory management.
14 */
15
16 /*
17 * Copyright 2019 Joyent, Inc.
18 */
19
20 #include <stdlib.h>
21 #include <err.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26 #include <sys/debug.h>
27
28 #include "libcustr.h"
29
30 typedef enum {
31 CUSTR_FIXEDBUF = 0x01
32 } custr_flags_t;
33
34 struct custr {
35 size_t cus_strlen;
36 size_t cus_datalen;
37 char *cus_data;
38 custr_flags_t cus_flags;
39 };
40
41 #define STRING_CHUNK_SIZE 64
42
43 void
custr_reset(custr_t * cus)44 custr_reset(custr_t *cus)
45 {
46 if (cus->cus_data == NULL)
47 return;
48
49 cus->cus_strlen = 0;
50 cus->cus_data[0] = '\0';
51 }
52
53 size_t
custr_len(custr_t * cus)54 custr_len(custr_t *cus)
55 {
56 return (cus->cus_strlen);
57 }
58
59 const char *
custr_cstr(custr_t * cus)60 custr_cstr(custr_t *cus)
61 {
62 if (cus->cus_data == NULL) {
63 VERIFY(cus->cus_strlen == 0);
64 VERIFY(cus->cus_datalen == 0);
65
66 /*
67 * This function should never return NULL. If no buffer has
68 * been allocated, return a pointer to a zero-length string.
69 */
70 return ("");
71 }
72 return (cus->cus_data);
73 }
74
75 int
custr_append_vprintf(custr_t * cus,const char * fmt,va_list ap)76 custr_append_vprintf(custr_t *cus, const char *fmt, va_list ap)
77 {
78 int len = vsnprintf(NULL, 0, fmt, ap);
79 size_t chunksz = STRING_CHUNK_SIZE;
80
81 if (len < 0) {
82 return (len);
83 }
84
85 while (chunksz < len) {
86 chunksz *= 2;
87 }
88
89 if (len + cus->cus_strlen + 1 >= cus->cus_datalen) {
90 char *new_data;
91 size_t new_datalen = cus->cus_datalen + chunksz;
92
93 if (cus->cus_flags & CUSTR_FIXEDBUF) {
94 errno = EOVERFLOW;
95 return (-1);
96 }
97
98 /*
99 * Allocate replacement memory:
100 */
101 if ((new_data = malloc(new_datalen)) == NULL) {
102 return (-1);
103 }
104
105 /*
106 * Copy existing data into replacement memory and free
107 * the old memory.
108 */
109 if (cus->cus_data != NULL) {
110 (void) memcpy(new_data, cus->cus_data,
111 cus->cus_strlen + 1);
112 free(cus->cus_data);
113 }
114
115 /*
116 * Swap in the replacement buffer:
117 */
118 cus->cus_data = new_data;
119 cus->cus_datalen = new_datalen;
120 }
121 /*
122 * Append new string to existing string:
123 */
124 if ((len = vsnprintf(cus->cus_data + cus->cus_strlen,
125 cus->cus_datalen - cus->cus_strlen, fmt, ap)) < 0) {
126 return (len);
127 }
128 cus->cus_strlen += len;
129
130 return (0);
131 }
132
133 int
custr_appendc(custr_t * cus,char newc)134 custr_appendc(custr_t *cus, char newc)
135 {
136 return (custr_append_printf(cus, "%c", newc));
137 }
138
139 int
custr_append_printf(custr_t * cus,const char * fmt,...)140 custr_append_printf(custr_t *cus, const char *fmt, ...)
141 {
142 va_list ap;
143 int ret;
144
145 va_start(ap, fmt);
146 ret = custr_append_vprintf(cus, fmt, ap);
147 va_end(ap);
148
149 return (ret);
150 }
151
152 int
custr_append(custr_t * cus,const char * name)153 custr_append(custr_t *cus, const char *name)
154 {
155 return (custr_append_printf(cus, "%s", name));
156 }
157
158 int
custr_alloc(custr_t ** cus)159 custr_alloc(custr_t **cus)
160 {
161 custr_t *t;
162
163 if ((t = calloc(1, sizeof (*t))) == NULL) {
164 *cus = NULL;
165 return (-1);
166 }
167
168 *cus = t;
169 return (0);
170 }
171
172 int
custr_alloc_buf(custr_t ** cus,void * buf,size_t buflen)173 custr_alloc_buf(custr_t **cus, void *buf, size_t buflen)
174 {
175 int ret;
176
177 if (buflen == 0 || buf == NULL) {
178 errno = EINVAL;
179 return (-1);
180 }
181
182 if ((ret = custr_alloc(cus)) != 0)
183 return (ret);
184
185 (*cus)->cus_data = buf;
186 (*cus)->cus_datalen = buflen;
187 (*cus)->cus_strlen = 0;
188 (*cus)->cus_flags = CUSTR_FIXEDBUF;
189 (*cus)->cus_data[0] = '\0';
190
191 return (0);
192 }
193
194 void
custr_free(custr_t * cus)195 custr_free(custr_t *cus)
196 {
197 if (cus == NULL)
198 return;
199
200 if ((cus->cus_flags & CUSTR_FIXEDBUF) == 0)
201 free(cus->cus_data);
202 free(cus);
203 }
204