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

Generated on Fri May 12 17:57:20 2006 for QOF by  doxygen 1.4.4