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 *
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
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
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 }