2005-01-26 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-sysdeps-util.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-sysdeps-util.c Would be in dbus-sysdeps.c, but not used in libdbus
3  * 
4  * Copyright (C) 2002, 2003, 2004, 2005  Red Hat, Inc.
5  * Copyright (C) 2003 CodeFactory AB
6  *
7  * Licensed under the Academic Free License version 2.1
8  * 
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include "dbus-sysdeps.h"
25 #include "dbus-internals.h"
26 #include "dbus-protocol.h"
27 #include "dbus-string.h"
28 #define DBUS_USERDB_INCLUDES_PRIVATE 1
29 #include "dbus-userdb.h"
30 #include "dbus-test.h"
31
32 #include <sys/types.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <signal.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <grp.h>
42 #include <sys/socket.h>
43 #include <dirent.h>
44 #include <sys/un.h>
45
46 #ifndef O_BINARY
47 #define O_BINARY 0
48 #endif
49
50 /**
51  * @addtogroup DBusInternalsUtils
52  * @{
53  */
54
55 /**
56  * Does the chdir, fork, setsid, etc. to become a daemon process.
57  *
58  * @param pidfile #NULL, or pidfile to create
59  * @param print_pid_fd file descriptor to print pid to, or -1 for none
60  * @param error return location for errors
61  * @returns #FALSE on failure
62  */
63 dbus_bool_t
64 _dbus_become_daemon (const DBusString *pidfile,
65                      int               print_pid_fd,
66                      DBusError        *error)
67 {
68   const char *s;
69   pid_t child_pid;
70   int dev_null_fd;
71
72   _dbus_verbose ("Becoming a daemon...\n");
73
74   _dbus_verbose ("chdir to /\n");
75   if (chdir ("/") < 0)
76     {
77       dbus_set_error (error, DBUS_ERROR_FAILED,
78                       "Could not chdir() to root directory");
79       return FALSE;
80     }
81
82   _dbus_verbose ("forking...\n");
83   switch ((child_pid = fork ()))
84     {
85     case -1:
86       _dbus_verbose ("fork failed\n");
87       dbus_set_error (error, _dbus_error_from_errno (errno),
88                       "Failed to fork daemon: %s", _dbus_strerror (errno));
89       return FALSE;
90       break;
91
92     case 0:
93       _dbus_verbose ("in child, closing std file descriptors\n");
94
95       /* silently ignore failures here, if someone
96        * doesn't have /dev/null we may as well try
97        * to continue anyhow
98        */
99       
100       dev_null_fd = open ("/dev/null", O_RDWR);
101       if (dev_null_fd >= 0)
102         {
103           dup2 (dev_null_fd, 0);
104           dup2 (dev_null_fd, 1);
105           
106           s = _dbus_getenv ("DBUS_DEBUG_OUTPUT");
107           if (s == NULL || *s == '\0')
108             dup2 (dev_null_fd, 2);
109           else
110             _dbus_verbose ("keeping stderr open due to DBUS_DEBUG_OUTPUT\n");
111         }
112
113       /* Get a predictable umask */
114       _dbus_verbose ("setting umask\n");
115       umask (022);
116       break;
117
118     default:
119       if (pidfile)
120         {
121           _dbus_verbose ("parent writing pid file\n");
122           if (!_dbus_write_pid_file (pidfile,
123                                      child_pid,
124                                      error))
125             {
126               _dbus_verbose ("pid file write failed, killing child\n");
127               kill (child_pid, SIGTERM);
128               return FALSE;
129             }
130         }
131
132       /* Write PID if requested */
133       if (print_pid_fd >= 0)
134         {
135           DBusString pid;
136           int bytes;
137           
138           if (!_dbus_string_init (&pid))
139             {
140               _DBUS_SET_OOM (error);
141               kill (child_pid, SIGTERM);
142               return FALSE;
143             }
144           
145           if (!_dbus_string_append_int (&pid, _dbus_getpid ()) ||
146               !_dbus_string_append (&pid, "\n"))
147             {
148               _dbus_string_free (&pid);
149               _DBUS_SET_OOM (error);
150               kill (child_pid, SIGTERM);
151               return FALSE;
152             }
153           
154           bytes = _dbus_string_get_length (&pid);
155           if (_dbus_write (print_pid_fd, &pid, 0, bytes) != bytes)
156             {
157               dbus_set_error (error, DBUS_ERROR_FAILED,
158                               "Printing message bus PID: %s\n",
159                               _dbus_strerror (errno));
160               _dbus_string_free (&pid);
161               kill (child_pid, SIGTERM);
162               return FALSE;
163             }
164           
165           _dbus_string_free (&pid);
166         }
167       _dbus_verbose ("parent exiting\n");
168       _exit (0);
169       break;
170     }
171
172   _dbus_verbose ("calling setsid()\n");
173   if (setsid () == -1)
174     _dbus_assert_not_reached ("setsid() failed");
175   
176   return TRUE;
177 }
178
179
180 /**
181  * Creates a file containing the process ID.
182  *
183  * @param filename the filename to write to
184  * @param pid our process ID
185  * @param error return location for errors
186  * @returns #FALSE on failure
187  */
188 dbus_bool_t
189 _dbus_write_pid_file (const DBusString *filename,
190                       unsigned long     pid,
191                       DBusError        *error)
192 {
193   const char *cfilename;
194   int fd;
195   FILE *f;
196
197   cfilename = _dbus_string_get_const_data (filename);
198   
199   fd = open (cfilename, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0644);
200   
201   if (fd < 0)
202     {
203       dbus_set_error (error, _dbus_error_from_errno (errno),
204                       "Failed to open \"%s\": %s", cfilename,
205                       _dbus_strerror (errno));
206       return FALSE;
207     }
208
209   if ((f = fdopen (fd, "w")) == NULL)
210     {
211       dbus_set_error (error, _dbus_error_from_errno (errno),
212                       "Failed to fdopen fd %d: %s", fd, _dbus_strerror (errno));
213       close (fd);
214       return FALSE;
215     }
216   
217   if (fprintf (f, "%lu\n", pid) < 0)
218     {
219       dbus_set_error (error, _dbus_error_from_errno (errno),
220                       "Failed to write to \"%s\": %s", cfilename,
221                       _dbus_strerror (errno));
222       return FALSE;
223     }
224
225   if (fclose (f) == EOF)
226     {
227       dbus_set_error (error, _dbus_error_from_errno (errno),
228                       "Failed to close \"%s\": %s", cfilename,
229                       _dbus_strerror (errno));
230       return FALSE;
231     }
232   
233   return TRUE;
234 }
235
236
237 /**
238  * Changes the user and group the bus is running as.
239  *
240  * @param uid the new user ID
241  * @param gid the new group ID
242  * @param error return location for errors
243  * @returns #FALSE on failure
244  */
245 dbus_bool_t
246 _dbus_change_identity  (dbus_uid_t     uid,
247                         dbus_gid_t     gid,
248                         DBusError     *error)
249 {
250   /* setgroups() only works if we are a privileged process,
251    * so we don't return error on failure; the only possible
252    * failure is that we don't have perms to do it.
253    * FIXME not sure this is right, maybe if setuid()
254    * is going to work then setgroups() should also work.
255    */
256   if (setgroups (0, NULL) < 0)
257     _dbus_warn ("Failed to drop supplementary groups: %s\n",
258                 _dbus_strerror (errno));
259   
260   /* Set GID first, or the setuid may remove our permission
261    * to change the GID
262    */
263   if (setgid (gid) < 0)
264     {
265       dbus_set_error (error, _dbus_error_from_errno (errno),
266                       "Failed to set GID to %lu: %s", gid,
267                       _dbus_strerror (errno));
268       return FALSE;
269     }
270   
271   if (setuid (uid) < 0)
272     {
273       dbus_set_error (error, _dbus_error_from_errno (errno),
274                       "Failed to set UID to %lu: %s", uid,
275                       _dbus_strerror (errno));
276       return FALSE;
277     }
278   
279   return TRUE;
280 }
281
282 /** Installs a UNIX signal handler
283  *
284  * @param sig the signal to handle
285  * @param handler the handler
286  */
287 void
288 _dbus_set_signal_handler (int               sig,
289                           DBusSignalHandler handler)
290 {
291   struct sigaction act;
292   sigset_t empty_mask;
293   
294   sigemptyset (&empty_mask);
295   act.sa_handler = handler;
296   act.sa_mask    = empty_mask;
297   act.sa_flags   = 0;
298   sigaction (sig,  &act, 0);
299 }
300
301
302 /**
303  * Removes a directory; Directory must be empty
304  * 
305  * @param filename directory filename
306  * @param error initialized error object
307  * @returns #TRUE on success
308  */
309 dbus_bool_t
310 _dbus_delete_directory (const DBusString *filename,
311                         DBusError        *error)
312 {
313   const char *filename_c;
314   
315   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
316
317   filename_c = _dbus_string_get_const_data (filename);
318
319   if (rmdir (filename_c) != 0)
320     {
321       dbus_set_error (error, DBUS_ERROR_FAILED,
322                       "Failed to remove directory %s: %s\n",
323                       filename_c, _dbus_strerror (errno));
324       return FALSE;
325     }
326   
327   return TRUE;
328 }
329
330 /** Checks if a file exists
331 *
332 * @param file full path to the file
333 * @returns #TRUE if file exists
334 */
335 dbus_bool_t 
336 _dbus_file_exists (const char *file)
337 {
338   return (access (file, F_OK) == 0);
339 }
340
341 /** Checks if user is at the console
342 *
343 * @param username user to check
344 * @param error return location for errors
345 * @returns #TRUE is the user is at the consolei and there are no errors
346 */
347 dbus_bool_t 
348 _dbus_user_at_console (const char *username,
349                        DBusError  *error)
350 {
351
352   DBusString f;
353   dbus_bool_t result;
354
355   result = FALSE;
356   if (!_dbus_string_init (&f))
357     {
358       _DBUS_SET_OOM (error);
359       return FALSE;
360     }
361
362   if (!_dbus_string_append (&f, DBUS_CONSOLE_DIR))
363     {
364       _DBUS_SET_OOM (error);
365       goto out;
366     }
367
368
369   if (!_dbus_string_append (&f, username))
370     {
371       _DBUS_SET_OOM (error);
372       goto out;
373     }
374
375   result = _dbus_file_exists (_dbus_string_get_const_data (&f));
376
377  out:
378   _dbus_string_free (&f);
379
380   return result;
381 }
382
383
384 /**
385  * Checks whether the filename is an absolute path
386  *
387  * @param filename the filename
388  * @returns #TRUE if an absolute path
389  */
390 dbus_bool_t
391 _dbus_path_is_absolute (const DBusString *filename)
392 {
393   if (_dbus_string_get_length (filename) > 0)
394     return _dbus_string_get_byte (filename, 0) == '/';
395   else
396     return FALSE;
397 }
398
399 /**
400  * stat() wrapper.
401  *
402  * @param filename the filename to stat
403  * @param statbuf the stat info to fill in
404  * @param error return location for error
405  * @returns #FALSE if error was set
406  */
407 dbus_bool_t
408 _dbus_stat (const DBusString *filename,
409             DBusStat         *statbuf,
410             DBusError        *error)
411 {
412   const char *filename_c;
413   struct stat sb;
414
415   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
416   
417   filename_c = _dbus_string_get_const_data (filename);
418
419   if (stat (filename_c, &sb) < 0)
420     {
421       dbus_set_error (error, _dbus_error_from_errno (errno),
422                       "%s", _dbus_strerror (errno));
423       return FALSE;
424     }
425
426   statbuf->mode = sb.st_mode;
427   statbuf->nlink = sb.st_nlink;
428   statbuf->uid = sb.st_uid;
429   statbuf->gid = sb.st_gid;
430   statbuf->size = sb.st_size;
431   statbuf->atime = sb.st_atime;
432   statbuf->mtime = sb.st_mtime;
433   statbuf->ctime = sb.st_ctime;
434
435   return TRUE;
436 }
437
438
439 /**
440  * Internals of directory iterator
441  */
442 struct DBusDirIter
443 {
444   DIR *d; /**< The DIR* from opendir() */
445   
446 };
447
448 /**
449  * Open a directory to iterate over.
450  *
451  * @param filename the directory name
452  * @param error exception return object or #NULL
453  * @returns new iterator, or #NULL on error
454  */
455 DBusDirIter*
456 _dbus_directory_open (const DBusString *filename,
457                       DBusError        *error)
458 {
459   DIR *d;
460   DBusDirIter *iter;
461   const char *filename_c;
462
463   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
464   
465   filename_c = _dbus_string_get_const_data (filename);
466
467   d = opendir (filename_c);
468   if (d == NULL)
469     {
470       dbus_set_error (error, _dbus_error_from_errno (errno),
471                       "Failed to read directory \"%s\": %s",
472                       filename_c,
473                       _dbus_strerror (errno));
474       return NULL;
475     }
476   iter = dbus_new0 (DBusDirIter, 1);
477   if (iter == NULL)
478     {
479       closedir (d);
480       dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
481                       "Could not allocate memory for directory iterator");
482       return NULL;
483     }
484
485   iter->d = d;
486
487   return iter;
488 }
489
490 /**
491  * Get next file in the directory. Will not return "." or ".."  on
492  * UNIX. If an error occurs, the contents of "filename" are
493  * undefined. The error is never set if the function succeeds.
494  *
495  * @todo for thread safety, I think we have to use
496  * readdir_r(). (GLib has the same issue, should file a bug.)
497  *
498  * @param iter the iterator
499  * @param filename string to be set to the next file in the dir
500  * @param error return location for error
501  * @returns #TRUE if filename was filled in with a new filename
502  */
503 dbus_bool_t
504 _dbus_directory_get_next_file (DBusDirIter      *iter,
505                                DBusString       *filename,
506                                DBusError        *error)
507 {
508   struct dirent *ent;
509
510   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
511   
512  again:
513   errno = 0;
514   ent = readdir (iter->d);
515   if (ent == NULL)
516     {
517       if (errno != 0)
518         dbus_set_error (error,
519                         _dbus_error_from_errno (errno),
520                         "%s", _dbus_strerror (errno));
521       return FALSE;
522     }
523   else if (ent->d_name[0] == '.' &&
524            (ent->d_name[1] == '\0' ||
525             (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
526     goto again;
527   else
528     {
529       _dbus_string_set_length (filename, 0);
530       if (!_dbus_string_append (filename, ent->d_name))
531         {
532           dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
533                           "No memory to read directory entry");
534           return FALSE;
535         }
536       else
537         return TRUE;
538     }
539 }
540
541 /**
542  * Closes a directory iteration.
543  */
544 void
545 _dbus_directory_close (DBusDirIter *iter)
546 {
547   closedir (iter->d);
548   dbus_free (iter);
549 }
550
551 static dbus_bool_t
552 fill_user_info_from_group (struct group  *g,
553                            DBusGroupInfo *info,
554                            DBusError     *error)
555 {
556   _dbus_assert (g->gr_name != NULL);
557   
558   info->gid = g->gr_gid;
559   info->groupname = _dbus_strdup (g->gr_name);
560
561   /* info->members = dbus_strdupv (g->gr_mem) */
562   
563   if (info->groupname == NULL)
564     {
565       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
566       return FALSE;
567     }
568
569   return TRUE;
570 }
571
572 static dbus_bool_t
573 fill_group_info (DBusGroupInfo    *info,
574                  dbus_gid_t        gid,
575                  const DBusString *groupname,
576                  DBusError        *error)
577 {
578   const char *group_c_str;
579
580   _dbus_assert (groupname != NULL || gid != DBUS_GID_UNSET);
581   _dbus_assert (groupname == NULL || gid == DBUS_GID_UNSET);
582
583   if (groupname)
584     group_c_str = _dbus_string_get_const_data (groupname);
585   else
586     group_c_str = NULL;
587   
588   /* For now assuming that the getgrnam() and getgrgid() flavors
589    * always correspond to the pwnam flavors, if not we have
590    * to add more configure checks.
591    */
592   
593 #if defined (HAVE_POSIX_GETPWNAME_R) || defined (HAVE_NONPOSIX_GETPWNAME_R)
594   {
595     struct group *g;
596     int result;
597     char buf[1024];
598     struct group g_str;
599
600     g = NULL;
601 #ifdef HAVE_POSIX_GETPWNAME_R
602
603     if (group_c_str)
604       result = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf),
605                            &g);
606     else
607       result = getgrgid_r (gid, &g_str, buf, sizeof (buf),
608                            &g);
609 #else
610     p = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf));
611     result = 0;
612 #endif /* !HAVE_POSIX_GETPWNAME_R */
613     if (result == 0 && g == &g_str)
614       {
615         return fill_user_info_from_group (g, info, error);
616       }
617     else
618       {
619         dbus_set_error (error, _dbus_error_from_errno (errno),
620                         "Group %s unknown or failed to look it up\n",
621                         group_c_str ? group_c_str : "???");
622         return FALSE;
623       }
624   }
625 #else /* ! HAVE_GETPWNAM_R */
626   {
627     /* I guess we're screwed on thread safety here */
628     struct group *g;
629
630     g = getgrnam (group_c_str);
631
632     if (g != NULL)
633       {
634         return fill_user_info_from_group (g, info, error);
635       }
636     else
637       {
638         dbus_set_error (error, _dbus_error_from_errno (errno),
639                         "Group %s unknown or failed to look it up\n",
640                         group_c_str ? group_c_str : "???");
641         return FALSE;
642       }
643   }
644 #endif  /* ! HAVE_GETPWNAM_R */
645 }
646
647 /**
648  * Initializes the given DBusGroupInfo struct
649  * with information about the given group name.
650  *
651  * @param info the group info struct
652  * @param groupname name of group
653  * @param error the error return
654  * @returns #FALSE if error is set
655  */
656 dbus_bool_t
657 _dbus_group_info_fill (DBusGroupInfo    *info,
658                        const DBusString *groupname,
659                        DBusError        *error)
660 {
661   return fill_group_info (info, DBUS_GID_UNSET,
662                           groupname, error);
663
664 }
665
666 /**
667  * Initializes the given DBusGroupInfo struct
668  * with information about the given group ID.
669  *
670  * @param info the group info struct
671  * @param gid group ID
672  * @param error the error return
673  * @returns #FALSE if error is set
674  */
675 dbus_bool_t
676 _dbus_group_info_fill_gid (DBusGroupInfo *info,
677                            dbus_gid_t     gid,
678                            DBusError     *error)
679 {
680   return fill_group_info (info, gid, NULL, error);
681 }
682
683 /**
684  * Frees the members of info (but not info itself).
685  *
686  * @param info the group info
687  */
688 void
689 _dbus_group_info_free (DBusGroupInfo    *info)
690 {
691   dbus_free (info->groupname);
692 }
693
694 /** @} */ /* End of DBusInternalsUtils functions */
695
696 /**
697  * @addtogroup DBusString
698  *
699  * @{
700  */
701 /**
702  * Get the directory name from a complete filename
703  * @param filename the filename
704  * @param dirname string to append directory name to
705  * @returns #FALSE if no memory
706  */
707 dbus_bool_t
708 _dbus_string_get_dirname  (const DBusString *filename,
709                            DBusString       *dirname)
710 {
711   int sep;
712   
713   _dbus_assert (filename != dirname);
714   _dbus_assert (filename != NULL);
715   _dbus_assert (dirname != NULL);
716
717   /* Ignore any separators on the end */
718   sep = _dbus_string_get_length (filename);
719   if (sep == 0)
720     return _dbus_string_append (dirname, "."); /* empty string passed in */
721     
722   while (sep > 0 && _dbus_string_get_byte (filename, sep - 1) == '/')
723     --sep;
724
725   _dbus_assert (sep >= 0);
726   
727   if (sep == 0)
728     return _dbus_string_append (dirname, "/");
729   
730   /* Now find the previous separator */
731   _dbus_string_find_byte_backward (filename, sep, '/', &sep);
732   if (sep < 0)
733     return _dbus_string_append (dirname, ".");
734   
735   /* skip multiple separators */
736   while (sep > 0 && _dbus_string_get_byte (filename, sep - 1) == '/')
737     --sep;
738
739   _dbus_assert (sep >= 0);
740   
741   if (sep == 0 &&
742       _dbus_string_get_byte (filename, 0) == '/')
743     return _dbus_string_append (dirname, "/");
744   else
745     return _dbus_string_copy_len (filename, 0, sep - 0,
746                                   dirname, _dbus_string_get_length (dirname));
747 }
748 /** @} */ /* DBusString stuff */
749
750
751 #ifdef DBUS_BUILD_TESTS
752 #include <stdlib.h>
753 static void
754 check_dirname (const char *filename,
755                const char *dirname)
756 {
757   DBusString f, d;
758   
759   _dbus_string_init_const (&f, filename);
760
761   if (!_dbus_string_init (&d))
762     _dbus_assert_not_reached ("no memory");
763
764   if (!_dbus_string_get_dirname (&f, &d))
765     _dbus_assert_not_reached ("no memory");
766
767   if (!_dbus_string_equal_c_str (&d, dirname))
768     {
769       _dbus_warn ("For filename \"%s\" got dirname \"%s\" and expected \"%s\"\n",
770                   filename,
771                   _dbus_string_get_const_data (&d),
772                   dirname);
773       exit (1);
774     }
775
776   _dbus_string_free (&d);
777 }
778
779 static void
780 check_path_absolute (const char *path,
781                      dbus_bool_t expected)
782 {
783   DBusString p;
784
785   _dbus_string_init_const (&p, path);
786
787   if (_dbus_path_is_absolute (&p) != expected)
788     {
789       _dbus_warn ("For path \"%s\" expected absolute = %d got %d\n",
790                   path, expected, _dbus_path_is_absolute (&p));
791       exit (1);
792     }
793 }
794
795 /**
796  * Unit test for dbus-sysdeps.c.
797  * 
798  * @returns #TRUE on success.
799  */
800 dbus_bool_t
801 _dbus_sysdeps_test (void)
802 {
803   DBusString str;
804   double val;
805   int pos;
806   
807   check_dirname ("foo", ".");
808   check_dirname ("foo/bar", "foo");
809   check_dirname ("foo//bar", "foo");
810   check_dirname ("foo///bar", "foo");
811   check_dirname ("foo/bar/", "foo");
812   check_dirname ("foo//bar/", "foo");
813   check_dirname ("foo///bar/", "foo");
814   check_dirname ("foo/bar//", "foo");
815   check_dirname ("foo//bar////", "foo");
816   check_dirname ("foo///bar///////", "foo");
817   check_dirname ("/foo", "/");
818   check_dirname ("////foo", "/");
819   check_dirname ("/foo/bar", "/foo");
820   check_dirname ("/foo//bar", "/foo");
821   check_dirname ("/foo///bar", "/foo");
822   check_dirname ("/", "/");
823   check_dirname ("///", "/");
824   check_dirname ("", ".");  
825
826
827   _dbus_string_init_const (&str, "3.5");
828   if (!_dbus_string_parse_double (&str,
829                                   0, &val, &pos))
830     {
831       _dbus_warn ("Failed to parse double");
832       exit (1);
833     }
834   if (ABS(3.5 - val) > 1e-6)
835     {
836       _dbus_warn ("Failed to parse 3.5 correctly, got: %f", val);
837       exit (1);
838     }
839   if (pos != 3)
840     {
841       _dbus_warn ("_dbus_string_parse_double of \"3.5\" returned wrong position %d", pos);
842       exit (1);
843     }
844
845   _dbus_string_init_const (&str, "0xff");
846   if (!_dbus_string_parse_double (&str,
847                                   0, &val, &pos))
848     {
849       _dbus_warn ("Failed to parse double");
850       exit (1);
851     }
852   if (ABS (0xff - val) > 1e-6)
853     {
854       _dbus_warn ("Failed to parse 0xff correctly, got: %f\n", val);
855       exit (1);
856     }
857   if (pos != 4)
858     {
859       _dbus_warn ("_dbus_string_parse_double of \"0xff\" returned wrong position %d", pos);
860       exit (1);
861     }
862   
863   check_path_absolute ("/", TRUE);
864   check_path_absolute ("/foo", TRUE);
865   check_path_absolute ("", FALSE);
866   check_path_absolute ("foo", FALSE);
867   check_path_absolute ("foo/bar", FALSE);
868   
869   return TRUE;
870 }
871 #endif /* DBUS_BUILD_TESTS */