Implement GUID using boost's implementation

Since we're maintaining a C api, the implementation is sometimes less than
intuitive from either a C or C++ perspective. I am trying to use as much
boost as possible while making all the guarantees that the C code makes.

One function was declared deprecated because it "wasn't thread safe". This
was straightforward to repair, and is no longer marked deprecated, and there
are now two ways to convert GUID to String: passing your own character
buffer, and having one returned to you that you need to free.
This commit is contained in:
lmat 2014-06-11 09:17:09 -04:00
parent 34a7e8d62c
commit 26a49f9644
2 changed files with 122 additions and 628 deletions

View File

@ -1,6 +1,7 @@
/********************************************************************\
* guid.c -- globally unique ID implementation *
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
* Copyright (C) 2014 Aaron Laws <dartmetrash@gmail.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@ -21,10 +22,8 @@
* *
\********************************************************************/
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
@ -53,30 +52,16 @@ extern "C"
#include "qof.h"
#include "md5.h"
#ifdef __cplusplus
}
#endif
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <sstream>
#include <string>
# ifndef P_tmpdir
# define P_tmpdir "/tmp"
# endif
using namespace std;
/* Constants *******************************************************/
#define DEBUG_GUID 0
#define BLOCKSIZE 4096
#ifdef G_PLATFORM_WIN32
/* Win32 has a smaller pool of random bits, but the displayed warning confuses
* really a lot of people. Hence, I think we'd better switch off this warning
* for this particular known case. */
# define THRESHOLD 1500
#else
# define THRESHOLD (2 * BLOCKSIZE)
#endif
/* Static global variables *****************************************/
static gboolean guid_initialized = FALSE;
static struct md5_ctx guid_context;
typedef boost::uuids::uuid gg;
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = QOF_MOD_ENGINE;
@ -101,12 +86,22 @@ gnc_value_get_guid (const GValue *value)
return val;
}
/* Memory management routines ***************************************/
GncGUID *
guid_new_ptr_return (void)
{
return reinterpret_cast<GncGUID*> (new boost::uuids::uuid);
}
/* Deprecated*/
GncGUID *
guid_malloc (void)
{
return g_slice_new(GncGUID);
/*We need to actually construct here (not just ::operator new (size_t) -- allocate)
because in guid_new, we assume that the object has already been constructed.
Note the boost UUID is a POD, so its constructor is trivial.*/
return reinterpret_cast<GncGUID*> (new boost::uuids::uuid);
}
void
@ -114,425 +109,36 @@ guid_free (GncGUID *guid)
{
if (!guid)
return;
g_slice_free(GncGUID, guid);
delete reinterpret_cast<boost::uuids::uuid*> (guid);
}
GncGUID *
guid_copy (const GncGUID *guid)
{
GncGUID *copy;
g_return_val_if_fail(guid, NULL);
copy = guid_malloc();
*copy = *guid;
return copy;
const boost::uuids::uuid * old {reinterpret_cast<const boost::uuids::uuid*> (guid)};
boost::uuids::uuid * ret {new boost::uuids::uuid (*old)};
return reinterpret_cast<GncGUID*>(ret);
}
static GncGUID * nullguid {reinterpret_cast<GncGUID*>(new boost::uuids::uuid{0})};
/*It looks like we are expected to provide the same pointer every time from this function*/
const GncGUID *
guid_null(void)
{
static int null_inited = 0;
static GncGUID null_guid;
if (!null_inited)
{
int i;
for (i = 0; i < GUID_DATA_SIZE; i++)
null_guid.data[i] = '\0';
null_inited = 1;
}
return &null_guid;
//static boost::uuids::nil_generator nil;
//boost::uuids::uuid * ret {new boost::uuids::uuid {0}};
//return reinterpret_cast<GncGUID*> (ret);
return nullguid;
}
/* Function implementations ****************************************/
/* This code is based on code in md5.c in GNU textutils. */
static size_t
init_from_stream(FILE *stream, size_t max_size)
{
char buffer[BLOCKSIZE + 72];
size_t sum, block_size, total;
ENTER("");
if (max_size <= 0)
{
LEAVE("max_size is 0 or less, skipping stream");
return 0;
}
total = 0;
/* Iterate over file contents. */
while (1)
{
/* We read the file in blocks of BLOCKSIZE bytes. One call of the
* computation function processes the whole buffer so that with the
* next round of the loop another block can be read. */
size_t n;
sum = 0;
if (max_size < BLOCKSIZE)
block_size = max_size;
else
block_size = BLOCKSIZE;
/* Read block. Take care for partial reads. */
do
{
n = fread (buffer + sum, 1, block_size - sum, stream);
sum += n;
}
while (sum < block_size && n != 0);
max_size -= sum;
if (n == 0 && ferror (stream))
{
LEAVE("error while reading stream");
return total;
}
/* If end of file or max_size is reached, end the loop. */
if ((n == 0) || (max_size == 0))
break;
/* Process buffer with BLOCKSIZE bytes. Note that
* BLOCKSIZE % 64 == 0 */
md5_process_block (buffer, BLOCKSIZE, &guid_context);
total += sum;
}
/* Add the last bytes if necessary. */
if (sum > 0)
{
md5_process_bytes (buffer, sum, &guid_context);
total += sum;
}
LEAVE("");
return total;
}
static size_t
init_from_file(const char *filename, size_t max_size)
{
struct stat stats;
size_t total = 0;
size_t file_bytes;
FILE *fp;
ENTER("filename: %s", filename);
memset(&stats, 0, sizeof(stats));
if (g_stat(filename, &stats) != 0)
{
LEAVE("unable to read file stats on %s", filename);
return 0;
}
md5_process_bytes(&stats, sizeof(stats), &guid_context);
total += sizeof(stats);
if (max_size <= 0)
{
LEAVE("no bytes in file %s", filename);
return total;
}
fp = g_fopen (filename, "r");
if (fp == NULL)
{
LEAVE("unable to open file %s", filename);
return total;
}
file_bytes = init_from_stream(fp, max_size);
#ifdef HAVE_SCANF_LLD
PINFO ("guid_init got %" G_GUINT64_FORMAT " bytes from %s",
(uint64_t) file_bytes,
filename);
#else
PINFO ("guid_init got %lu bytes from %s", (unsigned long int) file_bytes,
filename);
#endif
total += file_bytes;
fclose(fp);
LEAVE("file %s processed successfully", filename);
return total;
}
static size_t
init_from_dir(const char *dirname, unsigned int max_files)
{
char filename[1024];
const gchar *de;
struct stat stats;
size_t total;
int result;
GDir *dir;
ENTER("dirname: %s", dirname);
if (max_files <= 0)
{
LEAVE("max_files is 0 or less, skipping directory %s", dirname);
return 0;
}
dir = g_dir_open(dirname, 0, NULL);
if (dir == NULL)
{
LEAVE("unable to open directory %s", dirname);
return 0;
}
total = 0;
do
{
de = g_dir_read_name(dir);
if (de == NULL)
break;
md5_process_bytes(de, strlen(de), &guid_context);
total += strlen(de);
result = g_snprintf(filename, sizeof(filename),
"%s/%s", dirname, de);
if ((result < 0) || (result >= (int)sizeof(filename)))
continue;
memset(&stats, 0, sizeof(stats));
if (g_stat(filename, &stats) != 0)
continue;
md5_process_bytes(&stats, sizeof(stats), &guid_context);
total += sizeof(stats);
max_files--;
}
while (max_files > 0);
g_dir_close(dir);
LEAVE("");
return total;
}
static size_t
init_from_time(void)
{
size_t total;
time64 time;
#ifdef HAVE_SYS_TIMES_H
clock_t clocks;
struct tms tms_buf;
#endif
ENTER("");
total = 0;
time = gnc_time (NULL);
md5_process_bytes(&time, sizeof(time), &guid_context);
total += sizeof(time);
#ifdef HAVE_SYS_TIMES_H
clocks = times(&tms_buf);
md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
total += sizeof(clocks) + sizeof(tms_buf);
#endif
LEAVE("");
return total;
}
static size_t
init_from_int(int val)
{
ENTER("");
md5_process_bytes(&val, sizeof(val), &guid_context);
LEAVE("");
return sizeof(int);
}
static size_t
init_from_buff(unsigned char * buf, size_t buflen)
{
ENTER("");
md5_process_bytes(buf, buflen, &guid_context);
LEAVE("");
return buflen;
}
void
guid_init(void)
{
size_t bytes = 0;
ENTER("");
/* Not needed; taken care of on first malloc.
* guid_memchunk_init(); */
md5_init_ctx(&guid_context);
/* entropy pool
* FIXME /dev/urandom doesn't exist on Windows. We should
* use the Windows native CryptGenRandom or RtlGenRandom
* functions. See
* http://en.wikipedia.org/wiki/CryptGenRandom */
bytes += init_from_file ("/dev/urandom", 512);
/* files
* FIXME none of these directories make sense on
* Windows. We should figure out some proper
* alternatives there. */
{
const char * files[] =
{
"/etc/passwd",
"/proc/loadavg",
"/proc/meminfo",
"/proc/net/dev",
"/proc/rtc",
"/proc/self/environ",
"/proc/self/stat",
"/proc/stat",
"/proc/uptime",
NULL
};
int i;
for (i = 0; files[i] != NULL; i++)
bytes += init_from_file(files[i], BLOCKSIZE);
}
/* directories
* Note: P_tmpdir is set to "\" by mingw (Windows) This seems to
* trigger unwanted network access attempts (see bug #521817).
* So on Windows we explicitly set the temporary directory.
* FIXME other than "c:/temp" none of these directories make sense on
* Windows. We should figure out some proper
* alternatives there. */
{
const char * dirname;
const char * dirs[] =
{
"/proc",
#ifndef G_OS_WIN32
P_tmpdir,
#else
"c:/temp",
#endif
"/var/lock",
"/var/log",
"/var/mail",
"/var/spool/mail",
"/var/run",
NULL
};
int i;
for (i = 0; dirs[i] != NULL; i++)
bytes += init_from_dir(dirs[i], 32);
dirname = g_get_home_dir();
if (dirname != NULL)
bytes += init_from_dir(dirname, 32);
}
/* process and parent ids */
{
#ifdef HAVE_UNISTD_H
pid_t pid;
pid = getpid();
md5_process_bytes(&pid, sizeof(pid), &guid_context);
bytes += sizeof(pid);
#ifdef HAVE_GETPPID
pid = getppid();
md5_process_bytes(&pid, sizeof(pid), &guid_context);
bytes += sizeof(pid);
#endif
#endif
}
/* user info */
{
#ifdef HAVE_GETUID
uid_t uid;
gid_t gid;
char *s;
s = getlogin();
if (s != NULL)
{
md5_process_bytes(s, strlen(s), &guid_context);
bytes += strlen(s);
}
uid = getuid();
md5_process_bytes(&uid, sizeof(uid), &guid_context);
bytes += sizeof(uid);
gid = getgid();
md5_process_bytes(&gid, sizeof(gid), &guid_context);
bytes += sizeof(gid);
#endif
}
/* host info */
{
#ifdef HAVE_GETHOSTNAME
char string[1024];
memset(string, 0, sizeof(string));
gethostname(string, sizeof(string));
md5_process_bytes(string, sizeof(string), &guid_context);
bytes += sizeof(string);
#endif
}
/* plain old random */
{
int n, i;
srand((unsigned int) gnc_time (NULL));
for (i = 0; i < 32; i++)
{
n = rand();
md5_process_bytes(&n, sizeof(n), &guid_context);
bytes += sizeof(n);
}
}
/* time in secs and clock ticks */
bytes += init_from_time();
PINFO ("got %" G_GUINT64_FORMAT " bytes", (uint64_t) bytes);
if (bytes < THRESHOLD)
PWARN("only got %" G_GUINT64_FORMAT " bytes.\n"
"The identifiers might not be very random.\n",
(uint64_t)bytes);
guid_initialized = TRUE;
LEAVE();
/*Boost takes care of this*/
}
void
@ -540,208 +146,105 @@ guid_shutdown (void)
{
}
#define GUID_PERIOD 5000
/*Takes an already-constructed guid pointer and re-constructs it in place*/
void
guid_new(GncGUID *guid)
{
static int counter = 0;
struct md5_ctx ctx;
if (guid == NULL)
return;
if (!guid_initialized)
guid_init();
/* make the id */
ctx = guid_context;
md5_finish_ctx(&ctx, guid->data);
/* update the global context */
init_from_time();
/* Make it a little extra salty. I think init_from_time was buggy,
* or something, since duplicate id's actually happened. Or something
* like that. I think this is because init_from_time kept returning
* the same values too many times in a row. So we'll do some 'block
* chaining', and feed in the old guid as new random data.
*
* Anyway, I think the whole fact that I saw a bunch of duplicate
* id's at one point, but can't reproduce the bug is rather alarming.
* Something must be broken somewhere, and merely adding more salt
* is just hiding the problem, not fixing it.
*/
init_from_int (433781 * counter);
init_from_buff (guid->data, GUID_DATA_SIZE);
if (counter == 0)
{
FILE *fp;
fp = g_fopen ("/dev/urandom", "r");
if (fp == NULL)
return;
init_from_stream(fp, 32);
fclose(fp);
counter = GUID_PERIOD;
}
counter--;
static boost::uuids::random_generator gen;
boost::uuids::uuid * val {reinterpret_cast<boost::uuids::uuid*> (guid)};
val->boost::uuids::uuid::~uuid();
new (val) boost::uuids::uuid (gen ());
}
GncGUID
guid_new_return(void)
{
GncGUID guid;
guid_new (&guid);
return guid;
/*we need to construct our value as a boost guid so that
it can be deconstructed (in place) in guid_new*/
boost::uuids::uuid guid;
GncGUID * ret {reinterpret_cast<GncGUID*> (&guid)};
guid_new (ret);
/*return a copy*/
return *ret;
}
/* needs 32 bytes exactly, doesn't print a null char */
static void
encode_md5_data(const unsigned char *data, char *buffer)
{
size_t count;
for (count = 0; count < GUID_DATA_SIZE; count++, buffer += 2)
sprintf(buffer, "%02x", data[count]);
}
/* returns true if the first 32 bytes of buffer encode
* a hex number. returns false otherwise. Decoded number
* is packed into data in little endian order. */
static gboolean
decode_md5_string(const gchar *string, unsigned char *data)
{
unsigned char n1, n2;
size_t count = -1;
unsigned char c1, c2;
if (NULL == data) return FALSE;
if (NULL == string) goto badstring;
for (count = 0; count < GUID_DATA_SIZE; count++)
{
/* check for a short string e.g. null string ... */
if ((0 == string[2*count]) || (0 == string[2*count+1])) goto badstring;
c1 = tolower(string[2 * count]);
if (!isxdigit(c1)) goto badstring;
c2 = tolower(string[2 * count + 1]);
if (!isxdigit(c2)) goto badstring;
if (isdigit(c1))
n1 = c1 - '0';
else
n1 = c1 - 'a' + 10;
if (isdigit(c2))
n2 = c2 - '0';
else
n2 = c2 - 'a' + 10;
data[count] = (n1 << 4) | n2;
}
return TRUE;
badstring:
for (count = 0; count < GUID_DATA_SIZE; count++)
{
data[count] = 0;
}
return FALSE;
}
/* Allocate the key */
const char *
guid_to_string(const GncGUID * guid)
guid_to_string (const GncGUID * guid)
{
#ifdef G_THREADS_ENABLED
#ifndef HAVE_GLIB_2_32
static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
gchar *string;
string = static_cast<gchar*>(g_static_private_get (&guid_buffer_key));
if (string == NULL)
{
string = static_cast<gchar*>(malloc(GUID_ENCODING_LENGTH + 1));
g_static_private_set (&guid_buffer_key, string, g_free);
/* We need to malloc here, not 'new' because it will be freed
by the caller which will use free (not delete).*/
char * ret {reinterpret_cast<char*> (malloc (sizeof (char)*GUID_ENCODING_LENGTH+1))};
gchar * temp {guid_to_string_buff(guid, ret)};
if (!temp){
delete[] ret;
return temp;
}
#else
static GPrivate guid_buffer_key = G_PRIVATE_INIT(g_free);
gchar *string;
string = static_cast<char*>(g_private_get (&guid_buffer_key));
if (string == NULL)
{
string = static_cast<char*>(malloc(GUID_ENCODING_LENGTH + 1));
g_private_set (&guid_buffer_key, string);
}
#endif
#else
static char string[64];
#endif
encode_md5_data(guid->data, string);
string[GUID_ENCODING_LENGTH] = '\0';
return string;
return ret;
}
char *
guid_to_string_buff(const GncGUID * guid, char *string)
gchar *
guid_to_string_buff(const GncGUID * guid, char *str)
{
if (!string || !guid) return NULL;
if (!str || !guid) return NULL;
encode_md5_data(guid->data, string);
string[GUID_ENCODING_LENGTH] = '\0';
return &string[GUID_ENCODING_LENGTH];
static boost::uuids::string_generator str_gen;
stringstream ostr;
boost::uuids::uuid const & tempg = *reinterpret_cast<boost::uuids::uuid const *>(guid);
ostr << tempg;
string const & val (ostr.str());
/*unsigned size {val.size()};
size must equal GUID_ENCODING_LENGTH*/
unsigned stringspot{0};
for (unsigned destspot{0}; destspot < GUID_ENCODING_LENGTH; ++destspot, ++stringspot){
switch (stringspot){
/*there are hyphens in boost's output, and we need to skip them in our output*/
case 8:
case 13:
case 18:
case 23:
++stringspot;
}
str[destspot] = val[stringspot];
}
str[GUID_ENCODING_LENGTH] = '\0';
return &str[GUID_ENCODING_LENGTH];
}
gboolean
string_to_guid(const char * string, GncGUID * guid)
string_to_guid(const char * str, GncGUID * guid)
{
return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
static boost::uuids::string_generator strgen;
boost::uuids::uuid * converted {reinterpret_cast<boost::uuids::uuid*> (guid)};
new (converted) boost::uuids::uuid (strgen(str));
return true;
}
gboolean
guid_equal(const GncGUID *guid_1, const GncGUID *guid_2)
{
if (guid_1 && guid_2)
return (memcmp(guid_1, guid_2, GUID_DATA_SIZE) == 0);
else
return FALSE;
if (!guid_1 || !guid_2)
return false;
boost::uuids::uuid const * g1 {reinterpret_cast<boost::uuids::uuid const *> (guid_1)};
boost::uuids::uuid const * g2 {reinterpret_cast<boost::uuids::uuid const *> (guid_2)};
return *g1 == *g2;
}
gint
guid_compare(const GncGUID *guid_1, const GncGUID *guid_2)
{
if (guid_1 == guid_2)
return 0;
/* nothing is always less than something */
if (!guid_1 && guid_2)
boost::uuids::uuid const * g1 {reinterpret_cast<boost::uuids::uuid const *> (guid_1)};
boost::uuids::uuid const * g2 {reinterpret_cast<boost::uuids::uuid const *> (guid_2)};
if (*g1 < *g2)
return -1;
if (guid_1 && !guid_2)
return 1;
return memcmp (guid_1, guid_2, GUID_DATA_SIZE);
if (*g1 == *g2)
return 0;
return 1;
}
guint
guid_hash_to_guint (gconstpointer ptr)
{
const GncGUID *guid = static_cast<const GncGUID*>(ptr);
const boost::uuids::uuid * guid = reinterpret_cast<const boost::uuids::uuid*>(ptr);
if (!guid)
{
@ -749,33 +252,22 @@ guid_hash_to_guint (gconstpointer ptr)
return 0;
}
if (sizeof(guint) <= sizeof(guid->data))
{
const guint* ptr_data = (const guint *) guid->data;
return (*ptr_data);
}
else
{
guint hash = 0;
unsigned int i, j;
for (i = 0, j = 0; i < sizeof(guint); i++, j++)
{
if (j == GUID_DATA_SIZE) j = 0;
hash <<= 4;
hash |= guid->data[j];
}
return hash;
guint hash {0};
unsigned retspot {0};
boost::uuids::uuid::const_iterator guidspot {guid->begin()};
while (guidspot != guid->end()){
hash <<= 4;
hash |= *guidspot;
++guidspot;
}
return hash;
}
gint
guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
{
return guid_equal (static_cast<const GncGUID*>(guid_a),
static_cast<const GncGUID*>(guid_b));
return guid_equal (reinterpret_cast<const GncGUID*>(guid_a),
reinterpret_cast<const GncGUID*>(guid_b));
}
GHashTable *

View File

@ -29,6 +29,10 @@ extern "C"
{
#endif
typedef struct _gncGuid {
unsigned char reserved[16];
} GncGUID;
#include <stddef.h>
#include <glib-object.h>
@ -56,14 +60,6 @@ extern "C"
/** The type used to store guids */
#define GUID_DATA_SIZE 16
typedef union GNC_INTERNAL_GUID
{
guchar data[GUID_DATA_SIZE];
gint __align_me; /* this just ensures that GUIDs are 32-bit
* aligned on systems that need them to be. */
} GncGUID;
#define GNC_TYPE_GUID (gnc_guid_get_type())
#define GNC_VALUE_HOLDS_GUID(value) G_VALUE_HOLDS(value, GNC_TYPE_GUID)
@ -105,7 +101,7 @@ void guid_shutdown (void);
* you'd still have less than a one-in-a-million chance of coming up
* with a duplicate id. 2^128 == 10^38 is a really really big number.)
*/
void guid_new(GncGUID *guid);
void guid_new (GncGUID *guid);
/** Generate a new id. If no initialization function has been called,
* guid_init() will be called before the id is created.
@ -113,16 +109,24 @@ void guid_new(GncGUID *guid);
* @return guid A data structure containing a newly allocated GncGUID.
* Caller is responsible for calling guid_free().
*/
GncGUID guid_new_return(void);
GncGUID guid_new_return (void);
/** Returns a GncGUID which is guaranteed
to never reference any entity. */
const GncGUID * guid_null (void);
/** Efficiently allocate & free memory for GUIDs */
/** Efficiently allocate & free memory for GUIDs
* XXX This routine is deprecated. Please use guid_new_return_ptr
* instead.
*/
GncGUID * guid_malloc (void);
/* Return a guid set to all zero's */
/** Allocate and initialize a new guid, and return
a pointer to it. Caller must call guid_free after to
release this pointer*/
GncGUID * guid_new_ptr_return (void);
/*Free the guid pointed to. Do not use this guid any more.*/
void guid_free (GncGUID *guid);
GncGUID *guid_copy (const GncGUID *guid);
@ -133,9 +137,6 @@ GncGUID *guid_copy (const GncGUID *guid);
* 'a' through 'f'. The encoding will always be GUID_ENCODING_LENGTH
* characters long.
*
* XXX This routine is not thread safe and is deprecated. Please
* use the routine guid_to_string_buff() instead.
*
* @param guid The guid to print.
*
* @return A pointer to the starting character of the string. The
@ -155,7 +156,8 @@ const gchar * guid_to_string (const GncGUID * guid);
*
* @param buff The buffer to print it into.
*
* @return A pointer to the terminating null character of the string.
* @return A pointer to the terminating null character of the string,
* or, if no copy took place, NULL.
*/
gchar * guid_to_string_buff (const GncGUID * guid, /*@ out @*/ gchar *buff);