15670430创建于 2020年12月28日历史提交
/*-------------------------------------------------------------------------
 *
 * psprintf.c
 *        sprintf into an allocated-on-demand buffer
 *
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/common/psprintf.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND

#include "postgres.h"

#include "utils/memutils.h"

#else

#include "postgres_fe.h"
#include "common/fe_memutils.h"

/* It's possible we could use a different value for this in frontend code */
#define MaxAllocSize    ((Size) 0x3fffffff) /* 1 gigabyte - 1 */

#endif

size_t format_and_insert_text(char *buf, size_t len, const char *fmt, va_list args);

/*
 * format_text
 *
 * Format text data under the control of fmt (an sprintf-style format string)
 * and return it in an allocated-on-demand buffer.  The buffer is allocated
 * with palloc in the backend, or malloc in frontend builds.  Caller is
 * responsible to free the buffer when no longer needed, if appropriate.
 *
 * Errors are not returned to the caller, but are reported via elog(ERROR)
 * in the backend, or printf-to-stderr-and-exit() in frontend builds.
 * One should therefore think twice about using this in libpq.
 */
char *
format_text(const char *fmt,...)
{
    int            save_errno = errno;
    size_t        len = 128;        /* initial assumption about buffer size */

    for (;;)
    {
        char       *result;
        va_list        args;
        size_t        newlen;

        /*
         * Allocate result buffer.  Note that in frontend this maps to malloc
         * with exit-on-error.
         */
        result = (char *) palloc(len);

        /* Try to format the data. */
        errno = save_errno;
        va_start(args, fmt);
        newlen = format_and_insert_text(result, len, fmt, args);
        va_end(args);

        if (newlen < len)
            return result;        /* success */

        /* Release buffer and loop around to try again with larger len. */
        pfree(result);
        len = newlen;
    }
}

/*
 * format_and_insert_text
 *
 * Attempt to format text data under the control of fmt (an sprintf-style
 * format string) and insert it into buf (which has length len).
 *
 * If successful, return the number of bytes emitted, not counting the
 * trailing zero byte.  This will always be strictly less than len.
 *
 * If there's not enough space in buf, return an estimate of the buffer size
 * needed to succeed (this *must* be more than the given len, else callers
 * might loop infinitely).
 *
 * Other error cases do not return, but exit via elog(ERROR) or exit().
 * Hence, this shouldn't be used inside libpq.
 *
 * Caution: callers must be sure to preserve their entry-time errno
 * when looping, in case the fmt contains "%m".
 *
 * Note that the semantics of the return value are not exactly C99's.
 * First, we don't promise that the estimated buffer size is exactly right;
 * callers must be prepared to loop multiple times to get the right size.
 * (Given a C99-compliant vsnprintf, that won't happen, but it is rumored
 * that some implementations don't always return the same value ...)
 * Second, we return the recommended buffer size, not one less than that;
 * this lets overflow concerns be handled here rather than in the callers.
 */
size_t
format_and_insert_text(char *buf, size_t len, const char *fmt, va_list args)
{
    int            nprinted;

    nprinted = vsnprintf_s(buf, len, len - 1, fmt, args);

    /* We assume failure means the fmt is bogus, hence hard failure is OK */
    if (unlikely(nprinted < 0))
    {
#ifndef FRONTEND
        elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt);
#else
        fprintf(stderr, "vsnprintf failed: %s with format string \"%s\"\n",
                strerror(errno), fmt);
        exit(EXIT_FAILURE);
#endif
    }

    if ((size_t) nprinted < len)
    {
        /* Success.  Note nprinted does not include trailing null. */
        return (size_t) nprinted;
    }

    /*
     * We assume a C99-compliant vsnprintf, so believe its estimate of the
     * required space, and add one for the trailing null.  (If it's wrong, the
     * logic will still work, but we may loop multiple times.)
     *
     * Choke if the required space would exceed MaxAllocSize.  Note we use
     * this palloc-oriented overflow limit even when in frontend.
     */
    if (unlikely((size_t) nprinted > MaxAllocSize - 1))
    {
#ifndef FRONTEND
        ereport(ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                 errmsg("out of memory")));
#else
        fprintf(stderr, _("out of memory\n"));
        exit(EXIT_FAILURE);
#endif
    }

    return nprinted + 1;
}

static inline void *
gs_malloc_internal(size_t size, int flags)
{
    void       *tmp;

    /* Avoid unportable behavior of malloc(0) */
    if (size == 0)
        size = 1;
    tmp = malloc(size);
    if (tmp == NULL)
    {
        if ((flags & MCXT_ALLOC_NO_OOM) == 0)
        {
            fprintf(stderr, _("out of memory\n"));
            exit(EXIT_FAILURE);
        }
        return NULL;
    }

    if ((flags & MCXT_ALLOC_ZERO) != 0)
    {
        errno_t rc = memset_s(tmp, size, 0, size);
        securec_check_c(rc, "\0", "\0");
    }
    return tmp;
}


void *
gs_palloc0(Size size)
{
    return gs_malloc_internal(size, MCXT_ALLOC_ZERO);
}

char *
gs_pstrdup(const char *in)
{
    return pg_strdup(in);
}

void *
gs_repalloc(void *pointer, Size size)
{
    return pg_realloc(pointer, size);
}