Header And Logo

PostgreSQL
| The world's most advanced open source database.

Defines | Functions

tzparser.c File Reference

#include "postgres.h"
#include <ctype.h>
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/tzparser.h"
Include dependency graph for tzparser.c:

Go to the source code of this file.

Defines

#define WHITESPACE   " \t\n\r"

Functions

static bool validateTzEntry (tzEntry *tzentry)
static bool splitTzLine (const char *filename, int lineno, char *line, tzEntry *tzentry)
static int addToArray (tzEntry **base, int *arraysize, int n, tzEntry *entry, bool override)
static int ParseTzFile (const char *filename, int depth, tzEntry **base, int *arraysize, int n)
TimeZoneAbbrevTableload_tzoffsets (const char *filename)

Define Documentation

#define WHITESPACE   " \t\n\r"

Definition at line 34 of file tzparser.c.

Referenced by ParseTzFile(), and splitTzLine().


Function Documentation

static int addToArray ( tzEntry **  base,
int *  arraysize,
int  n,
tzEntry entry,
bool  override 
) [static]

Definition at line 173 of file tzparser.c.

References tzEntry::abbrev, tzEntry::filename, GUC_check_errdetail, GUC_check_errmsg, tzEntry::is_dst, tzEntry::lineno, memmove, tzEntry::offset, pstrdup(), and repalloc().

Referenced by ParseTzFile().

{
    tzEntry    *arrayptr;
    int         low;
    int         high;

    /*
     * Search the array for a duplicate; as a useful side effect, the array is
     * maintained in sorted order.  We use strcmp() to ensure we match the
     * sort order datetime.c expects.
     */
    arrayptr = *base;
    low = 0;
    high = n - 1;
    while (low <= high)
    {
        int         mid = (low + high) >> 1;
        tzEntry    *midptr = arrayptr + mid;
        int         cmp;

        cmp = strcmp(entry->abbrev, midptr->abbrev);
        if (cmp < 0)
            high = mid - 1;
        else if (cmp > 0)
            low = mid + 1;
        else
        {
            /*
             * Found a duplicate entry; complain unless it's the same.
             */
            if (midptr->offset == entry->offset &&
                midptr->is_dst == entry->is_dst)
            {
                /* return unchanged array */
                return n;
            }
            if (override)
            {
                /* same abbrev but something is different, override */
                midptr->offset = entry->offset;
                midptr->is_dst = entry->is_dst;
                return n;
            }
            /* same abbrev but something is different, complain */
            GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined",
                             entry->abbrev);
            GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.",
                                midptr->filename, midptr->lineno,
                                entry->filename, entry->lineno);
            return -1;
        }
    }

    /*
     * No match, insert at position "low".
     */
    if (n >= *arraysize)
    {
        *arraysize *= 2;
        *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry));
    }

    arrayptr = *base + low;

    memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry));

    memcpy(arrayptr, entry, sizeof(tzEntry));

    /* Must dup the abbrev to ensure it survives */
    arrayptr->abbrev = pstrdup(entry->abbrev);

    return n + 1;
}

TimeZoneAbbrevTable* load_tzoffsets ( const char *  filename  ) 

Definition at line 422 of file tzparser.c.

References ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE, ALLOCSET_SMALL_MINSIZE, AllocSetContextCreate(), ConvertTimeZoneAbbrevs(), CurrentMemoryContext, GUC_check_errmsg, malloc, MemoryContextDelete(), MemoryContextSwitchTo(), offsetof, palloc(), and ParseTzFile().

Referenced by check_timezone_abbreviations().

{
    TimeZoneAbbrevTable *result = NULL;
    MemoryContext tmpContext;
    MemoryContext oldContext;
    tzEntry    *array;
    int         arraysize;
    int         n;

    /*
     * Create a temp memory context to work in.  This makes it easy to clean
     * up afterwards.
     */
    tmpContext = AllocSetContextCreate(CurrentMemoryContext,
                                       "TZParserMemory",
                                       ALLOCSET_SMALL_MINSIZE,
                                       ALLOCSET_SMALL_INITSIZE,
                                       ALLOCSET_SMALL_MAXSIZE);
    oldContext = MemoryContextSwitchTo(tmpContext);

    /* Initialize array at a reasonable size */
    arraysize = 128;
    array = (tzEntry *) palloc(arraysize * sizeof(tzEntry));

    /* Parse the file(s) */
    n = ParseTzFile(filename, 0, &array, &arraysize, 0);

    /* If no errors so far, allocate result and let datetime.c convert data */
    if (n >= 0)
    {
        result = malloc(offsetof(TimeZoneAbbrevTable, abbrevs) +
                        n * sizeof(datetkn));
        if (!result)
            GUC_check_errmsg("out of memory");
        else
            ConvertTimeZoneAbbrevs(result, array, n);
    }

    /* Clean up */
    MemoryContextSwitchTo(oldContext);
    MemoryContextDelete(tmpContext);

    return result;
}

static int ParseTzFile ( const char *  filename,
int  depth,
tzEntry **  base,
int *  arraysize,
int  n 
) [static]

Definition at line 260 of file tzparser.c.

References addToArray(), AllocateDir(), AllocateFile(), FreeDir(), FreeFile(), get_share_path(), GUC_check_errhint, GUC_check_errmsg, my_exec_path, NULL, pg_strncasecmp(), pstrdup(), share_path, snprintf(), splitTzLine(), validateTzEntry(), and WHITESPACE.

Referenced by load_tzoffsets().

{
    char        share_path[MAXPGPATH];
    char        file_path[MAXPGPATH];
    FILE       *tzFile;
    char        tzbuf[1024];
    char       *line;
    tzEntry     tzentry;
    int         lineno = 0;
    bool        override = false;
    const char *p;

    /*
     * We enforce that the filename is all alpha characters.  This may be
     * overly restrictive, but we don't want to allow access to anything
     * outside the timezonesets directory, so for instance '/' *must* be
     * rejected.
     */
    for (p = filename; *p; p++)
    {
        if (!isalpha((unsigned char) *p))
        {
            /* at level 0, just use guc.c's regular "invalid value" message */
            if (depth > 0)
                GUC_check_errmsg("invalid time zone file name \"%s\"",
                                 filename);
            return -1;
        }
    }

    /*
     * The maximal recursion depth is a pretty arbitrary setting. It is hard
     * to imagine that someone needs more than 3 levels so stick with this
     * conservative setting until someone complains.
     */
    if (depth > 3)
    {
        GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"",
                         filename);
        return -1;
    }

    get_share_path(my_exec_path, share_path);
    snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s",
             share_path, filename);
    tzFile = AllocateFile(file_path, "r");
    if (!tzFile)
    {
        /*
         * Check to see if the problem is not the filename but the directory.
         * This is worth troubling over because if the installation share/
         * directory is missing or unreadable, this is likely to be the first
         * place we notice a problem during postmaster startup.
         */
        int         save_errno = errno;
        DIR        *tzdir;

        snprintf(file_path, sizeof(file_path), "%s/timezonesets",
                 share_path);
        tzdir = AllocateDir(file_path);
        if (tzdir == NULL)
        {
            GUC_check_errmsg("could not open directory \"%s\": %m",
                             file_path);
            GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.",
                              my_exec_path);
            return -1;
        }
        FreeDir(tzdir);
        errno = save_errno;

        /*
         * otherwise, if file doesn't exist and it's level 0, guc.c's
         * complaint is enough
         */
        if (errno != ENOENT || depth > 0)
            GUC_check_errmsg("could not read time zone file \"%s\": %m",
                             filename);

        return -1;
    }

    while (!feof(tzFile))
    {
        lineno++;
        if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL)
        {
            if (ferror(tzFile))
            {
                GUC_check_errmsg("could not read time zone file \"%s\": %m",
                                 filename);
                return -1;
            }
            /* else we're at EOF after all */
            break;
        }
        if (strlen(tzbuf) == sizeof(tzbuf) - 1)
        {
            /* the line is too long for tzbuf */
            GUC_check_errmsg("line is too long in time zone file \"%s\", line %d",
                             filename, lineno);
            return -1;
        }

        /* skip over whitespace */
        line = tzbuf;
        while (*line && isspace((unsigned char) *line))
            line++;

        if (*line == '\0')      /* empty line */
            continue;
        if (*line == '#')       /* comment line */
            continue;

        if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0)
        {
            /* pstrdup so we can use filename in result data structure */
            char       *includeFile = pstrdup(line + strlen("@INCLUDE"));

            includeFile = strtok(includeFile, WHITESPACE);
            if (!includeFile || !*includeFile)
            {
                GUC_check_errmsg("@INCLUDE without file name in time zone file \"%s\", line %d",
                                 filename, lineno);
                return -1;
            }
            n = ParseTzFile(includeFile, depth + 1,
                            base, arraysize, n);
            if (n < 0)
                return -1;
            continue;
        }

        if (pg_strncasecmp(line, "@OVERRIDE", strlen("@OVERRIDE")) == 0)
        {
            override = true;
            continue;
        }

        if (!splitTzLine(filename, lineno, line, &tzentry))
            return -1;
        if (!validateTzEntry(&tzentry))
            return -1;
        n = addToArray(base, arraysize, n, &tzentry, override);
        if (n < 0)
            return -1;
    }

    FreeFile(tzFile);

    return n;
}

static bool splitTzLine ( const char *  filename,
int  lineno,
char *  line,
tzEntry tzentry 
) [static]

Definition at line 101 of file tzparser.c.

References tzEntry::abbrev, tzEntry::filename, GUC_check_errmsg, tzEntry::is_dst, tzEntry::lineno, NULL, tzEntry::offset, pg_strcasecmp(), and WHITESPACE.

Referenced by ParseTzFile().

{
    char       *abbrev;
    char       *offset;
    char       *offset_endptr;
    char       *remain;
    char       *is_dst;

    tzentry->lineno = lineno;
    tzentry->filename = filename;

    abbrev = strtok(line, WHITESPACE);
    if (!abbrev)
    {
        GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d",
                         filename, lineno);
        return false;
    }
    tzentry->abbrev = abbrev;

    offset = strtok(NULL, WHITESPACE);
    if (!offset)
    {
        GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d",
                         filename, lineno);
        return false;
    }
    tzentry->offset = strtol(offset, &offset_endptr, 10);
    if (offset_endptr == offset || *offset_endptr != '\0')
    {
        GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
                         filename, lineno);
        return false;
    }

    is_dst = strtok(NULL, WHITESPACE);
    if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
    {
        tzentry->is_dst = true;
        remain = strtok(NULL, WHITESPACE);
    }
    else
    {
        /* there was no 'D' dst specifier */
        tzentry->is_dst = false;
        remain = is_dst;
    }

    if (!remain)                /* no more non-whitespace chars */
        return true;

    if (remain[0] != '#')       /* must be a comment */
    {
        GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d",
                         filename, lineno);
        return false;
    }
    return true;
}

static bool validateTzEntry ( tzEntry tzentry  )  [static]

Definition at line 51 of file tzparser.c.

References tzEntry::abbrev, tzEntry::filename, GUC_check_errmsg, tzEntry::lineno, tzEntry::offset, pg_tolower(), and TOKMAXLEN.

Referenced by ParseTzFile().

{
    unsigned char *p;

    /*
     * Check restrictions imposed by datetkntbl storage format (see
     * datetime.c)
     */
    if (strlen(tzentry->abbrev) > TOKMAXLEN)
    {
        GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d",
                         tzentry->abbrev, TOKMAXLEN,
                         tzentry->filename, tzentry->lineno);
        return false;
    }
    if (tzentry->offset % 900 != 0)
    {
        GUC_check_errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d",
                         tzentry->offset,
                         tzentry->filename, tzentry->lineno);
        return false;
    }

    /*
     * Sanity-check the offset: shouldn't exceed 14 hours
     */
    if (tzentry->offset > 14 * 60 * 60 ||
        tzentry->offset < -14 * 60 * 60)
    {
        GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d",
                         tzentry->offset,
                         tzentry->filename, tzentry->lineno);
        return false;
    }

    /*
     * Convert abbrev to lowercase (must match datetime.c's conversion)
     */
    for (p = (unsigned char *) tzentry->abbrev; *p; p++)
        *p = pg_tolower(*p);

    return true;
}