QOF
0.7.5
|
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 }