Merge branch 'master' of 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 it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
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,
93                                  NULL);
94   GNUNET_RESOLVER_connect (cc->cfg);
95   cc->task (cc->task_cls,
96             cc->args,
97             cc->cfgfile,
98             cc->cfg);
99 }
100
101
102 /**
103  * Compare function for 'qsort' to sort command-line arguments by the
104  * short option.
105  *
106  * @param a1 first command line option
107  * @param a2 second command line option
108  */
109 static int
110 cmd_sorter (const void *a1,
111             const void *a2)
112 {
113   const struct GNUNET_GETOPT_CommandLineOption *c1 = a1;
114   const struct GNUNET_GETOPT_CommandLineOption *c2 = a2;
115
116   if (toupper ((unsigned char) c1->shortName) >
117       toupper ((unsigned char) c2->shortName))
118     return 1;
119   if (toupper ((unsigned char) c1->shortName) <
120       toupper ((unsigned char) c2->shortName))
121     return -1;
122   if (c1->shortName > c2->shortName)
123     return 1;
124   if (c1->shortName < c2->shortName)
125     return -1;
126   return 0;
127 }
128
129
130 /**
131  * Run a standard GNUnet command startup sequence (initialize loggers
132  * and configuration, parse options).
133  *
134  * @param argc number of command line arguments in @a argv
135  * @param argv command line arguments
136  * @param binaryName our expected name
137  * @param binaryHelp help text for the program
138  * @param options command line options
139  * @param task main function to run
140  * @param task_cls closure for @a task
141  * @param run_without_scheduler #GNUNET_NO start the scheduler, #GNUNET_YES do not
142  *        start the scheduler just run the main task
143  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
144  */
145 int
146 GNUNET_PROGRAM_run2 (int argc,
147                      char *const *argv,
148                      const char *binaryName,
149                      const char *binaryHelp,
150                      const struct GNUNET_GETOPT_CommandLineOption *options,
151                      GNUNET_PROGRAM_Main task,
152                      void *task_cls,
153                      int run_without_scheduler)
154 {
155   struct CommandContext cc;
156 #if ENABLE_NLS
157   char *path;
158 #endif
159   char *loglev;
160   char *logfile;
161   char *cfg_fn;
162   const char *xdg;
163   int ret;
164   unsigned int cnt;
165   unsigned long long skew_offset;
166   unsigned long long skew_variance;
167   long long clock_offset;
168   struct GNUNET_CONFIGURATION_Handle *cfg;
169   struct GNUNET_GETOPT_CommandLineOption defoptions[] = {
170     GNUNET_GETOPT_option_cfgfile (&cc.cfgfile),
171     GNUNET_GETOPT_option_help (binaryHelp),
172     GNUNET_GETOPT_option_loglevel (&loglev),
173     GNUNET_GETOPT_option_logfile (&logfile),
174     GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION)
175   };
176   struct GNUNET_GETOPT_CommandLineOption *allopts;
177   const char *gargs;
178   char *lpfx;
179   char *spc;
180
181   logfile = NULL;
182   gargs = getenv ("GNUNET_ARGS");
183   if (NULL != gargs)
184   {
185     char **gargv;
186     unsigned int gargc;
187     char *cargs;
188
189     gargv = NULL;
190     gargc = 0;
191     for (int i = 0; i < argc; i++)
192       GNUNET_array_append (gargv,
193                            gargc,
194                            GNUNET_strdup (argv[i]));
195     cargs = GNUNET_strdup (gargs);
196     for (char *tok = strtok (cargs, " ");
197          NULL != tok;
198          tok = strtok (NULL, " "))
199       GNUNET_array_append (gargv,
200                            gargc,
201                            GNUNET_strdup (tok));
202     GNUNET_free (cargs);
203     GNUNET_array_append (gargv,
204                          gargc,
205                          NULL);
206     argv = (char *const *) gargv;
207     argc = gargc - 1;
208   }
209   memset (&cc,
210           0,
211           sizeof (cc));
212   loglev = NULL;
213   cc.task = task;
214   cc.task_cls = task_cls;
215   cc.cfg = cfg = GNUNET_CONFIGURATION_create ();
216   /* prepare */
217 #if ENABLE_NLS
218   setlocale (LC_ALL, "");
219   path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
220   if (NULL != path)
221   {
222     BINDTEXTDOMAIN ("GNUnet",
223                     path);
224     GNUNET_free (path);
225   }
226   textdomain ("GNUnet");
227 #endif
228   cnt = 0;
229   while (NULL != options[cnt].name)
230     cnt++;
231   allopts =
232       GNUNET_malloc ((cnt +
233                       1) * sizeof (struct GNUNET_GETOPT_CommandLineOption) +
234                      sizeof (defoptions));
235   GNUNET_memcpy (allopts,
236                  defoptions,
237                  sizeof (defoptions));
238   GNUNET_memcpy (&allopts
239                  [sizeof (defoptions) /
240                   sizeof (struct GNUNET_GETOPT_CommandLineOption)], options,
241                  (cnt + 1) * sizeof (struct GNUNET_GETOPT_CommandLineOption));
242   cnt += sizeof (defoptions) / sizeof (struct GNUNET_GETOPT_CommandLineOption);
243   qsort (allopts,
244          cnt,
245          sizeof (struct GNUNET_GETOPT_CommandLineOption),
246          &cmd_sorter);
247   loglev = NULL;
248   xdg = getenv ("XDG_CONFIG_HOME");
249   if (NULL != xdg)
250     GNUNET_asprintf (&cfg_fn,
251                      "%s%s%s",
252                      xdg,
253                      DIR_SEPARATOR_STR,
254                      GNUNET_OS_project_data_get ()->config_file);
255   else
256     cfg_fn = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
257   lpfx = GNUNET_strdup (binaryName);
258   if (NULL != (spc = strstr (lpfx, " ")))
259     *spc = '\0';
260   ret = GNUNET_GETOPT_run (binaryName,
261                            allopts,
262                            (unsigned int) argc,
263                            argv);
264   if ((GNUNET_OK > ret) ||
265       (GNUNET_OK !=
266        GNUNET_log_setup (lpfx,
267                          loglev,
268                          logfile)))
269   {
270     GNUNET_free (allopts);
271     GNUNET_free (lpfx);
272     goto cleanup;
273   }
274   if (NULL != cc.cfgfile)
275   {
276     if ( (GNUNET_YES !=
277           GNUNET_DISK_file_test (cc.cfgfile)) ||
278          (GNUNET_SYSERR ==
279           GNUNET_CONFIGURATION_load (cfg,
280                                      cc.cfgfile)) ) 
281     {
282       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
283                   _("Unreadable or malformed configuration file `%s', exit ...\n"),
284                   cc.cfgfile);
285       ret = GNUNET_SYSERR;
286       GNUNET_free (allopts);
287       GNUNET_free (lpfx);
288       goto cleanup;
289     }
290   }
291   else
292   {
293     if (GNUNET_YES ==
294         GNUNET_DISK_file_test (cfg_fn))
295     {
296       if (GNUNET_SYSERR ==
297           GNUNET_CONFIGURATION_load (cfg,
298                                      cfg_fn))
299       {
300         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
301                     _("Unreadable or malformed default configuration file `%s', exit ...\n"),
302                     cfg_fn);
303         ret = GNUNET_SYSERR;
304         GNUNET_free (allopts);
305         GNUNET_free (lpfx);
306         goto cleanup;
307       }
308     }
309     else
310     {
311       GNUNET_free (cfg_fn);
312       cfg_fn = NULL;
313       if (GNUNET_OK !=
314           GNUNET_CONFIGURATION_load (cfg,
315                                      NULL))
316       {
317         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
318                     _("Unreadable or malformed configuration, exit ...\n"));
319         ret = GNUNET_SYSERR;
320         GNUNET_free (allopts);
321         GNUNET_free (lpfx);
322         goto cleanup;
323       }
324     }
325   }
326   GNUNET_free (allopts);
327   GNUNET_free (lpfx);
328   if (GNUNET_OK ==
329       GNUNET_CONFIGURATION_get_value_number (cc.cfg,
330                                              "testing",
331                                              "skew_offset",
332                                              &skew_offset) &&
333       (GNUNET_OK ==
334        GNUNET_CONFIGURATION_get_value_number (cc.cfg,
335                                               "testing",
336                                               "skew_variance",
337                                               &skew_variance)))
338   {
339     clock_offset = skew_offset - skew_variance;
340     GNUNET_TIME_set_offset (clock_offset);
341   }
342   /* ARM needs to know which configuration file to use when starting
343      services.  If we got a command-line option *and* if nothing is
344      specified in the configuration, remember the command-line option
345      in "cfg".  This is typically really only having an effect if we
346      are running code in src/arm/, as obviously the rest of the code
347      has little business with ARM-specific options. */
348   if (GNUNET_YES !=
349       GNUNET_CONFIGURATION_have_value (cfg,
350                                        "arm",
351                                        "CONFIG"))
352   {
353     if (NULL != cc.cfgfile)
354       GNUNET_CONFIGURATION_set_value_string (cfg,
355                                              "arm",
356                                              "CONFIG",
357                                              cc.cfgfile);
358     else if (NULL != cfg_fn)
359       GNUNET_CONFIGURATION_set_value_string (cfg,
360                                              "arm",
361                                              "CONFIG",
362                                              cfg_fn);
363   }
364
365   /* run */
366   cc.args = &argv[ret];
367   if ( (NULL == cc.cfgfile) &&
368        (NULL != cfg_fn) )
369     cc.cfgfile = GNUNET_strdup (cfg_fn);
370   if (GNUNET_NO == run_without_scheduler)
371   {
372     GNUNET_SCHEDULER_run (&program_main,
373                           &cc);
374   }
375   else
376   {
377     GNUNET_RESOLVER_connect (cc.cfg);
378     cc.task (cc.task_cls,
379              cc.args,
380              cc.cfgfile,
381              cc.cfg);
382   }
383   ret = GNUNET_OK;
384  cleanup:
385   GNUNET_CONFIGURATION_destroy (cfg);
386   GNUNET_free_non_null (cc.cfgfile);
387   GNUNET_free_non_null (cfg_fn);
388   GNUNET_free_non_null (loglev);
389   GNUNET_free_non_null (logfile);
390   return ret;
391 }
392
393
394 /**
395  * Run a standard GNUnet command startup sequence (initialize loggers
396  * and configuration, parse options).
397  *
398  * @param argc number of command line arguments
399  * @param argv command line arguments
400  * @param binaryName our expected name
401  * @param binaryHelp help text for the program
402  * @param options command line options
403  * @param task main function to run
404  * @param task_cls closure for @a task
405  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
406  */
407 int
408 GNUNET_PROGRAM_run (int argc, char *const *argv,
409                     const char *binaryName,
410                     const char *binaryHelp,
411                     const struct GNUNET_GETOPT_CommandLineOption *options,
412                     GNUNET_PROGRAM_Main task,
413                     void *task_cls)
414 {
415   return GNUNET_PROGRAM_run2 (argc, argv,
416                               binaryName, binaryHelp,
417                               options,
418                               task, task_cls,
419                               GNUNET_NO);
420 }
421
422
423 /* end of program.c */