mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-23 12:40:41 +02:00
When using the BSD UUID functions, contrib/uuid-ossp expects uuid_create() to produce a version-1 UUID. FreeBSD still does so, but in recent NetBSD releases that function produces a version-4 (random) UUID instead. That's not acceptable for our purposes: if the user wanted v4 she would have asked for v4, not v1. Hence, check the version digit and complain if it's not '1'. Also drop the documentation's claim that the NetBSD implementation is usable. It might be, depending on which OS version you're using, but we're not going to get into that kind of detail. (Maybe someday we should ditch all these external libraries and just write our own UUID code, but today is not that day.) Nazir Bilal Yavuz, with cosmetic adjustments and docs by me. Backpatch to all supported versions. Discussion: https://postgr.es/m/3848059.1661038772@sss.pgh.pa.us Discussion: https://postgr.es/m/17358-89806e7420797025@postgresql.org
546 lines
13 KiB
C
546 lines
13 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* UUID generation functions using the BSD, E2FS or OSSP UUID library
|
|
*
|
|
* Copyright (c) 2007-2019, PostgreSQL Global Development Group
|
|
*
|
|
* Portions Copyright (c) 2009 Andrew Gierth
|
|
*
|
|
* contrib/uuid-ossp/uuid-ossp.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "fmgr.h"
|
|
#include "port/pg_bswap.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/uuid.h"
|
|
|
|
/*
|
|
* It's possible that there's more than one uuid.h header file present.
|
|
* We expect configure to set the HAVE_ symbol for only the one we want.
|
|
*
|
|
* BSD includes a uuid_hash() function that conflicts with the one in
|
|
* builtins.h; we #define it out of the way.
|
|
*/
|
|
#define uuid_hash bsd_uuid_hash
|
|
|
|
#if defined(HAVE_UUID_H)
|
|
#include <uuid.h>
|
|
#elif defined(HAVE_OSSP_UUID_H)
|
|
#include <ossp/uuid.h>
|
|
#elif defined(HAVE_UUID_UUID_H)
|
|
#include <uuid/uuid.h>
|
|
#else
|
|
#error "please use configure's --with-uuid switch to select a UUID library"
|
|
#endif
|
|
|
|
#undef uuid_hash
|
|
|
|
/*
|
|
* Some BSD variants offer md5 and sha1 implementations but Linux does not,
|
|
* so we use a copy of the ones from pgcrypto. Not needed with OSSP, though.
|
|
*/
|
|
#ifndef HAVE_UUID_OSSP
|
|
#include "md5.h"
|
|
#include "sha1.h"
|
|
#endif
|
|
|
|
|
|
/* Check our UUID length against OSSP's; better both be 16 */
|
|
#if defined(HAVE_UUID_OSSP) && (UUID_LEN != UUID_LEN_BIN)
|
|
#error UUID length mismatch
|
|
#endif
|
|
|
|
/* Define some constants like OSSP's, to make the code more readable */
|
|
#ifndef HAVE_UUID_OSSP
|
|
#define UUID_MAKE_MC 0
|
|
#define UUID_MAKE_V1 1
|
|
#define UUID_MAKE_V2 2
|
|
#define UUID_MAKE_V3 3
|
|
#define UUID_MAKE_V4 4
|
|
#define UUID_MAKE_V5 5
|
|
#endif
|
|
|
|
/*
|
|
* A DCE 1.1 compatible source representation of UUIDs, derived from
|
|
* the BSD implementation. BSD already has this; OSSP doesn't need it.
|
|
*/
|
|
#ifdef HAVE_UUID_E2FS
|
|
typedef struct
|
|
{
|
|
uint32_t time_low;
|
|
uint16_t time_mid;
|
|
uint16_t time_hi_and_version;
|
|
uint8_t clock_seq_hi_and_reserved;
|
|
uint8_t clock_seq_low;
|
|
uint8_t node[6];
|
|
} dce_uuid_t;
|
|
#else
|
|
#define dce_uuid_t uuid_t
|
|
#endif
|
|
|
|
/* If not OSSP, we need some endianness-manipulation macros */
|
|
#ifndef HAVE_UUID_OSSP
|
|
|
|
#define UUID_TO_NETWORK(uu) \
|
|
do { \
|
|
uu.time_low = pg_hton32(uu.time_low); \
|
|
uu.time_mid = pg_hton16(uu.time_mid); \
|
|
uu.time_hi_and_version = pg_hton16(uu.time_hi_and_version); \
|
|
} while (0)
|
|
|
|
#define UUID_TO_LOCAL(uu) \
|
|
do { \
|
|
uu.time_low = pg_ntoh32(uu.time_low); \
|
|
uu.time_mid = pg_ntoh16(uu.time_mid); \
|
|
uu.time_hi_and_version = pg_ntoh16(uu.time_hi_and_version); \
|
|
} while (0)
|
|
|
|
#define UUID_V3_OR_V5(uu, v) \
|
|
do { \
|
|
uu.time_hi_and_version &= 0x0FFF; \
|
|
uu.time_hi_and_version |= (v << 12); \
|
|
uu.clock_seq_hi_and_reserved &= 0x3F; \
|
|
uu.clock_seq_hi_and_reserved |= 0x80; \
|
|
} while(0)
|
|
|
|
#endif /* !HAVE_UUID_OSSP */
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
PG_FUNCTION_INFO_V1(uuid_nil);
|
|
PG_FUNCTION_INFO_V1(uuid_ns_dns);
|
|
PG_FUNCTION_INFO_V1(uuid_ns_url);
|
|
PG_FUNCTION_INFO_V1(uuid_ns_oid);
|
|
PG_FUNCTION_INFO_V1(uuid_ns_x500);
|
|
|
|
PG_FUNCTION_INFO_V1(uuid_generate_v1);
|
|
PG_FUNCTION_INFO_V1(uuid_generate_v1mc);
|
|
PG_FUNCTION_INFO_V1(uuid_generate_v3);
|
|
PG_FUNCTION_INFO_V1(uuid_generate_v4);
|
|
PG_FUNCTION_INFO_V1(uuid_generate_v5);
|
|
|
|
#ifdef HAVE_UUID_OSSP
|
|
|
|
static void
|
|
pguuid_complain(uuid_rc_t rc)
|
|
{
|
|
char *err = uuid_error(rc);
|
|
|
|
if (err != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
|
errmsg("OSSP uuid library failure: %s", err)));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
|
errmsg("OSSP uuid library failure: error code %d", rc)));
|
|
}
|
|
|
|
/*
|
|
* We create a uuid_t object just once per session and re-use it for all
|
|
* operations in this module. OSSP UUID caches the system MAC address and
|
|
* other state in this object. Reusing the object has a number of benefits:
|
|
* saving the cycles needed to fetch the system MAC address over and over,
|
|
* reducing the amount of entropy we draw from /dev/urandom, and providing a
|
|
* positive guarantee that successive generated V1-style UUIDs don't collide.
|
|
* (On a machine fast enough to generate multiple UUIDs per microsecond,
|
|
* or whatever the system's wall-clock resolution is, we'd otherwise risk
|
|
* collisions whenever random initialization of the uuid_t's clock sequence
|
|
* value chanced to produce duplicates.)
|
|
*
|
|
* However: when we're doing V3 or V5 UUID creation, uuid_make needs two
|
|
* uuid_t objects, one holding the namespace UUID and one for the result.
|
|
* It's unspecified whether it's safe to use the same uuid_t for both cases,
|
|
* so let's cache a second uuid_t for use as the namespace holder object.
|
|
*/
|
|
static uuid_t *
|
|
get_cached_uuid_t(int which)
|
|
{
|
|
static uuid_t *cached_uuid[2] = {NULL, NULL};
|
|
|
|
if (cached_uuid[which] == NULL)
|
|
{
|
|
uuid_rc_t rc;
|
|
|
|
rc = uuid_create(&cached_uuid[which]);
|
|
if (rc != UUID_RC_OK)
|
|
{
|
|
cached_uuid[which] = NULL;
|
|
pguuid_complain(rc);
|
|
}
|
|
}
|
|
return cached_uuid[which];
|
|
}
|
|
|
|
static char *
|
|
uuid_to_string(const uuid_t *uuid)
|
|
{
|
|
char *buf = palloc(UUID_LEN_STR + 1);
|
|
void *ptr = buf;
|
|
size_t len = UUID_LEN_STR + 1;
|
|
uuid_rc_t rc;
|
|
|
|
rc = uuid_export(uuid, UUID_FMT_STR, &ptr, &len);
|
|
if (rc != UUID_RC_OK)
|
|
pguuid_complain(rc);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
static void
|
|
string_to_uuid(const char *str, uuid_t *uuid)
|
|
{
|
|
uuid_rc_t rc;
|
|
|
|
rc = uuid_import(uuid, UUID_FMT_STR, str, UUID_LEN_STR + 1);
|
|
if (rc != UUID_RC_OK)
|
|
pguuid_complain(rc);
|
|
}
|
|
|
|
|
|
static Datum
|
|
special_uuid_value(const char *name)
|
|
{
|
|
uuid_t *uuid = get_cached_uuid_t(0);
|
|
char *str;
|
|
uuid_rc_t rc;
|
|
|
|
rc = uuid_load(uuid, name);
|
|
if (rc != UUID_RC_OK)
|
|
pguuid_complain(rc);
|
|
str = uuid_to_string(uuid);
|
|
|
|
return DirectFunctionCall1(uuid_in, CStringGetDatum(str));
|
|
}
|
|
|
|
/* len is unused with OSSP, but we want to have the same number of args */
|
|
static Datum
|
|
uuid_generate_internal(int mode, const uuid_t *ns, const char *name, int len)
|
|
{
|
|
uuid_t *uuid = get_cached_uuid_t(0);
|
|
char *str;
|
|
uuid_rc_t rc;
|
|
|
|
rc = uuid_make(uuid, mode, ns, name);
|
|
if (rc != UUID_RC_OK)
|
|
pguuid_complain(rc);
|
|
str = uuid_to_string(uuid);
|
|
|
|
return DirectFunctionCall1(uuid_in, CStringGetDatum(str));
|
|
}
|
|
|
|
|
|
static Datum
|
|
uuid_generate_v35_internal(int mode, pg_uuid_t *ns, text *name)
|
|
{
|
|
uuid_t *ns_uuid = get_cached_uuid_t(1);
|
|
|
|
string_to_uuid(DatumGetCString(DirectFunctionCall1(uuid_out,
|
|
UUIDPGetDatum(ns))),
|
|
ns_uuid);
|
|
|
|
return uuid_generate_internal(mode,
|
|
ns_uuid,
|
|
text_to_cstring(name),
|
|
0);
|
|
}
|
|
|
|
#else /* !HAVE_UUID_OSSP */
|
|
|
|
static Datum
|
|
uuid_generate_internal(int v, unsigned char *ns, const char *ptr, int len)
|
|
{
|
|
char strbuf[40];
|
|
|
|
switch (v)
|
|
{
|
|
case 0: /* constant-value uuids */
|
|
strlcpy(strbuf, ptr, 37);
|
|
break;
|
|
|
|
case 1: /* time/node-based uuids */
|
|
{
|
|
#ifdef HAVE_UUID_E2FS
|
|
uuid_t uu;
|
|
|
|
uuid_generate_time(uu);
|
|
uuid_unparse(uu, strbuf);
|
|
|
|
/*
|
|
* PTR, if set, replaces the trailing characters of the uuid;
|
|
* this is to support v1mc, where a random multicast MAC is
|
|
* used instead of the physical one
|
|
*/
|
|
if (ptr && len <= 36)
|
|
strcpy(strbuf + (36 - len), ptr);
|
|
#else /* BSD */
|
|
uuid_t uu;
|
|
uint32_t status = uuid_s_ok;
|
|
char *str = NULL;
|
|
|
|
uuid_create(&uu, &status);
|
|
|
|
if (status == uuid_s_ok)
|
|
{
|
|
uuid_to_string(&uu, &str, &status);
|
|
if (status == uuid_s_ok)
|
|
{
|
|
strlcpy(strbuf, str, 37);
|
|
|
|
/*
|
|
* In recent NetBSD, uuid_create() has started
|
|
* producing v4 instead of v1 UUIDs. Check the
|
|
* version field and complain if it's not v1.
|
|
*/
|
|
if (strbuf[14] != '1')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
|
/* translator: %c will be a hex digit */
|
|
errmsg("uuid_create() produced a version %c UUID instead of the expected version 1",
|
|
strbuf[14])));
|
|
|
|
/*
|
|
* PTR, if set, replaces the trailing characters of
|
|
* the uuid; this is to support v1mc, where a random
|
|
* multicast MAC is used instead of the physical one
|
|
*/
|
|
if (ptr && len <= 36)
|
|
strcpy(strbuf + (36 - len), ptr);
|
|
}
|
|
if (str)
|
|
free(str);
|
|
}
|
|
|
|
if (status != uuid_s_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
|
errmsg("uuid library failure: %d",
|
|
(int) status)));
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case 3: /* namespace-based MD5 uuids */
|
|
case 5: /* namespace-based SHA1 uuids */
|
|
{
|
|
dce_uuid_t uu;
|
|
#ifdef HAVE_UUID_BSD
|
|
uint32_t status = uuid_s_ok;
|
|
char *str = NULL;
|
|
#endif
|
|
|
|
if (v == 3)
|
|
{
|
|
MD5_CTX ctx;
|
|
|
|
MD5Init(&ctx);
|
|
MD5Update(&ctx, ns, sizeof(uu));
|
|
MD5Update(&ctx, (unsigned char *) ptr, len);
|
|
/* we assume sizeof MD5 result is 16, same as UUID size */
|
|
MD5Final((unsigned char *) &uu, &ctx);
|
|
}
|
|
else
|
|
{
|
|
SHA1_CTX ctx;
|
|
unsigned char sha1result[SHA1_RESULTLEN];
|
|
|
|
SHA1Init(&ctx);
|
|
SHA1Update(&ctx, ns, sizeof(uu));
|
|
SHA1Update(&ctx, (unsigned char *) ptr, len);
|
|
SHA1Final(sha1result, &ctx);
|
|
memcpy(&uu, sha1result, sizeof(uu));
|
|
}
|
|
|
|
/* the calculated hash is using local order */
|
|
UUID_TO_NETWORK(uu);
|
|
UUID_V3_OR_V5(uu, v);
|
|
|
|
#ifdef HAVE_UUID_E2FS
|
|
/* uuid_unparse expects local order */
|
|
UUID_TO_LOCAL(uu);
|
|
uuid_unparse((unsigned char *) &uu, strbuf);
|
|
#else /* BSD */
|
|
uuid_to_string(&uu, &str, &status);
|
|
|
|
if (status == uuid_s_ok)
|
|
strlcpy(strbuf, str, 37);
|
|
|
|
if (str)
|
|
free(str);
|
|
|
|
if (status != uuid_s_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
|
|
errmsg("uuid library failure: %d",
|
|
(int) status)));
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case 4: /* random uuid */
|
|
default:
|
|
{
|
|
#ifdef HAVE_UUID_E2FS
|
|
uuid_t uu;
|
|
|
|
uuid_generate_random(uu);
|
|
uuid_unparse(uu, strbuf);
|
|
#else /* BSD */
|
|
snprintf(strbuf, sizeof(strbuf),
|
|
"%08lx-%04x-%04x-%04x-%04x%08lx",
|
|
(unsigned long) arc4random(),
|
|
(unsigned) (arc4random() & 0xffff),
|
|
(unsigned) ((arc4random() & 0xfff) | 0x4000),
|
|
(unsigned) ((arc4random() & 0x3fff) | 0x8000),
|
|
(unsigned) (arc4random() & 0xffff),
|
|
(unsigned long) arc4random());
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
return DirectFunctionCall1(uuid_in, CStringGetDatum(strbuf));
|
|
}
|
|
|
|
#endif /* HAVE_UUID_OSSP */
|
|
|
|
|
|
Datum
|
|
uuid_nil(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
return special_uuid_value("nil");
|
|
#else
|
|
return uuid_generate_internal(0, NULL,
|
|
"00000000-0000-0000-0000-000000000000", 36);
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_ns_dns(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
return special_uuid_value("ns:DNS");
|
|
#else
|
|
return uuid_generate_internal(0, NULL,
|
|
"6ba7b810-9dad-11d1-80b4-00c04fd430c8", 36);
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_ns_url(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
return special_uuid_value("ns:URL");
|
|
#else
|
|
return uuid_generate_internal(0, NULL,
|
|
"6ba7b811-9dad-11d1-80b4-00c04fd430c8", 36);
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_ns_oid(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
return special_uuid_value("ns:OID");
|
|
#else
|
|
return uuid_generate_internal(0, NULL,
|
|
"6ba7b812-9dad-11d1-80b4-00c04fd430c8", 36);
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_ns_x500(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
return special_uuid_value("ns:X500");
|
|
#else
|
|
return uuid_generate_internal(0, NULL,
|
|
"6ba7b814-9dad-11d1-80b4-00c04fd430c8", 36);
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_generate_v1(PG_FUNCTION_ARGS)
|
|
{
|
|
return uuid_generate_internal(UUID_MAKE_V1, NULL, NULL, 0);
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_generate_v1mc(PG_FUNCTION_ARGS)
|
|
{
|
|
#ifdef HAVE_UUID_OSSP
|
|
char *buf = NULL;
|
|
#elif defined(HAVE_UUID_E2FS)
|
|
char strbuf[40];
|
|
char *buf;
|
|
uuid_t uu;
|
|
|
|
uuid_generate_random(uu);
|
|
|
|
/* set IEEE802 multicast and local-admin bits */
|
|
((dce_uuid_t *) &uu)->node[0] |= 0x03;
|
|
|
|
uuid_unparse(uu, strbuf);
|
|
buf = strbuf + 24;
|
|
#else /* BSD */
|
|
char buf[16];
|
|
|
|
/* set IEEE802 multicast and local-admin bits */
|
|
snprintf(buf, sizeof(buf), "-%04x%08lx",
|
|
(unsigned) ((arc4random() & 0xffff) | 0x0300),
|
|
(unsigned long) arc4random());
|
|
#endif
|
|
|
|
return uuid_generate_internal(UUID_MAKE_V1 | UUID_MAKE_MC, NULL,
|
|
buf, 13);
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_generate_v3(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_uuid_t *ns = PG_GETARG_UUID_P(0);
|
|
text *name = PG_GETARG_TEXT_PP(1);
|
|
|
|
#ifdef HAVE_UUID_OSSP
|
|
return uuid_generate_v35_internal(UUID_MAKE_V3, ns, name);
|
|
#else
|
|
return uuid_generate_internal(UUID_MAKE_V3, (unsigned char *) ns,
|
|
VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name));
|
|
#endif
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_generate_v4(PG_FUNCTION_ARGS)
|
|
{
|
|
return uuid_generate_internal(UUID_MAKE_V4, NULL, NULL, 0);
|
|
}
|
|
|
|
|
|
Datum
|
|
uuid_generate_v5(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_uuid_t *ns = PG_GETARG_UUID_P(0);
|
|
text *name = PG_GETARG_TEXT_PP(1);
|
|
|
|
#ifdef HAVE_UUID_OSSP
|
|
return uuid_generate_v35_internal(UUID_MAKE_V5, ns, name);
|
|
#else
|
|
return uuid_generate_internal(UUID_MAKE_V5, (unsigned char *) ns,
|
|
VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name));
|
|
#endif
|
|
}
|