554 lines
13 KiB
C
554 lines
13 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* UUID generation functions using the BSD, E2FS or OSSP UUID library
|
|
*
|
|
* Copyright (c) 2007-2022, PostgreSQL Global Development Group
|
|
*
|
|
* Portions Copyright (c) 2009 Andrew Gierth
|
|
*
|
|
* contrib/uuid-ossp/uuid-ossp.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "fmgr.h"
|
|
#include "common/cryptohash.h"
|
|
#include "common/sha1.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
|
|
|
|
/* 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)
|
|
{
|
|
pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_MD5);
|
|
|
|
if (pg_cryptohash_init(ctx) < 0)
|
|
elog(ERROR, "could not initialize %s context: %s", "MD5",
|
|
pg_cryptohash_error(ctx));
|
|
if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
|
|
pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
|
|
elog(ERROR, "could not update %s context: %s", "MD5",
|
|
pg_cryptohash_error(ctx));
|
|
/* we assume sizeof MD5 result is 16, same as UUID size */
|
|
if (pg_cryptohash_final(ctx, (unsigned char *) &uu,
|
|
sizeof(uu)) < 0)
|
|
elog(ERROR, "could not finalize %s context: %s", "MD5",
|
|
pg_cryptohash_error(ctx));
|
|
pg_cryptohash_free(ctx);
|
|
}
|
|
else
|
|
{
|
|
pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_SHA1);
|
|
unsigned char sha1result[SHA1_DIGEST_LENGTH];
|
|
|
|
if (pg_cryptohash_init(ctx) < 0)
|
|
elog(ERROR, "could not initialize %s context: %s", "SHA1",
|
|
pg_cryptohash_error(ctx));
|
|
if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
|
|
pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
|
|
elog(ERROR, "could not update %s context: %s", "SHA1",
|
|
pg_cryptohash_error(ctx));
|
|
if (pg_cryptohash_final(ctx, sha1result, sizeof(sha1result)) < 0)
|
|
elog(ERROR, "could not finalize %s context: %s", "SHA1",
|
|
pg_cryptohash_error(ctx));
|
|
pg_cryptohash_free(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
|
|
}
|