2007-07-24 Richard Hughes <richard@hughsie.com>
[platform/upstream/dbus.git] / bus / config-parser-trivial.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* config-parser-trivial.c  XML-library-agnostic configuration file parser
3  *                  Does not do includes or anything remotely complicated.
4  *
5  * Copyright (C) 2003, 2004, 2007 Red Hat, Inc.
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  
25 #include "config-parser-common.h"
26 #include "config-parser-trivial.h"
27 #include "utils.h"
28 #include <dbus/dbus-list.h>
29 #include <dbus/dbus-internals.h>
30 #include <string.h>
31
32 /**
33  * TRIVIAL parser for bus configuration file.
34  */
35 struct BusConfigParser
36 {
37   ElementType type;
38   DBusString user;                  /**< User the dbus-daemon runs as */
39   DBusString bus_type;              /**< Message bus type */
40   DBusString service_helper;        /**< Location of the setuid helper */
41   DBusList *service_dirs;           /**< Directories to look for services in */
42 };
43
44 static dbus_bool_t
45 service_dirs_find_dir (DBusList **service_dirs,
46                        const char *dir)
47 {
48   DBusList *link;
49
50   _dbus_assert (dir != NULL);
51
52   for (link = *service_dirs; link; link = _dbus_list_get_next_link(service_dirs, link))
53     {
54       const char *link_dir;
55
56       link_dir = (const char *)link->data;
57       if (strcmp (dir, link_dir) == 0)
58         return TRUE;
59     }
60
61   return FALSE;
62 }
63
64 static void
65 service_dirs_append_link_unique_or_free (DBusList **service_dirs,
66                                          DBusList *dir_link)
67 {
68   if (!service_dirs_find_dir (service_dirs, dir_link->data))
69     {
70       _dbus_list_append_link (service_dirs, dir_link);
71     }
72   else
73     {
74       dbus_free (dir_link->data);
75       _dbus_list_free_link (dir_link);
76     }
77 }
78
79 BusConfigParser*
80 bus_config_parser_new (const DBusString             *basedir,
81                        dbus_bool_t                   is_toplevel,
82                        const BusConfigParser        *parent)
83 {
84   BusConfigParser *parser;
85
86   parser = dbus_new0 (BusConfigParser, 1);
87   if (parser == NULL)
88     goto failed;
89
90   parser->type = ELEMENT_NONE;
91
92   /* init the lists */
93   parser->service_dirs = NULL;
94
95   /* init the strings */
96   if (!_dbus_string_init (&parser->user))
97     goto failed_parser;
98   if (!_dbus_string_init (&parser->bus_type))
99     goto failed_type;
100   if (!_dbus_string_init (&parser->service_helper))
101     goto failed_helper;
102
103   /* woot! */
104   return parser;
105
106 /* argh. we have do do this carefully because of OOM */
107 failed_helper:
108   _dbus_string_free (&parser->bus_type);
109 failed_type:
110   _dbus_string_free (&parser->user);
111 failed_parser:
112   dbus_free (parser);
113 failed:
114   return NULL;
115 }
116
117 void
118 bus_config_parser_unref (BusConfigParser *parser)
119 {
120   _dbus_string_free (&parser->user);
121   _dbus_string_free (&parser->service_helper);
122   _dbus_string_free (&parser->bus_type);
123
124   _dbus_list_foreach (&parser->service_dirs,
125                       (DBusForeachFunction) dbus_free,
126                       NULL);
127
128   _dbus_list_clear (&parser->service_dirs);
129
130   dbus_free (parser);
131 }
132
133 dbus_bool_t
134 bus_config_parser_start_element (BusConfigParser   *parser,
135                                  const char        *element_name,
136                                  const char       **attribute_names,
137                                  const char       **attribute_values,
138                                  DBusError         *error)
139 {
140   /* we don't do processing of attribute names, we don't need to */
141   parser->type = bus_config_parser_element_name_to_type (element_name);
142
143   switch (parser->type)
144     {
145     case ELEMENT_SERVICEHELPER:
146     case ELEMENT_USER:
147     case ELEMENT_TYPE:
148       /* content about to be handled */
149       break;
150
151     case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
152       {
153         DBusList *link;
154         DBusList *dirs;
155         dirs = NULL;
156
157         if (!_dbus_get_standard_system_servicedirs (&dirs))
158           {
159             BUS_SET_OOM (error);
160             return FALSE;
161           }
162
163           while ((link = _dbus_list_pop_first_link (&dirs)))
164             service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
165         break;
166       }
167
168     default:
169       {
170         /* we really don't care about the others... */
171         _dbus_verbose (" START We dont care about '%s' type '%i'\n", element_name, parser->type);
172         break;
173       }
174     }
175   return TRUE;
176 }
177
178 dbus_bool_t
179 bus_config_parser_end_element (BusConfigParser   *parser,
180                                const char               *element_name,
181                                DBusError                *error)
182 {
183   /* we don't care */
184   return TRUE;
185 }
186
187 dbus_bool_t
188 bus_config_parser_content (BusConfigParser   *parser,
189                            const DBusString  *content,
190                            DBusError         *error)
191 {
192   DBusString content_sane;
193   dbus_bool_t retval;
194
195   retval = FALSE;
196
197   if (!_dbus_string_init (&content_sane))
198     {
199       BUS_SET_OOM (error);
200       goto out;
201     }
202   if (!_dbus_string_copy (content, 0, &content_sane, 0))
203     {
204       BUS_SET_OOM (error);
205       goto out_content;
206     }
207
208   /* rip out white space */
209   _dbus_string_chop_white (&content_sane);
210   if (_dbus_string_get_length (&content_sane) == 0)
211     {
212       /* optimise, there is no content */
213       retval = TRUE;
214       goto out_content;
215     }
216
217   switch (parser->type)
218     {
219     case ELEMENT_SERVICEDIR:
220       {
221         char *cpath;
222
223         /* copy the sane data into a char array */
224         if (!_dbus_string_copy_data(&content_sane, &cpath))
225           {
226             BUS_SET_OOM (error);
227             goto out_content;
228           }
229
230         /* append the dynamic char string to service dirs */
231         if (!_dbus_list_append (&parser->service_dirs, cpath))
232           {
233             dbus_free (cpath);
234             BUS_SET_OOM (error);
235             goto out_content;
236           }
237       }
238       break;
239
240     case ELEMENT_SERVICEHELPER:
241       {
242         if (!_dbus_string_copy (&content_sane, 0, &parser->service_helper, 0))
243           {
244             BUS_SET_OOM (error);
245             goto out_content;
246           }
247       }
248       break;
249
250     case ELEMENT_USER:
251       {
252         if (!_dbus_string_copy (&content_sane, 0, &parser->user, 0))
253           {
254             BUS_SET_OOM (error);
255             goto out_content;
256           }
257       }
258       break;
259
260     case ELEMENT_TYPE:
261       {
262         if (!_dbus_string_copy (&content_sane, 0, &parser->bus_type, 0))
263           {
264             BUS_SET_OOM (error);
265             goto out_content;
266           }
267       }
268       break;
269     default:
270       {
271         /* we don't care about the others... really */
272         _dbus_verbose (" CONTENTS We dont care '%s' type '%i'\n", _dbus_string_get_const_data (&content_sane), parser->type);
273         break;
274       }
275     }
276
277   /* woot! */
278   retval = TRUE;
279
280 out_content:
281   _dbus_string_free (&content_sane);
282 out:
283   return retval;
284 }
285
286 dbus_bool_t
287 bus_config_parser_finished (BusConfigParser   *parser,
288                             DBusError         *error)
289 {
290   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
291   _dbus_verbose ("finished scanning!\n");
292   return TRUE;
293 }
294
295 const char*
296 bus_config_parser_get_user (BusConfigParser *parser)
297 {
298   return _dbus_string_get_const_data (&parser->user);
299 }
300
301 const char*
302 bus_config_parser_get_type (BusConfigParser *parser)
303 {
304   return _dbus_string_get_const_data (&parser->bus_type);
305 }
306
307 DBusList**
308 bus_config_parser_get_service_dirs (BusConfigParser *parser)
309 {
310   return &parser->service_dirs;
311 }
312
313 #ifdef DBUS_BUILD_TESTS
314 #include <stdio.h>
315 #include "test.h"
316
317 typedef enum
318 {
319   VALID,
320   INVALID,
321   UNKNOWN
322 } Validity;
323
324 static dbus_bool_t
325 check_return_values (const DBusString *full_path)
326 {
327   BusConfigParser *parser;
328   DBusError error;
329   dbus_bool_t retval;
330   const char *user;
331   const char *type;
332   DBusList **dirs;
333
334   dbus_error_init (&error);
335   retval = FALSE;
336
337   printf ("Testing values from: %s\n", _dbus_string_get_const_data (full_path));
338
339   parser = bus_config_load (full_path, TRUE, NULL, &error);
340   if (parser == NULL)
341     {
342       _DBUS_ASSERT_ERROR_IS_SET (&error);
343       if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
344         _dbus_verbose ("Failed to load valid file due to OOM\n");
345       goto finish;
346     }
347   _DBUS_ASSERT_ERROR_IS_CLEAR (&error);
348
349   /* check user return value is okay */
350   user = bus_config_parser_get_user (parser);
351   if (user == NULL)
352     {
353       _dbus_warn ("User was NULL!\n");
354       goto finish;
355     }
356   if (strcmp (user, "dbus") != 0)
357     {
358       _dbus_warn ("User was invalid; '%s'!\n", user);
359       goto finish;
360     }
361   printf ("    <user>dbus</user> OKAY!\n");
362
363   /* check type return value is okay */
364   type = bus_config_parser_get_type (parser);
365   if (type == NULL)
366     {
367       _dbus_warn ("Type was NULL!\n");
368       goto finish;
369     }
370   if (strcmp (type, "system") != 0)
371     {
372       _dbus_warn ("Type was invalid; '%s'!\n", user);
373       goto finish;
374     }
375   printf ("    <type>system</type> OKAY!\n");
376
377   /* check dirs return value is okay */
378   dirs = bus_config_parser_get_service_dirs (parser);
379   if (dirs == NULL)
380     {
381       _dbus_warn ("Service dirs are NULL!\n");
382       goto finish;
383     }
384   printf ("    <standard_system_service_dirs/> OKAY!\n");
385   /* NOTE: We tested the specific return values in the config-parser tests */
386
387   /* woohoo! */
388   retval = TRUE;
389 finish:
390   if (parser != NULL)
391     bus_config_parser_unref (parser);
392   dbus_error_free (&error);
393   return retval;
394 }
395
396 static dbus_bool_t
397 do_load (const DBusString *full_path,
398          Validity          validity,
399          dbus_bool_t       oom_possible)
400 {
401   BusConfigParser *parser;
402   DBusError error;
403
404   dbus_error_init (&error);
405
406   parser = bus_config_load (full_path, TRUE, NULL, &error);
407   if (parser == NULL)
408     {
409       _DBUS_ASSERT_ERROR_IS_SET (&error);
410
411       if (oom_possible &&
412           dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
413         {
414           _dbus_verbose ("Failed to load valid file due to OOM\n");
415           dbus_error_free (&error);
416           return TRUE;
417         }
418       else if (validity == VALID)
419         {
420           _dbus_warn ("Failed to load valid file but still had memory: %s\n",
421                       error.message);
422
423           dbus_error_free (&error);
424           return FALSE;
425         }
426       else
427         {
428           dbus_error_free (&error);
429           return TRUE;
430         }
431     }
432   else
433     {
434       _DBUS_ASSERT_ERROR_IS_CLEAR (&error);
435
436       bus_config_parser_unref (parser);
437
438       if (validity == INVALID)
439         {
440           _dbus_warn ("Accepted invalid file\n");
441           return FALSE;
442         }
443
444       return TRUE;
445     }
446 }
447
448 typedef struct
449 {
450   const DBusString *full_path;
451   Validity          validity;
452 } LoaderOomData;
453
454 static dbus_bool_t
455 check_loader_oom_func (void *data)
456 {
457   LoaderOomData *d = data;
458
459   return do_load (d->full_path, d->validity, TRUE);
460 }
461
462 static dbus_bool_t
463 process_test_valid_subdir (const DBusString *test_base_dir,
464                            const char       *subdir,
465                            Validity          validity)
466 {
467   DBusString test_directory;
468   DBusString filename;
469   DBusDirIter *dir;
470   dbus_bool_t retval;
471   DBusError error;
472
473   retval = FALSE;
474   dir = NULL;
475
476   if (!_dbus_string_init (&test_directory))
477     _dbus_assert_not_reached ("didn't allocate test_directory\n");
478
479   _dbus_string_init_const (&filename, subdir);
480
481   if (!_dbus_string_copy (test_base_dir, 0,
482                           &test_directory, 0))
483     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
484
485   if (!_dbus_concat_dir_and_file (&test_directory, &filename))
486     _dbus_assert_not_reached ("couldn't allocate full path");
487
488   _dbus_string_free (&filename);
489   if (!_dbus_string_init (&filename))
490     _dbus_assert_not_reached ("didn't allocate filename string\n");
491
492   dbus_error_init (&error);
493   dir = _dbus_directory_open (&test_directory, &error);
494   if (dir == NULL)
495     {
496       _dbus_warn ("Could not open %s: %s\n",
497                   _dbus_string_get_const_data (&test_directory),
498                   error.message);
499       dbus_error_free (&error);
500       goto failed;
501     }
502
503   if (validity == VALID)
504     printf ("Testing valid files:\n");
505   else if (validity == INVALID)
506     printf ("Testing invalid files:\n");
507   else
508     printf ("Testing unknown files:\n");
509
510  next:
511   while (_dbus_directory_get_next_file (dir, &filename, &error))
512     {
513       DBusString full_path;
514       LoaderOomData d;
515
516       if (!_dbus_string_init (&full_path))
517         _dbus_assert_not_reached ("couldn't init string");
518
519       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
520         _dbus_assert_not_reached ("couldn't copy dir to full_path");
521
522       if (!_dbus_concat_dir_and_file (&full_path, &filename))
523         _dbus_assert_not_reached ("couldn't concat file to dir");
524
525       if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
526         {
527           _dbus_verbose ("Skipping non-.conf file %s\n",
528                          _dbus_string_get_const_data (&filename));
529           _dbus_string_free (&full_path);
530           goto next;
531         }
532
533       printf ("    %s\n", _dbus_string_get_const_data (&filename));
534
535       _dbus_verbose (" expecting %s\n",
536                      validity == VALID ? "valid" :
537                      (validity == INVALID ? "invalid" :
538                       (validity == UNKNOWN ? "unknown" : "???")));
539
540       d.full_path = &full_path;
541       d.validity = validity;
542
543       /* FIXME hackaround for an expat problem, see
544        * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747
545        * http://freedesktop.org/pipermail/dbus/2004-May/001153.html
546        */
547       /* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */
548       if (!check_loader_oom_func (&d))
549         _dbus_assert_not_reached ("test failed");
550       
551       _dbus_string_free (&full_path);
552     }
553
554   if (dbus_error_is_set (&error))
555     {
556       _dbus_warn ("Could not get next file in %s: %s\n",
557                   _dbus_string_get_const_data (&test_directory),
558                   error.message);
559       dbus_error_free (&error);
560       goto failed;
561     }
562
563   retval = TRUE;
564
565  failed:
566
567   if (dir)
568     _dbus_directory_close (dir);
569   _dbus_string_free (&test_directory);
570   _dbus_string_free (&filename);
571
572   return retval;
573 }
574
575 /* convenience function, do not reuse outside of TEST */
576 static dbus_bool_t
577 make_full_path (const DBusString *test_data_dir,
578                                   const char       *subdir,
579                                   const char       *file,
580                                   DBusString       *full_path)
581 {
582   DBusString filename;
583   dbus_bool_t retval;
584
585   retval = FALSE;
586
587   if (!_dbus_string_init (full_path))
588     {
589       _dbus_warn ("couldn't allocate full path");
590       goto finish;
591     }
592
593   if (!_dbus_string_copy (test_data_dir, 0, full_path, 0))
594     {
595       _dbus_warn ("couldn't allocate full path");
596       goto finish;
597     }
598
599   _dbus_string_init_const (&filename, subdir);
600   if (!_dbus_concat_dir_and_file (full_path, &filename))
601     {
602       _dbus_warn ("couldn't allocate full path");
603       goto finish;
604     }
605   _dbus_string_free (&filename);
606
607   _dbus_string_init_const (&filename, file);
608   if (!_dbus_concat_dir_and_file (full_path, &filename))
609     {
610       _dbus_warn ("couldn't allocate full path");
611       goto finish;
612     }
613
614   /* woot! */
615   retval = TRUE;
616
617 finish:
618   _dbus_string_free (&filename);
619   return retval;
620 }
621
622 static dbus_bool_t
623 check_file_valid (DBusString *full_path,
624                                     Validity    validity)
625 {
626   dbus_bool_t retval;
627
628   if (validity == VALID)
629     printf ("Testing valid file:\n");
630   else if (validity == INVALID)
631     printf ("Testing invalid file:\n");
632   else
633     printf ("Testing unknown file:\n");
634
635   /* print the filename, just so we match the other output */
636   printf ("    %s\n", _dbus_string_get_const_data (full_path));
637
638   /* only test one file */
639   retval = do_load (full_path, validity, TRUE);
640
641   return retval;
642 }
643
644 dbus_bool_t
645 bus_config_parser_trivial_test (const DBusString *test_data_dir)
646 {
647   DBusString full_path;
648   dbus_bool_t retval;
649
650   retval = FALSE;
651
652   if (test_data_dir == NULL ||
653       _dbus_string_get_length (test_data_dir) == 0)
654     {
655       printf ("No test data\n");
656       return TRUE;
657     }
658   
659   /* We already test default_session_servicedirs and default_system_servicedirs
660    * in bus_config_parser_test() */
661   if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID))
662     goto finish;
663
664   /* we don't process all the invalid files, as the trivial parser can't hope
665    * to validate them all for all different syntaxes. We just check one broken
666    * file to see if junk is received */
667   if (!make_full_path (test_data_dir, "invalid-config-files", "not-well-formed.conf", &full_path))
668     goto finish;
669   if (!check_file_valid (&full_path, INVALID))
670     goto finish;
671   _dbus_string_free (&full_path);
672
673   /* just test if the check_file_valid works okay and we got sane values */
674   if (!make_full_path (test_data_dir, "valid-config-files", "system.conf", &full_path))
675     goto finish;
676   if (!check_file_valid (&full_path, VALID))
677     goto finish;
678   /* check to see if we got the correct values from the parser */
679   if (!check_return_values (&full_path))
680     goto finish;
681
682   /* woot! */
683   retval = TRUE;
684
685 finish:
686   _dbus_string_free (&full_path);
687
688   /* we don't process equiv-config-files as we don't handle <include> */
689   return retval;
690 }
691
692 #endif /* DBUS_BUILD_TESTS */
693