QOF: guid.c Source File

QOF 0.7.5

guid.c
1 /********************************************************************\
2 * guid.c -- globally unique ID implementation *
3 * Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
4 * *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License as *
7 * published by the Free Software Foundation; either version 2 of *
8 * the License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License*
16 * along with this program; if not, contact: *
17 * *
18 * Free Software Foundation Voice: +1-617-542-5942 *
19 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 * Boston, MA 02110-1301, USA gnu@gnu.org *
21 * *
22 \********************************************************************/
23
24 # include <config.h>
25
26 #ifdef HAVE_SYS_TYPES_H
27 # include <sys/types.h>
28 #endif
29 #include <ctype.h>
30 #include <dirent.h>
31 #include <glib.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #ifdef HAVE_SYS_TIMES_H
37 # include <sys/times.h>
38 #endif
39 #include <time.h>
40 #include <unistd.h>
41 #include "qof.h"
42 #include "md5.h"
43
44 # ifndef P_tmpdir
45 # define P_tmpdir "/tmp"
46 # endif
47
48 /* Constants *******************************************************/
49 #define DEBUG_GUID 0
50 #define BLOCKSIZE 4096
51 #define THRESHOLD (2 * BLOCKSIZE)
52
53
54 /* Static global variables *****************************************/
55 static gboolean guid_initialized = FALSE;
56 static struct md5_ctx guid_context;
57 #ifndef HAVE_GLIB29
58 static GMemChunk *guid_memchunk = NULL;
59 #endif
60
61 /* This static indicates the debugging module that this .o belongs to. */
62 static QofLogModule log_module = QOF_MOD_ENGINE;
63
64 /* Memory management routines ***************************************/
65 #ifdef HAVE_GLIB29
66 GUID *
67 guid_malloc (void)
68 {
69 return g_slice_new (GUID);
70 }
71
72 void
73 guid_free (GUID * guid)
74 {
75 if (!guid)
76 return;
77
78 g_slice_free (GUID, guid);
79 }
80 #else /* !HAVE_GLIB29 */
81
82 static void
83 guid_memchunk_init (void)
84 {
85 if (!guid_memchunk)
86 guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
87 }
88
89 static void
90 guid_memchunk_shutdown (void)
91 {
92 if (guid_memchunk)
93 {
94 g_mem_chunk_destroy (guid_memchunk);
95 guid_memchunk = NULL;
96 }
97 }
98
99 GUID *
100 guid_malloc (void)
101 {
102 if (!guid_memchunk)
103 guid_memchunk_init ();
104 return g_chunk_new (GUID, guid_memchunk);
105 }
106
107 void
108 guid_free (GUID * guid)
109 {
110 if (!guid)
111 return;
112
113 g_chunk_free (guid, guid_memchunk);
114 }
115 #endif
116
117
118 const GUID *
119 guid_null (void)
120 {
121 static int null_inited = 0;
122 static GUID null_guid;
123
124 if (!null_inited)
125 {
126 int i;
127 char *tmp = "NULLGUID.EMPTY.";
128
129 /* 16th space for '\O' */
130 for (i = 0; i < GUID_DATA_SIZE; i++)
131 null_guid.data[i] = tmp[i];
132
133 null_inited = 1;
134 }
135
136 return &null_guid;
137 }
138
139 /* Function implementations ****************************************/
140
141 /* This code is based on code in md5.c in GNU textutils. */
142 static size_t
143 init_from_stream (FILE * stream, size_t max_size)
144 {
145 char buffer[BLOCKSIZE + 72];
146 size_t sum, block_size, total;
147
148 if (max_size <= 0)
149 return 0;
150
151 total = 0;
152
153 /* Iterate over file contents. */
154 while (1)
155 {
156 /* We read the file in blocks of BLOCKSIZE bytes. One call of the
157 * computation function processes the whole buffer so that with the
158 * next round of the loop another block can be read. */
159 size_t n;
160 sum = 0;
161
162 if (max_size < BLOCKSIZE)
163 block_size = max_size;
164 else
165 block_size = BLOCKSIZE;
166
167 /* Read block. Take care for partial reads. */
168 do
169 {
170 n = fread (buffer + sum, 1, block_size - sum, stream);
171
172 sum += n;
173 }
174 while (sum < block_size && n != 0);
175
176 max_size -= sum;
177
178 if (n == 0 && ferror (stream))
179 return total;
180
181 /* If end of file or max_size is reached, end the loop. */
182 if ((n == 0) || (max_size == 0))
183 break;
184
185 /* Process buffer with BLOCKSIZE bytes. Note that
186 * BLOCKSIZE % 64 == 0 */
187 md5_process_block (buffer, BLOCKSIZE, &guid_context);
188
189 total += sum;
190 }
191
192 /* Add the last bytes if necessary. */
193 if (sum > 0)
194 {
195 md5_process_bytes (buffer, sum, &guid_context);
196 total += sum;
197 }
198
199 return total;
200 }
201
202 static size_t
203 init_from_file (const char *filename, size_t max_size)
204 {
205 struct stat stats;
206 size_t total = 0;
207 size_t file_bytes;
208 FILE *fp;
209
210 memset (&stats, 0, sizeof (stats));
211 if (stat (filename, &stats) != 0)
212 return 0;
213
214 md5_process_bytes (&stats, sizeof (stats), &guid_context);
215 total += sizeof (stats);
216
217 if (max_size <= 0)
218 return total;
219
220 fp = fopen (filename, "r");
221 if (fp == NULL)
222 return total;
223
224 file_bytes = init_from_stream (fp, max_size);
225
226 PINFO ("guid_init got %llu bytes from %s",
227 (unsigned long long int) file_bytes, filename);
228
229 total += file_bytes;
230
231 fclose (fp);
232
233 return total;
234 }
235
236 static size_t
237 init_from_dir (const char *dirname, unsigned int max_files)
238 {
239 char filename[1024];
240 struct dirent *de;
241 struct stat stats;
242 size_t total;
243 int result;
244 DIR *dir;
245
246 if (max_files <= 0)
247 return 0;
248
249 dir = opendir (dirname);
250 if (dir == NULL)
251 return 0;
252
253 total = 0;
254
255 do
256 {
257 de = readdir (dir);
258 if (de == NULL)
259 break;
260
261 md5_process_bytes (de->d_name, strlen (de->d_name), &guid_context);
262 total += strlen (de->d_name);
263
264 result = snprintf (filename, sizeof (filename),
265 "%s/%s", dirname, de->d_name);
266 if ((result < 0) || (result >= (int) sizeof (filename)))
267 continue;
268
269 memset (&stats, 0, sizeof (stats));
270 if (stat (filename, &stats) != 0)
271 continue;
272 md5_process_bytes (&stats, sizeof (stats), &guid_context);
273 total += sizeof (stats);
274
275 max_files--;
276 }
277 while (max_files > 0);
278
279 closedir (dir);
280
281 return total;
282 }
283
284 static size_t
285 init_from_time (void)
286 {
287 size_t total;
288 time_t t_time;
289 #ifdef HAVE_SYS_TIMES_H
290 clock_t clocks;
291 struct tms tms_buf;
292 #endif
293
294 total = 0;
295
296 t_time = time (NULL);
297 md5_process_bytes (&t_time, sizeof (t_time), &guid_context);
298 total += sizeof (t_time);
299
300 #ifdef HAVE_SYS_TIMES_H
301 clocks = times (&tms_buf);
302 md5_process_bytes (&clocks, sizeof (clocks), &guid_context);
303 md5_process_bytes (&tms_buf, sizeof (tms_buf), &guid_context);
304 total += sizeof (clocks) + sizeof (tms_buf);
305 #endif
306
307 return total;
308 }
309
310 static size_t
311 init_from_int (int val)
312 {
313 md5_process_bytes (&val, sizeof (val), &guid_context);
314 return sizeof (int);
315 }
316
317 static size_t
318 init_from_buff (unsigned char *buf, size_t buflen)
319 {
320 md5_process_bytes (buf, buflen, &guid_context);
321 return buflen;
322 }
323
324 void
325 guid_init (void)
326 {
327 size_t bytes = 0;
328
329 /* Not needed; taken care of on first malloc.
330 * guid_memchunk_init(); */
331
332 md5_init_ctx (&guid_context);
333
334 /* entropy pool */
335 bytes += init_from_file ("/dev/urandom", 512);
336
337 /* files */
338 {
339 const char *files[] = { "/etc/passwd",
340 "/proc/loadavg",
341 "/proc/meminfo",
342 "/proc/net/dev",
343 "/proc/rtc",
344 "/proc/self/environ",
345 "/proc/self/stat",
346 "/proc/stat",
347 "/proc/uptime",
348 NULL
349 };
350 int i;
351
352 for (i = 0; files[i] != NULL; i++)
353 bytes += init_from_file (files[i], BLOCKSIZE);
354 }
355
356 /* directories */
357 {
358 const char *dirname;
359 const char *dirs[] = {
360 "/proc",
361 P_tmpdir,
362 "/var/lock",
363 "/var/log",
364 "/var/mail",
365 "/var/spool/mail",
366 "/var/run",
367 NULL
368 };
369 int i;
370
371 for (i = 0; dirs[i] != NULL; i++)
372 bytes += init_from_dir (dirs[i], 32);
373
374 dirname = g_get_home_dir ();
375 if (dirname != NULL)
376 bytes += init_from_dir (dirname, 32);
377 }
378
379 /* process and parent ids */
380 {
381 pid_t pid;
382
383 pid = getpid ();
384 md5_process_bytes (&pid, sizeof (pid), &guid_context);
385 bytes += sizeof (pid);
386
387 #ifdef HAVE_GETPPID
388 pid = getppid ();
389 md5_process_bytes (&pid, sizeof (pid), &guid_context);
390 bytes += sizeof (pid);
391 #endif
392 }
393
394 /* user info */
395 {
396 #ifdef HAVE_GETUID
397 uid_t uid;
398 gid_t gid;
399 char *s;
400
401 s = getlogin ();
402 if (s != NULL)
403 {
404 md5_process_bytes (s, strlen (s), &guid_context);
405 bytes += strlen (s);
406 }
407
408 uid = getuid ();
409 md5_process_bytes (&uid, sizeof (uid), &guid_context);
410 bytes += sizeof (uid);
411
412 gid = getgid ();
413 md5_process_bytes (&gid, sizeof (gid), &guid_context);
414 bytes += sizeof (gid);
415 #endif
416 }
417
418 /* host info */
419 {
420 #ifdef HAVE_GETHOSTNAME
421 char string[1024];
422
423 memset (string, 0, sizeof (string));
424 gethostname (string, sizeof (string));
425 md5_process_bytes (string, sizeof (string), &guid_context);
426 bytes += sizeof (string);
427 #endif
428 }
429
430 /* plain old random */
431 {
432 int n, i;
433
434 srand ((unsigned int) time (NULL));
435
436 for (i = 0; i < 32; i++)
437 {
438 n = rand ();
439
440 md5_process_bytes (&n, sizeof (n), &guid_context);
441 bytes += sizeof (n);
442 }
443 }
444
445 /* time in secs and clock ticks */
446 bytes += init_from_time ();
447
448 PINFO ("got %llu bytes", (unsigned long long int) bytes);
449
450 if (bytes < THRESHOLD)
451 PWARN ("only got %llu bytes.\n"
452 "The identifiers might not be very random.\n",
453 (unsigned long long int) bytes);
454
455 guid_initialized = TRUE;
456 }
457
458 void
459 guid_init_with_salt (const void *salt, size_t salt_len)
460 {
461 guid_init ();
462
463 md5_process_bytes (salt, salt_len, &guid_context);
464 }
465
466 void
467 guid_init_only_salt (const void *salt, size_t salt_len)
468 {
469 md5_init_ctx (&guid_context);
470
471 md5_process_bytes (salt, salt_len, &guid_context);
472
473 guid_initialized = TRUE;
474 }
475
476 void
477 guid_shutdown (void)
478 {
479 #ifndef HAVE_GLIB29
480 guid_memchunk_shutdown ();
481 #endif
482 }
483
484 #define GUID_PERIOD 5000
485
486 void
487 guid_new (GUID * guid)
488 {
489 static int counter = 0;
490 struct md5_ctx ctx;
491
492 if (guid == NULL)
493 return;
494
495 if (!guid_initialized)
496 guid_init ();
497
498 /* make the id */
499 ctx = guid_context;
500 md5_finish_ctx (&ctx, guid->data);
501
502 /* update the global context */
503 init_from_time ();
504
505 /* Make it a little extra salty. I think init_from_time was buggy,
506 * or something, since duplicate id's actually happened. Or something
507 * like that. I think this is because init_from_time kept returning
508 * the same values too many times in a row. So we'll do some 'block
509 * chaining', and feed in the old guid as new random data.
510 *
511 * Anyway, I think the whole fact that I saw a bunch of duplicate
512 * id's at one point, but can't reproduce the bug is rather alarming.
513 * Something must be broken somewhere, and merely adding more salt
514 * is just hiding the problem, not fixing it.
515 */
516 init_from_int (433781 * counter);
517 init_from_buff (guid->data, GUID_DATA_SIZE);
518
519 if (counter == 0)
520 {
521 FILE *fp;
522
523 fp = fopen ("/dev/urandom", "r");
524 if (fp == NULL)
525 return;
526
527 init_from_stream (fp, 32);
528
529 fclose (fp);
530
531 counter = GUID_PERIOD;
532 }
533
534 counter--;
535 }
536
537 GUID
538 guid_new_return (void)
539 {
540 GUID guid;
541
542 guid_new (&guid);
543
544 return guid;
545 }
546
547 /* needs 32 bytes exactly, doesn't print a null char */
548 static void
549 encode_md5_data (const unsigned char *data, char *buffer)
550 {
551 size_t count;
552
553 for (count = 0; count < GUID_DATA_SIZE; count++, buffer += 2)
554 sprintf (buffer, "%02x", data[count]);
555 }
556
557 /* returns true if the first 32 bytes of buffer encode
558 * a hex number. returns false otherwise. Decoded number
559 * is packed into data in little endian order. */
560 static gboolean
561 decode_md5_string (const unsigned char *string, unsigned char *data)
562 {
563 unsigned char n1, n2;
564 size_t count = -1;
565 unsigned char c1, c2;
566
567 if (NULL == data)
568 return FALSE;
569 if (NULL == string)
570 goto badstring;
571
572 for (count = 0; count < GUID_DATA_SIZE; count++)
573 {
574 /* check for a short string e.g. null string ... */
575 if ((0 == string[2 * count]) || (0 == string[2 * count + 1]))
576 goto badstring;
577
578 c1 = tolower (string[2 * count]);
579 if (!isxdigit (c1))
580 goto badstring;
581
582 c2 = tolower (string[2 * count + 1]);
583 if (!isxdigit (c2))
584 goto badstring;
585
586 if (isdigit (c1))
587 n1 = c1 - '0';
588 else
589 n1 = c1 - 'a' + 10;
590
591 if (isdigit (c2))
592 n2 = c2 - '0';
593 else
594 n2 = c2 - 'a' + 10;
595
596 data[count] = (n1 << 4) | n2;
597 }
598 return TRUE;
599
600 badstring:
601 for (count = 0; count < GUID_DATA_SIZE; count++)
602 {
603 data[count] = 0;
604 }
605 return FALSE;
606 }
607
608 /* Allocate the key */
609
610 const char *
611 guid_to_string (const GUID * guid)
612 {
613 #ifdef G_THREADS_ENABLED
614 static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
615 gchar *string;
616
617 string = g_static_private_get (&guid_buffer_key);
618 if (string == NULL)
619 {
620 string = malloc (GUID_ENCODING_LENGTH + 1);
621 g_static_private_set (&guid_buffer_key, string, g_free);
622 }
623 #else
624 static char string[64];
625 #endif
626
627 encode_md5_data (guid->data, string);
628 string[GUID_ENCODING_LENGTH] = '\0';
629
630 return string;
631 }
632
633 char *
634 guid_to_string_buff (const GUID * guid, char *string)
635 {
636 if (!string || !guid)
637 return NULL;
638
639 encode_md5_data (guid->data, string);
640
641 string[GUID_ENCODING_LENGTH] = '\0';
642 return &string[GUID_ENCODING_LENGTH];
643 }
644
645 gboolean
646 string_to_guid (const char *string, GUID * guid)
647 {
648 return decode_md5_string (string, (guid != NULL) ? guid->data : NULL);
649 }
650
651 gboolean
652 guid_equal (const GUID * guid_1, const GUID * guid_2)
653 {
654 if (guid_1 && guid_2)
655 return (memcmp (guid_1, guid_2, GUID_DATA_SIZE) == 0);
656 else
657 return FALSE;
658 }
659
660 gint
661 guid_compare (const GUID * guid_1, const GUID * guid_2)
662 {
663 if (guid_1 == guid_2)
664 return 0;
665
666 /* nothing is always less than something */
667 if (!guid_1 && guid_2)
668 return -1;
669
670 if (guid_1 && !guid_2)
671 return 1;
672
673 return memcmp (guid_1, guid_2, GUID_DATA_SIZE);
674 }
675
676 guint
677 guid_hash_to_guint (gconstpointer ptr)
678 {
679 const GUID *guid = ptr;
680
681 if (!guid)
682 {
683 PERR ("received NULL guid pointer.");
684 return 0;
685 }
686
687 if (sizeof (guint) <= sizeof (guid->data))
688 {
689 return (*((guint *) guid->data));
690 }
691 else
692 {
693 guint hash = 0;
694 unsigned int i, j;
695
696 for (i = 0, j = 0; i < sizeof (guint); i++, j++)
697 {
698 if (j == GUID_DATA_SIZE)
699 j = 0;
700
701 hash <<= 4;
702 hash |= guid->data[j];
703 }
704
705 return hash;
706 }
707 }
708
709 static gint
710 guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
711 {
712 return guid_equal (guid_a, guid_b);
713 }
714
715 GHashTable *
716 guid_hash_table_new (void)
717 {
718 return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
719 }
guid_equal
gboolean guid_equal(const GUID *guid_1, const GUID *guid_2)
Definition: guid.c:652
PERR
#define PERR(format, args...)
Definition: qoflog.h:183
guid_new_return
GUID guid_new_return(void)
Definition: guid.c:538
guid_null
const GUID * guid_null(void)
Definition: guid.c:119
PINFO
#define PINFO(format, args...)
Definition: qoflog.h:199
guid_malloc
GUID * guid_malloc(void)
Definition: guid.c:100
guid_init_with_salt
void guid_init_with_salt(const void *salt, size_t salt_len)
Definition: guid.c:459
string_to_guid
gboolean string_to_guid(const gchar *string, GUID *guid)
GUID_ENCODING_LENGTH
#define GUID_ENCODING_LENGTH
Definition: guid.h:64
guid_shutdown
void guid_shutdown(void)
Definition: guid.c:477
GUID_DATA_SIZE
#define GUID_DATA_SIZE
Definition: guid.h:52
guid_to_string
const char * guid_to_string(const GUID *guid)
Definition: guid.c:611
guid_to_string_buff
gchar * guid_to_string_buff(const GUID *guid, gchar *buff)
guid_hash_to_guint
guint guid_hash_to_guint(gconstpointer ptr)
Definition: guid.c:677
_GUID
Definition: guid.h:53
guid_new
void guid_new(GUID *guid)
Definition: guid.c:487
guid_init_only_salt
void guid_init_only_salt(const void *salt, size_t salt_len)
Definition: guid.c:467
PWARN
#define PWARN(format, args...)
Definition: qoflog.h:191
guid_init
void guid_init(void)
Definition: guid.c:325
QofLogModule
const gchar * QofLogModule
Definition: qofid.h:85
md5_ctx
Definition: md5.h:80