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

Generated on Thu Jan 31 22:50:25 2008 for QOF by  doxygen 1.5.4