From 6057f6e378adc95e23c4de1d629b066df4c64f5c Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Tue, 19 Feb 2008 00:58:59 +0000 Subject: [PATCH] Replace use of (v)snprintf with (v)asprintf. This has several advantages: 1. (Mostly) easier to program with as the allocation is done for you. (Sometimes it's a little more complicated to get the deallocation right). The amount of code removed as a result by this commit is greater than the amount introduced (excluding the (v)asprintf implementation). 2. Safer: no buffer overflows. 3. Works on all platforms: BSD-licensed mature xnprintf code used (I've been using this for some years in Zile with no problems). No more overflows due to the previous bogus snprintf/vsnprintf fallbacks. Also add and use a MAX() macro in the same vein as MIN(). --- configure.ac | 4 +- src/apprentice.c | 66 ++--- src/asprintf.c | 43 ++++ src/file.h | 24 +- src/funcs.c | 106 +++----- src/magic.c | 20 +- src/softmagic.c | 16 +- src/vasprintf.c | 631 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 767 insertions(+), 143 deletions(-) create mode 100644 src/asprintf.c create mode 100644 src/vasprintf.c diff --git a/configure.ac b/configure.ac index 76869e7b..8ad8dc3e 100644 --- a/configure.ac +++ b/configure.ac @@ -107,10 +107,10 @@ typedef long int64_t; ]) dnl Checks for functions -AC_CHECK_FUNCS(mmap strerror strndup strtoul mbrtowc mkstemp utimes utime wcwidth snprintf vsnprintf strtof) +AC_CHECK_FUNCS(mmap strerror strndup strtoul mbrtowc mkstemp utimes utime wcwidth strtof) dnl Provide implementation of some required functions if necessary -AC_REPLACE_FUNCS(getopt_long) +AC_REPLACE_FUNCS(getopt_long asprintf vasprintf) dnl Checks for libraries AC_CHECK_LIB(z,gzopen) diff --git a/src/apprentice.c b/src/apprentice.c index 70a666ec..00c18471 100644 --- a/src/apprentice.c +++ b/src/apprentice.c @@ -47,7 +47,7 @@ #endif #ifndef lint -FILE_RCSID("@(#)$File: apprentice.c,v 1.120 2008/02/18 21:45:58 rrt Exp $") +FILE_RCSID("@(#)$File: apprentice.c,v 1.121 2008/02/18 21:50:24 rrt Exp $") #endif /* lint */ #define EATAB {while (isascii((unsigned char) *l) && \ @@ -106,7 +106,7 @@ private void bs1(struct magic *); private uint16_t swap2(uint16_t); private uint32_t swap4(uint32_t); private uint64_t swap8(uint64_t); -private char *mkdbname(const char *, char *, size_t, int); +private void mkdbname(const char *, char **, int); private int apprentice_map(struct magic_set *, struct magic **, uint32_t *, const char *); private int apprentice_compile(struct magic_set *, struct magic **, uint32_t *, @@ -360,8 +360,7 @@ file_apprentice(struct magic_set *ms, const char *fn, int action) if (*fn == '\0') break; file_err = apprentice_1(ms, fn, action, mlist); - if (file_err > errs) - errs = file_err; + errs = MAX(errs, file_err); fn = p; } if (errs == -1) { @@ -1765,40 +1764,40 @@ apprentice_map(struct magic_set *ms, struct magic **magicp, uint32_t *nmagicp, uint32_t *ptr; uint32_t version; int needsbyteswap; - char buf[MAXPATHLEN]; - char *dbname = mkdbname(fn, buf, sizeof(buf), 0); + char *dbname = NULL; void *mm = NULL; + mkdbname(fn, &dbname, 0); if (dbname == NULL) - return -1; + goto error2; if ((fd = open(dbname, O_RDONLY|O_BINARY)) == -1) - return -1; + goto error2; if (fstat(fd, &st) == -1) { file_error(ms, errno, "cannot stat `%s'", dbname); - goto error; + goto error1; } if (st.st_size < 8) { file_error(ms, 0, "file `%s' is too small", dbname); - goto error; + goto error1; } #ifdef QUICK if ((mm = mmap(0, (size_t)st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FILE, fd, (off_t)0)) == MAP_FAILED) { file_error(ms, errno, "cannot map `%s'", dbname); - goto error; + goto error1; } #define RET 2 #else if ((mm = malloc((size_t)st.st_size)) == NULL) { file_oomem(ms, (size_t)st.st_size); - goto error; + goto error1; } if (read(fd, mm, (size_t)st.st_size) != (size_t)st.st_size) { file_badread(ms); - goto error; + goto error1; } #define RET 1 #endif @@ -1809,7 +1808,7 @@ apprentice_map(struct magic_set *ms, struct magic **magicp, uint32_t *nmagicp, if (*ptr != MAGICNO) { if (swap4(*ptr) != MAGICNO) { file_error(ms, 0, "bad magic in `%s'"); - goto error; + goto error1; } needsbyteswap = 1; } else @@ -1822,7 +1821,7 @@ apprentice_map(struct magic_set *ms, struct magic **magicp, uint32_t *nmagicp, file_error(ms, 0, "File %d.%d supports only %d version magic " "files. `%s' is version %d", FILE_VERSION_MAJOR, patchlevel, VERSIONNO, dbname, version); - goto error; + goto error1; } *nmagicp = (uint32_t)(st.st_size / sizeof(struct magic)); if (*nmagicp > 0) @@ -1830,9 +1829,10 @@ apprentice_map(struct magic_set *ms, struct magic **magicp, uint32_t *nmagicp, (*magicp)++; if (needsbyteswap) byteswap(*magicp, *nmagicp); + free(dbname); return RET; -error: +error1: if (fd != -1) (void)close(fd); if (mm) { @@ -1845,6 +1845,8 @@ error: *magicp = NULL; *nmagicp = 0; } +error2: + free(dbname); return -1; } @@ -1859,44 +1861,49 @@ apprentice_compile(struct magic_set *ms, struct magic **magicp, uint32_t *nmagicp, const char *fn) { int fd; - char buf[MAXPATHLEN]; - char *dbname = mkdbname(fn, buf, sizeof(buf), 1); + char *dbname; + int rv = -1; + + mkdbname(fn, &dbname, 1); if (dbname == NULL) - return -1; + goto out; if ((fd = open(dbname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0644)) == -1) { file_error(ms, errno, "cannot open `%s'", dbname); - return -1; + goto out; } if (write(fd, ar, sizeof(ar)) != (ssize_t)sizeof(ar)) { file_error(ms, errno, "error writing `%s'", dbname); - return -1; + goto out; } if (lseek(fd, (off_t)sizeof(struct magic), SEEK_SET) != sizeof(struct magic)) { file_error(ms, errno, "error seeking `%s'", dbname); - return -1; + goto out; } if (write(fd, *magicp, (sizeof(struct magic) * *nmagicp)) != (ssize_t)(sizeof(struct magic) * *nmagicp)) { file_error(ms, errno, "error writing `%s'", dbname); - return -1; + goto out; } (void)close(fd); - return 0; + rv = 0; +out: + free(dbname); + return rv; } private const char ext[] = ".mgc"; /* * make a dbname */ -private char * -mkdbname(const char *fn, char *buf, size_t bufsiz, int strip) +private void +mkdbname(const char *fn, char **buf, int strip) { if (strip) { const char *p; @@ -1904,8 +1911,11 @@ mkdbname(const char *fn, char *buf, size_t bufsiz, int strip) fn = ++p; } - (void)snprintf(buf, bufsiz, "%s%s", fn, ext); - return buf; + (void)asprintf(buf, "%s%s", fn, ext); + if (*buf && strlen(*buf) > MAXPATHLEN) { + free(*buf); + *buf = NULL; + } } /* diff --git a/src/asprintf.c b/src/asprintf.c new file mode 100644 index 00000000..c103cf18 --- /dev/null +++ b/src/asprintf.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) Ian F. Darwin 1986-1995. + * Software written by Ian F. Darwin and others; + * maintained 1995-present by Christos Zoulas and others. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +int vasprintf(char **ptr, const char *format_string, va_list vargs); + +int asprintf(char **ptr, const char *fmt, ...) +{ + va_list vargs; + int retval; + + va_start(vargs, fmt); + retval = vasprintf(ptr, fmt, vargs); + va_end(vargs); + + return retval; +} diff --git a/src/file.h b/src/file.h index bfcc4e2b..0fae7d74 100644 --- a/src/file.h +++ b/src/file.h @@ -27,7 +27,7 @@ */ /* * file.h - definitions for file(1) program - * @(#)$File: file.h,v 1.95 2008/02/12 16:30:48 rrt Exp $ + * @(#)$File: file.h,v 1.96 2008/02/17 19:28:54 rrt Exp $ */ #ifndef __file_h__ @@ -50,6 +50,7 @@ #include /* Do this here and now, because struct stat gets re-defined on solaris */ #include +#include #define ENABLE_CONDITIONALS @@ -83,6 +84,10 @@ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + #ifndef HOWMANY # define HOWMANY (256 * 1024) /* how much of the file to look at */ #endif @@ -280,14 +285,8 @@ struct magic_set { } *li; } c; struct out { - /* Accumulation buffer */ - char *buf; - char *ptr; - size_t left; - size_t size; - /* Printable buffer */ - char *pbuf; - size_t psize; + char *buf; /* Accumulation buffer */ + char *pbuf; /* Printable buffer */ } o; uint32_t offset; int error; @@ -357,8 +356,11 @@ extern char *sys_errlist[]; #define strtoul(a, b, c) strtol(a, b, c) #endif -#ifndef HAVE_SNPRINTF -int snprintf(char *, size_t, const char *, ...); +#ifndef HAVE_VASPRINTF +int vasprintf(char **ptr, const char *format_string, va_list vargs); +#endif +#ifndef HAVE_ASPRINTF +int asprintf(char **ptr, const char *format_string, ...); #endif #if defined(HAVE_MMAP) && defined(HAVE_SYS_MMAN_H) && !defined(QUICK) diff --git a/src/funcs.c b/src/funcs.c index b0ce090e..67f01ceb 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -38,55 +38,38 @@ #endif #ifndef lint -FILE_RCSID("@(#)$File: funcs.c,v 1.36 2008/02/04 16:33:46 christos Exp $") +FILE_RCSID("@(#)$File: funcs.c,v 1.37 2008/02/07 00:58:52 christos Exp $") #endif /* lint */ -#ifndef HAVE_VSNPRINTF -int vsnprintf(char *, size_t, const char *, va_list); -#endif - /* - * Like printf, only we print to a buffer and advance it. + * Like printf, only we append to a buffer. */ protected int file_printf(struct magic_set *ms, const char *fmt, ...) { va_list ap; size_t size; - ssize_t len; - char *buf; + int len; + char *buf, *newstr; va_start(ap, fmt); - - len = vsnprintf(ms->o.ptr, ms->o.left, fmt, ap); - if (len == -1) + len = vasprintf(&buf, fmt, ap); + if (len < 0) goto out; - if (len >= (ssize_t)ms->o.left) { - long diff; /* XXX: really ptrdiff_t */ - - va_end(ap); - size = (ms->o.size - ms->o.left) + len + 1024; - if ((buf = realloc(ms->o.buf, size)) == NULL) { - file_oomem(ms, size); - return -1; - } - diff = ms->o.ptr - ms->o.buf; - ms->o.ptr = buf + diff; - ms->o.buf = buf; - ms->o.left = size - diff; - ms->o.size = size; + va_end(ap); - va_start(ap, fmt); - len = vsnprintf(ms->o.ptr, ms->o.left, fmt, ap); - if (len == -1) + if (ms->o.buf != NULL) { + len = asprintf(&newstr, "%s%s", ms->o.buf, buf); + free(buf); + if (len < 0) goto out; + free(ms->o.buf); + buf = newstr; } - va_end(ap); - ms->o.ptr += len; - ms->o.left -= len; + ms->o.buf = buf; return 0; out: - file_error(ms, errno, "vsnprintf failed"); + file_error(ms, errno, "vasprintf failed"); return -1; } @@ -98,21 +81,17 @@ private void file_error_core(struct magic_set *ms, int error, const char *f, va_list va, uint32_t lineno) { - size_t len; /* Only the first error is ok */ if (ms->haderr) return; - len = 0; if (lineno != 0) { - (void)snprintf(ms->o.buf, ms->o.size, "line %u: ", lineno); - len = strlen(ms->o.buf); - } - (void)vsnprintf(ms->o.buf + len, ms->o.size - len, f, va); - if (error > 0) { - len = strlen(ms->o.buf); - (void)snprintf(ms->o.buf + len, ms->o.size - len, " (%s)", - strerror(error)); + free(ms->o.buf); + ms->o.buf = NULL; + file_printf(ms, "line %u: ", lineno); } + file_printf(ms, f, va); + if (error > 0) + file_printf(ms, " (%s)", strerror(error)); ms->haderr++; ms->error = error; } @@ -240,8 +219,7 @@ file_reset(struct magic_set *ms) file_error(ms, 0, "no magic files loaded"); return -1; } - ms->o.ptr = ms->o.buf; - ms->o.left = ms->o.size; + ms->o.buf = NULL; ms->haderr = 0; ms->error = -1; return 0; @@ -267,21 +245,18 @@ file_getbuffer(struct magic_set *ms) if (ms->flags & MAGIC_RAW) return ms->o.buf; - len = ms->o.size - ms->o.left; /* * 4 is for octal representation, + 1 is for NUL */ + len = strlen(ms->o.buf); if (len > (SIZE_MAX - 1) / 4) { file_oomem(ms, len); return NULL; } psize = len * 4 + 1; - if (ms->o.psize < psize) { - if ((pbuf = realloc(ms->o.pbuf, psize)) == NULL) { - file_oomem(ms, psize); - return NULL; - } - ms->o.psize = psize; - ms->o.pbuf = pbuf; + if ((pbuf = realloc(ms->o.pbuf, psize)) == NULL) { + file_oomem(ms, psize); + return NULL; } + ms->o.pbuf = pbuf; #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH) { @@ -294,7 +269,7 @@ file_getbuffer(struct magic_set *ms) np = ms->o.pbuf; op = ms->o.buf; - eop = op + strlen(ms->o.buf); + eop = op + len; while (op < eop) { bytesconsumed = mbrtowc(&nextchar, op, @@ -354,28 +329,3 @@ file_check_mem(struct magic_set *ms, unsigned int level) #endif /* ENABLE_CONDITIONALS */ return 0; } -/* - * Yes these wrappers suffer from buffer overflows, but if your OS does not - * have the real functions, maybe you should consider replacing your OS? - */ -#ifndef HAVE_VSNPRINTF -int -vsnprintf(char *buf, size_t len, const char *fmt, va_list ap) -{ - return vsprintf(buf, fmt, ap); -} -#endif - -#ifndef HAVE_SNPRINTF -/*ARGSUSED*/ -int -snprintf(char *buf, size_t len, const char *fmt, ...) -{ - int rv; - va_list ap; - va_start(ap, fmt); - rv = vsprintf(buf, fmt, ap); - va_end(ap); - return rv; -} -#endif diff --git a/src/magic.c b/src/magic.c index ee1cdbe6..8ecbb8f0 100644 --- a/src/magic.c +++ b/src/magic.c @@ -63,7 +63,7 @@ #include "patchlevel.h" #ifndef lint -FILE_RCSID("@(#)$File: magic.c,v 1.48 2008/02/07 00:58:52 christos Exp $") +FILE_RCSID("@(#)$File: magic.c,v 1.49 2008/02/17 19:28:54 rrt Exp $") #endif /* lint */ #ifndef PIPE_BUF @@ -103,20 +103,14 @@ magic_open(int flags) if (magic_setflags(ms, flags) == -1) { errno = EINVAL; - goto free1; + goto free; } - ms->o.ptr = ms->o.buf = malloc(ms->o.left = ms->o.size = 1024); - if (ms->o.buf == NULL) - goto free1; - - ms->o.pbuf = malloc(ms->o.psize = 1024); - if (ms->o.pbuf == NULL) - goto free2; + ms->o.buf = ms->o.pbuf = NULL; ms->c.li = malloc((ms->c.len = 10) * sizeof(*ms->c.li)); if (ms->c.li == NULL) - goto free3; + goto free; ms->haderr = 0; ms->error = -1; @@ -124,11 +118,7 @@ magic_open(int flags) ms->file = "unknown"; ms->line = 0; return ms; -free3: - free(ms->o.pbuf); -free2: - free(ms->o.buf); -free1: +free: free(ms); return NULL; } diff --git a/src/softmagic.c b/src/softmagic.c index e68579a3..9926acd8 100644 --- a/src/softmagic.c +++ b/src/softmagic.c @@ -38,7 +38,7 @@ #ifndef lint -FILE_RCSID("@(#)$File: softmagic.c,v 1.110 2008/02/17 19:28:54 rrt Exp $") +FILE_RCSID("@(#)$File: softmagic.c,v 1.111 2008/02/18 00:43:45 rrt Exp $") #endif /* lint */ private int match(struct magic_set *, struct magic *, uint32_t, @@ -321,7 +321,7 @@ mprint(struct magic_set *ms, struct magic *m) float vf; double vd; int64_t t = 0; - char buf[512]; + char *buf; union VALUETYPE *p = &ms->ms_value; switch (m->type) { @@ -331,8 +331,7 @@ mprint(struct magic_set *ms, struct magic *m) case -1: return -1; case 1: - if (snprintf(buf, sizeof(buf), "%c", - (unsigned char)v) < 0) + if (asprintf(&buf, "%c", (unsigned char)v) < 0) return -1; if (file_printf(ms, MAGIC_DESC, buf) == -1) return -1; @@ -353,8 +352,7 @@ mprint(struct magic_set *ms, struct magic *m) case -1: return -1; case 1: - if (snprintf(buf, sizeof(buf), "%hu", - (unsigned short)v) < 0) + if (asprintf(&buf, "%hu", (unsigned short)v) < 0) return -1; if (file_printf(ms, MAGIC_DESC, buf) == -1) return -1; @@ -376,7 +374,7 @@ mprint(struct magic_set *ms, struct magic *m) case -1: return -1; case 1: - if (snprintf(buf, sizeof(buf), "%u", (uint32_t)v) < 0) + if (asprintf(&buf, "%u", (uint32_t)v) < 0) return -1; if (file_printf(ms, MAGIC_DESC, buf) == -1) return -1; @@ -462,7 +460,7 @@ mprint(struct magic_set *ms, struct magic *m) case -1: return -1; case 1: - if (snprintf(buf, sizeof(buf), "%g", vf) < 0) + if (asprintf(&buf, "%g", vf) < 0) return -1; if (file_printf(ms, MAGIC_DESC, buf) == -1) return -1; @@ -483,7 +481,7 @@ mprint(struct magic_set *ms, struct magic *m) case -1: return -1; case 1: - if (snprintf(buf, sizeof(buf), "%g", vd) < 0) + if (asprintf(&buf, "%g", vd) < 0) return -1; if (file_printf(ms, MAGIC_DESC, buf) == -1) return -1; diff --git a/src/vasprintf.c b/src/vasprintf.c new file mode 100644 index 00000000..acb6eda0 --- /dev/null +++ b/src/vasprintf.c @@ -0,0 +1,631 @@ +/* + * Copyright (c) Ian F. Darwin 1986-1995. + * Software written by Ian F. Darwin and others; + * maintained 1995-present by Christos Zoulas and others. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/*########################################################################### + # # + # vasprintf # + # # + # Copyright (c) 2002-2005 David TAILLANDIER # + # # + ###########################################################################*/ + +/* + +This software is distributed under the "modified BSD licence". + +This software is also released with GNU license (GPL) in another file (same +source-code, only license differ). + + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. Redistributions in binary +form must reproduce the above copyright notice, this list of conditions and +the following disclaimer in the documentation and/or other materials +provided with the distribution. The name of the author may not be used to +endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +==================== + +Hacked from xnprintf version of 26th February 2005 to provide only +vasprintf by Reuben Thomas . + +==================== + + +'printf' function family use the following format string: + +%[flag][width][.prec][modifier]type + +%% is the escape sequence to print a '%' +% followed by an unknown format will print the characters without +trying to do any interpretation + +flag: none + - # (blank) +width: n 0n * +prec: none .0 .n .* +modifier: F N L h l ('F' and 'N' are ms-dos/16-bit specific) +type: d i o u x X f e g E G c s p n + + +The function needs to allocate memory to store the full text before to +actually writting it. i.e if you want to fnprintf() 1000 characters, the +functions will allocate 1000 bytes. +This behaviour can be modified: you have to customise the code to flush the +internal buffer (writing to screen or file) when it reach a given size. Then +the buffer can have a shorter length. But what? If you really need to write +HUGE string, don't use printf! +During the process, some other memory is allocated (1024 bytes minimum) +to handle the output of partial sprintf() calls. If you have only 10000 bytes +free in memory, you *may* not be able to nprintf() a 8000 bytes-long text. + +note: if a buffer overflow occurs, exit() is called. This situation should +never appear ... but if you want to be *really* sure, you have to modify the +code to handle those situations (only one place to modify). +A buffer overflow can only occur if your sprintf() do strange things or when +you use strange formats. + +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#define ALLOC_CHUNK 2048 +#define ALLOC_SECURITY_MARGIN 1024 /* big value because some platforms have very big 'G' exponent */ +#if ALLOC_CHUNK < ALLOC_SECURITY_MARGIN +# error !!! ALLOC_CHUNK < ALLOC_SECURITY_MARGIN !!! +#endif +/* note: to have some interest, ALLOC_CHUNK should be much greater than ALLOC_SECURITY_MARGIN */ + +/* + * To save a lot of push/pop, every variable are stored into this + * structure, which is passed among nearly every sub-functions. + */ +typedef struct { + const char * src_string; /* current position into intput string */ + char * buffer_base; /* output buffer */ + char * dest_string; /* current position into output string */ + size_t buffer_len; /* length of output buffer */ + size_t real_len; /* real current length of output text */ + size_t pseudo_len; /* total length of output text if it were not limited in size */ + size_t maxlen; + va_list vargs; /* pointer to current position into vargs */ + char * sprintf_string; + FILE * fprintf_file; +} xprintf_struct; + +/* + * Realloc buffer if needed + * Return value: 0 = ok + * EOF = not enought memory + */ +static int realloc_buff(xprintf_struct *s, size_t len) +{ + char * ptr; + + if (len + ALLOC_SECURITY_MARGIN + s->real_len > s->buffer_len) { + len += s->real_len + ALLOC_CHUNK; + ptr = (char *)realloc((void *)(s->buffer_base), len); + if (ptr == NULL) { + s->buffer_base = NULL; + return EOF; + } + + s->dest_string = ptr + (size_t)(s->dest_string - s->buffer_base); + s->buffer_base = ptr; + s->buffer_len = len; + + (s->buffer_base)[s->buffer_len - 1] = 1; /* overflow marker */ + } + + return 0; +} + +/* + * Prints 'usual' characters up to next '%' + * or up to end of text + */ +static int usual_char(xprintf_struct * s) +{ + size_t len; + + len = strcspn(s->src_string, "%"); /* reachs the next '%' or end of input string */ + /* note: 'len' is never 0 because the presence of '%' */ + /* or end-of-line is checked in the calling function */ + + if (realloc_buff(s,len) == EOF) + return EOF; + + memcpy(s->dest_string, s->src_string, len); + s->src_string += len; + s->dest_string += len; + s->real_len += len; + s->pseudo_len += len; + + return 0; +} + +/* + * Return value: 0 = ok + * EOF = error + */ +static int print_it(xprintf_struct *s, size_t approx_len, + const char *format_string, ...) +{ + va_list varg; + int vsprintf_len; + size_t len; + + if (realloc_buff(s,approx_len) == EOF) + return EOF; + + va_start(varg, format_string); + vsprintf_len = vsprintf(s->dest_string, format_string, varg); + va_end(varg); + + /* Check for overflow */ + assert((s->buffer_base)[s->buffer_len - 1] == 1); + + if (vsprintf_len == EOF) /* must be done *after* overflow-check */ + return EOF; + + s->pseudo_len += vsprintf_len; + len = strlen(s->dest_string); + s->real_len += len; + s->dest_string += len; + + return 0; +} + +/* + * Prints a string (%s) + * We need special handling because: + * a: the length of the string is unknown + * b: when .prec is used, we must not access any extra byte of the + * string (of course, if the original sprintf() does... what the + * hell, not my problem) + * + * Return value: 0 = ok + * EOF = error + */ +static int type_s(xprintf_struct *s, int width, int prec, + const char *format_string, const char *arg_string) +{ + size_t string_len; + + if (arg_string == NULL) + return print_it(s, (size_t)6, "(null)", 0); + + /* hand-made strlen() whitch stops when 'prec' is reached. */ + /* if 'prec' is -1 then it is never reached. */ + string_len = 0; + while (arg_string[string_len] != 0 && (size_t)prec != string_len) + string_len++; + + if (width != -1 && string_len < (size_t)width) + string_len = (size_t)width; + + return print_it(s, string_len, format_string, arg_string); +} + +/* + * Read a serie of digits. Stop when non-digit is found. + * Return value: the value read (between 0 and 32767). + * Note: no checks are made against overflow. If the string contain a big + * number, then the return value won't be what we want (but, in this case, + * the programmer don't know whatr he wants, then no problem). + */ +static int getint(const char **string) +{ + int i = 0; + + while (isdigit(**string) != 0) { + i = i * 10 + (**string - '0'); + (*string)++; + } + + if (i < 0 || i > 32767) + i = 32767; /* if we have i==-10 this is not because the number is */ + /* negative; this is because the number is big */ + return i; +} + +/* + * Read a part of the format string. A part is 'usual characters' (ie "blabla") + * or '%%' escape sequence (to print a single '%') or any combination of + * format specifier (ie "%i" or "%10.2d"). + * After the current part is managed, the function returns to caller with + * everything ready to manage the following part. + * The caller must ensure than the string is not empty, i.e. the first byte + * is not zero. + * + * Return value: 0 = ok + * EOF = error + */ +static int dispatch(xprintf_struct *s) +{ + const char *initial_ptr; + char format_string[24]; /* max length may be something like "% +-#032768.32768Ld" */ + char *format_ptr; + int flag_plus, flag_minus, flag_space, flag_sharp, flag_zero; + int width, prec, modifier, approx_width; + char type; + /* most of those variables are here to rewrite the format string */ + +#define SRCTXT (s->src_string) +#define DESTTXT (s->dest_string) + + /* incoherent format string. Characters after the '%' will be printed with the next call */ +#define INCOHERENT() do {SRCTXT=initial_ptr; return 0;} while (0) /* do/while to avoid */ +#define INCOHERENT_TEST() do {if(*SRCTXT==0) INCOHERENT();} while (0) /* a null statement */ + + /* 'normal' text */ + if (*SRCTXT != '%') + return usual_char(s); + + /* we then have a '%' */ + SRCTXT++; + /* don't check for end-of-string ; this is done later */ + + /* '%%' escape sequence */ + if (*SRCTXT == '%') { + if (realloc_buff(s, (size_t)1) == EOF) /* because we can have "%%%%%%%%..." */ + return EOF; + *DESTTXT = '%'; + DESTTXT++; + SRCTXT++; + (s->real_len)++; + (s->pseudo_len)++; + return 0; + } + + /* '%' managing */ + initial_ptr = SRCTXT; /* save current pointer in case of incorrect */ + /* 'decoding'. Points just after the '%' so the '%' */ + /* won't be printed in any case, as required. */ + + /* flag */ + flag_plus = flag_minus = flag_space = flag_sharp = flag_zero = 0; + + for (;; SRCTXT++) { + if (*SRCTXT == ' ') + flag_space = 1; + else if (*SRCTXT == '+') + flag_plus = 1; + else if (*SRCTXT == '-') + flag_minus = 1; + else if (*SRCTXT == '#') + flag_sharp = 1; + else if (*SRCTXT == '0') + flag_zero = 1; + else + break; + } + + INCOHERENT_TEST(); /* here is the first test for end of string */ + + /* width */ + if (*SRCTXT == '*') { /* width given by next argument */ + SRCTXT++; + width = va_arg(s->vargs, int); + if ((size_t)width > 0x3fffU) /* 'size_t' to check against negative values too */ + width = 0x3fff; + } else if (isdigit(*SRCTXT)) /* width given as ASCII number */ + width = getint(&SRCTXT); + else + width = -1; /* no width specified */ + + INCOHERENT_TEST(); + + /* .prec */ + if (*SRCTXT == '.') { + SRCTXT++; + if (*SRCTXT == '*') { /* .prec given by next argument */ + prec = va_arg(s->vargs, int); + if ((size_t)prec >= 0x3fffU) /* 'size_t' to check against negative values too */ + prec = 0x3fff; + } else { /* .prec given as ASCII number */ + if (isdigit(*SRCTXT) == 0) + INCOHERENT(); + prec = getint(&SRCTXT); + } + INCOHERENT_TEST(); + } else + prec = -1; /* no .prec specified */ + + /* modifier */ + if (*SRCTXT == 'L' || *SRCTXT == 'h' || *SRCTXT == 'l') { + modifier = *SRCTXT; + SRCTXT++; + if (modifier=='l' && *SRCTXT=='l') { + SRCTXT++; + modifier = 'L'; /* 'll' == 'L' long long == long double */ + } /* only for compatibility ; not portable */ + INCOHERENT_TEST(); + } else + modifier = -1; /* no modifier specified */ + + /* type */ + type = *SRCTXT; + if (strchr("diouxXfegEGcspn",type) == NULL) + INCOHERENT(); /* unknown type */ + SRCTXT++; + + /* rewrite format-string */ + format_string[0] = '%'; + format_ptr = &(format_string[1]); + + if (flag_plus) { + *format_ptr = '+'; + format_ptr++; + } + if (flag_minus) { + *format_ptr = '-'; + format_ptr++; + } + if (flag_space) { + *format_ptr = ' '; + format_ptr++; + } + if (flag_sharp) { + *format_ptr = '#'; + format_ptr++; + } + if (flag_zero) { + *format_ptr = '0'; + format_ptr++; + } /* '0' *must* be the last one */ + + if (width != -1) { + sprintf(format_ptr, "%i", width); + format_ptr += strlen(format_ptr); + } + + if (prec != -1) { + *format_ptr = '.'; + format_ptr++; + sprintf(format_ptr, "%i", prec); + format_ptr += strlen(format_ptr); + } + + if (modifier != -1) { + *format_ptr = modifier; + format_ptr++; + } + + *format_ptr = type; + format_ptr++; + *format_ptr = 0; + + /* vague approximation of minimal length if width or prec are specified */ + approx_width = width + prec; + if (approx_width < 0) /* because width == -1 and/or prec == -1 */ + approx_width = 0; + + switch (type) { + /* int */ + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + switch (modifier) { + case -1 : + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int)); + case 'l': + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, long int)); + case 'h': + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int)); + /* 'int' instead of 'short int' because default promotion is 'int' */ + default: + INCOHERENT(); + } + + /* char */ + case 'c': + if (modifier != -1) + INCOHERENT(); + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, int)); + /* 'int' instead of 'char' because default promotion is 'int' */ + + /* math */ + case 'e': + case 'f': + case 'g': + case 'E': + case 'G': + switch (modifier) { + case -1 : /* because of default promotion, no modifier means 'l' */ + case 'l': + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, double)); + case 'L': + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, long double)); + default: + INCOHERENT(); + } + + /* string */ + case 's': + return type_s(s, width, prec, format_string, va_arg(s->vargs, const char*)); + + /* pointer */ + case 'p': + if (modifier == -1) + return print_it(s, (size_t)approx_width, format_string, va_arg(s->vargs, void *)); + INCOHERENT(); + + /* store */ + case 'n': + if (modifier == -1) { + int * p; + p = va_arg(s->vargs, int *); + if (p != NULL) { + *p = s->pseudo_len; + return 0; + } + return EOF; + } + INCOHERENT(); + + } /* switch */ + + INCOHERENT(); /* unknown type */ + +#undef INCOHERENT +#undef INCOHERENT_TEST +#undef SRCTXT +#undef DESTTXT +} + +/* + * Return value: number of *virtually* written characters + * EOF = error + */ +static int core(xprintf_struct *s) +{ + size_t len, save_len; + char *dummy_base; + + /* basic checks */ + if ((int)(s->maxlen) <= 0) /* 'int' to check against some conversion */ + return EOF; /* error for example if value is (int)-10 */ + s->maxlen--; /* because initial maxlen counts final 0 */ + /* note: now 'maxlen' _can_ be zero */ + + if (s->src_string == NULL) + s->src_string = "(null)"; + + /* struct init and memory allocation */ + s->buffer_base = NULL; + s->buffer_len = 0; + s->real_len = 0; + s->pseudo_len = 0; + if (realloc_buff(s, (size_t)0) == EOF) + return EOF; + s->dest_string = s->buffer_base; + + /* process source string */ + for (;;) { + /* up to end of source string */ + if (*(s->src_string) == 0) { + *(s->dest_string) = 0; /* final 0 */ + len = s->real_len + 1; + break; + } + + if (dispatch(s) == EOF) + goto free_EOF; + + /* up to end of dest string */ + if (s->real_len >= s->maxlen) { + (s->buffer_base)[s->maxlen] = 0; /* final 0 */ + len = s->maxlen + 1; + break; + } + } + + /* for (v)asnprintf */ + dummy_base = s->buffer_base; + save_len = 0; /* just to avoid a compiler warning */ + + dummy_base = s->buffer_base + s->real_len; + save_len = s->real_len; + + /* process the remaining of source string to compute 'pseudo_len'. We + * overwrite again and again, starting at 'dummy_base' because we don't + * need the text, only char count. */ + while(*(s->src_string) != 0) { /* up to end of source string */ + s->real_len = 0; + s->dest_string = dummy_base; + if (dispatch(s) == EOF) + goto free_EOF; + } + + s->buffer_base = (char *)realloc((void *)(s->buffer_base), save_len + 1); + if (s->buffer_base == NULL) + return EOF; /* should rarely happen because we shrink the buffer */ + return s->pseudo_len; + + free(s->buffer_base); + return s->pseudo_len; + + free_EOF: + if (s->buffer_base != NULL) + free(s->buffer_base); + return EOF; +} + +int vasprintf(char **ptr, const char *format_string, va_list vargs) +{ + xprintf_struct s; + int retval; + + s.src_string = format_string; +#ifdef va_copy + va_copy (s.vargs, vargs); +#else +#ifdef __va_copy + __va_copy (s.vargs, vargs); +#else + memcpy (&s.vargs, vargs, sizeof (va_list)); +#endif /* __va_copy */ +#endif /* va_copy */ + s.maxlen = (size_t)INT_MAX; + + retval = core(&s); + va_end(s.vargs); + if (retval == EOF) { + *ptr = NULL; + return EOF; + } + + *ptr = s.buffer_base; + return retval; +} -- 2.49.0