From: VMware, Inc <> Date: Thu, 27 Oct 2011 18:43:36 +0000 (-0700) Subject: Changes in shared code that don't affect open-vm-tools functionality. X-Git-Tag: 2011.10.26-514583~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bacfb71d72493bdb03c02b66b0acca7ff1e995cc;p=thirdparty%2Fopen-vm-tools.git Changes in shared code that don't affect open-vm-tools functionality. Signed-off-by: Marcelo Vanzin --- diff --git a/open-vm-tools/lib/file/fileIO.c b/open-vm-tools/lib/file/fileIO.c index 1bd2a393d..a0910430e 100644 --- a/open-vm-tools/lib/file/fileIO.c +++ b/open-vm-tools/lib/file/fileIO.c @@ -269,14 +269,15 @@ FileIO_Lock(FileIODescriptor *file, // IN/OUT: #if !defined(__FreeBSD__) && !defined(sun) if (access & FILEIO_OPEN_LOCKED) { - int err; + int err = 0; ASSERT(file->lockToken == NULL); file->lockToken = FileLock_Lock(file->fileName, (access & FILEIO_OPEN_ACCESS_WRITE) == 0, FILELOCK_DEFAULT_WAIT, - &err); + &err, + NULL); if (file->lockToken == NULL) { /* Describe the lock not acquired situation in detail */ @@ -338,11 +339,9 @@ FileIO_Unlock(FileIODescriptor *file) // IN/OUT: #if !defined(__FreeBSD__) && !defined(sun) if (file->lockToken != NULL) { - int err; + int err = 0; - err = FileLock_Unlock(file->lockToken); - - if (err != 0) { + if (!FileLock_Unlock(file->lockToken, &err, NULL)) { Warning(LGPFX" %s on '%s' failed: %s\n", __FUNCTION__, UTF8(file->fileName), strerror(err)); diff --git a/open-vm-tools/lib/file/fileInt.h b/open-vm-tools/lib/file/fileInt.h index 6b1f75a56..18033db11 100644 --- a/open-vm-tools/lib/file/fileInt.h +++ b/open-vm-tools/lib/file/fileInt.h @@ -236,6 +236,9 @@ Bool FileLockValidExecutionID(const char *executionID); Bool FileLockValidName(ConstUnicode fileName); +void FileLockAppendMessage(MsgList **msgs, + int err); + Bool FileIsWritableDir(ConstUnicode dirName); UnicodeIndex FileFirstSlashIndex(ConstUnicode pathName, diff --git a/open-vm-tools/lib/file/fileLockPosix.c b/open-vm-tools/lib/file/fileLockPosix.c index f84e9ab7c..bc79d04bb 100644 --- a/open-vm-tools/lib/file/fileLockPosix.c +++ b/open-vm-tools/lib/file/fileLockPosix.c @@ -573,6 +573,33 @@ FileLock_UnlockDevice(const char *deviceName) // IN: return TRUE; } + +/* + *---------------------------------------------------------------------- + * + * FileLockAppendMessage -- + * + * Append a detailed error message to the MsgList. + * + * Results: + * As above + * + * Side effects: + * Memory is allocated + * + *---------------------------------------------------------------------- + */ + +void +FileLockAppendMessage(MsgList **msgs, // IN/OPT: + int err) // IN: errno +{ + MsgList_Append(msgs, MSGID(fileLock.posix) + "A file locking error (%d) has occurred: %s", + err, Err_Errno2String(err)); +} + + #if defined(linux) /* *---------------------------------------------------------------------- @@ -1072,9 +1099,8 @@ FileLockNormalizePath(ConstUnicode filePath) // IN: * specifies "waiting forever" to acquire the lock. * * Results: - * NULL Lock not acquired. Check err. - * err 0 Lock Timed Out - * err !0 errno + * NULL Lock not acquired + * errno to *err, msg to **msgs - when appropriate * !NULL Lock Acquired. This is the "lockToken" for an unlock. * * Side effects: @@ -1087,8 +1113,10 @@ FileLockToken * FileLock_Lock(ConstUnicode filePath, // IN: const Bool readOnly, // IN: const uint32 msecMaxWaitTime, // IN: - int *err) // OUT: + int *err, // OUT/OPT: returns errno + MsgList **msgs) // OUT/OPT: add error message { + int res = 0; Unicode normalizedPath; FileLockToken *tokenPtr; @@ -1096,17 +1124,26 @@ FileLock_Lock(ConstUnicode filePath, // IN: ASSERT(err); normalizedPath = FileLockNormalizePath(filePath); + if (normalizedPath == NULL) { - *err = EINVAL; + res = EINVAL; tokenPtr = NULL; } else { tokenPtr = FileLockIntrinsic(normalizedPath, !readOnly, msecMaxWaitTime, - err); + &res); Unicode_Free(normalizedPath); } + if (err != NULL) { + *err = res; + } + + if (tokenPtr == NULL) { + FileLockAppendMessage(msgs, res); + } + return tokenPtr; } @@ -1119,37 +1156,43 @@ FileLock_Lock(ConstUnicode filePath, // IN: * Is a file currently locked (at the time of the call)? * * Results: - * TRUE YES - * FALSE NO; if err is not NULL may check *err for an error - * - * Side effects: - * None. + * TRUE YES + * FALSE Failure (errno to *err, msg to **msgs - when appropriate) * *---------------------------------------------------------------------- */ Bool FileLock_IsLocked(ConstUnicode filePath, // IN: - int *err) // OUT: + int *err, // OUT/OPT: returns errno + MsgList **msgs) // OUT/OPT: add error message { + int res = 0; Bool isLocked; Unicode normalizedPath; ASSERT(filePath); normalizedPath = FileLockNormalizePath(filePath); + if (normalizedPath == NULL) { - if (err != NULL) { - *err = EINVAL; - } + res = EINVAL; isLocked = FALSE; } else { - isLocked = FileLockIsLocked(normalizedPath, err); + isLocked = FileLockIsLocked(normalizedPath, &res); Unicode_Free(normalizedPath); } + if (err != NULL) { + *err = res; + } + + if (res != 0) { + FileLockAppendMessage(msgs, res); + } + return isLocked; } @@ -1162,8 +1205,8 @@ FileLock_IsLocked(ConstUnicode filePath, // IN: * Release the lock held on the specified file. * * Results: - * 0 unlocked - * >0 errno + * TRUE Success + * FALSE Failure (errno to *err, msg to **msgs - when appropriate) * * Side effects: * Changes the host file system. @@ -1171,12 +1214,26 @@ FileLock_IsLocked(ConstUnicode filePath, // IN: *---------------------------------------------------------------------- */ -int -FileLock_Unlock(const FileLockToken *lockToken) // IN: +Bool +FileLock_Unlock(const FileLockToken *lockToken, // IN: + int *err, // OUT/OPT: returns errno + MsgList **msgs) // OUT/OPT: add error message { + int res; + ASSERT(lockToken); - return FileUnlockIntrinsic((FileLockToken *) lockToken); + res = FileUnlockIntrinsic((FileLockToken *) lockToken); + + if (err != NULL) { + *err = res; + } + + if (res != 0) { + FileLockAppendMessage(msgs, res); + } + + return (res == 0); } #else diff --git a/open-vm-tools/lib/include/fileLock.h b/open-vm-tools/lib/include/fileLock.h index 801a147d8..4f50dd416 100644 --- a/open-vm-tools/lib/include/fileLock.h +++ b/open-vm-tools/lib/include/fileLock.h @@ -29,6 +29,7 @@ #include "includeCheck.h" #include "unicodeTypes.h" +#include "msgList.h" // The default time, in msec, to wait for a lock before giving up #define FILELOCK_DEFAULT_WAIT 2500 @@ -49,19 +50,29 @@ // File locking functions typedef struct FileLockToken FileLockToken; +Unicode FileLock_TokenPathName(const FileLockToken *fileLockToken); + FileLockToken *FileLock_Lock(ConstUnicode filePath, const Bool readOnly, const uint32 msecMaxWaitTime, - int *err); + int *err, + MsgList **msgs); -Unicode FileLock_TokenPathName(const FileLockToken *fileLockToken); -int FileLock_Unlock(const FileLockToken *lockToken); +Bool FileLock_Unlock(const FileLockToken *lockToken, + int *err, + MsgList **msgs); Bool FileLock_IsLocked(ConstUnicode filePath, - int *err); + int *err, + MsgList **msgs); + +Bool FileLock_Remove(ConstUnicode filePath, + int *err, + MsgList **msgs); -int FileLock_Remove(ConstUnicode filePath); -int FileLock_CleanupVM(ConstUnicode cfgfilePath); +Bool FileLock_CleanupVM(ConstUnicode cfgfilePath, + int *err, + MsgList **msgs); // Device locking functions, for compatibility int FileLock_LockDevice(const char *device); diff --git a/open-vm-tools/lib/misc/Makefile.am b/open-vm-tools/lib/misc/Makefile.am index 933f2b9ea..e26c143aa 100644 --- a/open-vm-tools/lib/misc/Makefile.am +++ b/open-vm-tools/lib/misc/Makefile.am @@ -37,6 +37,8 @@ libMisc_la_SOURCES += iovector.c libMisc_la_SOURCES += logFixed.c libMisc_la_SOURCES += machineID.c libMisc_la_SOURCES += miscSolaris.c +libMisc_la_SOURCES += msgfmt.c +libMisc_la_SOURCES += msgList.c libMisc_la_SOURCES += posixDlopen.c libMisc_la_SOURCES += posixPosix.c libMisc_la_SOURCES += random.c diff --git a/open-vm-tools/lib/misc/msgList.c b/open-vm-tools/lib/misc/msgList.c new file mode 100644 index 000000000..f8e554264 --- /dev/null +++ b/open-vm-tools/lib/misc/msgList.c @@ -0,0 +1,530 @@ +/********************************************************* + * Copyright (C) 2009-2011 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +/* + * msgList.c -- + * + * Utilities to manipulate (stateless) lists of messages. + * See also msg.h. + */ + + +#include +#include +#include + +#include "vmware.h" +#include "util.h" +#include "str.h" +#include "err.h" +#include "msgList.h" +#include "dynbuf.h" + +#define LOGLEVEL_MODULE main +#include "loglevel_user.h" + + +/* + *----------------------------------------------------------------------------- + * + * MsgId2MsgList -- + * + * Create a MsgList item from the input message. Does not handle arguments; + * the caller must handle those. + * + * Performs any needed sanity checks as well. + * + * Results: + * A newly-allocated MsgList. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static MsgList * +MsgId2MsgList(const char *idFmt) // IN message ID and English message +{ + MsgList *m; + const char *idp, *strp; + + /* All message strings must be prefixed by the message ID. */ + ASSERT(Msg_HasMsgID(idFmt)); + + /* + * Find the beginning of the ID (idp) and the string (strp). + * The string should have the correct MSG_MAGIC(...)... form. + */ + + idp = idFmt + MSG_MAGIC_LEN + 1; + strp = strchr(idp, ')') + 1; + + m = Util_SafeMalloc(sizeof *m); + m->format = Util_SafeStrdup(strp); + m->next = NULL; + m->args = NULL; + m->numArgs = 0; + + if (vmx86_debug) { + uint32 i; + static const char *prfx[] = { + "msg.", // bora/lib, VMX, ... + "vob.", // Vmkernel OBservation + "vpxa.", // VirtualCenter host agent + "vpxd.", // VirtualCenter server + "hostd.", // Host agent + // Additional prefixes go here, but do not add "button." + }; + + for (i = 0; i < ARRAYSIZE(prfx); i++) { + if (!Str_Strncasecmp(idp, prfx[i], strlen(prfx[i]))) { + break; + } + } + if (i >= ARRAYSIZE(prfx)) { + Panic("%s error: Invalid msg prefix in <%s>\n", __FUNCTION__, idp); + } + } + + m->id = Util_SafeStrndup(idp, strp - idp - 1 /* ')' character */); + + return m; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_AppendStr -- + * + * Create a MsgList item from the input message. The input message MUST + * have no arguments. Do not pass in formatted messages; use MsgList_Append + * for that. This variant is only for MSGIDs that have no format arguments. + * + * If the incoming list pointer reference is NULL, operate in 'silent' + * mode: skip all work (except preconditions). Note that in silent + + * vmx86_debug mode, this code does all work and throws away the result, + * to make sure all messages are parseable. + * + * Results: + * New item is attached to 'list' (and '*list' is updated). + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +void +MsgList_AppendStr(MsgList **list, // IN reference to existing list + const char *id) // IN message ID and English message +{ + ASSERT(id != NULL); + + /* Silently upgrade system errors to real MSGIDs. */ + if (!Msg_HasMsgID(id) && Err_String2Errno(id) != ERR_INVALID) { + MsgList_Append(list, MSGID(systemerror) "%s", id); + return; + } + + /* + * The MsgList_AppendStr variant does not accept format strings. This + * check disallows some legitimate strings, but it's probably easier + * on the msgconv parser to just disallow all format-string-like things. + */ + ASSERT(strchr(id, '%') == NULL); + + /* + * In silent mode, skip processing in release builds. Debug + * builds can afford the speed cost to verify message is constructable. + */ + if (list != NULL || vmx86_debug) { + MsgList *m = MsgId2MsgList(id); + + if (list != NULL) { + m->next = *list; + *list = m; + } else { + /* Silent mode, but constructed as a sanity test. Clean up. */ + ASSERT(vmx86_debug); + MsgList_Free(m); + } + } +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_VAppend -- + * + * Create a MsgList item from the message with va_list, + * and attach it to the incoming list. + * + * If the incoming list pointer reference is NULL, operate in 'silent' + * mode: skip all work (except preconditions). Note that in silent + + * vmx86_debug mode, this code does all work and throws away the result, + * to make sure all messages are parseable. + * + * Results: + * New item is attached to 'list' (and '*list' is updated). + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +void +MsgList_VAppend(MsgList **list, // IN reference to existing list + const char *idFmt, // IN message ID and English message + va_list args) // IN args +{ + ASSERT(idFmt != NULL); + + /* Silently upgrade system errors to real MSGIDs. */ + if (!Msg_HasMsgID(idFmt) && Err_String2Errno(idFmt) != ERR_INVALID) { + MsgList_Append(list, MSGID(systemerror) "%s", idFmt); + return; + } + + /* + * In silent mode, skip processing in release builds. Debug + * builds can afford the speed cost to verify message is constructable. + */ + if (list != NULL || vmx86_debug) { + MsgList *m = MsgId2MsgList(idFmt); + Bool status; + char *error; + + status = MsgFmt_GetArgs(m->format, args, &m->args, &m->numArgs, &error); + if (!status) { + Log("%s error: %s\nformat <%s>\n", __FUNCTION__, error, m->format); + PANIC(); + } + + if (list != NULL) { + m->next = *list; + *list = m; + } else { + /* Silent mode, but constructed as a sanity test. Clean up. */ + ASSERT(vmx86_debug); + MsgList_Free(m); + } + } +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_Append -- + * + * Create the MsgList item from the message with va_list. + * + * Results: + * New item is prepended to 'list' (and '*list' is new item). + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +void +MsgList_Append(MsgList **list, // IN reference to existing list + const char *idFmt, // IN message ID and English message + ...) // IN args +{ + va_list args; + + va_start(args, idFmt); + MsgList_VAppend(list, idFmt, args); + va_end(args); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_VCreate -- + * + * Create the MsgList item from the message. + * + * Results: + * New MsgList structure. + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +MsgList * +MsgList_VCreate(const char *idFmt, // IN message ID and English message + va_list args) // IN args +{ + MsgList *ml = NULL; + + MsgList_VAppend(&ml, idFmt, args); + + return ml; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_Create -- + * + * Create the MsgList item from the message with va_list. + * + * Results: + * New MsgList structure. + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +MsgList * +MsgList_Create(const char *idFmt, // IN message ID and English message + ...) // IN args +{ + MsgList *ml = NULL; + va_list args; + + va_start(args, idFmt); + MsgList_VAppend(&ml, idFmt, args); + va_end(args); + + return ml; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgList_CreateStr -- + * + * Create the MsgList item from the message with no format arguments. + * + * Results: + * New MsgList structure. + * + * Side effects: + * Callers are responsible to free the returned MsgList. + * + *----------------------------------------------------------------------------- + */ + +MsgList * +MsgList_CreateStr(const char *idFmt) // IN message ID and English message +{ + MsgList *ml = NULL; + + MsgList_AppendStr(&ml, idFmt); + + return ml; +} + + +/* + *---------------------------------------------------------------------- + * + * MsgList_Copy -- + * + * Makes a deep copy of the MsgList. + * + * Results: + * Newly allocated MsgList. Use MsgList_Free() to free. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +MsgList * +MsgList_Copy(const MsgList *src) // IN: +{ + MsgList *result = NULL; + MsgList **pdst = &result; + + while (src != NULL) { + MsgList *dst = Util_SafeMalloc(sizeof *dst); + + dst->id = Util_SafeStrdup(src->id); + dst->format = Util_SafeStrdup(src->format); + dst->args = MsgFmt_CopyArgs(src->args, src->numArgs); + dst->numArgs = src->numArgs; + dst->next = NULL; + src = src->next; + *pdst = dst; + pdst = &dst->next; + } + + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * MsgList_Free -- + * + * Frees the full MsgList chain. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +MsgList_Free(MsgList *messages) // IN: +{ + MsgList *m; + MsgList *next; + + for (m = messages; m != NULL; m = next) { + free(m->format); + free(m->id); + MsgFmt_FreeArgs(m->args, m->numArgs); + next = m->next; + free(m); + } +} + +/* + *---------------------------------------------------------------------- + * + * MsgList_GetMsgID -- + * + * Returns the "main" MSGID for the message stack. + * + * This is useful for Msg_Post, Msg_Hint, and Msg_Question, + * all of which have the semantic that the generalized MSGID + * is the MSGID of the last message in the stack. + * + * Results: + * Returns pointer to something within the MsgList, or + * NULL if the MsgList doesn't exist. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +const char * +MsgList_GetMsgID(const MsgList *messages) // IN: +{ + if (messages == NULL) { + return NULL; + } + while (messages->next != NULL) { + messages = messages->next; + } + + return messages->id; +} + + +/* + *---------------------------------------------------------------------- + * + * MsgList_ToString -- + * + * Returns the English representation of a MsgList chain. Does NOT + * localize. + * + * Results: + * Allocated memory containing message. Successive messages + * are separated by newlines. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +char * +MsgList_ToString(const MsgList *messages) // IN: +{ + char *result = NULL; + + if (messages != NULL) { + size_t len = 0; + char *formatted = MsgFmt_Asprintf(&len, messages->format, messages->args, + messages->numArgs); + const char *eol = (len > 0 && formatted != NULL && + formatted[len - 1] == '\n') ? "" : "\n"; + char *tail; + + if (messages->next != NULL) { + tail = MsgList_ToString(messages->next); + } else { + tail = Util_SafeStrdup(""); + } + result = Str_SafeAsprintf(NULL, "%s%s%s", formatted, eol, tail); + free(formatted); + free(tail); + } + + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * MsgList_Log -- + * + * Emits the English representation of a MsgList chain to Log(). + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +MsgList_Log(const MsgList *messages) // IN: +{ + const MsgList *m; + + for (m = messages; m != NULL; m = m->next) { + size_t len = 0; + char *formatted = MsgFmt_Asprintf(&len, m->format, m->args, m->numArgs); + + Log("[%s] %s%s", + m->id, formatted, + (len > 0 && formatted != NULL && formatted[len - 1] == '\n') ? "" + : "\n"); + free(formatted); + } +} diff --git a/open-vm-tools/lib/misc/msgfmt.c b/open-vm-tools/lib/misc/msgfmt.c new file mode 100644 index 000000000..885f66a77 --- /dev/null +++ b/open-vm-tools/lib/misc/msgfmt.c @@ -0,0 +1,2838 @@ +/* ********************************************************** + * Copyright 2007 VMware, Inc. All rights reserved. + * **********************************************************/ + +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * 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, 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* + * msgfmt.c -- + * + * MsgFmt: format messages for the Msg module + */ + + +#ifdef VMKERNEL + #include "vmkernel.h" + #include "vm_types.h" + #include "vm_libc.h" +#else + #include + #include + #include + #include + #include + #if defined(__FreeBSD__) + #include + #endif + #if !defined(_WIN32) && !defined(SOL9) && \ + (!defined(__FreeBSD__) || __FreeBSD_version >= 500029) + #include + #endif + #if !defined(__FreeBSD__) || __FreeBSD_version >= 400017 + #include + #endif + + #include "vmware.h" + #include "bsdfmt.h" + #include "err.h" +#endif + +#include "msgfmt.h" + +#ifdef HAS_BSD_PRINTF + #include + #include + #include "msgid.h" +#endif + +/* + * Older versions of FreeBSD don't have C99 support (stdint), and also + * do not have wide character support. Re-implement the stuff we need + * in those cases. + */ + +#if defined(__FreeBSD__) && __FreeBSD_version <= 320001 +static INLINE const wchar_t * +wmemchr(const wchar_t *s, wchar_t c, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) { + if (s[i] == c) { + return &s[i]; + } + } + + return NULL; +} + +static INLINE size_t +wcslen(const wchar_t *s) +{ + size_t i; + + for (i = 0; s[i]; i++); + + return i; +} +#endif + +/* + * The vmkernel doesn't have the Str module, malloc(), or + * some of the standard C string functions. + * The only ones we really need are Str_Vsnprintf() and memchr(). + */ + +#ifdef VMKERNEL // { + +typedef int32 wchar_t; +typedef int32 wint_t; +typedef int64 intmax_t; +typedef size_t ptrdiff_t; + +#define STUB(t, f, a) \ + static INLINE t f a { NOT_IMPLEMENTED(); return (t) 0;} +#define VSTUB(f, a) \ + static INLINE void f a { NOT_IMPLEMENTED(); } +STUB(char *, Str_Vasprintf, (char **b, const char *f, va_list a)) +STUB(void *, malloc, (size_t s)) +STUB(void *, realloc, (void *p, size_t s)) +STUB(wchar_t *, wmemchr, (const wchar_t *s, wchar_t c, size_t n)) +STUB(size_t, wcslen, (const wchar_t *s)) +STUB(char *, strdup, (const char *s)) +VSTUB(free, (void *p)) +#undef STUB +#undef VSTUB + +typedef int Err_Number; +#define ERR_INVALID (-1) +static INLINE Err_Number +Err_String2Errno(const char *string) +{ + return ERR_INVALID; +} + +#ifdef VMX86_DEBUG +static INLINE Err_Number +Err_String2ErrnoDebug(const char *string) +{ + return ERR_INVALID; +} +#endif + +static INLINE int +Str_Vsnprintf(char *str, size_t size, const char *format, va_list ap) { + int n = vsnprintf(str, size, format, ap); + ASSERT(n >= 0); + if (n >= size) { + str[size - 1] = '\0'; + n = -1; + } + return n; +} + +static INLINE const void * +memchr(const void *s, int c, size_t n) +{ + const uint8 *p = s; + const uint8 *e = p + n; + while (p < e) { + if (*p++ == c) { + return p; + } + } + return NULL; +} + +#endif // } + + +/* + * Local data + */ + +typedef struct MsgFmtParseState { + MsgFmt_Arg *args; + int numArgs; + int maxArgs; + char *error; + + /* + * Allocator state for caller-supplied buffer. + */ + + void *buf; + char *bufp; + char *bufe; +} MsgFmtParseState; + +/* d, i, o, u, x, X, e, E, f, F, g, G, a, A, c, s, C, S, p, and n --hpreg */ +static int const isSpecifier[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +/* + * Local functions + */ + +static MsgFmt_SpecFunc MsgFmtGetArg1; +static int MsgFmtAToI(char const **start, char const *end); +static void MsgFmtError(MsgFmtParseState *state, const char *fmt, ...); + +static void MsgFmtAllocInit(MsgFmtParseState *state, void *buf, size_t size); +static void *MsgFmtAlloc(MsgFmtParseState *state, size_t size); +static Bool MsgFmtAllocArgs(MsgFmtParseState *state, int n); +static char *MsgFmtVasprintf(MsgFmtParseState *state, + const char *fmt, va_list args); +static void MsgFmtFreeAll(MsgFmtParseState *state); +static size_t MsgFmtBufUsed(MsgFmtParseState *state); +#ifdef HAS_BSD_PRINTF +static int MsgFmtSnprintfWork(char **outbuf, size_t bufSize, const char *fmt0, + const struct MsgFmt_Arg *args, int numArgs); +#endif + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_ParseWin32 -- + * + * Convert the Win32 representation of a format string into another + * representation --hpreg + * + * XXX I haven't implemented %0 and %n, because they suck: + * . they mix content and presentation + * . they have nothing to do with parameters and hence have no + * equivalent in other systems + * + * Results: + * 0 on success + * -1 on failure: out of memory + * -2 on failure: invalid 'in' + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_ParseWin32(MsgFmt_LitFunc *litFunc, // IN + MsgFmt_SpecFunc *specFunc, // IN + void *clientData, // IN + char const *in) // IN +{ + char const *startUnescaped; + unsigned int sm; + char const *pos = 0 /* Compiler warning --hpreg */; + char const *type = 0 /* Compiler warning --hpreg */; + int status; + + startUnescaped = in; + sm = 0; + + for (; *in != '\0'; in++) { + /* Unsigned does matter --hpreg */ + unsigned char ubyte; + + ubyte = *in; + switch (sm) { + case 2: /* Found %<1-9>... --hpreg */ + if (ubyte >= '0' && ubyte <= '9') { + break; + } + if (ubyte == '!') { + type = in + 1; + sm = 3; + break; + } + if ((status = (*litFunc)(clientData, startUnescaped, + pos - 1 - startUnescaped)) < 0 || + (status = (*specFunc)(clientData, pos, in - pos, "s", 1)) < 0) { + return status; + } + startUnescaped = in; + sm = 0; + /* Fall through --hpreg */ + + case 0: /* Found --hpreg */ + if (ubyte == '%') { + pos = in + 1; + sm = 1; + } + break; + + case 1: /* Found % --hpreg */ + if (ubyte >= '1' && ubyte <= '9') { + sm = 2; + } else { + ASSERT_NOT_IMPLEMENTED(ubyte != '0' && ubyte != 'n'); + status = (*litFunc)(clientData, startUnescaped, + in - 1 - startUnescaped); + if (status < 0) { + return status; + } + startUnescaped = in; + sm = 0; + } + break; + + case 3: /* Found %<1-9>...!... --hpreg */ + if (ubyte == '!') { + if ( (status = (*litFunc)(clientData, startUnescaped, + pos - 1 - startUnescaped)) < 0 + || (status = (*specFunc)(clientData, pos, type - 1 - pos, + type, in - type)) < 0) { + return status; + } + startUnescaped = in + 1; + sm = 0; + } + break; + + default: + NOT_IMPLEMENTED(); + break; + } + } + + switch (sm) { + case 0: + status = (*litFunc)(clientData, startUnescaped, in - startUnescaped); + if (status < 0) { + return status; + } + break; + + case 2: + if ( (status = (*litFunc)(clientData, startUnescaped, + pos - 1 - startUnescaped)) < 0 + || (status = (*specFunc)(clientData, pos, in - pos, "s", 1)) < 0) { + return status; + } + break; + + case 1: + case 3: + return -2; + break; + + default: + NOT_IMPLEMENTED(); + break; + } + + return 0; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_Parse -- + * + * Parse a message format. + * + * Results: + * 0 on success + * -1 on failure: out of memory + * -2 on failure: invalid 'in' + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_Parse(MsgFmt_LitFunc *litFunc, // IN + MsgFmt_SpecFunc *specFunc, // IN + void *clientData, // IN + char const *in) // IN +{ + char const *startUnescaped; + unsigned int sm; + unsigned int counter; + int status; + char const *startEscaped = 0 /* Compiler warning --hpreg */; + char const *type = 0 /* Compiler warning --hpreg */; + Bool usePos = FALSE /* Compiler warning --hpreg */; + + startUnescaped = in; + sm = 0; + counter = 0; + + for (; *in != '\0'; in++) { + /* Unsigned does matter --hpreg */ + unsigned char ubyte; + + ubyte = *in; + switch (sm) { + case 0: /* Found --hpreg */ + if (ubyte == '%') { + sm = 1; + } + break; + + case 1: /* Found % --hpreg */ + if (ubyte == '%') { + if (litFunc != NULL && + (status = (*litFunc)(clientData, startUnescaped, + in - 1 - startUnescaped)) < 0) { + return status; + } + startUnescaped = in; + sm = 0; + break; + } + startEscaped = in; + type = in; + if (ubyte >= '1' && ubyte <= '9') { + sm = 2; + break; + } + sm = 3; + /* Fall through --hpreg */ + + case 3: /* Found %<1-9>...$... or %... --hpreg */ + variant3: + if (isSpecifier[ubyte]) { + char const *pos; + char const *posEnd; + char posBuf[10 /* 32 bits unsigned in decimal --hpreg */]; + + if (counter) { + if (usePos != (startEscaped != type)) { + return -2; + } + } else { + usePos = (startEscaped != type); + } + counter++; + + if (usePos) { + pos = startEscaped; + posEnd = type - 1; + } else { + char *current; + unsigned int value; + + current = posBuf + sizeof(posBuf); + posEnd = current; + value = counter; + ASSERT(value); + do { + current--; + ASSERT(current >= posBuf); + *current = '0' + value % 10; + value /= 10; + } while (value); + pos = current; + } + + if (litFunc != NULL && + (status = (*litFunc)(clientData, startUnescaped, + startEscaped - 1 - startUnescaped)) < 0) { + return status; + } + if ((status = (*specFunc)(clientData, pos, posEnd - pos, type, + in + 1 - type)) < 0) { + return status; + } + startUnescaped = in + 1; + sm = 0; + break; + } + /* Digits for field width & precision, zero for leading zeroes, + and dot for separator between width and precision. */ + if ((ubyte >= '0' && ubyte <= '9') || ubyte == '.') { + break; + } + /* Flags */ + if (ubyte == '#' || ubyte == '-' || ubyte == ' ' || ubyte == '+' || + ubyte == '\'') { + break; + } + /* Length modifiers */ + if (ubyte == 'L' || ubyte == 'l' || ubyte == 'h' || ubyte == 'z' || + ubyte == 'Z' || ubyte == 't' || ubyte == 'q' || ubyte == 'j' || + ubyte == 'I') { + break; + } + return -2; + + case 2: /* Found %<1-9>... --hpreg */ + if (ubyte >= '0' && ubyte <= '9') { + break; + } + if (ubyte == '$') { + type = in + 1; + sm = 3; + break; + } + sm = 3; + goto variant3; + + default: + NOT_IMPLEMENTED(); + break; + } + } + + if (sm) { + return -2; + } + if (litFunc != NULL && + (status = (*litFunc)(clientData, startUnescaped, + in - startUnescaped)) < 0) { + return status; + } + + return 0; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_ParseSpec -- + * + * Given a format specifier (the % stuff), return its contituent parts. + * + * Results: + * 0 on success, -2 (bad format) on failure. + * Out parameters: + * Width and precision are -1 if not specified. + * Length modifier is '\0' if not specified. + * Length modifier of "ll", "I64", or "q" is returned as 'L'. + * (This means we freely allow %llf and %qf, which is not strictly + * correct. However, glibc printf allows them (as well as %Ld), + * and they mean the same thing.) + * Length modifier of "hh" is returned as 'H'. + * Length modifier of "Z" is returned as 'z', for compatibility + * with old glibc. + * On failure, some or all of the out parameters may be modified + * in an undefined manner. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_ParseSpec(char const *pos, // IN: n$ location + unsigned int posSize, // IN: n$ length + char const *type, // IN: specifier after position + unsigned int typeSize, // IN: size of above + int *position, // OUT: argument position + int *flags, // OUT: flags + int *width, // OUT: width + int *precision, // OUT: precision + char *lengthMod, // OUT: length modifier + char *conversion) // OUT: conversion specifier +{ + char const *p = type; + char const *end = type + typeSize; + + /* + * Convert argument position to int. + * Fail if not a good decimal number greater than 0. + */ + + { + char const *posEnd = pos + posSize; + *position = MsgFmtAToI(&pos, posEnd); + if (*position <= 0 || pos != posEnd) { + return -2; + } + } + + /* + * The format specifier is, in this order, + * zero or more flags + * an optional width (a decimal number or *) + * an optional precision (. followed by optional decimal number or *) + * an optional length modifier (l, L, ll, z, etc.) + * conversion specifier (a character) + * + * The rest of this module does not recognize * as width or precision, + * so we don't do it here either. + * + * glibc 2.2 supports the I flag, which we don't. Instead, we + * support the I, I32, and I64 length modifiers used by Microsoft. + */ + + /* + * Flags + */ + + *flags = 0; + for (; p < end; p++) { + switch (*p) { + case '#': + *flags |= MSGFMT_FLAG_ALT; + continue; + case '0': + *flags |= MSGFMT_FLAG_ZERO; + continue; + case '-': + *flags |= MSGFMT_FLAG_MINUS; + continue; + case ' ': + *flags |= MSGFMT_FLAG_SPACE; + continue; + case '+': + *flags |= MSGFMT_FLAG_PLUS; + continue; + case '\'': + *flags |= MSGFMT_FLAG_QUOTE; + continue; + + default: + break; + } + break; + } + + /* + * Width + */ + + if (p >= end || *p < '1' || *p > '9') { + *width = -1; + } else { + *width = MsgFmtAToI(&p, end); + if (*width < 0) { + return -2; + } + } + + /* + * Precision + */ + + if (p >= end || *p != '.') { + *precision = -1; + } else { + p++; + *precision = MsgFmtAToI(&p, end); + if (*precision < 0) { + return -2; + } + } + + /* + * Length modifier + */ + + if (p >= end) { + return -2; + } + *lengthMod = '\0'; + switch (*p) { + case 'h': + p++; + if (p >= end || *p != 'h') { + *lengthMod = 'h'; + } else { + p++; + *lengthMod = 'H'; + } + break; + case 'l': + p++; + if (p >= end || *p != 'l') { + *lengthMod = 'l'; + } else { + p++; + *lengthMod = 'L'; + } + break; + case 'I': + /* + * Microsoft: + * I64 is 64-bit number. For us, the same as L. + * I32 is 32-bit number. For us, nothing. + * I is size_t. + */ + if (p + 2 < end && p[1] == '6' && p[2] == '4') { + p += 3; + *lengthMod = 'L'; + } else if (p + 2 < end && p[1] == '3' && p[2] == '2') { + p += 3; + } else { + p++; + *lengthMod = 'z'; + } + break; + case 'q': + p++; + *lengthMod = 'L'; + break; + case 'Z': + p++; + *lengthMod = 'z'; + break; + case 'L': + case 'j': + case 'z': + case 't': + *lengthMod = *p++; + break; + } + + /* + * Conversion specifier + * + * Return false if no conversion specifier or not the last character. + */ + + if (p + 1 == end && isSpecifier[(unsigned char) *p]) { + *conversion = *p; + return 0; + } + return -2; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtAToI -- + * + * Convert numeric string to integer. + * The range is 0 to MAX_INT32 (nonnegative 32-bit signed int). + * Empty string or a string that does not begin with + * a digit is treated as 0. + * + * Results: + * The number or -1 on overflow. + * Start pointer updated to point to first nonnumeric character. + * or first character before overflow. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static int +MsgFmtAToI(char const **start, // IN/OUT: string pointer + char const *end) // IN: end of string +{ + char const *p; + int n = 0; + + ASSERT_ON_COMPILE(sizeof (int) >= 4); + for (p = *start; p < end && *p >= '0' && *p <= '9'; p++) { + if (n > MAX_INT32 / 10) { + n = -1; + break; + } + n *= 10; + n += *p - '0'; + if (n < 0) { + n = -1; + break; + } + } + *start = p; + return n; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_GetArgs -- + * + * Parse a format string and return the arguments implied by it. + * + * Results: + * TRUE on sucess. + * Out parameters: + * The array of MsgFmt_Arg structures. + * The number of arguments. + * An error string on failure. + * + * Side effects: + * Memory is allocated. + * + *----------------------------------------------------------------------------- + */ + +Bool +MsgFmt_GetArgs(const char *fmt, // IN: format string + va_list va, // IN: the argument list + MsgFmt_Arg **args, // OUT: the returned arguments + int *numArgs, // OUT: number of returned arguments + char **error) // OUT: error string +{ + return MsgFmt_GetArgsWithBuf(fmt, va, args, numArgs, error, NULL, NULL); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_GetArgsWithBuf -- + * + * Parse a format string and return the arguments implied by it. + * + * If buf is supplied, allocate memory there instead of with malloc(). + * + * Results: + * TRUE on sucess. + * Out parameters: + * The array of MsgFmt_Arg structures. + * The number of arguments. + * An error string on failure. + * The amount of buf used (if caller supplied buf) + * + * Side effects: + * Memory may be allocated. + * + *----------------------------------------------------------------------------- + */ + +Bool +MsgFmt_GetArgsWithBuf(const char *fmt, // IN: format string + va_list va, // IN: the argument list + MsgFmt_Arg **args, // OUT: the returned arguments + int *numArgs, // OUT: number of returned arguments + char **error, // OUT: error string + void *buf, // OUT: memory to store output + size_t *bufSize) // IN/OUT: size of buf / + // amount of buf used +{ + MsgFmtParseState state; + int status; + int i; + + memset(&state, 0, sizeof state); + if (buf != NULL) { + ASSERT(bufSize != NULL); + MsgFmtAllocInit(&state, buf, *bufSize); + } + + /* + * First pass: parse format to get argument information + */ + + status = MsgFmt_Parse(NULL, MsgFmtGetArg1, &state, fmt); + if (status < 0) { + goto bad; + } + + /* + * Second pass: get argument values + * + * While we can store most values directly in the MsgFmt_Arg + * structure, strings have to be copied into allocated space. + * When precision is specified (see comment about it in + * MsgFmtGetArg1()), we copy at most that many bytes because + * that's how many printf() looks at, and we must not touch + * memory beyond what printf() would. + */ + + for (i = 0; i < state.numArgs; i++) { + MsgFmt_Arg *a = state.args + i; + switch (a->type) { + case MSGFMT_ARG_INVALID: + MsgFmtError(&state, "MsgFmt_GetArgs: gap in arguments at position %d", + i + 1); + goto bad; + break; + + case MSGFMT_ARG_INT32: + ASSERT_ON_COMPILE(sizeof (int) == sizeof (int32)); + a->v.signed32 = va_arg(va, int); + break; + case MSGFMT_ARG_INT64: + ASSERT_ON_COMPILE(sizeof (long long) == sizeof (int64)); + a->v.signed64 = va_arg(va, long long); + break; + + case MSGFMT_ARG_PTR32: + // we can only handle this case if native pointer is 4 bytes + ASSERT(sizeof (void *) == sizeof (uint32)); + a->v.unsigned32 = (uint32) (uintptr_t) va_arg(va, void *); + break; + case MSGFMT_ARG_PTR64: + // we can only handle this case if native pointer is 8 bytes + ASSERT(sizeof (void *) == sizeof (uint64)); + a->v.unsigned64 = (uint64) (uintptr_t) va_arg(va, void *); + break; + + case MSGFMT_ARG_FLOAT64: + ASSERT_ON_COMPILE(sizeof (double) == 8); + a->v.float64 = va_arg(va, double); + break; + + case MSGFMT_ARG_STRING8: { + const char *p = va_arg(va, char *); + size_t n; + Err_Number errorNumber; + ASSERT_ON_COMPILE(sizeof (char) == sizeof (int8)); + ASSERT_ON_COMPILE(offsetof(MsgFmt_Arg, v.string8) == + offsetof(MsgFmt_Arg, v.ptr)); + if (p == NULL) { + a->v.string8 = NULL; + } else { + if (a->p.precision < 0) { + n = strlen(p); + } else { + const char *q; + n = a->p.precision; + q = memchr(p, '\0', n); + if (q != NULL) { + n = q - p; + } + } + // yes, sizeof (int8) is 1. + a->v.string8 = MsgFmtAlloc(&state, n + 1); + if (a->v.string8 == NULL) { + status = -1; + goto bad; + } + memcpy(a->v.string8, p, n); + a->v.string8[n] = '\0'; + } + errorNumber = Err_String2Errno(p); +#ifdef VMX86_DEBUG + if (errorNumber == ERR_INVALID && p != NULL) { + // p may not be null terminated, so use string8 + errorNumber = Err_String2ErrnoDebug(a->v.string8char); + if (errorNumber != ERR_INVALID) { + // Err_String2ErrnoDebug already logged its info + Log("%s: failed to look up copied error string at %p.\n", + __FUNCTION__, p); + } + } +#endif + if (errorNumber != ERR_INVALID) { + ASSERT_NOT_IMPLEMENTED(MSGFMT_CURRENT_PLATFORM != + MSGFMT_PLATFORM_UNKNOWN); + ASSERT_ON_COMPILE(sizeof errorNumber == sizeof a->e.number); + a->type = MSGFMT_ARG_ERRNO; + a->e.platform = MSGFMT_CURRENT_PLATFORM; + a->e.number = errorNumber; + break; + } + break; + } + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: { + // we can only handle the case when native wchar_t matches + // the string char size + const wchar_t *p = va_arg(va, wchar_t *); + size_t n; + ASSERT(a->type == MSGFMT_ARG_STRING16 ? + sizeof (wchar_t) == sizeof (int16) : + sizeof (wchar_t) == sizeof (int32)); + ASSERT_ON_COMPILE(offsetof(MsgFmt_Arg, v.string16) == + offsetof(MsgFmt_Arg, v.ptr)); + ASSERT_ON_COMPILE(offsetof(MsgFmt_Arg, v.string32) == + offsetof(MsgFmt_Arg, v.ptr)); + if (p == NULL) { + a->v.ptr = NULL; + } else { + if ((n = a->p.precision) < 0) { + n = wcslen(p); + } else { +#ifndef _WIN32 + const wchar_t *q = wmemchr(p, 0, n); + if (q != NULL) { + n = q - p; + } +#else + // XXX no wmemchar() + // fix this when we get new compiler -- edward + const wchar_t *e = p + n; + const wchar_t *q; + for (q = p; q < e && *q != 0; q++) { + } + n = q - p; +#endif + } + a->v.ptr = MsgFmtAlloc(&state, sizeof (wchar_t) * (n + 1)); + if (a->v.ptr == NULL) { + status = -1; + goto bad; + } + memcpy(a->v.ptr, p, sizeof (wchar_t) * n); + ((wchar_t *) a->v.ptr)[n] = 0; + } + break; + } + + case MSGFMT_ARG_ERRNO: // there shouldn't be this case here + default: + NOT_REACHED(); + } + + // clear private data + memset(&a->p, 0, sizeof a->p); + } + + /* + * Pass results back + */ + + if (args == NULL) { + MsgFmtFreeAll(&state); + } else { + *args = state.args; + } + if (numArgs != NULL) { + *numArgs = state.numArgs; + } + if (bufSize != NULL) { + *bufSize = MsgFmtBufUsed(&state); + } + ASSERT(state.error == NULL); + *error = NULL; + return TRUE; + +bad: + if (state.error == NULL) { + switch (status) { + case -1: + MsgFmtError(&state, "MsgFmt_GetArgs: out of memory"); + break; + case -2: + MsgFmtError(&state, "MsgFmt_GetArgs: error in format string"); + break; + default: + MsgFmtError(&state, "MsgFmt_GetArgs: error %d", status); + } + } + ASSERT(state.args == NULL); // MsgFmtError() frees args + *error = state.error; + return FALSE; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtGetArg1 -- + * + * Process one format specifier for MsgFmt_GetArgs(). + * Called by MsgFmt_Parse(). + * + * Results: + * 0 on success, + * negative status on failure (see MsgFmt_Parse()). + * error string in state.error on failure. + * + * Side effects: + * Memory is allocated. + * + *----------------------------------------------------------------------------- + */ + +static int +MsgFmtGetArg1(void *clientData, // IN: state + const char *pos, // IN: n$ location + unsigned int posSize, // IN: n$ length + char const *type, // IN: specifier after position + unsigned int typeSize) // IN: size of above +{ + MsgFmtParseState *state = clientData; + MsgFmt_Arg *a; + int position; + int flags; + int width; + int precision; + char lengthMod; + char conversion; + MsgFmt_ArgType argType = MSGFMT_ARG_INVALID; + int status; + + /* + * Parse format specifier + */ + + status = MsgFmt_ParseSpec(pos, posSize, type, typeSize, + &position, &flags, &width, &precision, + &lengthMod, &conversion); + if (status < 0) { + MsgFmtError(state, + "MsgFmtGetArg1: bad specifier, " + "status %d, pos \"%.*s\", type \"%.*s\"", + status, posSize, pos, typeSize, type); + return status; + } + + /* + * Make room in argument array if necessary. + */ + + if (position > state->numArgs) { + if (!MsgFmtAllocArgs(state, position)) { + MsgFmtError(state, "MsgFmtGetArg1: out of memory at arg %d", + position); + return -1; + } + state->numArgs = position; + } + + /* + * Fill in argument structure based on the format specifier. + * + * For strings, the precision argument is the maximum length + * to print. We need to keep track of it so MsgFmt_GetArgs() + * can know how many characters to squirrel away, in case + * the string isn't null terminated, is very long, or falls off + * the end of the world. + * + * In all other cases, the precision is unimportant to us + * and we don't keep it around. + */ + + a = state->args + position - 1; + + switch (conversion) { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + switch (lengthMod) { + // all of these take an int argument, they just print differently + case '\0': + case 'h': + case 'H': + ASSERT_ON_COMPILE(sizeof (int) == sizeof (int32)); + argType = MSGFMT_ARG_INT32; + break; + + case 'l': + ASSERT_ON_COMPILE(sizeof (long) == sizeof (int32) || + sizeof (long) == sizeof (int64)); + if (sizeof (long) == sizeof (int32)) { + argType = MSGFMT_ARG_INT32; + } else { + argType = MSGFMT_ARG_INT64; + } + break; + + case 'j': +#ifndef _WIN32 // no intmax_t, bsd_vsnprintf() uses 64 bits + ASSERT_ON_COMPILE(sizeof (intmax_t) == sizeof (int64)); +#endif + case 'L': + ASSERT_ON_COMPILE(sizeof (long long) == sizeof (int64)); + argType = MSGFMT_ARG_INT64; + break; + + case 't': + ASSERT_ON_COMPILE(sizeof (ptrdiff_t) == sizeof (size_t)); + case 'z': + ASSERT_ON_COMPILE(sizeof (size_t) == sizeof (int32) || + sizeof (size_t) == sizeof (int64)); + if (sizeof (size_t) == sizeof (int32)) { + argType = MSGFMT_ARG_INT32; + } else { + argType = MSGFMT_ARG_INT64; + } + break; + default: + NOT_REACHED(); + } + break; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + switch (lengthMod) { + // l h hh t z are not defined by man page, but allowed by glibc + case '\0': + case 'l': + case 'h': + case 'H': + case 't': + case 'z': + ASSERT_ON_COMPILE(sizeof (double) == 8); + argType = MSGFMT_ARG_FLOAT64; + break; + // j is not defined by man page, but allowed by glibc + case 'L': + case 'j': + /* + * We don't do %Lf because it's not that useful to us, and + * long double has a number of implementations. For example, + * on Win32 it's the same as double, and it would have a hard + * time dealing with a bigger one passed to it. + * We can just coerce it down to a double at the source, + * but then why bother? + */ + MsgFmtError(state, + "MsgFmtGetArg1: %%%c%c not supported, " + "pos \"%.*s\", type \"%.*s\"", + lengthMod, conversion, posSize, pos, typeSize, type); + return -2; + default: + NOT_REACHED(); + } + break; + + case 'c': + switch (lengthMod) { + // h hh t z not defined by man page, but allowed by glibc + case '\0': + case 'h': + case 'H': + case 't': + case 'z': + ASSERT_ON_COMPILE(sizeof (int) == sizeof (int32)); + argType = MSGFMT_ARG_INT32; + break; + // j ll L not defined by man page nor actually supported + case 'l': + case 'j': + case 'L': + goto caseC; + default: + NOT_REACHED(); + } + break; + + case 'C': + caseC: + // man page says it's a wint_t argument, but we assume promotion to int + ASSERT_ON_COMPILE(sizeof (wint_t) <= sizeof (int) && + sizeof (int) == sizeof (int32)); + argType = MSGFMT_ARG_INT32; + break; + + case 's': + // we interpret the length modifier like we do for %c + switch (lengthMod) { + case '\0': + case 'h': + case 'H': + case 't': + case 'z': + ASSERT_ON_COMPILE(sizeof (char) == sizeof (int8)); + argType = MSGFMT_ARG_STRING8; + break; + case 'l': + case 'j': + case 'L': + goto caseS; + default: + NOT_REACHED(); + } + // keep track of maximum string length, see block comment above + a->p.precision = precision; + ASSERT(a->v.ptr == NULL); + break; + + case 'S': + caseS: + +#if defined __ANDROID__ + ASSERT_ON_COMPILE(sizeof (wchar_t) == sizeof (int16) || + sizeof (wchar_t) == sizeof (int32) || + sizeof (wchar_t) == sizeof (int8)); +#else + ASSERT_ON_COMPILE(sizeof (wchar_t) == sizeof (int16) || + sizeof (wchar_t) == sizeof (int32)); +#endif + + if (sizeof (wchar_t) == sizeof (int16)) { + argType = MSGFMT_ARG_STRING16; +#if defined __ANDROID__ + } else if (sizeof (wchar_t) == sizeof (int8)) { + argType = MSGFMT_ARG_STRING8; +#endif + } else { + argType = MSGFMT_ARG_STRING32; + } + // keep track of maximum string length, see block comment above + a->p.precision = precision; + ASSERT(a->v.ptr == NULL); + break; + + case 'p': + ASSERT_ON_COMPILE(sizeof (void *) == sizeof (int32) || + sizeof (void *) == sizeof (int64)); + if (sizeof (void *) == sizeof (int32)) { + argType = MSGFMT_ARG_PTR32; + } else { + argType = MSGFMT_ARG_PTR64; + } + break; + + case 'n': + MsgFmtError(state, + "MsgFmtGetArg1: %%n not supported, " + "pos \"%.*s\", type \"%.*s\"", + posSize, pos, typeSize, type); + return -2; + + // MsgFmt_ParseSpec() doesn't do %m, and we don't see %% + default: + MsgFmtError(state, + "MsgFmtGetArg1: %%%c not understood, " + "pos \"%.*s\", type \"%.*s\"", + conversion, posSize, pos, typeSize, type); + NOT_REACHED(); + } + + ASSERT(argType != MSGFMT_ARG_INVALID); + if (a->type != MSGFMT_ARG_INVALID && a->type != argType) { + MsgFmtError(state, + "MsgFmtGetArg1: incompatible specifiers for argument %d, " + "old type %d, new type %d, pos \"%.*s\", type \"%.*s\"", + position, a->type, argType, posSize, pos, typeSize, type); + return -2; + } + a->type = argType; + + return 0; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtError -- + * + * Format an error string and squirrel it away. + * + * Results: + * Error string returned in state variable. + * + * Side effects: + * Memory may be allocated. + * + *----------------------------------------------------------------------------- + */ + +static void +MsgFmtError(MsgFmtParseState *state, // IN/OUT: state structure + const char *fmt, // IN: error format + ...) // IN: error args +{ + va_list args; + + ASSERT(state->error == NULL); + // free up space (in call-supplied buffer) for error string + MsgFmtFreeAll(state); + va_start(args, fmt); + state->error = MsgFmtVasprintf(state, fmt, args); + va_end(args); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_FreeArgs -- + * + * Free an array of MsgFmt_Arg structures. + * Do not call this on an array in a caller-supplied + * buffer from MsgFmt_GetArgsWithBuf(). + * + * Results: + * None. + * + * Side effects: + * Memory is freed. + * + *----------------------------------------------------------------------------- + */ + +void +MsgFmt_FreeArgs(MsgFmt_Arg *args, // IN/OUT: arguments to free + int numArgs) // IN: number of arguments +{ + int i; + + for (i = 0; i < numArgs; i++) { + switch (args[i].type) { + case MSGFMT_ARG_STRING8: + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: + case MSGFMT_ARG_ERRNO: + free(args[i].v.ptr); + break; + default: + ; + } + } + free(args); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtAllocInit -- + * + * Initialize allocator for caller-supplied buffer. + * + * Results: + * None. + * + * Side effects: + * As described. + * + *----------------------------------------------------------------------------- + */ + +static void +MsgFmtAllocInit(MsgFmtParseState *state, // IN/OUT: state structure + void *buf, // IN: buffer + size_t size) // IN: size to allocate +{ + state->bufp = state->buf = buf; + state->bufe = state->bufp + size; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtAlloc -- + * + * Allocate memory from malloc() or from supplied buffer. + * + * Results: + * Pointer or NULL on failure. + * + * Side effects: + * Memory allocated or state updated. + * + *----------------------------------------------------------------------------- + */ + +static void * +MsgFmtAlloc(MsgFmtParseState *state, // IN/OUT: state structure + size_t size) // IN: size to allocate +{ + void *p; + + if (state->buf == NULL) { + p = malloc(size); + } else { + if (state->bufe - state->bufp < size) { + return NULL; + } + p = state->bufp; + state->bufp += size; + } + return p; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtAllocArgs -- + * + * Grow MsgFmt_Arg array to accomodate new entry. + * + * Results: + * TRUE on success. + * State updated. + * + * Side effects: + * Memory may be allocated. + * + *----------------------------------------------------------------------------- + */ + +static Bool +MsgFmtAllocArgs(MsgFmtParseState *state, // IN/OUT: state structure + int n) // IN: 1-based argument number +{ + if (n <= state->maxArgs) { + return TRUE; + } + + /* + * If using malloc, then reallocate() the array with some slack. + * If using our own buffer, just grow it exactly. + */ + + if (state->buf == NULL) { + void *p; + n = MAX(4, n + state->maxArgs); + p = realloc(state->args, n * sizeof *state->args); + if (p == NULL) { + return FALSE; + } + state->args = p; + } else { + if (state->args == NULL) { + // first time + state->args = (void *) state->bufp; + } else { + // growing: there must be nothing after the args array + ASSERT((void *) state->bufp == state->args + state->maxArgs); + } + if ((char *) (state->args + n) > state->bufe) { + return FALSE; + } + state->bufp = (char *) (state->args + n); + } + memset(state->args + state->maxArgs, 0, + sizeof *state->args * (n - state->maxArgs)); + state->maxArgs = n; + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtVasprintf -- + * + * Format a string in allocated space. + * + * Results: + * String. + * + * Side effects: + * Memory allocated or state updated. + * Panic if can't allocate. + * + *----------------------------------------------------------------------------- + */ + +static char * +MsgFmtVasprintf(MsgFmtParseState *state, // IN/OUT: state structure + const char *fmt, // IN: error format + va_list args) // IN: error args +{ + char *p; + + ASSERT(state->error == NULL); + if (state->buf == NULL) { + p = Str_Vasprintf(NULL, fmt, args); + ASSERT_MEM_ALLOC(p != NULL); + } else { + int n; + p = state->bufp; + // Str_Vsnprintf() may truncate + n = Str_Vsnprintf(p, (char *)state->bufe - p, fmt, args); + state->bufp = (n < 0) ? state->bufe : state->bufp + n + 1; + } + return p; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtFreeAll -- + * + * Free all memory associated with current MsgFmt_Arg array. + * + * Results: + * State updated. + * + * Side effects: + * Memory may be freed. + * + *----------------------------------------------------------------------------- + */ + +static void +MsgFmtFreeAll(MsgFmtParseState *state) // IN/OUT: state structure +{ + if (state->args == NULL) + return; + + if (state->buf == NULL) { + MsgFmt_FreeArgs(state->args, state->numArgs); + } else { + state->bufp = state->buf; + } + state->numArgs = state->maxArgs = 0; + state->args = NULL; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmtBufUsed -- + * + * Return the amount of space used in the caller supplied buffer. + * + * Results: + * size_t + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static size_t +MsgFmtBufUsed(MsgFmtParseState *state) // IN: state structure +{ + if (state->buf == NULL) { + return 0; + } else { + return state->bufp - (char *)state->buf; + } +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_SwizzleArgs -- + * + * Pointer swizzling. Flattens pointers in the MsgFmt_Arg array by + * converting them to offsets relative to the start of the args array. + * This should only be invoked if the MsgFmt_Arg array was allocated + * from a caller-supplied buffer from MsgFmt_GetArgsWithBuf. + * + * Results: + * None. + * + * Side effects: + * For all i such that args[i] is a string parameter, + * args[i].v.offset is set to the offset from args to the start + * of the string, or to 0 if the string was NULL. + * + *----------------------------------------------------------------------------- + */ + +void +MsgFmt_SwizzleArgs(MsgFmt_Arg *args, + int numArgs) +{ + int i; + int8* bufStart = (int8*)args; + + for (i = 0; i < numArgs; i++) { + + switch (args[i].type) { + case MSGFMT_ARG_STRING8: + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: + if (args[i].v.ptr == NULL) { + // offset is never 0 otherwise + args[i].v.offset = 0; + } else { + args[i].v.offset = (int8*)args[i].v.ptr - bufStart; + } + break; + default: + break; + } + } +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_GetSwizzledString -- + * + * Helper for pointer un-swizzling. Obtains the pointer encoded + * by a swizzled argument, if it is a string and the pointer is + * within the proper bounds. + * + * Results: + * NULL and a non-zero return value if the given argument is not + * a string, or the pointer is out of bounds (below the end of + * the args array or above the end of the buffer), or the string + * is not null-terminated within the buffer. + * + * Exception to the above: an offset of 0 is used to encode the + * NULL pointer. In this case, yields NULL and returns zero. + * + * Otherwise, yields a pointer to the string and returns zero. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_GetSwizzledString(const MsgFmt_Arg *args, // IN: argument array + int numArgs, // IN: size of the array + int i, // IN: index into the array + const void *bufEnd, // IN: string space bound + const int8 **str) // OUT: the string +{ + const int8 *bufStart = (const int8*)args; + const int8 *strStart = (const int8*)(args + numArgs); + const int8 *strEnd = bufEnd; + + switch(args[i].type) { + case MSGFMT_ARG_STRING8: + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: + if (args[i].v.offset == 0) { + // offset is never 0 otherwise + *str = NULL; + return 0; + } else { + const int8 *ptr = args[i].v.offset + bufStart; + + if (ptr < strStart || ptr >= strEnd + || memchr(ptr, '\0', strEnd - ptr) == NULL) { + *str = NULL; + return -1; + } else { + *str = ptr; + return 0; + } + } + break; + default: + *str = NULL; + return -1; + } + NOT_REACHED(); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_UnswizzleArgs -- + * + * Pointer un-swizzling. Re-instates the pointers in the arg array. + * This should only be invoked if the MsgFmt_Arg array was previously + * swizzled using MsgFmt_SwizzleArgs. + * + * If a reconstituted pointer would be out of range -- i.e., + * before the end of the args array or after the provided + * end-of-buffer pointer -- it is replaced with NULL and an error + * is returned. This is also done if the resulting string is not + * null-terminated within the provided bound. + * + * Results: + * 0 on success; -1 in case of bad pointer. + * + * Side effects: + * For all i such that args[i] is a string parameter, sets + * args[i].v.ptr to the string previously encoded as an offset, + * or to NULL if the offset was 0, or to NULL in case of error. + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_UnswizzleArgs(MsgFmt_Arg *args, // IN/OUT: the arguments (+ strings) + int numArgs, // IN: number of arguments + void *bufEnd) // IN: string space bound +{ + int i; + int failures = 0; + + for (i = 0; i < numArgs; i++) { + switch (args[i].type) { + case MSGFMT_ARG_STRING8: + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: + if (MsgFmt_GetSwizzledString(args, numArgs, i, bufEnd, + (const int8**)&args[i].v.ptr) != 0) { + ++failures; + } + break; + default: + break; + } + } + return failures > 0 ? -1 : 0; +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_CopyArgs -- + * + * Copy all args from the given 'copyArgs' array. + * + * Results: + * Pointer to copied args array. + * + * Side effects: + * Allocates memory for new args array. + * + *----------------------------------------------------------------------------- + */ + +MsgFmt_Arg* +MsgFmt_CopyArgs(MsgFmt_Arg* copyArgs, // IN: Args to be copied + int numArgs) // IN: number of args +{ + MsgFmt_Arg *args; + int i; + + args = malloc(numArgs * sizeof(MsgFmt_Arg)); + if (args == NULL) { + return NULL; + } + + memcpy(args, copyArgs, numArgs * sizeof(MsgFmt_Arg)); + + for (i = 0; i < numArgs; i++) { + switch (args[i].type) { + case MSGFMT_ARG_STRING8: + case MSGFMT_ARG_ERRNO: + if (args[i].v.string8 != NULL) { + args[i].v.string8char = strdup(copyArgs[i].v.string8char); + if (args[i].v.string8 == NULL) { + MsgFmt_FreeArgs(args, i); + return NULL; + } + } + break; + case MSGFMT_ARG_STRING16: + case MSGFMT_ARG_STRING32: + /* + * We don't care about these types. + */ + NOT_IMPLEMENTED(); + break; + default: + break; + } + } + + return args; +} + + +#ifdef HAS_BSD_PRINTF // { + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_Snprintf -- + * + * MsgFmt_Arg version of Str_Vsnprintf(). + * + * Results: + * Number of character written, not including null termination, + * or number of characters would have been written on overflow. + * (This is exactly the same as vsnprintf(), but different + * from Str_Vsnprintf().) + * String is always null terminated, even on overflow. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +int +MsgFmt_Snprintf(char *buf, // OUT: formatted string + size_t size, // IN: size of buffer + const char *format, // IN: format + const MsgFmt_Arg *args, // IN: message arguments + int numArgs) // IN: number of arguments +{ + return MsgFmtSnprintfWork(&buf, size, format, args, numArgs); +} + + +/* + *----------------------------------------------------------------------------- + * + * MsgFmt_Asprintf -- + * + * MsgFmt_Arg version of Str_Vasprintf(). + * + * Results: + * Allocated string on success. + * NULL on failure. + * Length of returned string (not including null termination) + * in *length (if length != NULL). + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +char * +MsgFmt_Asprintf(size_t *length, // OUT: length of returned string + const char *format, // IN: format + const MsgFmt_Arg *args, // IN: message arguments + int numArgs) // IN: number of arguments +{ + char *p = NULL; + int n = MsgFmtSnprintfWork(&p, 0, format, args, numArgs); + + if (n < 0) { + return NULL; + } + if (length != NULL) { + *length = n; + } + return p; +} + +static int +MsgFmtSnprintfWork(char **outbuf, size_t bufSize, const char *fmt0, + const MsgFmt_Arg *args, int numArgs) +{ + char *fmt; /* format string */ + int ch; /* character from fmt */ + int n; /* handy integer (short term usage) */ + char *cp; /* handy char pointer (short term usage) */ + BSDFmt_IOV *iovp; /* for PRINT macro */ + int flags; /* flags as above */ + int ret; /* return value accumulator */ + int width; /* width from format (%8d), or 0 */ + int prec; /* precision from format; <0 for N/A */ + char sign; /* sign prefix (' ', '+', '-', or \0) */ + char thousands_sep; /* locale specific thousands separator */ + const char *grouping; /* locale specific numeric grouping rules */ + +#ifndef NO_FLOATING_POINT + /* + * We can decompose the printed representation of floating + * point numbers into several parts, some of which may be empty: + * + * [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ + * A B ---C--- D E F + * + * A: 'sign' holds this value if present; '\0' otherwise + * B: ox[1] holds the 'x' or 'X'; '\0' if not hexadecimal + * C: cp points to the string MMMNNN. Leading and trailing + * zeros are not in the string and must be added. + * D: expchar holds this character; '\0' if no exponent, e.g. %f + * F: at least two digits for decimal, at least one digit for hex + */ + char *decimal_point; /* locale specific decimal point */ +#if defined __ANDROID__ + static const char dp = '.'; +#endif + int signflag; /* true if float is negative */ + union { /* floating point arguments %[aAeEfFgG] */ + double dbl; + long double ldbl; + } fparg; + int expt; /* integer value of exponent */ + char expchar; /* exponent character: [eEpP\0] */ + char *dtoaend; /* pointer to end of converted digits */ + int expsize; /* character count for expstr */ + int lead; /* sig figs before decimal or group sep */ + int ndig; /* actual number of digits returned by dtoa */ + char expstr[MAXEXPDIG+2]; /* buffer for exponent string: e+ZZZ */ + char *dtoaresult; /* buffer allocated by dtoa */ + int nseps; /* number of group separators with ' */ + int nrepeats; /* number of repeats of the last group */ +#endif + uintmax_t ujval; /* %j, %ll, %q, %t, %z integers */ + int base; /* base for [diouxX] conversion */ + int dprec; /* a copy of prec if [diouxX], 0 otherwise */ + int realsz; /* field size expanded by dprec, sign, etc */ + int size; /* size of converted field or string */ + int prsize; /* max size of printed field */ + const char *xdigs; /* digits for %[xX] conversion */ + BSDFmt_UIO uio; /* output information: summary */ + BSDFmt_IOV iov[BSDFMT_NIOV]; /* ... and individual io vectors */ + char buf[INT_CONV_BUF];/* buffer with space for digits of uintmax_t */ + char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */ + int nextarg; /* 1-based argument index */ + const MsgFmt_Arg *a; + char *convbuf; /* wide to multibyte conversion result */ + BSDFmt_StrBuf sbuf; + + /* + * BEWARE, these `goto error' on error, and PAD uses `n'. + */ +#define PRINT(ptr, len) { \ + iovp->iov_base = (ptr); \ + iovp->iov_len = (len); \ + uio.uio_resid += (len); \ + iovp++; \ + if (++uio.uio_iovcnt >= BSDFMT_NIOV) { \ + if (BSDFmt_SPrint(&sbuf, &uio)) \ + goto error; \ + iovp = iov; \ + } \ + } +#define PAD(howmany, with) { \ + if ((n = (howmany)) > 0) { \ + while (n > PADSIZE) { \ + PRINT(with, PADSIZE); \ + n -= PADSIZE; \ + } \ + PRINT(with, n); \ + } \ + } +#define PRINTANDPAD(p, ep, len, with) do { \ + int n2 = (ep) - (p); \ + if (n2 > (len)) \ + n2 = (len); \ + if (n2 > 0) \ + PRINT((p), n2); \ + PAD((len) - (n2 > 0 ? n2 : 0), (with)); \ + } while(0) +#define FLUSH() { \ + if (uio.uio_resid && BSDFmt_SPrint(&sbuf, &uio)) \ + goto error; \ + uio.uio_iovcnt = 0; \ + iovp = iov; \ + } + +#define FETCHARG(a, i) do { \ + int ii = (i) - 1; \ + if (ii >= numArgs) { \ + sbuf.error = TRUE; \ + goto error; \ + } \ + (a) = args + ii; \ +} while (FALSE) + + /* + * Get * arguments, including the form *nn$. + */ +#define GETASTER(val) do { \ + int n2 = 0; \ + char *cp = fmt; \ + const MsgFmt_Arg *a; \ + while (is_digit(*cp)) { \ + n2 = 10 * n2 + to_digit(*cp); \ + cp++; \ + } \ + if (*cp == '$') { \ + FETCHARG(a, n2); \ + fmt = cp + 1; \ + } else { \ + FETCHARG(a, nextarg++); \ + } \ + if (a->type != MSGFMT_ARG_INT32) { \ + sbuf.error = TRUE; \ + goto error; \ + } \ + val = a->v.signed32; \ +} while (FALSE) + + xdigs = xdigs_lower; + thousands_sep = '\0'; + grouping = NULL; + convbuf = NULL; +#ifndef NO_FLOATING_POINT + dtoaresult = NULL; +#if defined __ANDROID__ + /* + * Struct lconv is not working! For decimal_point, + * using '.' instead is a workaround. + */ + NOT_TESTED(); + decimal_point = &dp; +#else + decimal_point = localeconv()->decimal_point; +#endif +#endif + + fmt = (char *)fmt0; + nextarg = 1; + uio.uio_iov = iovp = iov; + uio.uio_resid = 0; + uio.uio_iovcnt = 0; + ret = 0; + + /* + * Set up output string buffer structure. + */ + + sbuf.alloc = *outbuf == NULL; + sbuf.error = FALSE; + sbuf.buf = *outbuf; + sbuf.size = bufSize; + sbuf.index = 0; + + /* + * If asprintf(), allocate initial buffer based on format length. + * Empty format only needs one byte. + * Otherwise, round up to multiple of 64. + */ + + if (sbuf.alloc) { + size_t n = strlen(fmt0) + 1; // +1 for \0 + if (n > 1) { + n = ROUNDUP(n, 64); + } + if ((sbuf.buf = malloc(n * sizeof (char))) == NULL) { + sbuf.error = TRUE; + goto error; + } + sbuf.size = n; + } + + // shut compile up +#ifndef NO_FLOATING_POINT + expt = 0; + expchar = 0; + dtoaend = NULL; + expsize = 0; + lead = 0; + ndig = 0; + nseps = 0; + nrepeats = 0; +#endif + ujval = 0; + + /* + * Scan the format for conversions (`%' character). + */ + for (;;) { + for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) + /* void */; + if ((n = fmt - cp) != 0) { + if ((unsigned)ret + n > INT_MAX) { + ret = EOF; + goto error; + } + PRINT(cp, n); + ret += n; + } + if (ch == '\0') + goto done; + fmt++; /* skip over '%' */ + + flags = 0; + dprec = 0; + width = 0; + prec = -1; + sign = '\0'; + ox[1] = '\0'; + + rflag: ch = *fmt++; + reswitch: switch (ch) { + case ' ': + /*- + * ``If the space and + flags both appear, the space + * flag will be ignored.'' + * -- ANSI X3J11 + */ + if (!sign) + sign = ' '; + goto rflag; + case '#': + flags |= ALT; + goto rflag; + case '*': + /*- + * ``A negative field width argument is taken as a + * - flag followed by a positive field width.'' + * -- ANSI X3J11 + * They don't exclude field widths read from args. + */ + GETASTER (width); + if (width >= 0) + goto rflag; + width = -width; + /* FALLTHROUGH */ + case '-': + flags |= LADJUST; + goto rflag; + case '+': + sign = '+'; + goto rflag; + case '\'': + flags |= GROUPING; +#if defined __ANDROID__ + /* + * Struct lconv is not working! The code below is a workaround. + */ + NOT_TESTED(); + thousands_sep = ','; +#else + thousands_sep = *(localeconv()->thousands_sep); + grouping = localeconv()->grouping; +#endif + goto rflag; + case '.': + if ((ch = *fmt++) == '*') { + GETASTER (prec); + goto rflag; + } + prec = 0; + while (is_digit(ch)) { + prec = 10 * prec + to_digit(ch); + ch = *fmt++; + } + goto reswitch; + case '0': + /*- + * ``Note that 0 is taken as a flag, not as the + * beginning of a field width.'' + * -- ANSI X3J11 + */ + flags |= ZEROPAD; + goto rflag; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = 0; + do { + n = 10 * n + to_digit(ch); + ch = *fmt++; + } while (is_digit(ch)); + if (ch == '$') { + nextarg = n; + goto rflag; + } + width = n; + goto reswitch; + case 'h': + if (flags & SHORTINT) { + flags &= ~SHORTINT; + flags |= CHARINT; + } else + flags |= SHORTINT; + goto rflag; + case 'j': + flags |= INTMAXT; + goto rflag; + case 'I': + /* could be I64 - long long int is 64bit */ + if (fmt[0] == '6' && fmt[1] == '4') { + fmt += 2; + flags |= LLONGINT; + goto rflag; + } + /* could be I32 - normal int is 32bit */ + if (fmt[0] == '3' && fmt[1] == '2') { + fmt += 2; + /* flags |= normal integer - it is 32bit for all our targets */ + goto rflag; + } + /* + * I alone - use Microsoft's semantic as size_t modifier. We do + * not support glibc's semantic to use alternative digits. + */ + flags |= SIZET; + goto rflag; + case 'l': + if (flags & LONGINT) { + flags &= ~LONGINT; + flags |= LLONGINT; + } else + flags |= LONGINT; + goto rflag; + case 'L': + case 'q': + flags |= LLONGINT; /* not necessarily */ + goto rflag; + case 't': + flags |= PTRDIFFT; + goto rflag; + case 'Z': + case 'z': + flags |= SIZET; + goto rflag; + case 'C': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'c': + FETCHARG(a, nextarg++); + if (a->type != MSGFMT_ARG_INT32) { + sbuf.error = TRUE; + goto error; + } + if (flags & LONGINT) { + static const mbstate_t initial; + mbstate_t mbs; + size_t mbseqlen; + + mbs = initial; + // XXX must deal with mismatch between wchar_t size + mbseqlen = wcrtomb(cp = buf, (wchar_t)a->v.signed32, &mbs); + if (mbseqlen == (size_t)-1) { + sbuf.error = TRUE; + goto error; + } + size = (int)mbseqlen; + } else { + *(cp = buf) = a->v.signed32; + size = 1; + } + sign = '\0'; + break; + case 'D': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'd': + case 'i': + FETCHARG(a, nextarg++); + if ((flags & (INTMAXT|LLONGINT)) != 0) { + if (a->type == MSGFMT_ARG_INT64) { + ujval = a->v.signed64; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & (SIZET|PTRDIFFT|LONGINT)) != 0) { + if (a->type == MSGFMT_ARG_INT64) { + ujval = a->v.signed64; + } else if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) a->v.signed32; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & SHORTINT) != 0) { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) (short) a->v.signed32; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & CHARINT) != 0) { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) (signed char) a->v.signed32; + } else { + sbuf.error = TRUE; + goto error; + } + } else { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) a->v.signed32; + } else { + sbuf.error = TRUE; + goto error; + } + } + if ((intmax_t)ujval < 0) { + ujval = -ujval; + sign = '-'; + } + base = 10; + goto number; +#ifndef NO_FLOATING_POINT + case 'e': + case 'E': + expchar = ch; + if (prec < 0) /* account for digit before decpt */ + prec = DEFPREC + 1; + else + prec++; + goto fp_begin; + case 'f': + case 'F': + expchar = '\0'; + goto fp_begin; + case 'g': + case 'G': + expchar = ch - ('g' - 'e'); + if (prec == 0) + prec = 1; + fp_begin: + if (flags & LLONGINT) { + sbuf.error = TRUE; + goto error; + } + if (prec < 0) + prec = DEFPREC; + if (dtoaresult != NULL) + freedtoa(dtoaresult); + FETCHARG(a, nextarg++); + if (a->type != MSGFMT_ARG_FLOAT64) { + sbuf.error = TRUE; + goto error; + } + fparg.dbl = a->v.float64; + dtoaresult = cp = + dtoa(fparg.dbl, expchar ? 2 : 3, prec, + &expt, &signflag, &dtoaend); + if (expt == 9999) + expt = INT_MAX; + if (signflag) + sign = '-'; + if (expt == INT_MAX) { /* inf or nan */ + if (*cp == 'N') { + cp = (ch >= 'a') ? "nan" : "NAN"; + sign = '\0'; + } else + cp = (ch >= 'a') ? "inf" : "INF"; + size = 3; + break; + } + flags |= FPT; + ndig = dtoaend - cp; + if (ch == 'g' || ch == 'G') { + if (expt > -4 && expt <= prec) { + /* Make %[gG] smell like %[fF] */ + expchar = '\0'; + if (flags & ALT) + prec -= expt; + else + prec = ndig - expt; + if (prec < 0) + prec = 0; + } else { + /* + * Make %[gG] smell like %[eE], but + * trim trailing zeroes if no # flag. + */ + if (!(flags & ALT)) + prec = ndig; + } + } + if (expchar) { + expsize = BSDFmt_Exponent(expstr, expt - 1, expchar); + size = expsize + prec; + if (prec > 1 || flags & ALT) + ++size; + } else { + /* space for digits before decimal point */ + if (expt > 0) + size = expt; + else /* "0" */ + size = 1; + /* space for decimal pt and following digits */ + if (prec || flags & ALT) + size += prec + 1; + if (grouping && expt > 0) { + /* space for thousands' grouping */ + nseps = nrepeats = 0; + lead = expt; + while (*grouping != CHAR_MAX) { + if (lead <= *grouping) + break; + lead -= *grouping; + if (*(grouping+1)) { + nseps++; + grouping++; + } else + nrepeats++; + } + size += nseps + nrepeats; + } else + lead = expt; + } + break; +#endif /* !NO_FLOATING_POINT */ + case 'n': + sbuf.error = TRUE; + goto error; + case 'O': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'o': + base = 8; + goto get_unsigned; + case 'p': + /*- + * ``The argument shall be a pointer to void. The + * value of the pointer is converted to a sequence + * of printable characters, in an implementation- + * defined manner.'' + * -- ANSI X3J11 + */ + FETCHARG(a, nextarg++); + if (a->type == MSGFMT_ARG_PTR32) { + ujval = a->v.unsigned32; + } else if (a->type == MSGFMT_ARG_PTR64) { + ujval = a->v.unsigned64; + } else { + sbuf.error = TRUE; + goto error; + } + base = 16; + xdigs = xdigs_upper; + flags = flags | INTMAXT; + /* + * PR 103201 + * VisualC sscanf doesn't grok '0x', so prefix zeroes. + */ +// ox[1] = 'x'; + goto nosign; + case 'S': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 's': + FETCHARG(a, nextarg++); + if (flags & LONGINT) { + wchar_t *wcp; +#if defined __ANDROID__ + ASSERT_ON_COMPILE(sizeof (wchar_t) == sizeof (int16) || + sizeof (wchar_t) == sizeof (int32) || + sizeof (wchar_t) == sizeof (int8)); + if ((sizeof (wchar_t) == sizeof (int16) && + a->type != MSGFMT_ARG_STRING16) || + (sizeof (wchar_t) == sizeof (int32) && + a->type != MSGFMT_ARG_STRING32) || + (sizeof (wchar_t) == sizeof (int8) && + a->type != MSGFMT_ARG_STRING8)) { +#else + ASSERT_ON_COMPILE(sizeof (wchar_t) == 2 || sizeof (wchar_t) == 4); + if (sizeof (wchar_t) == 2 ? + a->type != MSGFMT_ARG_STRING16 : + a->type != MSGFMT_ARG_STRING32) { +#endif + sbuf.error = TRUE; + goto error; + } + if ((wcp = (wchar_t *) a->v.ptr) == NULL) + cp = "(null)"; + else { + if (convbuf != NULL) + free(convbuf); + convbuf = BSDFmt_WCharToUTF8(wcp, prec); + if (convbuf == NULL) { + sbuf.error = TRUE; + goto error; + } + cp = convbuf; + } + } else { + if (a->type != MSGFMT_ARG_STRING8 && + a->type != MSGFMT_ARG_ERRNO) { + sbuf.error = TRUE; + goto error; + } + + /* + * Use localized string (in localString) if available. + * Strip off Msg ID if unlocalized string has one. + * Use (null) for null pointer. + */ + + if (a->p.localString != NULL) { + cp = a->p.localString; + } else if (a->v.string8 != NULL) { + cp = (char *) Msg_StripMSGID(a->v.string8char); + } else { + cp = "(null)"; + } + } + if (prec >= 0) { + /* + * We can use strlen here because the string is always + * terminated, unlike the string passed to MsgFmt_GetArgs. + * However, it's somewhat faster to use memchr. + */ + char *p = memchr(cp, 0, prec); + + if (p != NULL) { + size = p - cp; + if (size > prec) + size = prec; + } else + size = prec; + } else + size = strlen(cp); + sign = '\0'; + break; + case 'U': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'u': + base = 10; + goto get_unsigned; + case 'X': + xdigs = xdigs_upper; + goto hex; + case 'x': + xdigs = xdigs_lower; + hex: + base = 16; + if (flags & ALT) + ox[1] = ch; + flags &= ~GROUPING; + + get_unsigned: + FETCHARG(a, nextarg++); + if ((flags & (INTMAXT|LLONGINT)) != 0) { + if (a->type == MSGFMT_ARG_INT64) { + ujval = a->v.unsigned64; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & (SIZET|PTRDIFFT|LONGINT)) != 0) { + if (a->type == MSGFMT_ARG_INT64) { + ujval = a->v.unsigned64; + } else if (a->type == MSGFMT_ARG_INT32) { + ujval = (uintmax_t) a->v.unsigned32; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & SHORTINT) != 0) { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) (unsigned short) a->v.unsigned32; + } else { + sbuf.error = TRUE; + goto error; + } + } else if ((flags & CHARINT) != 0) { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) (unsigned char) a->v.unsigned32; + } else { + sbuf.error = TRUE; + goto error; + } + } else { + if (a->type == MSGFMT_ARG_INT32) { + ujval = (intmax_t) a->v.unsigned32; + } else { + sbuf.error = TRUE; + goto error; + } + } + if (ujval == 0) /* squash 0x/X if zero */ + ox[1] = '\0'; + + /* unsigned conversions */ + nosign: + sign = '\0'; + /*- + * ``... diouXx conversions ... if a precision is + * specified, the 0 flag will be ignored.'' + * -- ANSI X3J11 + */ + number: + if ((dprec = prec) >= 0) + flags &= ~ZEROPAD; + + /*- + * ``The result of converting a zero value with an + * explicit precision of zero is no characters.'' + * -- ANSI X3J11 + * + * ``The C Standard is clear enough as is. The call + * printf("%#.0o", 0) should print 0.'' + * -- Defect Report #151 + */ + cp = buf + INT_CONV_BUF; + if (ujval != 0 || prec != 0 || + (flags & ALT && base == 8)) + cp = BSDFmt_UJToA(ujval, cp, base, + flags & ALT, xdigs, + flags & GROUPING, thousands_sep, + grouping); + size = buf + INT_CONV_BUF - cp; + if (size > INT_CONV_BUF) /* should never happen */ + abort(); + break; + default: /* "%?" prints ?, unless ? is NUL */ + if (ch == '\0') + goto done; + /* pretend it was %c with argument ch */ + cp = buf; + *cp = ch; + size = 1; + sign = '\0'; + break; + } + + /* + * All reasonable formats wind up here. At this point, `cp' + * points to a string which (if not flags&LADJUST) should be + * padded out to `width' places. If flags&ZEROPAD, it should + * first be prefixed by any sign or other prefix; otherwise, + * it should be blank padded before the prefix is emitted. + * After any left-hand padding and prefixing, emit zeroes + * required by a decimal [diouxX] precision, then print the + * string proper, then emit zeroes required by any leftover + * floating precision; finally, if LADJUST, pad with blanks. + * + * Compute actual size, so we know how much to pad. + * size excludes decimal prec; realsz includes it. + */ + realsz = dprec > size ? dprec : size; + if (sign) + realsz++; + if (ox[1]) + realsz += 2; + + prsize = width > realsz ? width : realsz; + if ((unsigned)ret + prsize > INT_MAX) { + ret = EOF; + goto error; + } + + /* right-adjusting blank padding */ + if ((flags & (LADJUST|ZEROPAD)) == 0) + PAD(width - realsz, blanks); + + /* prefix */ + if (sign) + PRINT(&sign, 1); + + if (ox[1]) { /* ox[1] is either x, X, or \0 */ + ox[0] = '0'; + PRINT(ox, 2); + } + + /* right-adjusting zero padding */ + if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) + PAD(width - realsz, zeroes); + + /* leading zeroes from decimal precision */ + PAD(dprec - size, zeroes); + + /* the string or number proper */ +#ifndef NO_FLOATING_POINT + if ((flags & FPT) == 0) { + PRINT(cp, size); + } else { /* glue together f_p fragments */ + if (!expchar) { /* %[fF] or sufficiently short %[gG] */ + if (expt <= 0) { + PRINT(zeroes, 1); + if (prec || flags & ALT) + PRINT(decimal_point, 1); + PAD(-expt, zeroes); + /* already handled initial 0's */ + prec += expt; + } else { + PRINTANDPAD(cp, dtoaend, lead, zeroes); + cp += lead; + if (grouping) { + while (nseps>0 || nrepeats>0) { + if (nrepeats > 0) + nrepeats--; + else { + grouping--; + nseps--; + } + PRINT(&thousands_sep, + 1); + PRINTANDPAD(cp,dtoaend, + *grouping, zeroes); + cp += *grouping; + } + if (cp > dtoaend) + cp = dtoaend; + } + if (prec || flags & ALT) + PRINT(decimal_point,1); + } + PRINTANDPAD(cp, dtoaend, prec, zeroes); + } else { /* %[eE] or sufficiently long %[gG] */ + if (prec > 1 || flags & ALT) { + buf[0] = *cp++; + buf[1] = *decimal_point; + PRINT(buf, 2); + PRINT(cp, ndig-1); + PAD(prec - ndig, zeroes); + } else /* XeYYY */ + PRINT(cp, 1); + PRINT(expstr, expsize); + } + } +#else + PRINT(cp, size); +#endif + /* left-adjusting padding (always blank) */ + if (flags & LADJUST) + PAD(width - realsz, blanks); + + /* finally, adjust ret */ + ret += prsize; + + FLUSH(); /* copy out the I/O vectors */ + } +done: + FLUSH(); + + /* + * Always null terminate, unless buffer is size 0. + */ + + ASSERT(!sbuf.error && ret >= 0); + if (sbuf.size <= 0) { + ASSERT(!sbuf.alloc); + } else { + ASSERT(sbuf.index < sbuf.size); + sbuf.buf[sbuf.index] = '\0'; + } + +error: +#ifndef NO_FLOATING_POINT + if (dtoaresult != NULL) + freedtoa(dtoaresult); +#endif + if (convbuf != NULL) + free(convbuf); + if (sbuf.error) { + ret = EOF; + } + + // return allocated buffer on success, free it on failure + if (sbuf.alloc) { + if (ret < 0) { + free(sbuf.buf); + } else { + *outbuf = sbuf.buf; + } + } + + return (ret); + /* NOTREACHED */ + +#undef PRINT +#undef PAD +#undef PRINTANDPAD +#undef FLUSH +#undef FETCHARG +#undef GETASTER +} + +#endif // }