#include "precompile.h" #include #include #include #include #include #include /* SSIZE_MAX */ #include "strutil.h" #include "memutil.h" #include "toolkit.h" #include #include #include "dbgutil.h" #pragma warning(disable : 4311) TOOLKIT_API const char *memstr(const char *buf, int n, const char *str) { const char *p, *e; size_t len; TOOLKIT_ASSERT(buf); TOOLKIT_ASSERT(str); len = strlen(str); for (p = buf, e = buf+n-len; p <= e; ++p) { if (memcmp(p, str, len) == 0) return p; } return NULL; } TOOLKIT_API char *memstr1(char *buf, int n, const char *str) { char *p, *e; size_t len; TOOLKIT_ASSERT(buf); TOOLKIT_ASSERT(str); len = strlen(str); for (p = buf, e = buf+n-len; p <= e; ++p) { if (memcmp(p, str, len) == 0) return p; } return NULL; } TOOLKIT_API const char *memrstr(const char *buf, int n, const char *str) { const char *p, *e; size_t len; TOOLKIT_ASSERT(buf); TOOLKIT_ASSERT(str); len = strlen(str); for (p = buf, e = buf+n-len; e >= p; e--) { if (memcmp(e, str, len) == 0) return e; } return NULL; } TOOLKIT_API const char *memistr(const char *buf, int n, const char *str) { const char *p, *e; size_t len; TOOLKIT_ASSERT(buf); TOOLKIT_ASSERT(str); len = strlen(str); for (p = buf, e = buf+n-len; p <= e; ++p) { if (_memicmp(p, str, len) == 0) return p; } return NULL; } TOOLKIT_API const char *memristr(const char *buf, int n, const char *str) { const char *p, *e; size_t len; TOOLKIT_ASSERT(buf); TOOLKIT_ASSERT(str); len = strlen(str); for (p = buf, e = buf+n-len; e >= p; e--) { if (_memicmp(e, str, len) == 0) return e; } return NULL; } #ifdef _WIN32 /* Naive implementation of memmem() */ TOOLKIT_API void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { size_t i; char const *hs = haystack; if (needlelen == 0) return (void *)haystack; if (needlelen > haystacklen || haystack == NULL || needle == NULL) return NULL; for (i = 0; i <= haystacklen - needlelen; i++) { if (memcmp(hs + i, needle, needlelen) == 0) return (void *)(hs + i); } return NULL; } /* Naive implementation of strcasestr() */ TOOLKIT_API char *strcasestr(const char *haystack, const char *needle) { unsigned char lcn, ucn; unsigned i; if (haystack == NULL || needle == NULL) return NULL; lcn = ucn = needle[0]; if (isupper(lcn)) lcn = tolower(lcn); else if (islower(ucn)) ucn = toupper(ucn); if (lcn == 0) return (char *)haystack; while (haystack[0] != 0) { if (lcn == haystack[0] || ucn == haystack[0]) { for (i = 1; ; i++) { char n = needle[i], h = haystack[i]; if (n == 0) return (char *)haystack; if (h == 0) return NULL; if (isupper(n)) n = tolower(n); if (isupper(h)) h = tolower(h); if (n != h) break; } } haystack++; } return NULL; /* Not found */ } #endif //_WIN32 TOOLKIT_API char *strltrim(char *s, const char *trims) { char *k = s; char *p; while (strchr(trims, *k)) ++k; if (k != s) for (p = s; *p++ = *k++;); return s; } TOOLKIT_API char *strrtrim(char *s, const char *trims) { size_t n = strlen(s); while (n && strchr(trims, s[n-1])) n--; s[n] = 0; return s; } TOOLKIT_API char *strtrim(char *s, const char *trims) { return strltrim(strrtrim(s, trims), trims); } TOOLKIT_API char *strnormws(char *s) { char *in = s, *out = s; int ch; while (isspace((ch = *in++))) ; if (ch != '\0') for (;;) { *out++ = ch; ch = *in++; if (ch == '\0') break; if (isspace(ch)) { while(isspace((ch = *in++))) ; if (ch == '\0') break; *out++ = ' '; } } *out = '\0'; return s; } #define ONES_WORD 0x01010101 #define EIGHTS_WORD 0x80808080 #ifdef _WIN32 #define word_has_nullbyte(w) (((w) - 0x01010101) & ~(w) & EIGHTS_WORD) TOOLKIT_API char *stpcpy(char *dst, const char *src) { const int *p = (const int *)src; int *q = (int*)dst; const char *sp; char *sq; if (!((int)p&(sizeof(int)-1)) && !((int)q&(sizeof(int)-1))) { int x = *p; while (!word_has_nullbyte(x)) { *q++ = x; x = *++p; } } sp = (const char*)p; sq = (char*)q; while (*sq = *sp++) sq++; return sq; } TOOLKIT_API char* strsep(char** stringp, const char* delim) { char* str, * end; str = *stringp + strspn(*stringp, delim); if (*str == '\0') { end = str; str = NULL; } else { end = str + strcspn(str, delim); if (*end != '\0') *end++ = '\0'; } *stringp = end; return str; } TOOLKIT_API char* strtok_r(char* str, const char* delim, char** tracker) { if (str != NULL) *tracker = str; return strsep(tracker, delim); } #endif //_WIN32 TOOLKIT_API char * strreplace(const char *src, const char *old, const char *news) { # define SUBS_TOP 32 const char *subs[SUBS_TOP], *p, *sub_start, *src_without_old; char *dest, *ret; size_t old_len, new_len, dest_size, tmp; int i; old_len = strlen(old); new_len = strlen(news); /* Find substrings and compute size of result */ dest_size = 1; src_without_old = src; for (i = 0, p = src ;; p = sub_start + old_len) { sub_start = strstr(p, old); if (i < SUBS_TOP) subs[i++] = sub_start; if (sub_start == NULL) break; dest_size += new_len; if (dest_size < new_len) { return NULL; } src_without_old += old_len; } tmp = (p - src_without_old) + strlen(p); dest_size += tmp; if (dest_size < tmp) { return NULL; } /* Make result string */ ret = malloc(dest_size); if (ret) { dest = ret; for (i = 0;; ) { p = (i < SUBS_TOP ? subs[i++] : strstr(src, old)); if (p == NULL) break; tmp = p - src; memcpy(dest, src, tmp); src += tmp + old_len; dest = stpcpy(dest + tmp, news); } strcpy(dest, src); } return ret; } TOOLKIT_API char **strsplit(const char *s, const char *delim) { const char *sp; char *p, **ret, **rp; size_t i; /* skip to first substring */ s += strspn(s, delim); /* allocate array */ for (i = 1, sp = s; *sp != '\0'; i++) { sp += strcspn(sp, delim); sp += strspn(sp, delim); } ret = rp = malloc(i * sizeof(char *)); if (ret != NULL) { while (*s != '\0') { /* found new substring */ i = strcspn(s, delim); *rp++ = p = malloc(i + 1); if (p == NULL) { strfreev(ret); return NULL; } memcpy(p, s, i); p[i] = '\0'; s += i; s += strspn(s, delim); } *rp = NULL; } return ret; } TOOLKIT_API void strfreev(char **strings) { if (strings) { char **p; for (p = strings; *p != NULL; ++p) free(*p); free(strings); } } TOOLKIT_API char *strsub(char *dest,char *src,size_t offset,size_t len) { *dest = '\0'; if (!memchr(src, '\0', offset)) strncat(dest, src + offset, len); return dest; } TOOLKIT_API char *strleft(char *dest, char *src, size_t len) { *dest = '\0'; strncat(dest, src, len); return dest; } TOOLKIT_API char *strright(char *dest, char *src, size_t len) { size_t src_len; src_len = strlen(src); if(src_len > len) src += src_len - len; return strcpy(dest, src); } static char * TOOLKIT_CC __strallocv(int stack, const char *arg1, va_list arg_list) { size_t len; const char *arg; char *ret, *end; int bad; va_list ap; /* compute length of result string */ len = 1; bad = 0; //TODO: need to test its validity #ifdef _WIN32 /*gcc compile error: assignment to expression with array type*/ ap = arg_list; #else va_copy(ap, arg_list); #endif for (arg = arg1; arg != (char *)0; arg = va_arg(ap, char *)) { size_t arglen = strlen(arg); len += arglen; if(len < arglen) { /* string length too large for size_t */ bad = 1; break; } } if(bad) { return NULL; } /* create result string */ if (!stack) ret = malloc(len); else ret = _alloca(len); if(ret != NULL) { end = ret; *end = '\0'; #ifdef _WIN32 /*gcc compile error: assignment to expression with array type*/ ap = arg_list; #else va_copy(ap, arg_list); #endif for (arg = arg1; arg != (char *)0; arg = va_arg(ap, char *)) end = stpcpy(end, arg); } return ret; } TOOLKIT_API char * TOOLKIT_CC stralloc(const char *arg1, ...) { char *r; va_list ap; va_start(ap, arg1); r = __strallocv(0, arg1, ap); va_end(ap); return r; } TOOLKIT_API char * TOOLKIT_CC stralloca(const char *arg1, ...) { char *r; va_list ap; va_start(ap, arg1); r = __strallocv(1, arg1, ap); va_end(ap); return r; } TOOLKIT_API void *memdup(const void *buf, int len) { void *ret; if (buf) { ret = malloc(len); if (ret) { memcpy(ret, buf, len); } } else { ret = NULL; } return ret; } TOOLKIT_API void *memdupa(const void *buf, int len) { void *ret; if (buf) { ret = _alloca(len); if (ret) { memcpy(ret, buf, len); } } else { ret = NULL; } return ret; } #ifdef _WIN32 TOOLKIT_API char *strdupa(const char * s) { size_t size; char *p; TOOLKIT_ASSERT(s); size = strlen(s) + 1; if (size == 0) p = NULL; else if ((p = _alloca(size)) != NULL) memcpy(p, s, size); return p; } TOOLKIT_API char *strndup(const char *str, int n) { char *new_str; if (str) { new_str = (char*)malloc(n+1); strncpy(new_str, str, n); new_str[n] = '\0'; } else { new_str = NULL; } return new_str; } #endif //_WIN32 TOOLKIT_API char* strnfill(int length, int fill_char) { char *str; str = (char*)malloc(length + 1); memset (str, fill_char, length); str[length] = '\0'; return str; } TOOLKIT_API int strcmp0 (const char *str1,const char *str2) { if (!str1) return -(str1 != str2); if (!str2) return str1 != str2; return strcmp (str1, str2); } TOOLKIT_API char* TOOLKIT_CC strdup_printf(const char *format, ...) { char *buffer; va_list args; va_start(args, format); buffer = strdup_vprintf(format, args); va_end(args); return buffer; } static char *vasnprintf (char *resultbuf, size_t *lengthp, const char *format, va_list args); TOOLKIT_API char* TOOLKIT_CC strdup_vprintf(const char *format, va_list args) { size_t length; return vasnprintf (NULL, &length, format, args); } TOOLKIT_API size_t strlcpy(char *dest, const char *src, size_t size) { const char *start; start = src; if (size) { while (--size && *src) *dest++ = *src++; *dest = '\0'; } while (*src) ++src; return src - start; } TOOLKIT_API size_t strlcat(char * dest, const char *src, size_t size) { size_t len; char *end; for (end = dest; *end; ++end) ; len = end - dest; return len + strlcpy(end, src, size > len ? size - len : 0); } TOOLKIT_API ssize_t strscpy(char* dest, const char* src, size_t size) { size_t i; for (i = 0; i < size; i++) if ('\0' == (dest[i] = src[i])) return i > SSIZE_MAX ? TOOLKIT_E2BIG : (ssize_t)i; if (i == 0) return 0; dest[--i] = '\0'; return TOOLKIT_E2BIG; } TOOLKIT_API char** strdupv (char **str_array) { if (str_array) { int i; char **retval; i = 0; while (str_array[i]) ++i; retval = (char**)malloc(i+1); i = 0; while (str_array[i]) { retval[i] = _strdup (str_array[i]); ++i; } retval[i] = NULL; return retval; } else return NULL; } TOOLKIT_API char* strjoinv (const char *separator,char **str_array) { char *string; char *ptr; if (separator == NULL) separator = ""; if (*str_array) { int i; size_t len; size_t separator_len; separator_len = strlen (separator); /* First part, getting length */ len = 1 + strlen (str_array[0]); for (i = 1; str_array[i] != NULL; i++) len += strlen (str_array[i]); len += separator_len * (i - 1); /* Second part, building string */ string = (char*)malloc(len); ptr = stpcpy (string, *str_array); for (i = 1; str_array[i] != NULL; i++) { ptr = stpcpy (ptr, separator); ptr = stpcpy (ptr, str_array[i]); } } else string = _strdup (""); return string; } TOOLKIT_API char* TOOLKIT_CC strjoin (const char *separator, ...) { char *string, *s; va_list args; size_t len; size_t separator_len; char *ptr; if (separator == NULL) separator = ""; separator_len = strlen (separator); va_start (args, separator); s = va_arg (args, char*); if (s) { /* First part, getting length */ len = 1 + strlen (s); s = va_arg (args, char*); while (s) { len += separator_len + strlen (s); s = va_arg (args, char*); } va_end (args); /* Second part, building string */ string = (char*)malloc(len); va_start (args, separator); s = va_arg (args, char*); ptr = stpcpy (string, s); s = va_arg (args, char*); while (s) { ptr = stpcpy (ptr, separator); ptr = stpcpy (ptr, s); s = va_arg (args, char*); } } else string = _strdup (""); va_end (args); return string; } TOOLKIT_API int str_has_suffix (const char *str, const char *suffix) { size_t str_len; size_t suffix_len; str_len = strlen (str); suffix_len = strlen (suffix); if (str_len < suffix_len) return FALSE; return strcmp (str + str_len - suffix_len, suffix) == 0; } TOOLKIT_API int str_has_prefix (const char *str,const char *prefix) { size_t str_len; size_t prefix_len; str_len = strlen (str); prefix_len = strlen (prefix); if (str_len < prefix_len) return FALSE; return strncmp (str, prefix, prefix_len) == 0; } TOOLKIT_API unsigned int strv_length (char **str_array) { unsigned int i = 0; while (str_array[i]) ++i; return i; } /* asnprintf, extract from glib */ #define FLAG_GROUP 1 /* ' flag */ #define FLAG_LEFT 2 /* - flag */ #define FLAG_SHOWSIGN 4 /* + flag */ #define FLAG_SPACE 8 /* space flag */ #define FLAG_ALT 16 /* # flag */ #define FLAG_ZERO 32 /* A parsed directive. */ typedef struct { const char* dir_start; const char* dir_end; int flags; const char* width_start; const char* width_end; int width_arg_index; const char* precision_start; const char* precision_end; int precision_arg_index; char conversion; /* d i o u x X f e E g G c s p n U % but not C S */ int arg_index; } char_directive; /* A parsed format string. */ typedef struct { unsigned int count; char_directive *dir; unsigned int max_width_length; unsigned int max_precision_length; } char_directives; /* Argument types */ typedef enum { TYPE_NONE, TYPE_SCHAR, TYPE_UCHAR, TYPE_SHORT, TYPE_USHORT, TYPE_INT, TYPE_UINT, TYPE_LONGINT, TYPE_ULONGINT, TYPE_LONGLONGINT, TYPE_ULONGLONGINT, TYPE_INT64, TYPE_UINT64, TYPE_DOUBLE, TYPE_LONGDOUBLE, TYPE_CHAR, TYPE_STRING, TYPE_POINTER, TYPE_COUNT_SCHAR_POINTER, TYPE_COUNT_SHORT_POINTER, TYPE_COUNT_INT_POINTER, TYPE_COUNT_LONGINT_POINTER , TYPE_COUNT_LONGLONGINT_POINTER } arg_type; /* Polymorphic argument */ typedef struct { arg_type type; union { signed char a_schar; unsigned char a_uchar; short a_short; unsigned short a_ushort; int a_int; unsigned int a_uint; long int a_longint; unsigned long int a_ulongint; long long int a_longlongint; unsigned long long int a_ulonglongint; __int64 a_int64; u__int64_t a_uint64; float a_float; double a_double; long double a_longdouble; int a_char; const char* a_string; void* a_pointer; signed char * a_count_schar_pointer; short * a_count_short_pointer; int * a_count_int_pointer; long int * a_count_longint_pointer; long long int * a_count_longlongint_pointer; } a; } argument; typedef struct { unsigned int count; argument *arg; } arguments; static int printf_parse (const char *format, char_directives *d, arguments *a) { const char *cp = format; /* pointer into format */ int arg_posn = 0; /* number of regular arguments consumed */ unsigned int d_allocated; /* allocated elements of d->dir */ unsigned int a_allocated; /* allocated elements of a->arg */ unsigned int max_width_length = 0; unsigned int max_precision_length = 0; d->count = 0; d_allocated = 1; d->dir = malloc (d_allocated * sizeof (char_directive)); if (d->dir == NULL) /* Out of memory. */ return -1; a->count = 0; a_allocated = 0; a->arg = NULL; #define REGISTER_ARG(_index_,_type_) \ { \ unsigned int n = (_index_); \ if (n >= a_allocated) \ { \ argument *memory; \ a_allocated = 2 * a_allocated; \ if (a_allocated <= n) \ a_allocated = n + 1; \ memory = (a->arg \ ? realloc (a->arg, a_allocated * sizeof (argument)) \ : malloc (a_allocated * sizeof (argument))); \ if (memory == NULL) \ /* Out of memory. */ \ goto error; \ a->arg = memory; \ } \ while (a->count <= n) \ a->arg[a->count++].type = TYPE_NONE; \ if (a->arg[n].type == TYPE_NONE) \ a->arg[n].type = (_type_); \ else if (a->arg[n].type != (_type_)) \ /* Ambiguous type for positional argument. */ \ goto error; \ } while (*cp != '\0') { char c = *cp++; if (c == '%') { int arg_index = -1; char_directive *dp = &d->dir[d->count];/* pointer to next directive */ /* Initialize the next directive. */ dp->dir_start = cp - 1; dp->flags = 0; dp->width_start = NULL; dp->width_end = NULL; dp->width_arg_index = -1; dp->precision_start = NULL; dp->precision_end = NULL; dp->precision_arg_index = -1; dp->arg_index = -1; /* Test for positional argument. */ if (*cp >= '0' && *cp <= '9') { const char *np; for (np = cp; *np >= '0' && *np <= '9'; np++) ; if (*np == '$') { unsigned int n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) n = 10 * n + (*np - '0'); if (n == 0) /* Positional argument 0. */ goto error; arg_index = n - 1; cp = np + 1; } } /* Read the flags. */ for (;;) { if (*cp == '\'') { dp->flags |= FLAG_GROUP; cp++; } else if (*cp == '-') { dp->flags |= FLAG_LEFT; cp++; } else if (*cp == '+') { dp->flags |= FLAG_SHOWSIGN; cp++; } else if (*cp == ' ') { dp->flags |= FLAG_SPACE; cp++; } else if (*cp == '#') { dp->flags |= FLAG_ALT; cp++; } else if (*cp == '0') { dp->flags |= FLAG_ZERO; cp++; } else break; } /* Parse the field width. */ if (*cp == '*') { dp->width_start = cp; cp++; dp->width_end = cp; if (max_width_length < 1) max_width_length = 1; /* Test for positional argument. */ if (*cp >= '0' && *cp <= '9') { const char *np; for (np = cp; *np >= '0' && *np <= '9'; np++) ; if (*np == '$') { unsigned int n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) n = 10 * n + (*np - '0'); if (n == 0) /* Positional argument 0. */ goto error; dp->width_arg_index = n - 1; cp = np + 1; } } if (dp->width_arg_index < 0) dp->width_arg_index = arg_posn++; REGISTER_ARG (dp->width_arg_index, TYPE_INT); } else if (*cp >= '0' && *cp <= '9') { unsigned int width_length; dp->width_start = cp; for (; *cp >= '0' && *cp <= '9'; cp++) ; dp->width_end = cp; width_length = (unsigned int)(dp->width_end - dp->width_start); if (max_width_length < width_length) max_width_length = width_length; } /* Parse the precision. */ if (*cp == '.') { cp++; if (*cp == '*') { dp->precision_start = cp - 1; cp++; dp->precision_end = cp; if (max_precision_length < 2) max_precision_length = 2; /* Test for positional argument. */ if (*cp >= '0' && *cp <= '9') { const char *np; for (np = cp; *np >= '0' && *np <= '9'; np++) ; if (*np == '$') { unsigned int n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) n = 10 * n + (*np - '0'); if (n == 0) /* Positional argument 0. */ goto error; dp->precision_arg_index = n - 1; cp = np + 1; } } if (dp->precision_arg_index < 0) dp->precision_arg_index = arg_posn++; REGISTER_ARG (dp->precision_arg_index, TYPE_INT); } else { unsigned int precision_length; dp->precision_start = cp - 1; for (; *cp >= '0' && *cp <= '9'; cp++) ; dp->precision_end = cp; precision_length = (unsigned int)(dp->precision_end - dp->precision_start); if (max_precision_length < precision_length) max_precision_length = precision_length; } } { arg_type type; /* Parse argument type/size specifiers. */ { int flags = 0; for (;;) { if (*cp == 'h') { flags |= (1 << (flags & 1)); cp++; } else if (*cp == 'L') { flags |= 4; cp++; } else if (*cp == 'l') { flags += 8; cp++; } else if (cp[0] == 'I' && cp[1] == '6' && cp[2] == '4') { flags = 64; cp += 3; } else if (*cp == 'z' || *cp == 'Z') { /* 'z' is standardized in ISO C 99, but glibc uses 'Z' because the warning facility in gcc-2.95.2 understands only 'Z' (see gcc-2.95.2/gcc/c-common.c:1784). */ if (sizeof (size_t) > sizeof (long)) { /* size_t = long long */ flags += 16; } else if (sizeof (size_t) > sizeof (int)) { /* size_t = long */ flags += 8; } cp++; } else if (*cp == 't') { if (sizeof (ptrdiff_t) > sizeof (long)) { /* ptrdiff_t = long long */ flags += 16; } else if (sizeof (ptrdiff_t) > sizeof (int)) { /* ptrdiff_t = long */ flags += 8; } cp++; } else break; } /* Read the conversion character. */ c = *cp++; switch (c) { case 'd': case 'i': if (flags == 64) type = TYPE_INT64; else if (flags >= 16 || (flags & 4)) type = TYPE_LONGLONGINT; else if (flags >= 8) type = TYPE_LONGINT; else if (flags & 2) type = TYPE_SCHAR; else if (flags & 1) type = TYPE_SHORT; else type = TYPE_INT; break; case 'o': case 'u': case 'x': case 'X': if (flags == 64) type = TYPE_UINT64; else if (flags >= 16 || (flags & 4)) type = TYPE_ULONGLONGINT; else if (flags >= 8) type = TYPE_ULONGINT; else if (flags & 2) type = TYPE_UCHAR; else if (flags & 1) type = TYPE_USHORT; else type = TYPE_UINT; break; case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': if (flags >= 16 || (flags & 4)) type = TYPE_LONGDOUBLE; else type = TYPE_DOUBLE; break; case 'c': if (flags >= 8) goto error; else type = TYPE_CHAR; break; case 's': if (flags >= 8) goto error; else type = TYPE_STRING; break; case 'p': type = TYPE_POINTER; break; case 'n': if (flags >= 16 || (flags & 4)) type = TYPE_COUNT_LONGLONGINT_POINTER; else if (flags >= 8) type = TYPE_COUNT_LONGINT_POINTER; else if (flags & 2) type = TYPE_COUNT_SCHAR_POINTER; else if (flags & 1) type = TYPE_COUNT_SHORT_POINTER; else type = TYPE_COUNT_INT_POINTER; break; case '%': type = TYPE_NONE; break; default: /* Unknown conversion character. */ goto error; } } if (type != TYPE_NONE) { dp->arg_index = arg_index; if (dp->arg_index < 0) dp->arg_index = arg_posn++; REGISTER_ARG (dp->arg_index, type); } dp->conversion = c; dp->dir_end = cp; } d->count++; if (d->count >= d_allocated) { char_directive *memory; d_allocated = 2 * d_allocated; memory = realloc (d->dir, d_allocated * sizeof (char_directive)); if (memory == NULL) /* Out of memory. */ goto error; d->dir = memory; } } } d->dir[d->count].dir_start = cp; d->max_width_length = max_width_length; d->max_precision_length = max_precision_length; return 0; error: if (a->arg) free (a->arg); if (d->dir) free (d->dir); return -1; } static int printf_fetchargs (va_list args, arguments *a) { unsigned int i; argument *ap; for (i = 0, ap = &a->arg[0]; i < a->count; i++, ap++) switch (ap->type) { case TYPE_SCHAR: ap->a.a_schar = va_arg (args, /*signed char*/ int); break; case TYPE_UCHAR: ap->a.a_uchar = va_arg (args, /*unsigned char*/ int); break; case TYPE_SHORT: ap->a.a_short = va_arg (args, /*short*/ int); break; case TYPE_USHORT: ap->a.a_ushort = va_arg (args, /*unsigned short*/ int); break; case TYPE_INT: ap->a.a_int = va_arg (args, int); break; case TYPE_UINT: ap->a.a_uint = va_arg (args, unsigned int); break; case TYPE_LONGINT: ap->a.a_longint = va_arg (args, long int); break; case TYPE_ULONGINT: ap->a.a_ulongint = va_arg (args, unsigned long int); break; case TYPE_LONGLONGINT: ap->a.a_longlongint = va_arg (args, long long int); break; case TYPE_ULONGLONGINT: ap->a.a_ulonglongint = va_arg (args, unsigned long long int); break; case TYPE_INT64: ap->a.a_int64 = va_arg (args, __int64); break; case TYPE_UINT64: ap->a.a_uint64 = va_arg (args, u__int64_t); break; case TYPE_DOUBLE: ap->a.a_double = va_arg (args, double); break; case TYPE_LONGDOUBLE: ap->a.a_longdouble = va_arg (args, long double); break; case TYPE_CHAR: ap->a.a_char = va_arg (args, int); break; case TYPE_STRING: ap->a.a_string = va_arg (args, const char *); break; case TYPE_POINTER: ap->a.a_pointer = va_arg (args, void *); break; case TYPE_COUNT_SCHAR_POINTER: ap->a.a_count_schar_pointer = va_arg (args, signed char *); break; case TYPE_COUNT_SHORT_POINTER: ap->a.a_count_short_pointer = va_arg (args, short *); break; case TYPE_COUNT_INT_POINTER: ap->a.a_count_int_pointer = va_arg (args, int *); break; case TYPE_COUNT_LONGINT_POINTER: ap->a.a_count_longint_pointer = va_arg (args, long int *); break; case TYPE_COUNT_LONGLONGINT_POINTER: ap->a.a_count_longlongint_pointer = va_arg (args, long long int *); break; default: /* Unknown type. */ return -1; } return 0; } static int print_long_long (char *buf, int len, int width,int precision,unsigned long flags,char conversion,unsigned long long number) { int negative = FALSE; char buffer[128]; char *bufferend; char *pointer; int base; static const char *upper = "0123456789ABCDEFX"; static const char *lower = "0123456789abcdefx"; const char *digits; int i; char *p; int count; #define EMIT(c) \ if (p - buf == len - 1) \ { \ *p++ = '\0'; \ return len; \ } \ else \ *p++ = c; p = buf; switch (conversion) { case 'o': base = 8; digits = lower; negative = FALSE; break; case 'x': base = 16; digits = lower; negative = FALSE; break; case 'X': base = 16; digits = upper; negative = FALSE; break; default: base = 10; digits = lower; negative = (long long)number < 0; if (negative) number = -((long long)number); break; } /* Build number */ pointer = bufferend = &buffer[sizeof(buffer) - 1]; *pointer-- = '\0'; for (i = 1; i < (int)sizeof(buffer); i++) { *pointer-- = digits[number % base]; number /= base; if (number == 0) break; } /* Adjust width */ width -= (int)((bufferend - pointer) - 1); /* Adjust precision */ if (precision != -1) { precision -= (int)((bufferend - pointer) - 1); if (precision < 0) precision = 0; flags |= FLAG_ZERO; } /* Adjust width further */ if (negative || (flags & FLAG_SHOWSIGN) || (flags & FLAG_SPACE)) width--; if (flags & FLAG_ALT) { switch (base) { case 16: width -= 2; break; case 8: width--; break; default: break; } } /* Output prefixes spaces if needed */ if (! ((flags & FLAG_LEFT) || ((flags & FLAG_ZERO) && (precision == -1)))) { count = (precision == -1) ? 0 : precision; while (width-- > count) *p++ = ' '; } /* width has been adjusted for signs and alternatives */ if (negative) { EMIT ('-'); } else if (flags & FLAG_SHOWSIGN) { EMIT('+'); } else if (flags & FLAG_SPACE) { EMIT(' '); } if (flags & FLAG_ALT) { switch (base) { case 8: EMIT('0'); break; case 16: EMIT('0'); EMIT(digits[16]); break; default: break; } /* switch base */ } /* Output prefixed zero padding if needed */ if (flags & FLAG_ZERO) { if (precision == -1) precision = width; while (precision-- > 0) { EMIT('0'); width--; } } /* Output the number itself */ while (*(++pointer)) { EMIT(*pointer); } /* Output trailing spaces if needed */ if (flags & FLAG_LEFT) { while (width-- > 0) EMIT(' '); } EMIT('\0'); return (int)(p - buf - 1); } static char *vasnprintf (char *resultbuf, size_t *lengthp, const char *format, va_list args) { char_directives d; arguments a; if (printf_parse (format, &d, &a) < 0) { errno = EINVAL; return NULL; } #define CLEANUP() \ free (d.dir); \ if (a.arg) \ free (a.arg); if (printf_fetchargs (args, &a) < 0) { CLEANUP (); errno = EINVAL; return NULL; } { char *buf = (char *) alloca (7 + d.max_width_length + d.max_precision_length + 6); const char *cp; unsigned int i; char_directive *dp; /* Output string accumulator. */ char *result; size_t allocated; size_t length; if (resultbuf != NULL) { result = resultbuf; allocated = *lengthp; } else { result = NULL; allocated = 0; } length = 0; /* Invariants: result is either == resultbuf or == NULL or malloc-allocated. If length > 0, then result != NULL. */ #define ENSURE_ALLOCATION(needed) \ if ((needed) > allocated) \ { \ char *memory; \ \ allocated = (allocated > 0 ? 2 * allocated : 12); \ if ((needed) > allocated) \ allocated = (needed); \ if (result == resultbuf || result == NULL) \ memory = (char *) malloc (allocated); \ else \ memory = (char *) realloc (result, allocated); \ \ if (memory == NULL) \ { \ if (!(result == resultbuf || result == NULL)) \ free (result); \ CLEANUP (); \ errno = ENOMEM; \ return NULL; \ } \ if (result == resultbuf && length > 0) \ memcpy (memory, result, length); \ result = memory; \ } for (cp = format, i = 0, dp = &d.dir[0]; ; cp = dp->dir_end, i++, dp++) { if (cp != dp->dir_start) { size_t n = dp->dir_start - cp; ENSURE_ALLOCATION (length + n); memcpy (result + length, cp, n); length += n; } if (i == d.count) break; /* Execute a single directive. */ if (dp->conversion == '%') { if (!(dp->arg_index < 0)) abort (); ENSURE_ALLOCATION (length + 1); result[length] = '%'; length += 1; } else { if (!(dp->arg_index >= 0)) abort (); if (dp->conversion == 'n') { switch (a.arg[dp->arg_index].type) { case TYPE_COUNT_SCHAR_POINTER: *a.arg[dp->arg_index].a.a_count_schar_pointer = (char)length; break; case TYPE_COUNT_SHORT_POINTER: *a.arg[dp->arg_index].a.a_count_short_pointer = (short)length; break; case TYPE_COUNT_INT_POINTER: *a.arg[dp->arg_index].a.a_count_int_pointer = (int)length; break; case TYPE_COUNT_LONGINT_POINTER: *a.arg[dp->arg_index].a.a_count_longint_pointer = (long)length; break; case TYPE_COUNT_LONGLONGINT_POINTER: *a.arg[dp->arg_index].a.a_count_longlongint_pointer = length; break; default: abort (); } } else { arg_type type = a.arg[dp->arg_index].type; char *p; unsigned int prefix_count; int prefixes[2]; unsigned int tmp_length; char tmpbuf[700]; char *tmp; /* Allocate a temporary buffer of sufficient size for calling sprintf. */ { unsigned int width; unsigned int precision; width = 0; if (dp->width_start != dp->width_end) { if (dp->width_arg_index >= 0) { int arg; if (!(a.arg[dp->width_arg_index].type == TYPE_INT)) abort (); arg = a.arg[dp->width_arg_index].a.a_int; width = (arg < 0 ? -arg : arg); } else { const char *digitp = dp->width_start; do width = width * 10 + (*digitp++ - '0'); while (digitp != dp->width_end); } } precision = 6; if (dp->precision_start != dp->precision_end) { if (dp->precision_arg_index >= 0) { int arg; if (!(a.arg[dp->precision_arg_index].type == TYPE_INT)) abort (); arg = a.arg[dp->precision_arg_index].a.a_int; precision = (arg < 0 ? 0 : arg); } else { const char *digitp = dp->precision_start + 1; precision = 0; while (digitp != dp->precision_end) precision = precision * 10 + (*digitp++ - '0'); } } switch (dp->conversion) { case 'd': case 'i': case 'u': if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT) tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.30103 /* binary -> decimal */ * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ else if (type == TYPE_LONGINT || type == TYPE_ULONGINT) tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.30103 /* binary -> decimal */ * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ else tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.30103 /* binary -> decimal */ * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ break; case 'o': if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT) tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.333334 /* binary -> octal */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ else if (type == TYPE_LONGINT || type == TYPE_ULONGINT) tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.333334 /* binary -> octal */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ else tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.333334 /* binary -> octal */ ) + 1 /* turn floor into ceil */ + 1; /* account for leading sign */ break; case 'x': case 'X': if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT) tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.25 /* binary -> hexadecimal */ ) + 1 /* turn floor into ceil */ + 2; /* account for leading sign or alternate form */ else if (type == TYPE_INT64 || type == TYPE_UINT64) tmp_length = (unsigned int) (sizeof (u__int64_t) * CHAR_BIT * 0.25 /* binary -> hexadecimal */ ) + 1 /* turn floor into ceil */ + 2; /* account for leading sign or alternate form */ else if (type == TYPE_LONGINT || type == TYPE_ULONGINT) tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.25 /* binary -> hexadecimal */ ) + 1 /* turn floor into ceil */ + 2; /* account for leading sign or alternate form */ else tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.25 /* binary -> hexadecimal */ ) + 1 /* turn floor into ceil */ + 2; /* account for leading sign or alternate form */ break; case 'f': case 'F': if (type == TYPE_LONGDOUBLE) tmp_length = (unsigned int) (LDBL_MAX_EXP * 0.30103 /* binary -> decimal */ * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ + precision + 10; /* sign, decimal point etc. */ else tmp_length = (unsigned int) (DBL_MAX_EXP * 0.30103 /* binary -> decimal */ * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ + precision + 10; /* sign, decimal point etc. */ break; case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': tmp_length = precision + 12; /* sign, decimal point, exponent etc. */ break; case 'c': tmp_length = 1; break; case 's': tmp_length = (unsigned int)strlen (a.arg[dp->arg_index].a.a_string); break; case 'p': tmp_length = (unsigned int) (sizeof (void *) * CHAR_BIT * 0.25 /* binary -> hexadecimal */ ) + 1 /* turn floor into ceil */ + 2; /* account for leading 0x */ break; default: abort (); } if (tmp_length < width) tmp_length = width; tmp_length++; /* account for trailing NUL */ } if (tmp_length <= sizeof (tmpbuf)) tmp = tmpbuf; else { tmp = (char *) malloc (tmp_length); if (tmp == NULL) { /* Out of memory. */ if (!(result == resultbuf || result == NULL)) free (result); CLEANUP (); errno = ENOMEM; return NULL; } } /* Construct the format string for calling snprintf or sprintf. */ p = buf; *p++ = '%'; if (dp->flags & FLAG_GROUP) *p++ = '\''; if (dp->flags & FLAG_LEFT) *p++ = '-'; if (dp->flags & FLAG_SHOWSIGN) *p++ = '+'; if (dp->flags & FLAG_SPACE) *p++ = ' '; if (dp->flags & FLAG_ALT) *p++ = '#'; if (dp->flags & FLAG_ZERO) *p++ = '0'; if (dp->width_start != dp->width_end) { size_t n = dp->width_end - dp->width_start; memcpy (p, dp->width_start, n); p += n; } if (dp->precision_start != dp->precision_end) { size_t n = dp->precision_end - dp->precision_start; memcpy (p, dp->precision_start, n); p += n; } switch (type) { case TYPE_INT64: case TYPE_UINT64: *p++ = 'I'; *p++ = '6'; *p++ = '4'; break; case TYPE_LONGLONGINT: case TYPE_ULONGLONGINT: *p++ = 'I'; *p++ = '6'; *p++ = '4'; break; *p++ = 'l'; /*FALLTHROUGH*/ case TYPE_LONGINT: case TYPE_ULONGINT: *p++ = 'l'; break; case TYPE_LONGDOUBLE: *p++ = 'L'; break; default: break; } *p = dp->conversion; p[1] = '\0'; /* Construct the arguments for calling snprintf or sprintf. */ prefix_count = 0; if (dp->width_arg_index >= 0) { if (!(a.arg[dp->width_arg_index].type == TYPE_INT)) abort (); prefixes[prefix_count++] = a.arg[dp->width_arg_index].a.a_int; } if (dp->precision_arg_index >= 0) { if (!(a.arg[dp->precision_arg_index].type == TYPE_INT)) abort (); prefixes[prefix_count++] = a.arg[dp->precision_arg_index].a.a_int; } for (;;) { size_t maxlen; int count; int retcount; maxlen = allocated - length; count = -1; retcount = 0; #define SNPRINTF_BUF(arg) \ switch (prefix_count) \ { \ case 0: \ count = sprintf (tmp, buf, arg); \ break; \ case 1: \ count = sprintf (tmp, buf, prefixes[0], arg); \ break; \ case 2: \ count = sprintf (tmp, buf, prefixes[0], prefixes[1],\ arg); \ break; \ default: \ abort (); \ } switch (type) { case TYPE_SCHAR: { int arg = a.arg[dp->arg_index].a.a_schar; SNPRINTF_BUF (arg); } break; case TYPE_UCHAR: { unsigned int arg = a.arg[dp->arg_index].a.a_uchar; SNPRINTF_BUF (arg); } break; case TYPE_SHORT: { int arg = a.arg[dp->arg_index].a.a_short; SNPRINTF_BUF (arg); } break; case TYPE_USHORT: { unsigned int arg = a.arg[dp->arg_index].a.a_ushort; SNPRINTF_BUF (arg); } break; case TYPE_INT: { int arg = a.arg[dp->arg_index].a.a_int; SNPRINTF_BUF (arg); } break; case TYPE_UINT: { unsigned int arg = a.arg[dp->arg_index].a.a_uint; SNPRINTF_BUF (arg); } break; case TYPE_LONGINT: { long int arg = a.arg[dp->arg_index].a.a_longint; SNPRINTF_BUF (arg); } break; case TYPE_ULONGINT: { unsigned long int arg = a.arg[dp->arg_index].a.a_ulongint; SNPRINTF_BUF (arg); } break; case TYPE_INT64: { __int64 arg = a.arg[dp->arg_index].a.a_int64; SNPRINTF_BUF (arg); } break; case TYPE_UINT64: { u__int64_t arg = a.arg[dp->arg_index].a.a_uint64; SNPRINTF_BUF (arg); } break; case TYPE_LONGLONGINT: case TYPE_ULONGLONGINT: { unsigned long long int arg = a.arg[dp->arg_index].a.a_ulonglongint; int width; int precision; width = 0; if (dp->width_start != dp->width_end) { if (dp->width_arg_index >= 0) { int arg; if (!(a.arg[dp->width_arg_index].type == TYPE_INT)) abort (); arg = a.arg[dp->width_arg_index].a.a_int; width = (arg < 0 ? -arg : arg); } else { const char *digitp = dp->width_start; do width = width * 10 + (*digitp++ - '0'); while (digitp != dp->width_end); } } precision = -1; if (dp->precision_start != dp->precision_end) { if (dp->precision_arg_index >= 0) { int arg; if (!(a.arg[dp->precision_arg_index].type == TYPE_INT)) abort (); arg = a.arg[dp->precision_arg_index].a.a_int; precision = (arg < 0 ? 0 : arg); } else { const char *digitp = dp->precision_start + 1; precision = 0; do precision = precision * 10 + (*digitp++ - '0'); while (digitp != dp->precision_end); } } count = print_long_long (tmp, tmp_length, width, precision, dp->flags, dp->conversion, arg); } break; case TYPE_DOUBLE: { double arg = a.arg[dp->arg_index].a.a_double; SNPRINTF_BUF (arg); } break; case TYPE_LONGDOUBLE: { long double arg = a.arg[dp->arg_index].a.a_longdouble; SNPRINTF_BUF (arg); } break; case TYPE_CHAR: { int arg = a.arg[dp->arg_index].a.a_char; SNPRINTF_BUF (arg); } break; case TYPE_STRING: { const char *arg = a.arg[dp->arg_index].a.a_string; SNPRINTF_BUF (arg); } break; case TYPE_POINTER: { void *arg = a.arg[dp->arg_index].a.a_pointer; SNPRINTF_BUF (arg); } break; default: abort (); } /* Attempt to handle failure. */ if (count < 0) { if (!(result == resultbuf || result == NULL)) free (result); CLEANUP (); errno = EINVAL; return NULL; } if (count >= (int)tmp_length) /* tmp_length was incorrectly calculated - fix the code above! */ abort (); /* Make room for the result. */ if (count >= (int)maxlen) { /* Need at least count bytes. But allocate proportionally, to avoid looping eternally if snprintf() reports a too small count. */ size_t n = length + count; if (n < 2 * allocated) n = 2 * allocated; ENSURE_ALLOCATION (n); } /* Append the sprintf() result. */ memcpy (result + length, tmp, count); if (tmp != tmpbuf) free (tmp); length += count; break; } } } } /* Add the final NUL. */ ENSURE_ALLOCATION (length + 1); result[length] = '\0'; if (result != resultbuf && length + 1 < allocated) { /* Shrink the allocated memory if possible. */ char *memory; memory = (char *) realloc (result, length + 1); if (memory != NULL) result = memory; } CLEANUP (); *lengthp = length; return result; } } #define _XT(str) str #define CHR_LT_ _XT('<') #define CHR_GT_ _XT('>') #define CHR_AMP_ _XT('&') #define CHR_APOS_ _XT('\'') #define CHR_QUOT_ _XT('"') #define XML_LT_ _XT("<") #define XML_GT_ _XT(">") #define XML_AMP_ _XT("&") #define XML_APOS_ _XT("'") #define XML_QUOT_ _XT(""") enum { LT_SIZE_ = 4, /**< Size of < */ GT_SIZE_ = 4, /**< Size of > */ AMP_SIZE_ = 5, /**< Size of & */ APOS_SIZE_ = 6, /**< Size of ' */ QUOT_SIZE_ = 6 /**< Size of " */ }; TOOLKIT_API char *str_xml_escape(const char *src) { char *p = (char *) src; char *escaped = NULL; unsigned int len = 0; assert (src != NULL); /* We first need to calculate the size of the new escaped string. */ while (*p != _XT('\0')) { switch (*p) { case CHR_LT_: len += LT_SIZE_; break; case CHR_GT_: len += GT_SIZE_; break; case CHR_AMP_: len += AMP_SIZE_; break; case CHR_APOS_: len += APOS_SIZE_; break; case CHR_QUOT_: len += QUOT_SIZE_; break; default: len += 1; break; } p += 1; } /* Allocate new string (if necessary). */ escaped = calloc (len + 1, sizeof (char)); /* Append characters to new string, escaping the needed ones. */ p = (char *) src; len = 0; while (*p != _XT('\0')) { switch (*p) { case CHR_LT_: memcpy (&escaped[len], XML_LT_, LT_SIZE_); len += LT_SIZE_; break; case CHR_GT_: memcpy (&escaped[len], XML_GT_, GT_SIZE_); len += GT_SIZE_; break; case CHR_AMP_: memcpy (&escaped[len], XML_AMP_, AMP_SIZE_); len += AMP_SIZE_; break; case CHR_APOS_: memcpy (&escaped[len], XML_APOS_, APOS_SIZE_); len += APOS_SIZE_; break; case CHR_QUOT_: memcpy (&escaped[len], XML_QUOT_, QUOT_SIZE_); len += QUOT_SIZE_; break; default: escaped[len] = *p; len += 1; break; } p += 1; } return escaped; } /*! * @brief * @param[in] cmdstart * @param[out] argv, args, numargs, numchars * @return : */ TOOLKIT_API void str_parse_cmdline (char *cmdstart, char **argv, char *args, int *numargs, int *numchars) { char *p; char c; int inquote; /* 1 = inside quotes */ int copychar; /* 1 = copy char to *args */ unsigned numslash; /* num of backslashes seen */ TOOLKIT_ASSERT(numargs); TOOLKIT_ASSERT(numchars); *numchars = 0; *numargs = 1; /* the program name at least */ /* first scan the program name, copy it, and count the bytes */ p = cmdstart; if (argv) *argv++ = args; /* A quoted program name is handled here. The handling is much simpler than for other arguments. Basically, whatever lies between the leading double-quote and next one, or a terminal null character is simply accepted. Fancier handling is not required because the program name must be a legal NTFS/HPFS file name. Note that the double-quote characters are not copied, nor do they contribute to numchars. */ inquote = FALSE; do { if (*p == '\"' ) { inquote = !inquote; c = (char) *p++; continue; } ++*numchars; if (args) *args++ = *p; /*char in cmdstart, the capacity of {args} equals with numchars*/ c = (char) *p++; } while ( (c != 0 && (inquote || (c !=' ' && c != '\t'))) ); if ( c == 0 ) { p--; } else { if (args) *(args-1) = 0; } inquote = 0; /* loop on each argument */ for(;;) { if ( *p ) { while (*p == ' ' || *p == '\t') ++p; } if (*p == 0) break; /* end of args */ /* scan an argument */ if (argv) *argv++ = args; /* store ptr to arg */ ++*numargs; /* loop through scanning one argument */ for (;;) { copychar = 1; /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote 2N+1 backslashes + " ==> N backslashes + literal " N backslashes ==> N backslashes */ numslash = 0; while (*p == '\\') { /* count number of backslashes for use below */ ++p; ++numslash; } if (*p == '\"') { /* if 2N backslashes before, start/end quote, otherwise copy literally */ if (numslash % 2 == 0) { if (inquote && p[1] == '\"') { p++; /* Double quote inside quoted string */ } else { /* skip first quote char and copy second */ copychar = 0; /* don't copy quote */ inquote = !inquote; } } numslash /= 2; /* divide numslash by two */ } /* copy slashes */ while (numslash--) { if (args) *args++ = '\\'; ++*numchars; } /* if at end of arg, break loop */ if (*p == 0 || (!inquote && (*p == ' ' || *p == '\t'))) break; if (copychar) { if (args) *args++ = *p; ++*numchars; } ++p; } /* null-terminate the argument */ if (args) *args++ = 0; /* terminate string */ ++*numchars; } /* We put one last argument in -- a null ptr */ if (argv) *argv++ = NULL; ++*numargs; }