guid.c

00001 /********************************************************************\
00002  * guid.c -- globally unique ID implementation                      *
00003  * Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu>      *
00004  *                                                                  *
00005  * This program is free software; you can redistribute it and/or    *
00006  * modify it under the terms of the GNU General Public License as   *
00007  * published by the Free Software Foundation; either version 2 of   *
00008  * the License, or (at your option) any later version.              *
00009  *                                                                  *
00010  * This program is distributed in the hope that it will be useful,  *
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00013  * GNU General Public License for more details.                     *
00014  *                                                                  *
00015  * You should have received a copy of the GNU General Public License*
00016  * along with this program; if not, contact:                        *
00017  *                                                                  *
00018  * Free Software Foundation           Voice:  +1-617-542-5942       *
00019  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00020  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00021  *                                                                  *
00022 \********************************************************************/
00023 
00024 #ifdef HAVE_CONFIG_H
00025 # include <config.h>
00026 #endif
00027 
00028 #ifdef HAVE_SYS_TYPES_H
00029 # include <sys/types.h>
00030 #endif
00031 #include <ctype.h>
00032 #include <dirent.h>
00033 #include <glib.h>
00034 #include <glib/gstdio.h>
00035 #include <stdlib.h>
00036 #include <string.h>
00037 #include <sys/stat.h>
00038 #ifdef HAVE_SYS_TIMES_H
00039 # include <sys/times.h>
00040 #endif
00041 #include <time.h>
00042 #include <unistd.h>
00043 #include "qof.h"
00044 #include "md5.h"
00045 
00046 # ifndef P_tmpdir
00047 #  define P_tmpdir "/tmp"
00048 # endif
00049 
00050 /* Constants *******************************************************/
00051 #define DEBUG_GUID 0
00052 #define BLOCKSIZE 4096
00053 #define THRESHOLD (2 * BLOCKSIZE)
00054 
00055 
00056 /* Static global variables *****************************************/
00057 static gboolean guid_initialized = FALSE;
00058 static struct md5_ctx guid_context;
00059 #ifndef HAVE_GLIB29
00060 static GMemChunk *guid_memchunk = NULL;
00061 #endif
00062 
00063 /* This static indicates the debugging module that this .o belongs to.  */
00064 static QofLogModule log_module = QOF_MOD_ENGINE;
00065 
00072 G_CONST_RETURN GUID*
00073 gnc_value_get_guid (const GValue *value)
00074 {
00075         GUID *val;
00076 
00077         g_return_val_if_fail (value && G_IS_VALUE (value), NULL);
00078         g_return_val_if_fail (GNC_VALUE_HOLDS_GUID (value), NULL);
00079 
00080         val = (GUID*) g_value_get_boxed (value);
00081 
00082         return val;
00083 }
00084 
00085 
00086 /* Memory management routines ***************************************/
00087 #ifdef HAVE_GLIB29
00088 GUID *
00089 guid_malloc (void)
00090 {
00091   return g_slice_new(GUID);
00092 }
00093 
00094 void
00095 guid_free (GUID *guid)
00096 {
00097   if (!guid)
00098     return;
00099 
00100   g_slice_free(GUID, guid);
00101 }
00102 #else /* !HAVE_GLIB29 */
00103 
00104 static void
00105 guid_memchunk_init (void)
00106 {
00107   if (!guid_memchunk)
00108     guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
00109 }
00110 
00111 static void
00112 guid_memchunk_shutdown (void)
00113 {
00114   if (guid_memchunk)
00115   {
00116     g_mem_chunk_destroy (guid_memchunk);
00117     guid_memchunk = NULL;
00118   }
00119 }
00120 
00121 GUID *
00122 guid_malloc (void)
00123 {
00124   if (!guid_memchunk) guid_memchunk_init();
00125   return g_chunk_new (GUID, guid_memchunk);
00126 }
00127 
00128 void
00129 guid_free (GUID *guid)
00130 {
00131   if (!guid)
00132     return;
00133 
00134   g_chunk_free (guid, guid_memchunk);
00135 }
00136 #endif
00137 
00138 
00139 GUID *
00140 guid_copy (const GUID *guid)
00141 {
00142   GUID *copy;
00143 
00144   g_return_val_if_fail(guid, NULL);
00145   copy = guid_malloc();
00146   *copy = *guid;
00147   return copy;
00148 }
00149 
00150 const GUID *
00151 guid_null(void)
00152 {
00153   static int null_inited = 0;
00154   static GUID null_guid;
00155 
00156   if (!null_inited) {
00157       int i;
00158 
00159       for (i = 0; i < GUID_DATA_SIZE; i++)
00160           null_guid.data[i] = '\0';
00161 
00162       null_inited = 1;
00163   }
00164 
00165   return &null_guid;
00166 }
00167 
00168 /* Function implementations ****************************************/
00169 
00170 /* This code is based on code in md5.c in GNU textutils. */
00171 static size_t
00172 init_from_stream(FILE *stream, size_t max_size)
00173 {
00174   char buffer[BLOCKSIZE + 72];
00175   size_t sum, block_size, total;
00176 
00177   if (max_size <= 0)
00178     return 0;
00179 
00180   total = 0;
00181 
00182   /* Iterate over file contents. */
00183   while (1)
00184   {
00185     /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
00186      * computation function processes the whole buffer so that with the
00187      * next round of the loop another block can be read.  */
00188     size_t n;
00189     sum = 0;
00190 
00191     if (max_size < BLOCKSIZE)
00192       block_size = max_size;
00193     else
00194       block_size = BLOCKSIZE;
00195 
00196     /* Read block.  Take care for partial reads.  */
00197     do
00198     {
00199       n = fread (buffer + sum, 1, block_size - sum, stream);
00200 
00201       sum += n;
00202     }
00203     while (sum < block_size && n != 0);
00204 
00205     max_size -= sum;
00206 
00207     if (n == 0 && ferror (stream))
00208       return total;
00209 
00210     /* If end of file or max_size is reached, end the loop. */
00211     if ((n == 0) || (max_size == 0))
00212       break;
00213 
00214     /* Process buffer with BLOCKSIZE bytes.  Note that
00215      * BLOCKSIZE % 64 == 0  */
00216     md5_process_block (buffer, BLOCKSIZE, &guid_context);
00217 
00218     total += sum;
00219   }
00220 
00221   /* Add the last bytes if necessary.  */
00222   if (sum > 0)
00223   {
00224     md5_process_bytes (buffer, sum, &guid_context);
00225     total += sum;
00226   }
00227 
00228   return total;
00229 }
00230 
00231 static size_t
00232 init_from_file(const char *filename, size_t max_size)
00233 {
00234   struct stat stats;
00235   size_t total = 0;
00236   size_t file_bytes;
00237   FILE *fp;
00238 
00239   memset(&stats, 0, sizeof(stats));
00240   if (g_stat(filename, &stats) != 0)
00241     return 0;
00242 
00243   md5_process_bytes(&stats, sizeof(stats), &guid_context);
00244   total += sizeof(stats);
00245 
00246   if (max_size <= 0)
00247     return total;
00248 
00249   fp = g_fopen (filename, "r");
00250   if (fp == NULL)
00251     return total;
00252 
00253   file_bytes = init_from_stream(fp, max_size);
00254 
00255   PINFO ("guid_init got %llu bytes from %s", (unsigned long long int) file_bytes,
00256          filename);
00257 
00258   total += file_bytes;
00259 
00260   fclose(fp);
00261 
00262   return total;
00263 }
00264 
00265 static size_t
00266 init_from_dir(const char *dirname, unsigned int max_files)
00267 {
00268   char filename[1024];
00269   const gchar *de;
00270   struct stat stats;
00271   size_t total;
00272   int result;
00273   GDir *dir;
00274 
00275   if (max_files <= 0)
00276     return 0;
00277 
00278   dir = g_dir_open(dirname, 0, NULL);
00279   if (dir == NULL)
00280     return 0;
00281 
00282   total = 0;
00283 
00284   do
00285   {
00286     de = g_dir_read_name(dir);
00287     if (de == NULL)
00288       break;
00289 
00290     md5_process_bytes(de, strlen(de), &guid_context);
00291     total += strlen(de);
00292 
00293     result = snprintf(filename, sizeof(filename),
00294                       "%s/%s", dirname, de);
00295     if ((result < 0) || (result >= (int)sizeof(filename)))
00296       continue;
00297 
00298     memset(&stats, 0, sizeof(stats));
00299     if (g_stat(filename, &stats) != 0)
00300       continue;
00301     md5_process_bytes(&stats, sizeof(stats), &guid_context);
00302     total += sizeof(stats);
00303 
00304     max_files--;
00305   } while (max_files > 0);
00306 
00307   g_dir_close(dir);
00308 
00309   return total;
00310 }
00311 
00312 static size_t
00313 init_from_time(void)
00314 {
00315   size_t total;
00316   time_t t_time;
00317 #ifdef HAVE_SYS_TIMES_H
00318   clock_t clocks;
00319   struct tms tms_buf;
00320 #endif
00321 
00322   total = 0;
00323 
00324   t_time = time(NULL);
00325   md5_process_bytes(&t_time, sizeof(t_time), &guid_context);
00326   total += sizeof(t_time);
00327 
00328 #ifdef HAVE_SYS_TIMES_H
00329   clocks = times(&tms_buf);
00330   md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
00331   md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
00332   total += sizeof(clocks) + sizeof(tms_buf);
00333 #endif
00334 
00335   return total;
00336 }
00337 
00338 static size_t
00339 init_from_int(int val)
00340 {
00341   md5_process_bytes(&val, sizeof(val), &guid_context);
00342   return sizeof(int);
00343 }
00344 
00345 static size_t
00346 init_from_buff(unsigned char * buf, size_t buflen)
00347 {
00348   md5_process_bytes(buf, buflen, &guid_context);
00349   return buflen;
00350 }
00351 
00352 void
00353 guid_init(void)
00354 {
00355   size_t bytes = 0;
00356 
00357   /* Not needed; taken care of on first malloc.
00358    * guid_memchunk_init(); */
00359 
00360   md5_init_ctx(&guid_context);
00361 
00362   /* entropy pool */
00363   bytes += init_from_file ("/dev/urandom", 512);
00364 
00365   /* files */
00366   {
00367     const char * files[] =
00368     { "/etc/passwd",
00369       "/proc/loadavg",
00370       "/proc/meminfo",
00371       "/proc/net/dev",
00372       "/proc/rtc",
00373       "/proc/self/environ",
00374       "/proc/self/stat",
00375       "/proc/stat",
00376       "/proc/uptime",
00377       NULL
00378     };
00379     int i;
00380 
00381     for (i = 0; files[i] != NULL; i++)
00382       bytes += init_from_file(files[i], BLOCKSIZE);
00383   }
00384 
00385   /* directories */
00386   {
00387     const char * dirname;
00388     const char * dirs[] =
00389     {
00390       "/proc",
00391       P_tmpdir,
00392       "/var/lock",
00393       "/var/log",
00394       "/var/mail",
00395       "/var/spool/mail",
00396       "/var/run",
00397       NULL
00398     };
00399     int i;
00400 
00401     for (i = 0; dirs[i] != NULL; i++)
00402       bytes += init_from_dir(dirs[i], 32);
00403 
00404     dirname = g_get_home_dir();
00405     if (dirname != NULL)
00406       bytes += init_from_dir(dirname, 32);
00407   }
00408 
00409   /* process and parent ids */
00410   {
00411     pid_t pid;
00412 
00413     pid = getpid();
00414     md5_process_bytes(&pid, sizeof(pid), &guid_context);
00415     bytes += sizeof(pid);
00416 
00417 #ifdef HAVE_GETPPID
00418     pid = getppid();
00419     md5_process_bytes(&pid, sizeof(pid), &guid_context);
00420     bytes += sizeof(pid);
00421 #endif
00422   }
00423 
00424   /* user info */
00425   {
00426 #ifdef HAVE_GETUID
00427     uid_t uid;
00428     gid_t gid;
00429     char *s;
00430 
00431     s = getlogin();
00432     if (s != NULL)
00433     {
00434       md5_process_bytes(s, strlen(s), &guid_context);
00435       bytes += strlen(s);
00436     }
00437 
00438     uid = getuid();
00439     md5_process_bytes(&uid, sizeof(uid), &guid_context);
00440     bytes += sizeof(uid);
00441 
00442     gid = getgid();
00443     md5_process_bytes(&gid, sizeof(gid), &guid_context);
00444     bytes += sizeof(gid);
00445 #endif
00446   }
00447 
00448   /* host info */
00449   {
00450 #ifdef HAVE_GETHOSTNAME
00451     char string[1024];
00452 
00453     memset(string, 0, sizeof(string));
00454     gethostname(string, sizeof(string));
00455     md5_process_bytes(string, sizeof(string), &guid_context);
00456     bytes += sizeof(string);
00457 #endif
00458   }
00459 
00460   /* plain old random */
00461   {
00462     int n, i;
00463 
00464     srand((unsigned int) time(NULL));
00465 
00466     for (i = 0; i < 32; i++)
00467     {
00468       n = rand();
00469 
00470       md5_process_bytes(&n, sizeof(n), &guid_context);
00471       bytes += sizeof(n);
00472     }
00473   }
00474 
00475   /* time in secs and clock ticks */
00476   bytes += init_from_time();
00477 
00478   PINFO ("got %llu bytes", (unsigned long long int) bytes);
00479 
00480   if (bytes < THRESHOLD)
00481     PWARN("only got %llu bytes.\n"
00482               "The identifiers might not be very random.\n",
00483           (unsigned long long int)bytes);
00484 
00485   guid_initialized = TRUE;
00486 }
00487 
00488 void
00489 guid_init_with_salt(const void *salt, size_t salt_len)
00490 {
00491   guid_init();
00492 
00493   md5_process_bytes(salt, salt_len, &guid_context);
00494 }
00495 
00496 void
00497 guid_init_only_salt(const void *salt, size_t salt_len)
00498 {
00499   md5_init_ctx(&guid_context);
00500 
00501   md5_process_bytes(salt, salt_len, &guid_context);
00502 
00503   guid_initialized = TRUE;
00504 }
00505 
00506 void 
00507 guid_shutdown (void)
00508 {
00509 #ifndef HAVE_GLIB29
00510         guid_memchunk_shutdown();
00511 #endif
00512 }
00513 
00514 #define GUID_PERIOD 5000
00515 
00516 void
00517 guid_new(GUID *guid)
00518 {
00519   static int counter = 0;
00520   struct md5_ctx ctx;
00521 
00522   if (guid == NULL)
00523     return;
00524 
00525   if (!guid_initialized)
00526     guid_init();
00527 
00528   /* make the id */
00529   ctx = guid_context;
00530   md5_finish_ctx(&ctx, guid->data);
00531 
00532   /* update the global context */
00533   init_from_time();
00534 
00535   /* Make it a little extra salty.  I think init_from_time was buggy,
00536         * or something, since duplicate id's actually happened. Or something
00537         * like that.  I think this is because init_from_time kept returning
00538         * the same values too many times in a row.  So we'll do some 'block
00539         * chaining', and feed in the old guid as new random data.
00540         *
00541         * Anyway, I think the whole fact that I saw a bunch of duplicate 
00542         * id's at one point, but can't reproduce the bug is rather alarming.
00543         * Something must be broken somewhere, and merely adding more salt
00544         * is just hiding the problem, not fixing it.
00545         */
00546   init_from_int (433781*counter);
00547   init_from_buff (guid->data, GUID_DATA_SIZE);
00548 
00549   if (counter == 0)
00550   {
00551     FILE *fp;
00552 
00553     fp = g_fopen ("/dev/urandom", "r");
00554     if (fp == NULL)
00555       return;
00556 
00557     init_from_stream(fp, 32);
00558 
00559     fclose(fp);
00560 
00561     counter = GUID_PERIOD;
00562   }
00563 
00564   counter--;
00565 }
00566 
00567 GUID
00568 guid_new_return(void)
00569 {
00570   GUID guid;
00571 
00572   guid_new (&guid);
00573 
00574   return guid;
00575 }
00576 
00577 /* needs 32 bytes exactly, doesn't print a null char */
00578 static void
00579 encode_md5_data(const unsigned char *data, char *buffer)
00580 {
00581   size_t count;
00582 
00583   for (count = 0; count < GUID_DATA_SIZE; count++, buffer += 2)
00584     sprintf(buffer, "%02x", data[count]);
00585 }
00586 
00587 /* returns true if the first 32 bytes of buffer encode
00588  * a hex number. returns false otherwise. Decoded number
00589  * is packed into data in little endian order. */
00590 static gboolean
00591 decode_md5_string(const unsigned char *string, unsigned char *data)
00592 {
00593   unsigned char n1, n2;
00594   size_t count = -1;
00595   unsigned char c1, c2;
00596 
00597   if (NULL == data) return FALSE;
00598   if (NULL == string) goto badstring;
00599 
00600   for (count = 0; count < GUID_DATA_SIZE; count++)
00601   {
00602     /* check for a short string e.g. null string ... */
00603     if ((0==string[2*count]) || (0==string[2*count+1])) goto badstring;
00604 
00605     c1 = tolower(string[2 * count]);
00606     if (!isxdigit(c1)) goto badstring;
00607 
00608     c2 = tolower(string[2 * count + 1]);
00609     if (!isxdigit(c2)) goto badstring;
00610 
00611     if (isdigit(c1))
00612       n1 = c1 - '0';
00613     else
00614       n1 = c1 - 'a' + 10;
00615 
00616     if (isdigit(c2))
00617       n2 = c2 - '0';
00618     else
00619       n2 = c2 - 'a' + 10;
00620 
00621     data[count] = (n1 << 4) | n2;
00622   }
00623   return TRUE;
00624 
00625 badstring:
00626   for (count = 0; count < GUID_DATA_SIZE; count++)
00627   {
00628     data[count] = 0;
00629   }
00630   return FALSE;
00631 }
00632 
00633 /* Allocate the key */
00634 
00635 const char *
00636 guid_to_string(const GUID * guid)
00637 {
00638 #ifdef G_THREADS_ENABLED
00639   static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
00640   gchar *string;
00641 
00642   string = g_static_private_get (&guid_buffer_key);
00643   if (string == NULL) {
00644     string = malloc(GUID_ENCODING_LENGTH+1);
00645     g_static_private_set (&guid_buffer_key, string, g_free);
00646   }
00647 #else
00648   static char string[64];
00649 #endif
00650 
00651   encode_md5_data(guid->data, string);
00652   string[GUID_ENCODING_LENGTH] = '\0';
00653 
00654   return string;
00655 }
00656 
00657 char *
00658 guid_to_string_buff(const GUID * guid, char *string)
00659 {
00660   if (!string || !guid) return NULL;
00661 
00662   encode_md5_data(guid->data, string);
00663 
00664   string[GUID_ENCODING_LENGTH] = '\0';
00665   return &string[GUID_ENCODING_LENGTH];
00666 }
00667 
00668 gboolean
00669 string_to_guid(const char * string, GUID * guid)
00670 {
00671   return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
00672 }
00673 
00674 gboolean
00675 guid_equal(const GUID *guid_1, const GUID *guid_2)
00676 {
00677   if (guid_1 && guid_2)
00678     return (memcmp(guid_1, guid_2, GUID_DATA_SIZE) == 0);
00679   else
00680     return FALSE;
00681 }
00682 
00683 gint
00684 guid_compare(const GUID *guid_1, const GUID *guid_2)
00685 {
00686   if (guid_1 == guid_2)
00687     return 0;
00688 
00689   /* nothing is always less than something */
00690   if (!guid_1 && guid_2)
00691     return -1;
00692 
00693   if (guid_1 && !guid_2)
00694     return 1;
00695 
00696   return memcmp (guid_1, guid_2, GUID_DATA_SIZE);
00697 }
00698 
00699 guint
00700 guid_hash_to_guint (gconstpointer ptr)
00701 {
00702   const GUID *guid = ptr;
00703 
00704   if (!guid)
00705   {
00706     PERR ("received NULL guid pointer.");
00707     return 0;
00708   }
00709 
00710   if (sizeof(guint) <= sizeof(guid->data))
00711   {
00712     return (*((guint *) guid->data));
00713   }
00714   else
00715   {
00716     guint hash = 0;
00717     unsigned int i, j;
00718 
00719     for (i = 0, j = 0; i < sizeof(guint); i++, j++) {
00720       if (j == GUID_DATA_SIZE) j = 0;
00721 
00722       hash <<= 4;
00723       hash |= guid->data[j];
00724     }
00725 
00726     return hash;
00727   }
00728 }
00729 
00730 static gint
00731 guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
00732 {
00733   return guid_equal (guid_a, guid_b);
00734 }
00735 
00736 GHashTable *
00737 guid_hash_table_new (void)
00738 {
00739   return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
00740 }
00741 
00742 /***************************/
00743 static void
00744 gnc_string_to_guid (const GValue *src, GValue *dest)
00745 {
00746         /* FIXME: add more checks*/
00747         GUID *guid;
00748         const gchar *as_string;
00749 
00750         g_return_if_fail (G_VALUE_HOLDS_STRING (src) &&
00751                           GNC_VALUE_HOLDS_GUID (dest));
00752 
00753         as_string = g_value_get_string (src);
00754 
00755         guid = g_new0 (GUID, 1);
00756         string_to_guid(as_string, guid);
00757 
00758         g_value_take_boxed (dest, guid);
00759 }
00760 
00761 static void
00762 gnc_guid_to_string (const GValue *src, GValue *dest)
00763 {
00764         const gchar *str;
00765 
00766         g_return_if_fail (G_VALUE_HOLDS_STRING (dest) &&
00767                           GNC_VALUE_HOLDS_GUID (src));
00768 
00769         str = guid_to_string(gnc_value_get_guid (src));
00770 
00771         g_value_set_string (dest, str);
00772 }
00773 
00774 GType
00775 gnc_guid_get_type (void)
00776 {
00777         static GType type = 0;
00778 
00779         if (G_UNLIKELY (type == 0)) {
00780                 type = g_boxed_type_register_static ("GUID",
00781                                                      (GBoxedCopyFunc)guid_copy,
00782                                                      (GBoxedFreeFunc)guid_free);
00783 
00784                 g_value_register_transform_func (G_TYPE_STRING,
00785                                                  type,
00786                                                  gnc_string_to_guid);
00787 
00788                 g_value_register_transform_func (type,
00789                                                  G_TYPE_STRING,
00790                                                  gnc_guid_to_string);
00791         }
00792 
00793         return type;
00794 }

Generated on Mon Sep 8 05:04:15 2008 for GnuCash by  doxygen 1.5.2