1 /*
2 * Copyright (c) 2003-2012 Hypertriton, Inc. <http://hypertriton.com/>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 /*
26 * Copyright (c) 1987 Regents of the University of California.
27 * All rights reserved.
28 *
29 * Redistribution and use in source and binary forms are permitted
30 * provided that the above copyright notice and this paragraph are
31 * duplicated in all such forms and that any documentation,
32 * advertising materials, and other materials related to such
33 * distribution and use acknowledge that the software was developed
34 * by the University of California, Berkeley. The name of the
35 * University may not be used to endorse or promote products derived
36 * from this software without specific prior written permission.
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
38 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
39 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
40 */
41 /*
42 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
43 *
44 * Permission to use, copy, modify, and distribute this software for any
45 * purpose with or without fee is hereby granted, provided that the above
46 * copyright notice and this permission notice appear in all copies.
47 *
48 * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
49 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
50 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
51 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
52 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
53 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
54 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
55 */
56
57 /*
58 * C-string related routines.
59 */
60
61 #include <agar/core/core.h>
62
63 #include <string.h>
64 #include <errno.h>
65 #include <stdio.h>
66 #include <ctype.h>
67
68 #include <agar/config/have_iconv.h>
69 #ifdef HAVE_ICONV
70 # include <agar/config/have_iconv_const.h>
71 # include <iconv.h>
72 #endif
73
74 #include "string_strcasecmp.h"
75
76 /* AG_Printf() buffers */
77 static char *agPrintBuf[AG_STRING_BUFFERS_MAX];
78 #ifdef AG_THREADS
79 static AG_ThreadKey agPrintBufKey[AG_STRING_BUFFERS_MAX];
80 #endif
81
82 /* Formatting engine extensions */
83 static AG_FmtStringExt *agFmtExtensions = NULL;
84 static Uint agFmtExtensionCount = 0;
85 #ifdef AG_THREADS
86 static AG_Mutex agFmtExtensionsLock;
87 #endif
88
89 size_t AG_DoPrintf(char *, size_t, const char *, va_list);
90
91 /*
92 * Built-in extended format specifiers.
93 */
94 static size_t
PrintU8(AG_FmtString * fs,char * dst,size_t dstSize)95 PrintU8(AG_FmtString *fs, char *dst, size_t dstSize)
96 {
97 Uint8 *val = AG_FMTSTRING_ARG(fs);
98 return StrlcpyUint(dst, (Uint)*val, dstSize);
99 }
100 static size_t
PrintS8(AG_FmtString * fs,char * dst,size_t dstSize)101 PrintS8(AG_FmtString *fs, char *dst, size_t dstSize)
102 {
103 Sint8 *val = AG_FMTSTRING_ARG(fs);
104 return StrlcpyInt(dst, (int)*val, dstSize);
105 }
106 static size_t
PrintU16(AG_FmtString * fs,char * dst,size_t dstSize)107 PrintU16(AG_FmtString *fs, char *dst, size_t dstSize)
108 {
109 Uint16 *val = AG_FMTSTRING_ARG(fs);
110 return StrlcpyUint(dst, (Uint)*val, dstSize);
111 }
112 static size_t
PrintS16(AG_FmtString * fs,char * dst,size_t dstSize)113 PrintS16(AG_FmtString *fs, char *dst, size_t dstSize)
114 {
115 Sint16 *val = AG_FMTSTRING_ARG(fs);
116 return StrlcpyInt(dst, (int)*val, dstSize);
117 }
118 static size_t
PrintU32(AG_FmtString * fs,char * dst,size_t dstSize)119 PrintU32(AG_FmtString *fs, char *dst, size_t dstSize)
120 {
121 Uint32 *val = AG_FMTSTRING_ARG(fs);
122 return StrlcpyUint(dst, (Uint)*val, dstSize);
123 }
124 static size_t
PrintS32(AG_FmtString * fs,char * dst,size_t dstSize)125 PrintS32(AG_FmtString *fs, char *dst, size_t dstSize)
126 {
127 Sint32 *val = AG_FMTSTRING_ARG(fs);
128 return StrlcpyInt(dst, (int)*val, dstSize);
129 }
130
131 #ifdef HAVE_64BIT
132 static size_t
PrintU64(AG_FmtString * fs,char * dst,size_t dstSize)133 PrintU64(AG_FmtString *fs, char *dst, size_t dstSize)
134 {
135 Uint64 *val = AG_FMTSTRING_ARG(fs);
136 return Snprintf(dst, dstSize, "%llu", (unsigned long long)*val);
137 }
138 static size_t
PrintS64(AG_FmtString * fs,char * dst,size_t dstSize)139 PrintS64(AG_FmtString *fs, char *dst, size_t dstSize)
140 {
141 Sint64 *val = AG_FMTSTRING_ARG(fs);
142 return Snprintf(dst, dstSize, "%lld", (long long)*val);
143 }
144 #endif /* HAVE_64BIT */
145
146 static size_t
PrintOBJNAME(AG_FmtString * fs,char * dst,size_t dstSize)147 PrintOBJNAME(AG_FmtString *fs, char *dst, size_t dstSize)
148 {
149 AG_Object *ob = AG_FMTSTRING_ARG(fs);
150 return Strlcpy(dst, (ob != NULL) ? ob->name : "(null)", dstSize);
151 }
152 static size_t
PrintOBJTYPE(AG_FmtString * fs,char * dst,size_t dstSize)153 PrintOBJTYPE(AG_FmtString *fs, char *dst, size_t dstSize)
154 {
155 AG_Object *ob = AG_FMTSTRING_ARG(fs);
156 return Strlcpy(dst, (ob != NULL) ? ob->cls->name : "(null)", dstSize);
157 }
158
159 /* Register a new extended format specifier. */
160 void
AG_RegisterFmtStringExt(const char * fmt,AG_FmtStringExtFn fn)161 AG_RegisterFmtStringExt(const char *fmt, AG_FmtStringExtFn fn)
162 {
163 AG_FmtStringExt *fs;
164
165 AG_MutexLock(&agFmtExtensionsLock);
166 agFmtExtensions = Realloc(agFmtExtensions,
167 (agFmtExtensionCount+1)*sizeof(AG_FmtStringExt));
168 fs = &agFmtExtensions[agFmtExtensionCount++];
169 fs->fmt = Strdup(fmt);
170 fs->fmtLen = strlen(fmt);
171 fs->fn = fn;
172 AG_MutexUnlock(&agFmtExtensionsLock);
173 }
174
175 /* Unregister an extended format specifier. */
176 void
AG_UnregisterFmtStringExt(const char * fmt)177 AG_UnregisterFmtStringExt(const char *fmt)
178 {
179 Uint i;
180
181 AG_MutexLock(&agFmtExtensionsLock);
182 for (i = 0; i < agFmtExtensionCount; i++) {
183 if (strcmp(agFmtExtensions[i].fmt, fmt) == 0)
184 break;
185 }
186 if (i < agFmtExtensionCount) {
187 free(agFmtExtensions[i].fmt);
188 if (i < agFmtExtensionCount-1) {
189 memmove(&agFmtExtensions[i], &agFmtExtensions[i+1],
190 (agFmtExtensionCount-i-1)*sizeof(AG_FmtStringExt));
191 }
192 agFmtExtensionCount--;
193 }
194 AG_MutexUnlock(&agFmtExtensionsLock);
195 }
196
197 /* Release all resources allocated by a format string. */
198 void
AG_FreeFmtString(AG_FmtString * fs)199 AG_FreeFmtString(AG_FmtString *fs)
200 {
201 Free(fs->s);
202 free(fs);
203 }
204
205 /*
206 * Implementation of AG_ProcessFmtString().
207 */
208
209 #undef FSARG
210 #define FSARG(fs,_type) (*(_type *)(fs)->p[fs->curArg++])
211
212 #ifdef HAVE_64BIT
213 static size_t
ProcessFmtString64(AG_FmtString * fs,const char * f,char * dst,size_t dstSize)214 ProcessFmtString64(AG_FmtString *fs, const char *f, char *dst, size_t dstSize)
215 {
216 switch (*f) {
217 # ifdef HAVE_LONG_DOUBLE
218 case 'f':
219 return Snprintf(dst, dstSize, "%.2Lf", FSARG(fs,long double));
220 case 'g':
221 return Snprintf(dst, dstSize, "%.2Lg", FSARG(fs,long double));
222 # endif
223 case 'd':
224 case 'i':
225 return Snprintf(dst, dstSize, "%lld", (long long)FSARG(fs,Sint64));
226 case 'o':
227 return Snprintf(dst, dstSize, "%llo", (unsigned long long)FSARG(fs,Uint64));
228 case 'u':
229 return Snprintf(dst, dstSize, "%llu", (unsigned long long)FSARG(fs,Uint64));
230 case 'x':
231 return Snprintf(dst, dstSize, "%llx", (unsigned long long)FSARG(fs,Uint64));
232 case 'X':
233 return Snprintf(dst, dstSize, "%llX", (unsigned long long)FSARG(fs,Uint64));
234 }
235 return (0);
236 }
237 #endif /* HAVE_64BIT */
238
239 /*
240 * Construct a string from the given AG_FmtString. The arguments are
241 * dereferenced, and the resulting string is written to a fixed-size
242 * buffer dst of dstSize bytes. Returns the number of characters that
243 * would have been copied were dstSize unlimited.
244 */
245 size_t
AG_ProcessFmtString(AG_FmtString * fs,char * dst,size_t dstSize)246 AG_ProcessFmtString(AG_FmtString *fs, char *dst, size_t dstSize)
247 {
248 char *pDst = &dst[0];
249 char *pEnd = &dst[dstSize-1];
250 char *f;
251 size_t rv;
252 Uint i;
253
254 fs->curArg = 0;
255
256 if (dstSize < 1) {
257 return (0);
258 }
259 *pDst = '\0';
260
261 for (f = &fs->s[0]; *f != '\0'; f++) {
262 if (f[0] != '%' || f[1] == '\0') {
263 *pDst = *f;
264 if (++pDst >= pEnd) {
265 *pEnd = '\0'; /* Truncate */
266 goto out;
267 }
268 continue;
269 }
270 rv = 0;
271 switch (f[1]) {
272 case '[':
273 for (i = 0; i < agFmtExtensionCount; i++) {
274 AG_FmtStringExt *fExt = &agFmtExtensions[i];
275
276 if (strncmp(fExt->fmt, &f[2], fExt->fmtLen) != 0) {
277 continue;
278 }
279 rv = fExt->fn(fs, pDst, (pEnd-pDst));
280 f += fExt->fmtLen + 1; /* Closing "]" */
281 break;
282 }
283 break;
284 case 'l':
285 switch (f[2]) {
286 case 'f':
287 rv = Snprintf(pDst, (pEnd-pDst), "%.2f", FSARG(fs,double));
288 f++;
289 break;
290 case 'g':
291 rv = Snprintf(pDst, (pEnd-pDst), "%g", FSARG(fs,double));
292 f++;
293 break;
294 #ifdef HAVE_64BIT
295 case 'l':
296 rv = ProcessFmtString64(fs, &f[3], pDst, (pEnd-pDst));
297 f+=2;
298 break;
299 #endif
300 }
301 break;
302 case 'd':
303 case 'i':
304 rv = StrlcpyInt(pDst, FSARG(fs,int), (pEnd-pDst));
305 break;
306 case 'u':
307 rv = StrlcpyUint(pDst, FSARG(fs,Uint), (pEnd-pDst));
308 break;
309 case 'f':
310 rv = Snprintf(pDst, (pEnd-pDst), "%.2f", FSARG(fs,float));
311 break;
312 case 'g':
313 rv = Snprintf(pDst, (pEnd-pDst), "%g", FSARG(fs,float));
314 break;
315 case 's':
316 rv = Strlcpy(pDst, &FSARG(fs,char), (pEnd-pDst));
317 break;
318 case 'o':
319 rv = Snprintf(pDst, (pEnd-pDst), "%o", FSARG(fs,Uint));
320 break;
321 case 'x':
322 rv = Snprintf(pDst, (pEnd-pDst), "%x", FSARG(fs,Uint));
323 break;
324 case 'X':
325 rv = Snprintf(pDst, (pEnd-pDst), "%X", FSARG(fs,Uint));
326 break;
327 case 'c':
328 *pDst = FSARG(fs,char);
329 rv = 1;
330 break;
331 case '%':
332 *pDst = '%';
333 rv = 1;
334 break;
335 }
336 if ((pDst += rv) > pEnd) {
337 *pEnd = '\0'; /* Truncate */
338 goto out;
339 }
340 f++;
341 }
342 out:
343 if (pDst < pEnd) {
344 *pDst = '\0';
345 } else {
346 *pEnd = '\0';
347 }
348 return (pDst - dst);
349 }
350
351 #undef FSARG
352
353 #undef CAT_SPEC
354 #define CAT_SPEC(c) \
355 if ((pSpec+2) >= pSpecEnd) { AG_FatalError("Format"); } \
356 pSpec[0] = (c); \
357 pSpec[1] = '\0'; \
358 pSpec++;
359
360 /*
361 * Build a C string from a format string (see AG_String(3) for details on
362 * the format). AG_DoPrintf() writes the formatted output to a fixed-size
363 * buffer (if the buffer is too small, the string is truncated and the
364 * function returns the number of characters that would have been copied
365 * were dstSize unlimited).
366 */
367 size_t
AG_DoPrintf(char * dst,size_t dstSize,const char * fmt,va_list ap)368 AG_DoPrintf(char *dst, size_t dstSize, const char *fmt, va_list ap)
369 {
370 char spec[32], *pSpec, *pSpecEnd = &spec[32];
371 AG_FmtString fs;
372 char *pDst = &dst[0];
373 char *pEnd = &dst[dstSize-1];
374 const char *f;
375 size_t rv;
376 Uint i;
377
378 if (dstSize < 1) {
379 return (1);
380 }
381 *pDst = '\0';
382
383 fs.s = NULL;
384
385 for (f = &fmt[0]; *f != '\0'; f++) {
386 if (f[0] != '%' || f[1] == '\0') {
387 *pDst = *f;
388 if (++pDst >= pEnd) {
389 *pEnd = '\0'; /* Truncate */
390 goto out;
391 }
392 continue;
393 }
394 spec[0] = '%';
395 spec[1] = f[1];
396 spec[2] = '\0';
397 pSpec = &spec[1];
398 next_char:
399 rv = 0;
400 switch (f[1]) {
401 case '[':
402 for (i = 0; i < agFmtExtensionCount; i++) {
403 AG_FmtStringExt *fExt = &agFmtExtensions[i];
404
405 if (strncmp(fExt->fmt, &f[2], fExt->fmtLen) != 0) {
406 continue;
407 }
408 fs.curArg = 0;
409 fs.p[0] = va_arg(ap, void *);
410 rv = fExt->fn(&fs, pDst, (pEnd-pDst));
411 f += fExt->fmtLen + 1; /* Closing "]" */
412 break;
413 }
414 break;
415 case 'd':
416 case 'i':
417 CAT_SPEC(f[1]);
418 if (pSpec == &spec[2]) { /* Optimized (%d) */
419 rv = StrlcpyInt(pDst, va_arg(ap,int), (pEnd-pDst));
420 } else {
421 rv = Snprintf(pDst, (pEnd-pDst), spec,
422 va_arg(ap,Uint));
423 }
424 break;
425 case 'u':
426 CAT_SPEC(f[1]);
427 if (pSpec == &spec[2]) { /* Optimized (%u) */
428 rv = StrlcpyUint(pDst, va_arg(ap,Uint), (pEnd-pDst));
429 } else {
430 rv = Snprintf(pDst, (pEnd-pDst), spec,
431 va_arg(ap,Uint));
432 }
433 break;
434 case 'o':
435 case 'x':
436 case 'X':
437 CAT_SPEC(f[1]);
438 rv = Snprintf(pDst, (pEnd-pDst), spec, va_arg(ap,Uint));
439 break;
440 case 'c':
441 CAT_SPEC(f[1]);
442 if (pSpec == &spec[2]) { /* Optimized (%c) */
443 if ((pDst+1) > pEnd) {
444 *pEnd = '\0'; /* Truncate */
445 goto out;
446 }
447 *pDst = (char)va_arg(ap,int);
448 rv = 1;
449 } else {
450 rv = Snprintf(pDst, (pEnd-pDst), spec,
451 (char)va_arg(ap,int));
452 }
453 break;
454 case 'f':
455 case 'g':
456 CAT_SPEC(f[1]);
457 rv = Snprintf(pDst, (pEnd-pDst), spec, va_arg(ap,double));
458 break;
459 case 's':
460 CAT_SPEC(f[1]);
461 if (pSpec == &spec[2]) { /* Optimized (%s) */
462 rv = Strlcpy(pDst, va_arg(ap,char *), (pEnd-pDst));
463 } else {
464 rv = Snprintf(pDst, (pEnd-pDst), spec,
465 va_arg(ap,char *));
466 }
467 break;
468 case 'l':
469 CAT_SPEC(f[1]);
470 switch (f[2]) {
471 case 'd':
472 case 'i':
473 CAT_SPEC(f[2]);
474 rv = Snprintf(pDst, (pEnd-pDst), spec,
475 va_arg(ap,long));
476 break;
477 case 'o':
478 case 'u':
479 case 'x':
480 case 'X':
481 CAT_SPEC(f[2]);
482 rv = Snprintf(pDst, (pEnd-pDst), spec,
483 va_arg(ap,Ulong));
484 break;
485 case 'f':
486 case 'g':
487 CAT_SPEC(f[2]);
488 rv = Snprintf(pDst, (pEnd-pDst), spec,
489 va_arg(ap,double));
490 break;
491 #ifdef HAVE_64BIT
492 case 'l':
493 CAT_SPEC(f[2]);
494 switch (f[3]) {
495 # ifdef HAVE_LONG_DOUBLE
496 case 'f':
497 case 'g':
498 CAT_SPEC(f[3]);
499 rv = Snprintf(pDst, (pEnd-pDst), spec,
500 va_arg(ap,long double));
501 break;
502 # endif
503 case 'd':
504 case 'i':
505 CAT_SPEC(f[3]);
506 rv = Snprintf(pDst, (pEnd-pDst), spec,
507 (long long)va_arg(ap,Sint64));
508 break;
509 case 'o':
510 case 'u':
511 case 'x':
512 case 'X':
513 CAT_SPEC(f[3]);
514 rv = Snprintf(pDst, (pEnd-pDst), spec,
515 (unsigned long long)va_arg(ap,Uint64));
516 break;
517 }
518 f++;
519 #endif /* HAVE_64BIT */
520 }
521 f++;
522 break;
523 case '#':
524 case '0':
525 case '-':
526 case ' ':
527 case '+':
528 case '\'':
529 case '.':
530 case '1':
531 case '2':
532 case '3':
533 case '4':
534 case '5':
535 case '6':
536 case '7':
537 case '8':
538 case '9':
539 CAT_SPEC(f[1]);
540 f++;
541 goto next_char;
542 case '%':
543 if ((pDst+1) > pEnd) {
544 *pEnd = '\0'; /* Truncate */
545 goto out;
546 }
547 *pDst = '%';
548 rv = 1;
549 break;
550 }
551 if ((pDst += rv) > pEnd) {
552 *pEnd = '\0'; /* Truncate */
553 goto out;
554 }
555 f++;
556 }
557 out:
558 if (pDst < pEnd) {
559 *pDst = '\0';
560 } else {
561 *pEnd = '\0';
562 }
563 return (pDst - dst);
564 }
565
566 #undef CAT_SPEC
567
568 /*
569 * AG_Printf() performs formatted output conversion and returns a pointer
570 * to an internally-managed buffer (the caller must never free() this buffer).
571 * The AG_PrintfN() variant allows a buffer index to be specified.
572 *
573 * In multi-threaded mode, the AG_Printf() buffers are allocated as
574 * thread-local storage. They will remain valid until the application
575 * or thread exits (or a subsequent AG_Printf() call is made, which
576 * will overwrite it).
577 */
578 char *
AG_Printf(const char * fmt,...)579 AG_Printf(const char *fmt, ...)
580 {
581 va_list ap;
582 char *dst, *dstNew;
583 size_t dstSize = AG_FMTSTRING_BUFFER_INIT, rv;
584
585 dst = Malloc(dstSize);
586 restart:
587 dst[0] = '\0';
588 va_start(ap, fmt);
589 if ((rv = AG_DoPrintf(dst, dstSize, fmt, ap)) >= dstSize) {
590 va_end(ap);
591 dstNew = TryRealloc(dst, (rv+AG_FMTSTRING_BUFFER_GROW));
592 if (dstNew == NULL) {
593 AG_FatalError("Out of memory for AG_Printf");
594 return (NULL);
595 }
596 dst = dstNew;
597 dstSize = (rv+AG_FMTSTRING_BUFFER_GROW);
598 goto restart;
599 }
600 va_end(ap);
601
602 #ifdef AG_THREADS
603 if ((agPrintBuf[0] = (char *)AG_ThreadKeyGet(agPrintBufKey[0])) != NULL) {
604 free(agPrintBuf[0]);
605 }
606 agPrintBuf[0] = dst;
607 AG_ThreadKeySet(agPrintBufKey[0], agPrintBuf[0]);
608 #else
609 Free(agPrintBuf[0]);
610 agPrintBuf[0] = dst;
611 #endif
612 return (dst);
613 }
614 char *
AG_PrintfN(Uint idx,const char * fmt,...)615 AG_PrintfN(Uint idx, const char *fmt, ...)
616 {
617 va_list ap;
618 char *dst, *dstNew;
619 size_t dstSize = AG_FMTSTRING_BUFFER_INIT, rv;
620
621 dst = Malloc(dstSize);
622 restart:
623 dst[0] = '\0';
624 va_start(ap, fmt);
625 if ((rv = AG_DoPrintf(dst, dstSize, fmt, ap)) >= dstSize) {
626 va_end(ap);
627 dstNew = TryRealloc(dst, (rv+AG_FMTSTRING_BUFFER_GROW));
628 if (dstNew == NULL) {
629 AG_FatalError("Out of memory for AG_Printf");
630 return (NULL);
631 }
632 dst = dstNew;
633 dstSize = (rv+AG_FMTSTRING_BUFFER_GROW);
634 goto restart;
635 }
636 va_end(ap);
637
638 #ifdef AG_THREADS
639 if ((agPrintBuf[idx] = (char *)AG_ThreadKeyGet(agPrintBufKey[idx])) != NULL) {
640 free(agPrintBuf[idx]);
641 }
642 agPrintBuf[idx] = dst;
643 AG_ThreadKeySet(agPrintBufKey[idx], agPrintBuf[idx]);
644 #else
645 Free(agPrintBuf[idx]);
646 agPrintBuf[idx] = dst;
647 #endif
648 return (dst);
649 }
650
651 /*
652 * Allocate and return a new AG_FmtString (which describes a string built
653 * from arguments to be later accessed, as the string is printed).
654 *
655 * Optionally, mutexes may be associated with arguments like so:
656 *
657 * AG_PrintfP("My string: %m<%s>", myMutex, myString);
658 *
659 * This instructs the formatting engine to acquire myMutex before
660 * accessing the contents of myString.
661 */
662 AG_FmtString *
AG_PrintfP(const char * fmt,...)663 AG_PrintfP(const char *fmt, ...)
664 {
665 const char *p;
666 AG_FmtString *fs;
667 va_list ap;
668 AG_Mutex *mu = NULL;
669
670 if ((fs = TryMalloc(sizeof(AG_FmtString))) == NULL) {
671 AG_FatalError(NULL);
672 }
673 fs->s = Strdup(fmt);
674 fs->n = 0;
675
676 va_start(ap, fmt);
677 for (p = fmt; *p != '\0'; p++) {
678 if (*p != '%') {
679 continue;
680 }
681 switch (p[1]) {
682 case '%':
683 case ' ':
684 p++;
685 break;
686 case 'm':
687 mu = (AG_Mutex *)va_arg(ap, void *);
688 break;
689 case '>':
690 mu = NULL;
691 break;
692 case '\0':
693 break;
694 default:
695 if (fs->n+1 >= AG_STRING_POINTERS_MAX) {
696 AG_FatalError("Too many arguments");
697 }
698 fs->p[fs->n] = va_arg(ap, void *);
699 fs->mu[fs->n] = mu;
700 fs->n++;
701 mu = NULL;
702 break;
703 }
704 }
705 va_end(ap);
706 return (fs);
707 }
708
709 /*
710 * Copy src to string dst of size siz. At most siz-1 characters
711 * will be copied. Always NUL terminates (unless siz == 0).
712 * Returns strlen(src); if retval >= siz, truncation occurred.
713 */
714 size_t
AG_Strlcpy(char * dst,const char * src,size_t siz)715 AG_Strlcpy(char *dst, const char *src, size_t siz)
716 {
717 char *d = dst;
718 const char *s = src;
719 size_t n = siz;
720
721 /* Copy as many bytes as will fit */
722 if (n != 0 && --n != 0) {
723 do {
724 if ((*d++ = *s++) == 0) {
725 break;
726 }
727 } while (--n != 0);
728 }
729
730 /* Not enough room in dst, add NUL and traverse rest of src */
731 if (n == 0) {
732 if (siz != 0)
733 *d = '\0'; /* NUL-terminate dst */
734 while (*s++)
735 ;
736 }
737
738 return (s - src - 1); /* count does not include NUL */
739 }
740
741 /*
742 * Appends src to string dst of size siz (unlike strncat, siz is the
743 * full size of dst, not space left). At most siz-1 characters
744 * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
745 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
746 * If retval >= siz, truncation occurred.
747 */
748 size_t
AG_Strlcat(char * dst,const char * src,size_t siz)749 AG_Strlcat(char *dst, const char *src, size_t siz)
750 {
751 char *d = dst;
752 const char *s = src;
753 size_t dlen, n = siz;
754
755 /* Find the end of dst and adjust bytes left but don't go past end */
756 while (n-- != 0 && *d != '\0') {
757 d++;
758 }
759 dlen = d - dst;
760 n = siz - dlen;
761
762 if (n == 0) {
763 return (dlen + strlen(s));
764 }
765 while (*s != '\0') {
766 if (n != 1) {
767 *d++ = *s;
768 n--;
769 }
770 s++;
771 }
772 *d = '\0';
773
774 return (dlen + (s - src)); /* count does not include NUL */
775 }
776
777 /*
778 * Get next token from string *stringp, where tokens are possibly-empty
779 * strings separated by characters from delim.
780 *
781 * Writes NULs into the string at *stringp to end tokens.
782 * delim need not remain constant from call to call.
783 * On return, *stringp points past the last NUL written (if there might
784 * be further tokens), or is NULL (if there are definitely no more tokens).
785 *
786 * If *stringp is NULL, AG_Strsep returns NULL.
787 */
788 char *
AG_Strsep(char ** stringp,const char * delim)789 AG_Strsep(char **stringp, const char *delim)
790 {
791 char *s;
792 const char *spanp;
793 int c, sc;
794 char *tok;
795
796 if ((s = *stringp) == NULL) {
797 return (NULL);
798 }
799 for (tok = s;;) {
800 c = *s++;
801 spanp = delim;
802 do {
803 if ((sc = *spanp++) == c) {
804 if (c == 0) {
805 s = NULL;
806 } else {
807 s[-1] = 0;
808 }
809 *stringp = s;
810 return (tok);
811 }
812 } while (sc != 0);
813 }
814 }
815
816 /* Duplicate a string. */
817 char *
AG_Strdup(const char * s)818 AG_Strdup(const char *s)
819 {
820 size_t buflen;
821 char *ns;
822
823 buflen = strlen(s)+1;
824 ns = Malloc(buflen);
825 memcpy(ns, s, buflen);
826 return (ns);
827 }
828
829 /* Duplicate a string or fail. */
830 char *
AG_TryStrdup(const char * s)831 AG_TryStrdup(const char *s)
832 {
833 size_t buflen;
834 char *ns;
835
836 buflen = strlen(s)+1;
837 if ((ns = TryMalloc(buflen)) == NULL) {
838 return (NULL);
839 }
840 memcpy(ns, s, buflen);
841 return (ns);
842 }
843
844 /*
845 * Locate a substring ignoring case.
846 */
847 const char *
AG_Strcasestr(const char * s,const char * find)848 AG_Strcasestr(const char *s, const char *find)
849 {
850 char c, sc;
851 size_t len;
852
853 if ((c = *find++) != 0) {
854 c = (char)tolower((unsigned char)c);
855 len = strlen(find);
856 do {
857 do {
858 if ((sc = *s++) == 0)
859 return (NULL);
860 } while ((char)tolower((unsigned char)sc) != c);
861 } while (Strncasecmp(s, find, len) != 0);
862 s--;
863 }
864 return (s);
865 }
866
867 #ifdef HAVE_ICONV
868
869 static Uint32 *
ImportUnicodeICONV(const char * encoding,const char * s,size_t sLen,size_t * pOutLen,size_t * pOutSize)870 ImportUnicodeICONV(const char *encoding, const char *s, size_t sLen,
871 size_t *pOutLen, size_t *pOutSize)
872 {
873 Uint32 *ucs, *ucsNew;
874 const char *inPtr = s;
875 char *wrPtr;
876 size_t outSize = (sLen+1)*sizeof(Uint32);
877 iconv_t cd;
878
879 if ((ucs = TryMalloc(outSize)) == NULL) {
880 return (NULL);
881 }
882 if ((cd = iconv_open("UCS-4-INTERNAL", encoding)) == (iconv_t)-1) {
883 AG_SetError("iconv_open: %s", strerror(errno));
884 goto fail;
885 }
886 wrPtr = (char *)ucs;
887
888 #ifdef HAVE_ICONV_CONST
889 if (iconv(cd, &inPtr, &sLen, &wrPtr, &outSize) == (size_t)-1) {
890 AG_SetError("iconv: %s", strerror(errno));
891 iconv_close(cd);
892 goto fail;
893 }
894 #else
895 {
896 char *tmpBuf;
897 if ((tmpBuf = AG_TryStrdup(inPtr)) == NULL) {
898 iconv_close(cd);
899 goto fail;
900 }
901 if (iconv(cd, &tmpBuf, &sLen, &wrPtr, &outSize) == (size_t)-1) {
902 AG_SetError("iconv: %s", strerror(errno));
903 iconv_close(cd);
904 free(tmpBuf);
905 goto fail;
906 }
907 free(tmpBuf);
908 }
909 #endif /* !HAVE_ICONV_CONST */
910
911 iconv_close(cd);
912
913 outSize = (wrPtr - (char *)ucs)/sizeof(Uint32);
914 if (pOutLen != NULL) { *pOutLen = outSize; }
915
916 /* Shrink the buffer down to the actual string length. */
917 ucsNew = TryRealloc(ucs, (outSize+1)*sizeof(Uint32));
918 if (ucsNew == NULL) {
919 goto fail;
920 }
921 ucs = ucsNew;
922 ucs[outSize] = '\0';
923 if (pOutSize != NULL) { *pOutSize = (outSize+1)*sizeof(Uint32); }
924 return (ucs);
925 fail:
926 Free(ucs);
927 return (NULL);
928 }
929
930 #endif /* HAVE_ICONV */
931
932 /*
933 * Return an internal UCS-4 buffer from the given string and specified
934 * encoding. Optionally returns number of characters converted in
935 * pOutLen, and allocated buffer size in pOutSize.
936 */
937 Uint32 *
AG_ImportUnicode(const char * encoding,const char * s,size_t * pOutLen,size_t * pOutSize)938 AG_ImportUnicode(const char *encoding, const char *s, size_t *pOutLen,
939 size_t *pOutSize)
940 {
941 Uint32 *ucs;
942 size_t i, j;
943 size_t sLen = strlen(s);
944 size_t bufLen, utf8len;
945
946 if (strcmp(encoding, "UTF-8") == 0) {
947 if (AG_LengthUTF8(s, &utf8len) == -1) {
948 return (NULL);
949 }
950 bufLen = (utf8len + 1)*sizeof(Uint32);
951 if ((ucs = TryMalloc(bufLen)) == NULL) {
952 return (NULL);
953 }
954 for (i = 0, j = 0; i < sLen; i++, j++) {
955 switch (AG_CharLengthUTF8(s[i])) {
956 case 1:
957 ucs[j] = (Uint32)s[i];
958 break;
959 case 2:
960 ucs[j] = (Uint32)(s[i] & 0x1f) << 6;
961 ucs[j] |= (Uint32)(s[++i] & 0x3f);
962 break;
963 case 3:
964 ucs[j] = (Uint32)(s[i] & 0x0f) << 12;
965 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 6;
966 ucs[j] |= (Uint32)(s[++i] & 0x3f);
967 break;
968 case 4:
969 ucs[j] = (Uint32)(s[i] & 0x07) << 18;
970 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 12;
971 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 6;
972 ucs[j] |= (Uint32)(s[++i] & 0x3f);
973 break;
974 case 5:
975 ucs[j] = (Uint32)(s[i] & 0x03) << 24;
976 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 18;
977 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 12;
978 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 6;
979 ucs[j] |= (Uint32)(s[++i] & 0x3f);
980 break;
981 case 6:
982 ucs[j] = (Uint32)(s[i] & 0x01) << 30;
983 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 24;
984 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 18;
985 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 12;
986 ucs[j] |= (Uint32)(s[++i] & 0x3f) << 6;
987 ucs[j] |= (Uint32)(s[++i] & 0x3f);
988 break;
989 case -1:
990 Free(ucs);
991 return (NULL);
992 }
993 }
994 ucs[j] = '\0';
995 if (pOutLen != NULL) { *pOutLen = j; }
996 if (pOutSize != NULL) { *pOutSize = bufLen; }
997 } else if (strcmp(encoding, "US-ASCII") == 0) {
998 bufLen = (sLen + 1)*sizeof(Uint32);
999 if ((ucs = TryMalloc(bufLen)) == NULL) {
1000 return (NULL);
1001 }
1002 for (i = 0; i < sLen; i++) {
1003 ucs[i] = ((const unsigned char *)s)[i];
1004 }
1005 ucs[i] = '\0';
1006 if (pOutLen != NULL) { *pOutLen = i; }
1007 if (pOutSize != NULL) { *pOutSize = bufLen; }
1008 } else {
1009 #ifdef HAVE_ICONV
1010 ucs = ImportUnicodeICONV(encoding, s, sLen, pOutLen, pOutSize);
1011 #else
1012 AG_SetError("Unknown encoding: %s (no iconv support)", encoding);
1013 return (NULL);
1014 #endif
1015 }
1016 return (ucs);
1017 }
1018
1019 #ifdef HAVE_ICONV
1020
1021 static int
ExportUnicodeICONV(const char * encoding,char * dst,const Uint32 * ucs,size_t dstSize)1022 ExportUnicodeICONV(const char *encoding, char *dst, const Uint32 *ucs,
1023 size_t dstSize)
1024 {
1025 const char *inPtr = (const char *)ucs;
1026 size_t inSize = AG_LengthUCS4(ucs)*sizeof(Uint32);
1027 char *wrPtr = dst;
1028 size_t outSize = dstSize;
1029 iconv_t cd;
1030
1031 if ((cd = iconv_open(encoding, "UCS-4-INTERNAL")) == (iconv_t)-1) {
1032 AG_SetError("iconv_open: %s", strerror(errno));
1033 return (-1);
1034 }
1035
1036 #ifdef HAVE_ICONV_CONST
1037 if (iconv(cd, &inPtr, &inSize, &wrPtr, &outSize) == (size_t)-1) {
1038 AG_SetError("iconv: %s", strerror(errno));
1039 iconv_close(cd);
1040 return (-1);
1041 }
1042 #else
1043 {
1044 char *tmpBuf;
1045 if ((tmpBuf = AG_TryStrdup(inPtr)) == NULL) {
1046 iconv_close(cd);
1047 return (-1);
1048 }
1049 if (iconv(cd, &tmpBuf, &inSize, &wrPtr, &outSize) == (size_t)-1) {
1050 AG_SetError("iconv: %s", strerror(errno));
1051 iconv_close(cd);
1052 free(tmpBuf);
1053 return (-1);
1054 }
1055 free(tmpBuf);
1056 }
1057 #endif
1058
1059 iconv_close(cd);
1060
1061 if (outSize >= sizeof(char)) {
1062 outSize = wrPtr - dst;
1063 dst[outSize] = '\0';
1064 } else {
1065 AG_SetError("iconv: Out of space for NUL");
1066 return (-1);
1067 }
1068 return (0);
1069 }
1070
1071 #endif /* HAVE_ICONV */
1072
1073 /*
1074 * Convert an internal UCS-4 string to a fixed-size buffer using the specified
1075 * encoding. At most dstSize-1 bytes will be copied. The string is always
1076 * NUL-terminated.
1077 */
1078 int
AG_ExportUnicode(const char * encoding,char * dst,const Uint32 * ucs,size_t dstSize)1079 AG_ExportUnicode(const char *encoding, char *dst, const Uint32 *ucs,
1080 size_t dstSize)
1081 {
1082 size_t len;
1083
1084 if (strcmp(encoding, "UTF-8") == 0) {
1085 for (len = 0; *ucs != '\0' && len < dstSize; ucs++) {
1086 Uint32 uch = *ucs;
1087 int chlen, ch1, i;
1088
1089 if (uch < 0x80) {
1090 chlen = 1;
1091 ch1 = 0;
1092 } else if (uch < 0x800) {
1093 chlen = 2;
1094 ch1 = 0xc0;
1095 } else if (uch < 0x10000) {
1096 chlen = 3;
1097 ch1 = 0xe0;
1098 } else if (uch < 0x200000) {
1099 chlen = 4;
1100 ch1 = 0xf0;
1101 } else if (uch < 0x4000000) {
1102 chlen = 5;
1103 ch1 = 0xf8;
1104 } else if (uch <= 0x7fffffff) {
1105 chlen = 6;
1106 ch1 = 0xfc;
1107 } else {
1108 AG_SetError("Bad UTF-8 sequence");
1109 return (-1);
1110 }
1111 if (len+chlen+1 > dstSize) {
1112 AG_SetError("Out of space");
1113 return (-1);
1114 }
1115 for (i = chlen - 1; i > 0; i--) {
1116 dst[i] = (uch & 0x3f) | 0x80;
1117 uch >>= 6;
1118 }
1119 dst[0] = uch | ch1;
1120 dst += chlen;
1121 len += chlen;
1122 }
1123 *dst = '\0';
1124 return (0);
1125 } else if (strcmp(encoding, "US-ASCII") == 0) {
1126 for (len = 0; *ucs != '\0' && len < dstSize; ucs++) {
1127 if (!isascii((int)*ucs)) {
1128 AG_SetError("Bad ASCII character");
1129 return (-1);
1130 }
1131 *dst = (char)*ucs;
1132 dst++;
1133 len++;
1134 }
1135 *dst = '\0';
1136 return (0);
1137 } else {
1138 #ifdef HAVE_ICONV
1139 return ExportUnicodeICONV(encoding, dst, ucs, dstSize);
1140 #else
1141 AG_SetError("Unknown encoding: %s (no iconv support)", encoding);
1142 return (-1);
1143 #endif
1144 }
1145 }
1146
1147 /* Reverse the characters of a string. */
1148 void
AG_StrReverse(char * s)1149 AG_StrReverse(char *s)
1150 {
1151 char *p, *q, c;
1152
1153 if (*s == '\0') {
1154 return;
1155 }
1156 q = s;
1157 while (*(++q) != '\0')
1158 ;;
1159 for (p = s; p < --q; p++) {
1160 c = *p;
1161 *p = *q;
1162 *q = c;
1163 }
1164 }
1165
1166 /*
1167 * Format an int (base 10) into a fixed-size buffer.
1168 * XXX on truncation, this returns the size of the buffer + 1 byte
1169 * (we should return the total length required instead).
1170 */
1171 size_t
AG_StrlcpyInt(char * s,int val,size_t size)1172 AG_StrlcpyInt(char *s, int val, size_t size)
1173 {
1174 static const char *digits = "0123456789";
1175 int sign = (val < 0);
1176 int i = 0;
1177
1178 if (size < 1) {
1179 return (1);
1180 }
1181 val = (val < 0) ? -val : val;
1182 do {
1183 if (i+1 > size) {
1184 goto trunc;
1185 }
1186 s[i++] = digits[val % 10];
1187 } while ((val /= 10) > 0);
1188
1189 if (sign) {
1190 if (i+1 > size) {
1191 goto trunc;
1192 }
1193 s[i++] = '-';
1194 }
1195 if (i+1 > size) {
1196 goto trunc;
1197 }
1198 s[i] = '\0';
1199 AG_StrReverse(s);
1200 return (i);
1201 trunc:
1202 s[size-1] = '\0';
1203 AG_StrReverse(s);
1204 return (i+1);
1205 }
1206
1207 /*
1208 * Format an unsigned int (base 10) into a fixed-size buffer.
1209 * XXX on truncation, this returns the size of the buffer + 1 byte
1210 * (we should return the total length required instead).
1211 */
1212 size_t
AG_StrlcpyUint(char * s,Uint val,size_t size)1213 AG_StrlcpyUint(char *s, Uint val, size_t size)
1214 {
1215 static const char *digits = "0123456789";
1216 int i = 0;
1217
1218 if (size < 1) {
1219 return (1);
1220 }
1221 do {
1222 if (i+1 > size) {
1223 goto trunc;
1224 }
1225 s[i++] = digits[val % 10];
1226 } while ((val /= 10) > 0);
1227
1228 if (i+1 > size) {
1229 goto trunc;
1230 }
1231 s[i] = '\0';
1232 AG_StrReverse(s);
1233 return (i);
1234 trunc:
1235 s[size-1] = '\0';
1236 AG_StrReverse(s);
1237 return (i+1);
1238 }
1239
1240 /*
1241 * Format an int (base 10) into a fixed-size buffer (concatenate).
1242 * XXX on truncation, this returns the size of the buffer + 1 byte
1243 * (we should return the total length required instead).
1244 */
1245 size_t
AG_StrlcatInt(char * s,int val,size_t size)1246 AG_StrlcatInt(char *s, int val, size_t size)
1247 {
1248 static const char *digits = "0123456789";
1249 int sign = (val < 0);
1250 int i = strlen(s);
1251 int iStart = i;
1252
1253 if (size < 1) {
1254 return (1);
1255 }
1256 val = (val < 0) ? -val : val;
1257 do {
1258 if (i+1 > size) {
1259 goto trunc;
1260 }
1261 s[i++] = digits[val % 10];
1262 } while ((val /= 10) > 0);
1263
1264 if (sign) {
1265 if (i+1 > size) {
1266 goto trunc;
1267 }
1268 s[i++] = '-';
1269 }
1270 if (i+1 > size) {
1271 goto trunc;
1272 }
1273 s[i] = '\0';
1274 AG_StrReverse(&s[iStart]);
1275 return (i);
1276 trunc:
1277 s[size-1] = '\0';
1278 AG_StrReverse(&s[iStart]);
1279 return (i+1);
1280 }
1281
1282 /*
1283 * Format an unsigned int (base 10) into a fixed-size buffer (concatenate).
1284 * XXX on truncation, this returns the size of the buffer + 1 byte
1285 * (we should return the total length required instead).
1286 */
1287 size_t
AG_StrlcatUint(char * s,Uint val,size_t size)1288 AG_StrlcatUint(char *s, Uint val, size_t size)
1289 {
1290 static const char *digits = "0123456789";
1291 int i = strlen(s);
1292 int iStart = i;
1293
1294 if (size < 1) {
1295 return (1);
1296 }
1297 do {
1298 if (i+1 > size) {
1299 goto trunc;
1300 }
1301 s[i++] = digits[val % 10];
1302 } while ((val /= 10) > 0);
1303
1304 if (i+1 > size) {
1305 goto trunc;
1306 }
1307 s[i] = '\0';
1308 AG_StrReverse(&s[iStart]);
1309 return (i);
1310 trunc:
1311 s[size-1] = '\0';
1312 AG_StrReverse(&s[iStart]);
1313 return (i+1);
1314 }
1315
1316 #ifdef AG_THREADS
DestroyPrintBuffer(void * buf)1317 static void DestroyPrintBuffer(void *buf) { Free(buf); }
1318 #endif /* AG_THREADS */
1319
1320 int
AG_InitStringSubsystem(void)1321 AG_InitStringSubsystem(void)
1322 {
1323 Uint i;
1324
1325 /* Initialize the AG_Printf() buffers. */
1326 for (i = 0; i < AG_STRING_BUFFERS_MAX; i++) {
1327 agPrintBuf[i] = NULL;
1328 #ifdef AG_THREADS
1329 if (AG_ThreadKeyTryCreate(&agPrintBufKey[i], DestroyPrintBuffer) == -1) {
1330 return (-1);
1331 }
1332 AG_ThreadKeySet(agPrintBufKey[i], NULL);
1333 #endif
1334 }
1335
1336 /* Initialize the formatting engine extensions. */
1337 AG_MutexInit(&agFmtExtensionsLock);
1338 AG_RegisterFmtStringExt("u8", PrintU8);
1339 AG_RegisterFmtStringExt("s8", PrintS8);
1340 AG_RegisterFmtStringExt("u16", PrintU16);
1341 AG_RegisterFmtStringExt("s16", PrintS16);
1342 AG_RegisterFmtStringExt("u32", PrintU32);
1343 AG_RegisterFmtStringExt("s32", PrintS32);
1344 #ifdef HAVE_64BIT
1345 AG_RegisterFmtStringExt("u64", PrintU64);
1346 AG_RegisterFmtStringExt("s64", PrintS64);
1347 #endif
1348 AG_RegisterFmtStringExt("objName", PrintOBJNAME);
1349 AG_RegisterFmtStringExt("objType", PrintOBJTYPE);
1350
1351 return (0);
1352 }
1353
1354 void
AG_DestroyStringSubsystem(void)1355 AG_DestroyStringSubsystem(void)
1356 {
1357 Uint i;
1358
1359 /* Free the AG_Printf() buffers. */
1360 for (i = 0; i < AG_STRING_BUFFERS_MAX; i++) {
1361 #ifdef AG_THREADS
1362 if ((agPrintBuf[i] = (char *)AG_ThreadKeyGet(agPrintBufKey[i]))
1363 != NULL) {
1364 free(agPrintBuf[i]);
1365 }
1366 AG_ThreadKeyDelete(agPrintBufKey[i]);
1367 #else
1368 Free(agPrintBuf[i]);
1369 #endif
1370 agPrintBuf[i] = NULL;
1371 }
1372
1373 /* Free the formatting engine extensions. */
1374 for (i = 0; i < agFmtExtensionCount; i++) {
1375 Free(agFmtExtensions[i].fmt);
1376 }
1377 Free(agFmtExtensions);
1378 agFmtExtensions = NULL;
1379 agFmtExtensionCount = 0;
1380
1381 AG_MutexDestroy(&agFmtExtensionsLock);
1382 }
1383