1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-25 01:02:05 +03:00

Arrange for timezone names to be recognized case-insensitively; for

example SET TIME ZONE 'america/new_york' works now.  This seems a good
idea on general user-friendliness grounds, and is part of the solution
to the timestamp-input parsing problems I noted recently.
This commit is contained in:
Tom Lane
2006-10-16 19:58:27 +00:00
parent a2ebf81913
commit 0b35b01e7a
5 changed files with 195 additions and 78 deletions

View File

@ -3,7 +3,7 @@
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.14 2006/06/07 22:24:46 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.15 2006/10/16 19:58:26 tgl Exp $
*/
/*
@ -122,7 +122,7 @@ detzcode(const char *codep)
}
int
tzload(const char *name, struct state * sp)
tzload(const char *name, char *canonname, struct state *sp)
{
const char *p;
int i;
@ -130,36 +130,11 @@ tzload(const char *name, struct state * sp)
if (name == NULL && (name = TZDEFAULT) == NULL)
return -1;
{
int doaccess;
char fullname[MAXPGPATH];
if (name[0] == ':')
++name;
doaccess = name[0] == '/';
if (!doaccess)
{
p = pg_TZDIR();
if (p == NULL)
return -1;
if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
return -1;
(void) strcpy(fullname, p);
(void) strcat(fullname, "/");
(void) strcat(fullname, name);
/*
* Set doaccess if '.' (as in "../") shows up in name.
*/
if (strchr(name, '.') != NULL)
doaccess = TRUE;
name = fullname;
}
if (doaccess && access(name, R_OK) != 0)
return -1;
if ((fid = open(name, O_RDONLY | PG_BINARY, 0)) == -1)
return -1;
}
if (name[0] == ':')
++name;
fid = pg_open_tzfile(name, canonname);
if (fid < 0)
return -1;
{
struct tzhead *tzhp;
union
@ -587,7 +562,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
if (name == NULL)
return -1;
}
load_result = tzload(TZDEFRULES, sp);
load_result = tzload(TZDEFRULES, NULL, sp);
if (load_result != 0)
sp->leapcnt = 0; /* so, we're off a little */
if (*name != '\0')
@ -794,7 +769,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
static void
gmtload(struct state * sp)
{
if (tzload(gmt, sp) != 0)
if (tzload(gmt, NULL, sp) != 0)
(void) tzparse(gmt, sp, TRUE);
}

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.46 2006/10/04 00:30:14 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.47 2006/10/16 19:58:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -15,6 +15,7 @@
#include "postgres.h"
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
@ -31,8 +32,11 @@ pg_tz *global_timezone = NULL;
static char tzdir[MAXPGPATH];
static int done_tzdir = 0;
static bool done_tzdir = false;
static bool scan_directory_ci(const char *dirname,
const char *fname, int fnamelen,
char *canonname, int canonnamelen);
static const char *identify_system_timezone(void);
static const char *select_default_timezone(void);
static bool set_global_timezone(const char *tzname);
@ -41,20 +45,123 @@ static bool set_global_timezone(const char *tzname);
/*
* Return full pathname of timezone data directory
*/
char *
static char *
pg_TZDIR(void)
{
if (done_tzdir)
return tzdir;
get_share_path(my_exec_path, tzdir);
strcat(tzdir, "/timezone");
strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
done_tzdir = 1;
done_tzdir = true;
return tzdir;
}
/*
* Given a timezone name, open() the timezone data file. Return the
* file descriptor if successful, -1 if not.
*
* The input name is searched for case-insensitively (we assume that the
* timezone database does not contain case-equivalent names).
*
* If "canonname" is not NULL, then on success the canonical spelling of the
* given name is stored there (it is assumed to be > TZ_STRLEN_MAX bytes!).
*/
int
pg_open_tzfile(const char *name, char *canonname)
{
const char *fname;
char fullname[MAXPGPATH];
int fullnamelen;
int orignamelen;
/*
* Loop to split the given name into directory levels; for each level,
* search using scan_directory_ci().
*/
strcpy(fullname, pg_TZDIR());
orignamelen = fullnamelen = strlen(fullname);
fname = name;
for (;;)
{
const char *slashptr;
int fnamelen;
slashptr = strchr(fname, '/');
if (slashptr)
fnamelen = slashptr - fname;
else
fnamelen = strlen(fname);
if (fullnamelen + 1 + fnamelen >= MAXPGPATH)
return -1; /* not gonna fit */
if (!scan_directory_ci(fullname, fname, fnamelen,
fullname + fullnamelen + 1,
MAXPGPATH - fullnamelen - 1))
return -1;
fullname[fullnamelen++] = '/';
fullnamelen += strlen(fullname + fullnamelen);
if (slashptr)
fname = slashptr + 1;
else
break;
}
if (canonname)
strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1);
return open(fullname, O_RDONLY | PG_BINARY, 0);
}
/*
* Scan specified directory for a case-insensitive match to fname
* (of length fnamelen --- fname may not be null terminated!). If found,
* copy the actual filename into canonname and return true.
*/
static bool
scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
char *canonname, int canonnamelen)
{
bool found = false;
DIR *dirdesc;
struct dirent *direntry;
dirdesc = AllocateDir(dirname);
if (!dirdesc)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m", dirname)));
return false;
}
while ((direntry = ReadDir(dirdesc, dirname)) != NULL)
{
/*
* Ignore . and .., plus any other "hidden" files. This is a security
* measure to prevent access to files outside the timezone directory.
*/
if (direntry->d_name[0] == '.')
continue;
if (strlen(direntry->d_name) == fnamelen &&
pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
{
/* Found our match */
strlcpy(canonname, direntry->d_name, canonnamelen);
found = true;
break;
}
}
FreeDir(dirdesc);
return found;
}
/*
* The following block of code attempts to determine which timezone in our
* timezone database is the best match for the active system timezone.
@ -167,7 +274,7 @@ score_timezone(const char *tzname, struct tztry * tt)
* Load timezone directly. Don't use pg_tzset, because we don't want all
* timezones loaded in the cache at startup.
*/
if (tzload(tzname, &tz.state) != 0)
if (tzload(tzname, NULL, &tz.state) != 0)
{
if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0)
{
@ -958,10 +1065,20 @@ identify_system_timezone(void)
/*
* We keep loaded timezones in a hashtable so we don't have to
* load and parse the TZ definition file every time it is selected.
* load and parse the TZ definition file every time one is selected.
* Because we want timezone names to be found case-insensitively,
* the hash key is the uppercased name of the zone.
*/
typedef struct
{
/* tznameupper contains the all-upper-case name of the timezone */
char tznameupper[TZ_STRLEN_MAX + 1];
pg_tz tz;
} pg_tz_cache;
static HTAB *timezone_cache = NULL;
static bool
init_timezone_hashtable(void)
{
@ -970,7 +1087,7 @@ init_timezone_hashtable(void)
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = TZ_STRLEN_MAX + 1;
hash_ctl.entrysize = sizeof(pg_tz);
hash_ctl.entrysize = sizeof(pg_tz_cache);
timezone_cache = hash_create("Timezones",
4,
@ -989,8 +1106,11 @@ init_timezone_hashtable(void)
struct pg_tz *
pg_tzset(const char *name)
{
pg_tz *tzp;
pg_tz tz;
pg_tz_cache *tzp;
struct state tzstate;
char uppername[TZ_STRLEN_MAX + 1];
char canonname[TZ_STRLEN_MAX + 1];
char *p;
if (strlen(name) > TZ_STRLEN_MAX)
return NULL; /* not going to fit */
@ -999,37 +1119,49 @@ pg_tzset(const char *name)
if (!init_timezone_hashtable())
return NULL;
tzp = (pg_tz *) hash_search(timezone_cache,
name,
HASH_FIND,
NULL);
/*
* Upcase the given name to perform a case-insensitive hashtable search.
* (We could alternatively downcase it, but we prefer upcase so that we
* can get consistently upcased results from tzparse() in case the name
* is a POSIX-style timezone spec.)
*/
p = uppername;
while (*name)
*p++ = pg_toupper((unsigned char) *name++);
*p = '\0';
tzp = (pg_tz_cache *) hash_search(timezone_cache,
uppername,
HASH_FIND,
NULL);
if (tzp)
{
/* Timezone found in cache, nothing more to do */
return tzp;
return &tzp->tz;
}
if (tzload(name, &tz.state) != 0)
if (tzload(uppername, canonname, &tzstate) != 0)
{
if (name[0] == ':' || tzparse(name, &tz.state, FALSE) != 0)
if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0)
{
/* Unknown timezone. Fail our call instead of loading GMT! */
return NULL;
}
/* For POSIX timezone specs, use uppercase name as canonical */
strcpy(canonname, uppername);
}
strcpy(tz.TZname, name);
/* Save timezone in the cache */
tzp = hash_search(timezone_cache,
name,
HASH_ENTER,
NULL);
tzp = (pg_tz_cache *) hash_search(timezone_cache,
uppername,
HASH_ENTER,
NULL);
strcpy(tzp->TZname, tz.TZname);
memcpy(&tzp->state, &tz.state, sizeof(tz.state));
/* hash_search already copied uppername into the hash key */
strcpy(tzp->tz.TZname, canonname);
memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
return tzp;
return &tzp->tz;
}
@ -1241,14 +1373,13 @@ pg_tzenumerate_next(pg_tzenum *dir)
* Load this timezone using tzload() not pg_tzset(), so we don't fill
* the cache
*/
if (tzload(fullname + dir->baselen, &dir->tz.state) != 0)
if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state) != 0)
{
/* Zone could not be loaded, ignore it */
continue;
}
/* Timezone loaded OK. */
strcpy(dir->tz.TZname, fullname + dir->baselen);
return &dir->tz;
}

View File

@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.17 2006/07/11 13:54:25 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.18 2006/10/16 19:58:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -19,7 +19,6 @@
#include "tzfile.h"
#include "pgtime.h"
extern char *pg_TZDIR(void);
#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
@ -55,11 +54,17 @@ struct state
struct pg_tz
{
/* TZname contains the canonically-cased name of the timezone */
char TZname[TZ_STRLEN_MAX + 1];
struct state state;
};
int tzload(const char *name, struct state * sp);
int tzparse(const char *name, struct state * sp, int lastditch);
/* in pgtz.c */
extern int pg_open_tzfile(const char *name, char *canonname);
/* in localtime.c */
extern int tzload(const char *name, char *canonname, struct state *sp);
extern int tzparse(const char *name, struct state *sp, int lastditch);
#endif /* _PGTZ_H */

View File

@ -3,7 +3,7 @@
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/zic.c,v 1.16 2005/10/15 02:49:51 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/zic.c,v 1.17 2006/10/16 19:58:27 tgl Exp $
*/
#include "postgres.h"
@ -2387,11 +2387,11 @@ link(const char *oldpath, const char *newpath)
#endif
/*
* This allows zic to compile by just assigning a dummy value.
* This allows zic to compile by just returning a dummy value.
* localtime.c references it, but no one uses it from zic.
*/
char *
pg_TZDIR(void)
int
pg_open_tzfile(const char *name, char *canonname)
{
return NULL;
return -1;
}