Merge branch 'master' of ssh://gnunet.org/gnunet
[oweals/gnunet.git] / src / util / program.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009-2013 GNUnet e.V.
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPROSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file util/program.c
23  * @brief standard code for GNUnet startup and shutdown
24  * @author Christian Grothoff
25  */
26
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_resolver_service.h"
30 #include "gnunet_constants.h"
31 #include "speedup.h"
32 #include <gcrypt.h>
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "util-program", __VA_ARGS__)
35
36 #define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util-program", syscall, filename)
37
38 /**
39  * Context for the command.
40  */
41 struct CommandContext
42 {
43   /**
44    * Argv argument.
45    */
46   char *const *args;
47
48   /**
49    * Name of the configuration file used, can be NULL!
50    */
51   char *cfgfile;
52
53   /**
54    * Main function to run.
55    */
56   GNUNET_PROGRAM_Main task;
57
58   /**
59    * Closure for @e task.
60    */
61   void *task_cls;
62
63   /**
64    * Configuration to use.
65    */
66   const struct GNUNET_CONFIGURATION_Handle *cfg;
67
68 };
69
70
71 /**
72  * task run when the scheduler shuts down
73  */
74 static void
75 shutdown_task (void *cls)
76 {
77   (void) cls;
78   GNUNET_SPEEDUP_stop_ ();
79 }
80
81
82 /**
83  * Initial task called by the scheduler for each
84  * program.  Runs the program-specific main task.
85  */
86 static void
87 program_main (void *cls)
88 {
89   struct CommandContext *cc = cls;
90
91   GNUNET_SPEEDUP_start_(cc->cfg);
92   GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
93   GNUNET_RESOLVER_connect (cc->cfg);
94   cc->task (cc->task_cls, cc->args, cc->cfgfile, cc->cfg);
95 }
96
97
98 /**
99  * Compare function for 'qsort' to sort command-line arguments by the
100  * short option.
101  *
102  * @param a1 first command line option
103  * @param a2 second command line option
104  */
105 static int
106 cmd_sorter (const void *a1, const void *a2)
107 {
108   const struct GNUNET_GETOPT_CommandLineOption *c1 = a1;
109   const struct GNUNET_GETOPT_CommandLineOption *c2 = a2;
110
111   if (toupper ((unsigned char) c1->shortName) >
112       toupper ((unsigned char) c2->shortName))
113     return 1;
114   if (toupper ((unsigned char) c1->shortName) <
115       toupper ((unsigned char) c2->shortName))
116     return -1;
117   if (c1->shortName > c2->shortName)
118     return 1;
119   if (c1->shortName < c2->shortName)
120     return -1;
121   return 0;
122 }
123
124
125 /**
126  * Run a standard GNUnet command startup sequence (initialize loggers
127  * and configuration, parse options).
128  *
129  * @param argc number of command line arguments in @a argv
130  * @param argv command line arguments
131  * @param binaryName our expected name
132  * @param binaryHelp help text for the program
133  * @param options command line options
134  * @param task main function to run
135  * @param task_cls closure for @a task
136  * @param run_without_scheduler #GNUNET_NO start the scheduler, #GNUNET_YES do not
137  *        start the scheduler just run the main task
138  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
139  */
140 int
141 GNUNET_PROGRAM_run2 (int argc, char *const *argv, const char *binaryName,
142                      const char *binaryHelp,
143                      const struct GNUNET_GETOPT_CommandLineOption *options,
144                      GNUNET_PROGRAM_Main task, void *task_cls,
145                      int run_without_scheduler)
146 {
147   struct CommandContext cc;
148 #if ENABLE_NLS
149   char *path;
150 #endif
151   char *loglev;
152   char *logfile;
153   char *cfg_fn;
154   const char *xdg;
155   int ret;
156   unsigned int cnt;
157   unsigned long long skew_offset;
158   unsigned long long skew_variance;
159   long long clock_offset;
160   struct GNUNET_CONFIGURATION_Handle *cfg;
161
162   struct GNUNET_GETOPT_CommandLineOption defoptions[] = {
163     GNUNET_GETOPT_option_cfgfile (&cc.cfgfile),
164     GNUNET_GETOPT_option_help (binaryHelp),
165     GNUNET_GETOPT_option_loglevel (&loglev),
166     GNUNET_GETOPT_option_logfile (&logfile),
167     GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION)
168   };
169   struct GNUNET_GETOPT_CommandLineOption *allopts;
170   const char *gargs;
171   char *lpfx;
172   char *spc;
173
174   logfile = NULL;
175   gargs = getenv ("GNUNET_ARGS");
176   if (NULL != gargs)
177   {
178     char **gargv;
179     unsigned int gargc;
180     int i;
181     char *tok;
182     char *cargs;
183
184     gargv = NULL;
185     gargc = 0;
186     for (i = 0; i < argc; i++)
187       GNUNET_array_append (gargv, gargc, GNUNET_strdup (argv[i]));
188     cargs = GNUNET_strdup (gargs);
189     for (tok = strtok (cargs, " "); NULL != tok; tok = strtok (NULL, " "))
190       GNUNET_array_append (gargv, gargc, GNUNET_strdup (tok));
191     GNUNET_free (cargs);
192     GNUNET_array_append (gargv, gargc, NULL);
193     argv = (char *const *) gargv;
194     argc = gargc - 1;
195   }
196   memset (&cc, 0, sizeof (cc));
197   loglev = NULL;
198   cc.task = task;
199   cc.task_cls = task_cls;
200   cc.cfg = cfg = GNUNET_CONFIGURATION_create ();
201   /* prepare */
202 #if ENABLE_NLS
203   setlocale (LC_ALL, "");
204   path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
205   if (NULL != path)
206   {
207     BINDTEXTDOMAIN ("GNUnet", path);
208     GNUNET_free (path);
209   }
210   textdomain ("GNUnet");
211 #endif
212   cnt = 0;
213   while (NULL != options[cnt].name)
214     cnt++;
215   allopts =
216       GNUNET_malloc ((cnt +
217                       1) * sizeof (struct GNUNET_GETOPT_CommandLineOption) +
218                      sizeof (defoptions));
219   GNUNET_memcpy (allopts, defoptions, sizeof (defoptions));
220   GNUNET_memcpy (&allopts
221           [sizeof (defoptions) /
222            sizeof (struct GNUNET_GETOPT_CommandLineOption)], options,
223           (cnt + 1) * sizeof (struct GNUNET_GETOPT_CommandLineOption));
224   cnt += sizeof (defoptions) / sizeof (struct GNUNET_GETOPT_CommandLineOption);
225   qsort (allopts, cnt, sizeof (struct GNUNET_GETOPT_CommandLineOption),
226          &cmd_sorter);
227   loglev = NULL;
228   xdg = getenv ("XDG_CONFIG_HOME");
229   if (NULL != xdg)
230     GNUNET_asprintf (&cfg_fn,
231                      "%s%s%s",
232                      xdg,
233                      DIR_SEPARATOR_STR,
234                      GNUNET_OS_project_data_get ()->config_file);
235   else
236     cfg_fn = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
237   lpfx = GNUNET_strdup (binaryName);
238   if (NULL != (spc = strstr (lpfx, " ")))
239     *spc = '\0';
240   ret = GNUNET_GETOPT_run (binaryName, allopts, (unsigned int) argc, argv);
241   if ((GNUNET_OK > ret) ||
242       (GNUNET_OK != GNUNET_log_setup (lpfx, loglev, logfile)))
243   {
244     GNUNET_free (allopts);
245     GNUNET_free (lpfx);
246     goto cleanup;
247   }
248   if (NULL == cc.cfgfile)
249     cc.cfgfile = GNUNET_strdup (cfg_fn);
250   if (GNUNET_YES ==
251       GNUNET_DISK_file_test (cc.cfgfile))
252   {
253     if (GNUNET_SYSERR == GNUNET_CONFIGURATION_load (cfg, cc.cfgfile))
254     {
255       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
256                   _("Malformed configuration file `%s', exit ...\n"),
257                   cc.cfgfile);
258       ret = GNUNET_SYSERR;
259       GNUNET_free (allopts);
260       GNUNET_free (lpfx);
261       goto cleanup;
262     }
263   }
264   else
265   {
266     if (0 != strcmp (cc.cfgfile, cfg_fn))
267       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
268                   _("Could not access configuration file `%s'\n"),
269                   cc.cfgfile);
270     if (GNUNET_SYSERR == GNUNET_CONFIGURATION_load (cfg, NULL))
271     {
272       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
273                   _("Malformed configuration, exit ...\n"));
274       ret = GNUNET_SYSERR;
275       GNUNET_free (allopts);
276       GNUNET_free (lpfx);
277       goto cleanup;
278     }
279   }
280   GNUNET_free (allopts);
281   GNUNET_free (lpfx);
282   if (GNUNET_OK ==
283       GNUNET_CONFIGURATION_get_value_number (cc.cfg, "testing", "skew_offset",
284                                              &skew_offset) &&
285       (GNUNET_OK ==
286        GNUNET_CONFIGURATION_get_value_number (cc.cfg, "testing",
287                                               "skew_variance", &skew_variance)))
288   {
289     clock_offset = skew_offset - skew_variance;
290     GNUNET_TIME_set_offset (clock_offset);
291   }
292   /* ARM needs to know which configuration file to use when starting
293      services.  If we got a command-line option *and* if nothing is
294      specified in the configuration, remember the command-line option
295      in "cfg".  This is typically really only having an effect if we
296      are running code in src/arm/, as obviously the rest of the code
297      has little business with ARM-specific options. */
298   if (GNUNET_YES !=
299       GNUNET_CONFIGURATION_have_value (cfg,
300                                        "arm",
301                                        "CONFIG"))
302   {
303     GNUNET_CONFIGURATION_set_value_string (cfg,
304                                            "arm", "CONFIG",
305                                            cc.cfgfile);
306   }
307
308   /* run */
309   cc.args = &argv[ret];
310   if (GNUNET_NO == run_without_scheduler)
311   {
312     GNUNET_SCHEDULER_run (&program_main, &cc);
313   }
314   else
315   {
316     GNUNET_RESOLVER_connect (cc.cfg);
317     cc.task (cc.task_cls, cc.args, cc.cfgfile, cc.cfg);
318   }
319   ret = GNUNET_OK;
320  cleanup:
321   GNUNET_CONFIGURATION_destroy (cfg);
322   GNUNET_free_non_null (cc.cfgfile);
323   GNUNET_free (cfg_fn);
324   GNUNET_free_non_null (loglev);
325   GNUNET_free_non_null (logfile);
326   return ret;
327 }
328
329 /**
330  * Run a standard GNUnet command startup sequence (initialize loggers
331  * and configuration, parse options).
332  *
333  * @param argc number of command line arguments
334  * @param argv command line arguments
335  * @param binaryName our expected name
336  * @param binaryHelp help text for the program
337  * @param options command line options
338  * @param task main function to run
339  * @param task_cls closure for @a task
340  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
341  */
342 int
343 GNUNET_PROGRAM_run (int argc, char *const *argv,
344                     const char *binaryName,
345                     const char *binaryHelp,
346                     const struct GNUNET_GETOPT_CommandLineOption *options,
347                     GNUNET_PROGRAM_Main task,
348                     void *task_cls)
349 {
350   return GNUNET_PROGRAM_run2 (argc, argv,
351                               binaryName, binaryHelp,
352                               options,
353                               task, task_cls,
354                               GNUNET_NO);
355 }
356
357
358 /* end of program.c */