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  * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
00020  * Boston, MA  02111-1307,  USA       gnu@gnu.org                   *
00021  *                                                                  *
00022 \********************************************************************/
00023 
00024 #define _GNU_SOURCE
00025 
00026 #ifdef HAVE_CONFIG_H
00027 # include <config.h>
00028 #endif
00029 
00030 #include <sys/types.h>
00031 #include <ctype.h>
00032 #include <dirent.h>
00033 #include <glib.h>
00034 #include <stdlib.h>
00035 #include <stdio.h>
00036 #include <string.h>
00037 #include <sys/stat.h>
00038 #include <sys/times.h>
00039 #include <time.h>
00040 #include <unistd.h>
00041 
00042 #include "guid.h"
00043 #include "md5.h"
00044 #include "gnc-trace.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 static GMemChunk *guid_memchunk = NULL;
00060 
00061 /* This static indicates the debugging module that this .o belongs to.  */
00062 static QofLogModule log_module = QOF_MOD_ENGINE;
00063 
00064 /* Memory management routines ***************************************/
00065 static void
00066 guid_memchunk_init (void)
00067 {
00068   if (!guid_memchunk)
00069     guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
00070 }
00071 
00072 static void
00073 guid_memchunk_shutdown (void)
00074 {
00075   if (guid_memchunk)
00076   {
00077     g_mem_chunk_destroy (guid_memchunk);
00078     guid_memchunk = NULL;
00079   }
00080 }
00081 
00082 GUID *
00083 guid_malloc (void)
00084 {
00085   if (!guid_memchunk) guid_memchunk_init();
00086   return g_chunk_new (GUID, guid_memchunk);
00087 }
00088 
00089 void
00090 guid_free (GUID *guid)
00091 {
00092   if (!guid)
00093     return;
00094 
00095   g_chunk_free (guid, guid_memchunk);
00096 }
00097 
00098 
00099 const GUID *
00100 guid_null(void)
00101 {
00102   static int null_inited = 0;
00103   static GUID null_guid;
00104 
00105   if (!null_inited)
00106   {
00107     int i;
00108     char *tmp = "NULLGUID.EMPTY.";
00109 
00110       /* 16th space for '\O' */
00111     for (i = 0; i < 16; i++)
00112       null_guid.data[i] = tmp[i];
00113 
00114     null_inited = 1;
00115   }
00116 
00117   return &null_guid;
00118 }
00119 
00120 /* Function implementations ****************************************/
00121 
00122 /* This code is based on code in md5.c in GNU textutils. */
00123 static size_t
00124 init_from_stream(FILE *stream, size_t max_size)
00125 {
00126   char buffer[BLOCKSIZE + 72];
00127   size_t sum, block_size, total;
00128 
00129   if (max_size <= 0)
00130     return 0;
00131 
00132   total = 0;
00133 
00134   /* Iterate over file contents. */
00135   while (1)
00136   {
00137     /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
00138      * computation function processes the whole buffer so that with the
00139      * next round of the loop another block can be read.  */
00140     size_t n;
00141     sum = 0;
00142 
00143     if (max_size < BLOCKSIZE)
00144       block_size = max_size;
00145     else
00146       block_size = BLOCKSIZE;
00147 
00148     /* Read block.  Take care for partial reads.  */
00149     do
00150     {
00151       n = fread (buffer + sum, 1, block_size - sum, stream);
00152 
00153       sum += n;
00154     }
00155     while (sum < block_size && n != 0);
00156 
00157     max_size -= sum;
00158 
00159     if (n == 0 && ferror (stream))
00160       return total;
00161 
00162     /* If end of file or max_size is reached, end the loop. */
00163     if ((n == 0) || (max_size == 0))
00164       break;
00165 
00166     /* Process buffer with BLOCKSIZE bytes.  Note that
00167      * BLOCKSIZE % 64 == 0  */
00168     md5_process_block (buffer, BLOCKSIZE, &guid_context);
00169 
00170     total += sum;
00171   }
00172 
00173   /* Add the last bytes if necessary.  */
00174   if (sum > 0)
00175   {
00176     md5_process_bytes (buffer, sum, &guid_context);
00177     total += sum;
00178   }
00179 
00180   return total;
00181 }
00182 
00183 static size_t
00184 init_from_file(const char *filename, size_t max_size)
00185 {
00186   struct stat stats;
00187   size_t total = 0;
00188   size_t file_bytes;
00189   FILE *fp;
00190 
00191   memset(&stats, 0, sizeof(stats));
00192   if (stat(filename, &stats) != 0)
00193     return 0;
00194 
00195   md5_process_bytes(&stats, sizeof(stats), &guid_context);
00196   total += sizeof(stats);
00197 
00198   if (max_size <= 0)
00199     return total;
00200 
00201   fp = fopen (filename, "r");
00202   if (fp == NULL)
00203     return total;
00204 
00205   file_bytes = init_from_stream(fp, max_size);
00206 
00207   PINFO ("guid_init got %llu bytes from %s", (unsigned long long int) file_bytes,
00208          filename);
00209 
00210   total += file_bytes;
00211 
00212   fclose(fp);
00213 
00214   return total;
00215 }
00216 
00217 static size_t
00218 init_from_dir(const char *dirname, unsigned int max_files)
00219 {
00220   char filename[1024];
00221   struct dirent *de;
00222   struct stat stats;
00223   size_t total;
00224   int result;
00225   DIR *dir;
00226 
00227   if (max_files <= 0)
00228     return 0;
00229 
00230   dir = opendir (dirname);
00231   if (dir == NULL)
00232     return 0;
00233 
00234   total = 0;
00235 
00236   do
00237   {
00238     de = readdir(dir);
00239     if (de == NULL)
00240       break;
00241 
00242     md5_process_bytes(de, sizeof(struct dirent), &guid_context);
00243     total += sizeof(struct dirent);
00244 
00245     result = snprintf(filename, sizeof(filename),
00246                       "%s/%s", dirname, de->d_name);
00247     if ((result < 0) || (result >= (int)sizeof(filename)))
00248       continue;
00249 
00250     memset(&stats, 0, sizeof(stats));
00251     if (stat(filename, &stats) != 0)
00252       continue;
00253     md5_process_bytes(&stats, sizeof(stats), &guid_context);
00254     total += sizeof(stats);
00255 
00256     max_files--;
00257   } while (max_files > 0);
00258 
00259   closedir(dir);
00260 
00261   return total;
00262 }
00263 
00264 static size_t
00265 init_from_time(void)
00266 {
00267   size_t total;
00268   time_t t_time;
00269   clock_t clocks;
00270   struct tms tms_buf;
00271 
00272   total = 0;
00273 
00274   t_time = time(NULL);
00275   md5_process_bytes(&t_time, sizeof(t_time), &guid_context);
00276   total += sizeof(t_time);
00277 
00278   clocks = times(&tms_buf);
00279   md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
00280   md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
00281   total += sizeof(clocks) + sizeof(tms_buf);
00282 
00283   return total;
00284 }
00285 
00286 static size_t
00287 init_from_int(int val)
00288 {
00289   md5_process_bytes(&val, sizeof(val), &guid_context);
00290   return sizeof(int);
00291 }
00292 
00293 static size_t
00294 init_from_buff(unsigned char * buf, size_t buflen)
00295 {
00296   md5_process_bytes(buf, buflen, &guid_context);
00297   return buflen;
00298 }
00299 
00300 void
00301 guid_init(void)
00302 {
00303   size_t bytes = 0;
00304 
00305   /* Not needed; taken care of on first malloc.
00306    * guid_memchunk_init(); */
00307 
00308   md5_init_ctx(&guid_context);
00309 
00310   /* entropy pool */
00311   bytes += init_from_file ("/dev/urandom", 512);
00312 
00313   /* files */
00314   {
00315     const char * files[] =
00316     { "/etc/passwd",
00317       "/proc/loadavg",
00318       "/proc/meminfo",
00319       "/proc/net/dev",
00320       "/proc/rtc",
00321       "/proc/self/environ",
00322       "/proc/self/stat",
00323       "/proc/stat",
00324       "/proc/uptime",
00325       NULL
00326     };
00327     int i;
00328 
00329     for (i = 0; files[i] != NULL; i++)
00330       bytes += init_from_file(files[i], BLOCKSIZE);
00331   }
00332 
00333   /* directories */
00334   {
00335     const char * dirname;
00336     const char * dirs[] =
00337     {
00338       "/proc",
00339       P_tmpdir,
00340       "/var/lock",
00341       "/var/log",
00342       "/var/mail",
00343       "/var/spool/mail",
00344       "/var/run",
00345       NULL
00346     };
00347     int i;
00348 
00349     for (i = 0; dirs[i] != NULL; i++)
00350       bytes += init_from_dir(dirs[i], 32);
00351 
00352     dirname = getenv("HOME");
00353     if (dirname != NULL)
00354       bytes += init_from_dir(dirname, 32);
00355   }
00356 
00357   /* process and parent ids */
00358   {
00359     pid_t pid;
00360 
00361     pid = getpid();
00362     md5_process_bytes(&pid, sizeof(pid), &guid_context);
00363     bytes += sizeof(pid);
00364 
00365     pid = getppid();
00366     md5_process_bytes(&pid, sizeof(pid), &guid_context);
00367     bytes += sizeof(pid);
00368   }
00369 
00370   /* user info */
00371   {
00372     uid_t uid;
00373     gid_t gid;
00374     char *s;
00375 
00376     s = getlogin();
00377     if (s != NULL)
00378     {
00379       md5_process_bytes(s, strlen(s), &guid_context);
00380       bytes += strlen(s);
00381     }
00382 
00383     uid = getuid();
00384     md5_process_bytes(&uid, sizeof(uid), &guid_context);
00385     bytes += sizeof(uid);
00386 
00387     gid = getgid();
00388     md5_process_bytes(&gid, sizeof(gid), &guid_context);
00389     bytes += sizeof(gid);
00390   }
00391 
00392   /* host info */
00393   {
00394     char string[1024];
00395 
00396     memset(string, 0, sizeof(string));
00397     gethostname(string, sizeof(string));
00398     md5_process_bytes(string, sizeof(string), &guid_context);
00399     bytes += sizeof(string);
00400   }
00401 
00402   /* plain old random */
00403   {
00404     int n, i;
00405 
00406     srand((unsigned int) time(NULL));
00407 
00408     for (i = 0; i < 32; i++)
00409     {
00410       n = rand();
00411 
00412       md5_process_bytes(&n, sizeof(n), &guid_context);
00413       bytes += sizeof(n);
00414     }
00415   }
00416 
00417   /* time in secs and clock ticks */
00418   bytes += init_from_time();
00419 
00420   PINFO ("got %llu bytes", (unsigned long long int) bytes);
00421 
00422   if (bytes < THRESHOLD)
00423     PWARN("only got %llu bytes.\n"
00424               "The identifiers might not be very random.\n",
00425           (unsigned long long int)bytes);
00426 
00427   guid_initialized = TRUE;
00428 }
00429 
00430 void
00431 guid_init_with_salt(const void *salt, size_t salt_len)
00432 {
00433   guid_init();
00434 
00435   md5_process_bytes(salt, salt_len, &guid_context);
00436 }
00437 
00438 void
00439 guid_init_only_salt(const void *salt, size_t salt_len)
00440 {
00441   md5_init_ctx(&guid_context);
00442 
00443   md5_process_bytes(salt, salt_len, &guid_context);
00444 
00445   guid_initialized = TRUE;
00446 }
00447 
00448 void 
00449 guid_shutdown (void)
00450 {
00451         guid_memchunk_shutdown();
00452 }
00453 
00454 #define GUID_PERIOD 5000
00455 
00456 void
00457 guid_new(GUID *guid)
00458 {
00459   static int counter = 0;
00460   struct md5_ctx ctx;
00461 
00462   if (guid == NULL)
00463     return;
00464 
00465   if (!guid_initialized)
00466     guid_init();
00467 
00468   /* make the id */
00469   ctx = guid_context;
00470   md5_finish_ctx(&ctx, guid->data);
00471 
00472   /* update the global context */
00473   init_from_time();
00474 
00475   /* Make it a little extra salty.  I think init_from_time was buggy,
00476         * or something, since duplicate id's actually happened. Or something
00477         * like that.  I think this is because init_from_time kept returning
00478         * the same values too many times in a row.  So we'll do some 'block
00479         * chaining', and feed in the old guid as new random data.
00480         *
00481         * Anyway, I think the whole fact that I saw a bunch of duplicate 
00482         * id's at one point, but can't reproduce the bug is rather alarming.
00483         * Something must be broken somewhere, and merely adding more salt
00484         * is just hiding the problem, not fixing it.
00485         */
00486   init_from_int (433781*counter);
00487   init_from_buff (guid->data, 16);
00488 
00489   if (counter == 0)
00490   {
00491     FILE *fp;
00492 
00493     fp = fopen ("/dev/urandom", "r");
00494     if (fp == NULL)
00495       return;
00496 
00497     init_from_stream(fp, 32);
00498 
00499     fclose(fp);
00500 
00501     counter = GUID_PERIOD;
00502   }
00503 
00504   counter--;
00505 }
00506 
00507 GUID
00508 guid_new_return(void)
00509 {
00510   GUID guid;
00511 
00512   guid_new (&guid);
00513 
00514   return guid;
00515 }
00516 
00517 /* needs 32 bytes exactly, doesn't print a null char */
00518 static void
00519 encode_md5_data(const unsigned char *data, char *buffer)
00520 {
00521   size_t count;
00522 
00523   for (count = 0; count < 16; count++, buffer += 2)
00524     sprintf(buffer, "%02x", data[count]);
00525 }
00526 
00527 /* returns true if the first 32 bytes of buffer encode
00528  * a hex number. returns false otherwise. Decoded number
00529  * is packed into data in little endian order. */
00530 static gboolean
00531 decode_md5_string(const char *string, unsigned char *data)
00532 {
00533   unsigned char n1, n2;
00534   size_t count = -1;
00535   char c1, c2;
00536 
00537   if (NULL == data) return FALSE;
00538   if (NULL == string) goto badstring;
00539 
00540   for (count = 0; count < 16; count++)
00541   {
00542     /* check for a short string e.g. null string ... */
00543     if ((0==string[2*count]) || (0==string[2*count+1])) goto badstring;
00544 
00545     c1 = tolower(string[2 * count]);
00546     if (!isxdigit(c1)) goto badstring;
00547 
00548     c2 = tolower(string[2 * count + 1]);
00549     if (!isxdigit(c2)) goto badstring;
00550 
00551     if (isdigit(c1))
00552       n1 = c1 - '0';
00553     else
00554       n1 = c1 - 'a' + 10;
00555 
00556     if (isdigit(c2))
00557       n2 = c2 - '0';
00558     else
00559       n2 = c2 - 'a' + 10;
00560 
00561     data[count] = (n1 << 4) | n2;
00562   }
00563   return TRUE;
00564 
00565 badstring:
00566   for (count = 0; count < 16; count++)
00567   {
00568     data[count] = 0;
00569   }
00570   return FALSE;
00571 }
00572 
00573 /* Allocate the key */
00574 
00575 const char *
00576 guid_to_string(const GUID * guid)
00577 {
00578 #ifdef G_THREADS_ENABLED
00579   static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
00580   gchar *string;
00581 
00582   string = g_static_private_get (&guid_buffer_key);
00583   if (string == NULL) {
00584     string = malloc(GUID_ENCODING_LENGTH+1);
00585     g_static_private_set (&guid_buffer_key, string, g_free);
00586   }
00587 #else
00588   static char string[64];
00589 #endif
00590 
00591   encode_md5_data(guid->data, string);
00592   string[GUID_ENCODING_LENGTH] = '\0';
00593 
00594   return string;
00595 }
00596 
00597 char *
00598 guid_to_string_buff(const GUID * guid, char *string)
00599 {
00600   if (!string || !guid) return NULL;
00601 
00602   encode_md5_data(guid->data, string);
00603 
00604   string[GUID_ENCODING_LENGTH] = '\0';
00605   return &string[GUID_ENCODING_LENGTH];
00606 }
00607 
00608 gboolean
00609 string_to_guid(const char * string, GUID * guid)
00610 {
00611   return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
00612 }
00613 
00614 gboolean
00615 guid_equal(const GUID *guid_1, const GUID *guid_2)
00616 {
00617   if (guid_1 && guid_2)
00618     return (memcmp(guid_1, guid_2, sizeof(GUID)) == 0);
00619   else
00620     return FALSE;
00621 }
00622 
00623 gint
00624 guid_compare(const GUID *guid_1, const GUID *guid_2)
00625 {
00626   if (guid_1 == guid_2)
00627     return 0;
00628 
00629   /* nothing is always less than something */
00630   if (!guid_1 && guid_2)
00631     return -1;
00632 
00633   if (guid_1 && !guid_2)
00634     return 1;
00635 
00636   return memcmp (guid_1, guid_2, sizeof (GUID));
00637 }
00638 
00639 guint
00640 guid_hash_to_guint (gconstpointer ptr)
00641 {
00642   const GUID *guid = ptr;
00643 
00644   if (!guid)
00645   {
00646     PERR ("received NULL guid pointer.");
00647     return 0;
00648   }
00649 
00650   if (sizeof(guint) <= sizeof(guid->data))
00651   {
00652     return (*((guint *) guid->data));
00653   }
00654   else
00655   {
00656     guint hash = 0;
00657     unsigned int i, j;
00658 
00659     for (i = 0, j = 0; i < sizeof(guint); i++, j++) {
00660       if (j == 16) j = 0;
00661 
00662       hash <<= 4;
00663       hash |= guid->data[j];
00664     }
00665 
00666     return hash;
00667   }
00668 }
00669 
00670 static gint
00671 guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
00672 {
00673   return guid_equal (guid_a, guid_b);
00674 }
00675 
00676 GHashTable *
00677 guid_hash_table_new (void)
00678 {
00679   return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
00680 }

Generated on Fri Oct 21 15:49:54 2005 for QOF by  doxygen 1.4.5