1 /*
2   ***** BEGIN LICENSE BLOCK *****
3 
4   Copyright (C) 2001-2020 Olof Hagsand
5 
6   This file is part of CLIgen.
7 
8   Licensed under the Apache License, Version 2.0 (the "License");
9   you may not use this file except in compliance with the License.
10   You may obtain a copy of the License at
11 
12     http://www.apache.org/licenses/LICENSE-2.0
13 
14   Unless required by applicable law or agreed to in writing, software
15   distributed under the License is distributed on an "AS IS" BASIS,
16   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   See the License for the specific language governing permissions and
18   limitations under the License.
19 
20   Alternatively, the contents of this file may be used under the terms of
21   the GNU General Public License Version 2 or later (the "GPL"),
22   in which case the provisions of the GPL are applicable instead
23   of those above. If you wish to allow use of your version of this file only
24   under the terms of the GPL, and not to allow others to
25   use your version of this file under the terms of Apache License version 2, indicate
26   your decision by deleting the provisions above and replace them with the
27   notice and other provisions required by the GPL. If you do not delete
28   the provisions above, a recipient may use your version of this file under
29   the terms of any one of the Apache License version 2 or the GPL.
30 
31   ***** END LICENSE BLOCK *****
32 
33  * CLIgen dynamic buffers
34  * @code
35  *   cbuf *cb;
36  *   if ((cb = cbuf_new()) == NULL)
37  *      err();
38  *   cprintf(cb, "content %d", 42);
39  *   if (write(f, cbuf_get(cb), cbuf_len(cb)) < 0)
40  *      err();
41  *   cbuf_free(cb);
42  * @endcode
43  */
44 
45 /*
46  * Constants
47  */
48 /* Initial alloc mem length of a cbuf, then grows exponentially, with 2*, 4*, etc
49  * 1K could be a bit much for large syntaxes and small entries
50  * @see cbuf_alloc_set
51  */
52 #define CBUFLEN_START 1024
53 #define CBUFLEN_THRESHOLD 65536
54 
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <stdint.h>
58 #include <string.h>
59 #include <errno.h>
60 
61 #include "cligen_buf.h"              /* External API */
62 #include "cligen_buf_internal.h"
63 
64 /*
65  * Variables
66  */
67 /* This is how large an initial cbuf is after calling cbuf_new. Note that the cbuf
68  * may grow after calls to cprintf or cbuf_alloc
69  */
70 static size_t cbuflen_start     = CBUFLEN_START;
71 
72 /* Threshold to where a cbuf grows exponentially, thereafter it grows linearly
73  * If 0 continue with exponential growth
74  */
75 static size_t cbuflen_threshold = CBUFLEN_THRESHOLD;
76 
77 /*! Get global cbuf initial memory allocation size
78  * This is how large a cbuf is after calling cbuf_new. Note that the cbuf
79  * may grow after calls to cprintf or cbuf_alloc
80  * @param[out]  default   Initial default cbuf size
81  * @param[out]  threshold Threshold where cbuf grows linearly instead of exponentially
82  */
83 int
cbuf_alloc_get(size_t * start,size_t * threshold)84 cbuf_alloc_get(size_t *start,
85 	       size_t *threshold)
86 {
87     *start = cbuflen_start;
88     *threshold = cbuflen_threshold;
89     return 0;
90 }
91 
92 /*! Set global cbuf initial memory allocation size
93  * This is how large a cbuf is after calling cbuf_new. Note that the cbuf
94  * may grow after calls to cprintf or cbuf_alloc
95  * If 0 continue with exponential growth
96  */
97 int
cbuf_alloc_set(size_t start,size_t threshold)98 cbuf_alloc_set(size_t start,
99 	       size_t threshold)
100 {
101     cbuflen_start = start;
102     cbuflen_threshold = threshold;
103     return 0;
104 }
105 
106 /*! Allocate cligen buffer. Returned handle can be used in sprintf calls
107  * which dynamically print a string.
108  * The handle should be freed by cbuf_free()
109  * @param[in]   How much buffer space for initial allocation
110  * @retval cb   The allocated objecyt handle on success.
111  * @retval NULL Error.
112  * @see cbuf_new  with auto buffer allocation
113  */
114 cbuf *
cbuf_new_alloc(size_t sz)115 cbuf_new_alloc(size_t sz)
116 {
117     cbuf *cb;
118 
119     if ((cb = (cbuf*)malloc(sizeof(*cb))) == NULL)
120 	return NULL;
121     memset(cb, 0, sizeof(*cb));
122     cb->cb_buflen = sz;
123     if ((cb->cb_buffer = malloc(cb->cb_buflen)) == NULL)
124 	return NULL;
125     memset(cb->cb_buffer, 0, cb->cb_buflen);
126     cb->cb_strlen = 0;
127     return cb;
128 }
129 
130 /*! Allocate cligen buffer with auto buffer allocation. Returned handle can be used in sprintf calls
131  * which dynamically print a string.
132  * The handle should be freed by cbuf_free()
133  * @retval cb   The allocated objecyt handle on success.
134  * @retval NULL Error.
135  * @see cbuf_new_alloc  with explicit buffer allocation
136  */
137 cbuf *
cbuf_new(void)138 cbuf_new(void)
139 {
140     return cbuf_new_alloc(cbuflen_start);
141 }
142 
143 /*! Free cligen buffer previously allocated with cbuf_new
144  * @param[in]   cb  Cligen buffer
145  */
146 void
cbuf_free(cbuf * cb)147 cbuf_free(cbuf *cb)
148 {
149     if (cb) {
150 	if (cb->cb_buffer)
151 	    free(cb->cb_buffer);
152 	free(cb);
153     }
154 }
155 
156 /*! Return actual byte buffer of cligen buffer
157  * @param[in]   cb  Cligen buffer
158  */
159 char*
cbuf_get(cbuf * cb)160 cbuf_get(cbuf *cb)
161 {
162     return cb->cb_buffer;
163 }
164 
165 /*! Return length of string in cligen buffer (not buffer length itself)
166  * @param[in]   cb  Cligen buffer
167  * @see cbuf_buflen
168  */
169 int
cbuf_len(cbuf * cb)170 cbuf_len(cbuf *cb)
171 {
172     return cb->cb_strlen;
173 }
174 
175 /*! Return length of buffer itself, ie allocated bytes
176  * @param[in]   cb  Cligen buffer
177  * @see cbuf_len
178  */
179 int
cbuf_buflen(cbuf * cb)180 cbuf_buflen(cbuf *cb)
181 {
182     return cb->cb_buflen;
183 }
184 
185 /*! Reset a cligen buffer. That is, restart it from scratch.
186  * @param[in]   cb  Cligen buffer
187  */
188 void
cbuf_reset(cbuf * cb)189 cbuf_reset(cbuf *cb)
190 {
191     cb->cb_strlen    = 0;
192     cb->cb_buffer[0] = '\0';
193 }
194 
195 /*! Internal buffer reallocator, Ensure buffer is large enough
196  * use quadratic expansion (2* size)
197  * @param[in] cb   CLIgen buffer
198  * @param[in] len  Extra length added
199  */
200 static int
cbuf_realloc(cbuf * cb,size_t sz)201 cbuf_realloc(cbuf  *cb,
202 	     size_t sz)
203 {
204     int retval = -1;
205     int diff;
206 
207     diff = cb->cb_buflen - (cb->cb_strlen + sz + 1);
208     if (diff <= 0){
209 	while (diff <= 0){
210 	    if (cbuflen_threshold == 0 || cb->cb_buflen < cbuflen_threshold)
211 		cb->cb_buflen *= 2; /* Double the space - exponential */
212 	    else
213 		cb->cb_buflen += cbuflen_threshold; /* Add - linear growth*/
214 	    diff = cb->cb_buflen - (cb->cb_strlen + sz + 1);
215 	}
216 	if ((cb->cb_buffer = realloc(cb->cb_buffer, cb->cb_buflen)) == NULL)
217 	    goto done;
218     }
219     retval = 0;
220  done:
221     return retval;
222 }
223 
224 /*! Append a cligen buf by printf like semantics
225  *
226  * @param [in]  cb      cligen buffer allocated by cbuf_new(), may be reallocated.
227  * @param [in]  format  arguments uses printf syntax.
228  * @retval      See printf
229  * @see cbuf_append_str for the optimized special case of string append
230  * @note cprintf assume null-terminated string as %s, use cbuf_memcp for a raw interface
231  */
232 int
cprintf(cbuf * cb,const char * format,...)233 cprintf(cbuf       *cb,
234 	const char *format, ...)
235 {
236     int     retval = -1;
237     va_list ap;
238     int     len;
239     int     ret;
240 
241     if (cb == NULL)
242 	goto ok;
243     va_start(ap, format); /* dryrun */
244     if ((len = vsnprintf(NULL, 0, format, ap)) < 0) /* dryrun, just get len */
245 	goto done;
246     va_end(ap);
247     /* Ensure buffer is large enough */
248     if (cbuf_realloc(cb, len) < 0)
249     	goto done;
250     va_start(ap, format); /* real */
251     if ((ret = vsnprintf(cb->cb_buffer+cb->cb_strlen, /* str */
252 			 cb->cb_buflen-cb->cb_strlen, /* size */
253 			 format, ap)) < 0)
254 	goto done;
255     va_end(ap);
256     cb->cb_strlen += ret;
257  ok:
258     retval = 0;
259  done:
260     return retval;
261 }
262 
263 /*! Append a string to a cbuf
264   *
265   * An optimized special case of cprintf
266   * @param [in]  cb  cligen buffer allocated by cbuf_new(), may be reallocated.
267   * @param [in]  str string
268   * @retval 0    OK
269   * @retval -1   Error
270   * @see cprintf for the generic function
271   */
272 int
cbuf_append_str(cbuf * cb,char * str)273 cbuf_append_str(cbuf       *cb,
274 		char       *str)
275 {
276     size_t  len0;
277     size_t  len;
278 
279     if (str == NULL){
280 	errno = EINVAL;
281 	return -1;
282     }
283     len0 = strlen(str);
284     len = cb->cb_strlen + len0;
285     /* Ensure buffer is large enough */
286     if (cbuf_realloc(cb, len) < 0)
287 	return -1;
288     strncpy(cb->cb_buffer+cb->cb_strlen, str, len0+1);
289     cb->cb_strlen = len;
290     return 0;
291 }
292 
293 /*! Append a character to a cbuf
294   *
295   * @param [in]  cb  cligen buffer allocated by cbuf_new(), may be reallocated.
296   * @param [in]  c   character to append
297   * @retval 0    OK
298   * @retval -1   Error
299   * @see cbuf_append_str, use that function instead
300   */
301 int
cbuf_append(cbuf * cb,int c)302 cbuf_append(cbuf       *cb,
303             int        c)
304 {
305     char str[2] = {0,};
306 
307     str[0] = c;
308     return cbuf_append_str(cb, str);
309 }
310 
311 /*! Append a raw memory buffer and add null-termion
312   *
313   * A raw buffer handler to cprintf
314   * @param [in]  cb  cligen buffer allocated by cbuf_new(), may be reallocated.
315   * @param [in]  src Source buffer
316   * @param [in]  n   Number of bytes to copy, add a null
317   * @retval 0    OK
318   * @retval -1   Error
319   * @see cprintf for the generic function
320   */
321 int
cbuf_append_buf(cbuf * cb,void * src,size_t n)322 cbuf_append_buf(cbuf  *cb,
323 		void  *src,
324 		size_t n)
325 {
326     size_t  len0;
327     size_t  len;
328 
329     if (src == NULL){
330 	errno = EINVAL;
331 	return -1;
332     }
333     len0 = cb->cb_strlen;
334     len = cb->cb_strlen + n + 1;
335     /* Ensure buffer is large enough */
336     if (cbuf_realloc(cb, len) < 0)
337 	return -1;
338     memcpy(cb->cb_buffer+len0, src, n);
339     cb->cb_buffer[len-1] = '\0'; /* Add a null byte */
340     cb->cb_strlen = len;
341     return 0;
342 }
343 
344