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