style fix
[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) \
37   GNUNET_log_from_strerror_file(kind, "util-program", syscall, filename)
38
39 /**
40  * Context for the command.
41  */
42 struct CommandContext {
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  * task run when the scheduler shuts down
72  */
73 static void
74 shutdown_task(void *cls)
75 {
76   (void)cls;
77   GNUNET_SPEEDUP_stop_();
78 }
79
80
81 /**
82  * Initial task called by the scheduler for each
83  * program.  Runs the program-specific main task.
84  */
85 static void
86 program_main(void *cls)
87 {
88   struct CommandContext *cc = cls;
89
90   GNUNET_SPEEDUP_start_(cc->cfg);
91   GNUNET_SCHEDULER_add_shutdown(&shutdown_task, NULL);
92   GNUNET_RESOLVER_connect(cc->cfg);
93   cc->task(cc->task_cls, cc->args, cc->cfgfile, cc->cfg);
94 }
95
96
97 /**
98  * Compare function for 'qsort' to sort command-line arguments by the
99  * short option.
100  *
101  * @param a1 first command line option
102  * @param a2 second command line option
103  */
104 static int
105 cmd_sorter(const void *a1, const void *a2)
106 {
107   const struct GNUNET_GETOPT_CommandLineOption *c1 = a1;
108   const struct GNUNET_GETOPT_CommandLineOption *c2 = a2;
109
110   if (toupper((unsigned char)c1->shortName) >
111       toupper((unsigned char)c2->shortName))
112     return 1;
113   if (toupper((unsigned char)c1->shortName) <
114       toupper((unsigned char)c2->shortName))
115     return -1;
116   if (c1->shortName > c2->shortName)
117     return 1;
118   if (c1->shortName < c2->shortName)
119     return -1;
120   return 0;
121 }
122
123
124 /**
125  * Run a standard GNUnet command startup sequence (initialize loggers
126  * and configuration, parse options).
127  *
128  * @param argc number of command line arguments in @a argv
129  * @param argv command line arguments
130  * @param binaryName our expected name
131  * @param binaryHelp help text for the program
132  * @param options command line options
133  * @param task main function to run
134  * @param task_cls closure for @a task
135  * @param run_without_scheduler #GNUNET_NO start the scheduler, #GNUNET_YES do not
136  *        start the scheduler just run the main task
137  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
138  */
139 int
140 GNUNET_PROGRAM_run2(int argc,
141                     char *const *argv,
142                     const char *binaryName,
143                     const char *binaryHelp,
144                     const struct GNUNET_GETOPT_CommandLineOption *options,
145                     GNUNET_PROGRAM_Main task,
146                     void *task_cls,
147                     int run_without_scheduler)
148 {
149   struct CommandContext cc;
150
151 #if ENABLE_NLS
152   char *path;
153 #endif
154   char *loglev;
155   char *logfile;
156   char *cfg_fn;
157   const char *xdg;
158   int ret;
159   unsigned int cnt;
160   unsigned long long skew_offset;
161   unsigned long long skew_variance;
162   long long clock_offset;
163   struct GNUNET_CONFIGURATION_Handle *cfg;
164   const struct GNUNET_OS_ProjectData *pd = GNUNET_OS_project_data_get();
165   struct GNUNET_GETOPT_CommandLineOption defoptions[] =
166   { GNUNET_GETOPT_option_cfgfile(&cc.cfgfile),
167     GNUNET_GETOPT_option_help(binaryHelp),
168     GNUNET_GETOPT_option_loglevel(&loglev),
169     GNUNET_GETOPT_option_logfile(&logfile),
170     GNUNET_GETOPT_option_version(pd->version) };
171   struct GNUNET_GETOPT_CommandLineOption *allopts;
172   const char *gargs;
173   char *lpfx;
174   char *spc;
175
176   logfile = NULL;
177   gargs = getenv("GNUNET_ARGS");
178   if (NULL != gargs)
179     {
180       char **gargv;
181       unsigned int gargc;
182       char *cargs;
183
184       gargv = NULL;
185       gargc = 0;
186       for (int i = 0; i < argc; i++)
187         GNUNET_array_append(gargv, gargc, GNUNET_strdup(argv[i]));
188       cargs = GNUNET_strdup(gargs);
189       for (char *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   if (NULL != pd->gettext_domain)
204     {
205       setlocale(LC_ALL, "");
206       path = (NULL == pd->gettext_path)
207              ? GNUNET_OS_installation_get_path(GNUNET_OS_IPK_LOCALEDIR)
208              : GNUNET_strdup(pd->gettext_path);
209       if (NULL != path)
210         {
211           bindtextdomain(pd->gettext_domain, path);
212           GNUNET_free(path);
213         }
214       textdomain(pd->gettext_domain);
215     }
216 #endif
217   cnt = 0;
218   while (NULL != options[cnt].name)
219     cnt++;
220   allopts =
221     GNUNET_malloc((cnt + 1) * sizeof(struct GNUNET_GETOPT_CommandLineOption) +
222                   sizeof(defoptions));
223   GNUNET_memcpy(allopts, defoptions, sizeof(defoptions));
224   GNUNET_memcpy(&allopts[sizeof(defoptions) /
225                          sizeof(struct GNUNET_GETOPT_CommandLineOption)],
226                 options,
227                 (cnt + 1) * sizeof(struct GNUNET_GETOPT_CommandLineOption));
228   cnt += sizeof(defoptions) / sizeof(struct GNUNET_GETOPT_CommandLineOption);
229   qsort(allopts,
230         cnt,
231         sizeof(struct GNUNET_GETOPT_CommandLineOption),
232         &cmd_sorter);
233   loglev = NULL;
234   xdg = getenv("XDG_CONFIG_HOME");
235   if (NULL != xdg)
236     GNUNET_asprintf(&cfg_fn,
237                     "%s%s%s",
238                     xdg,
239                     DIR_SEPARATOR_STR,
240                     pd->config_file);
241   else
242     cfg_fn = GNUNET_strdup(pd->user_config_file);
243   lpfx = GNUNET_strdup(binaryName);
244   if (NULL != (spc = strstr(lpfx, " ")))
245     *spc = '\0';
246   ret = GNUNET_GETOPT_run(binaryName, allopts, (unsigned int)argc, argv);
247   if ((GNUNET_OK > ret) ||
248       (GNUNET_OK != GNUNET_log_setup(lpfx, loglev, logfile)))
249     {
250       GNUNET_free(allopts);
251       GNUNET_free(lpfx);
252       goto cleanup;
253     }
254   if (NULL != cc.cfgfile)
255     {
256       if ((GNUNET_YES != GNUNET_DISK_file_test(cc.cfgfile)) ||
257           (GNUNET_SYSERR == GNUNET_CONFIGURATION_load(cfg, cc.cfgfile)))
258         {
259           GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
260                      _(
261                        "Unreadable or malformed configuration file `%s', exit ...\n"),
262                      cc.cfgfile);
263           ret = GNUNET_SYSERR;
264           GNUNET_free(allopts);
265           GNUNET_free(lpfx);
266           goto cleanup;
267         }
268     }
269   else
270     {
271       if (GNUNET_YES == GNUNET_DISK_file_test(cfg_fn))
272         {
273           if (GNUNET_SYSERR == GNUNET_CONFIGURATION_load(cfg, cfg_fn))
274             {
275               GNUNET_log(
276                 GNUNET_ERROR_TYPE_ERROR,
277                 _(
278                   "Unreadable or malformed default configuration file `%s', exit ...\n"),
279                 cfg_fn);
280               ret = GNUNET_SYSERR;
281               GNUNET_free(allopts);
282               GNUNET_free(lpfx);
283               goto cleanup;
284             }
285         }
286       else
287         {
288           GNUNET_free(cfg_fn);
289           cfg_fn = NULL;
290           if (GNUNET_OK != GNUNET_CONFIGURATION_load(cfg, NULL))
291             {
292               GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
293                          _("Unreadable or malformed configuration, exit ...\n"));
294               ret = GNUNET_SYSERR;
295               GNUNET_free(allopts);
296               GNUNET_free(lpfx);
297               goto cleanup;
298             }
299         }
300     }
301   GNUNET_free(allopts);
302   GNUNET_free(lpfx);
303   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number(cc.cfg,
304                                                          "testing",
305                                                          "skew_offset",
306                                                          &skew_offset) &&
307       (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number(cc.cfg,
308                                                           "testing",
309                                                           "skew_variance",
310                                                           &skew_variance)))
311     {
312       clock_offset = skew_offset - skew_variance;
313       GNUNET_TIME_set_offset(clock_offset);
314     }
315   /* ARM needs to know which configuration file to use when starting
316      services.  If we got a command-line option *and* if nothing is
317      specified in the configuration, remember the command-line option
318      in "cfg".  This is typically really only having an effect if we
319      are running code in src/arm/, as obviously the rest of the code
320      has little business with ARM-specific options. */
321   if (GNUNET_YES != GNUNET_CONFIGURATION_have_value(cfg, "arm", "CONFIG"))
322     {
323       if (NULL != cc.cfgfile)
324         GNUNET_CONFIGURATION_set_value_string(cfg, "arm", "CONFIG", cc.cfgfile);
325       else if (NULL != cfg_fn)
326         GNUNET_CONFIGURATION_set_value_string(cfg, "arm", "CONFIG", cfg_fn);
327     }
328
329   /* run */
330   cc.args = &argv[ret];
331   if ((NULL == cc.cfgfile) && (NULL != cfg_fn))
332     cc.cfgfile = GNUNET_strdup(cfg_fn);
333   if (GNUNET_NO == run_without_scheduler)
334     {
335       GNUNET_SCHEDULER_run(&program_main, &cc);
336     }
337   else
338     {
339       GNUNET_RESOLVER_connect(cc.cfg);
340       cc.task(cc.task_cls, cc.args, cc.cfgfile, cc.cfg);
341     }
342   ret = GNUNET_OK;
343 cleanup:
344   GNUNET_CONFIGURATION_destroy(cfg);
345   GNUNET_free_non_null(cc.cfgfile);
346   GNUNET_free_non_null(cfg_fn);
347   GNUNET_free_non_null(loglev);
348   GNUNET_free_non_null(logfile);
349   return ret;
350 }
351
352
353 /**
354  * Run a standard GNUnet command startup sequence (initialize loggers
355  * and configuration, parse options).
356  *
357  * @param argc number of command line arguments
358  * @param argv command line arguments
359  * @param binaryName our expected name
360  * @param binaryHelp help text for the program
361  * @param options command line options
362  * @param task main function to run
363  * @param task_cls closure for @a task
364  * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
365  */
366 int
367 GNUNET_PROGRAM_run(int argc,
368                    char *const *argv,
369                    const char *binaryName,
370                    const char *binaryHelp,
371                    const struct GNUNET_GETOPT_CommandLineOption *options,
372                    GNUNET_PROGRAM_Main task,
373                    void *task_cls)
374 {
375   return GNUNET_PROGRAM_run2(argc,
376                              argv,
377                              binaryName,
378                              binaryHelp,
379                              options,
380                              task,
381                              task_cls,
382                              GNUNET_NO);
383 }
384
385
386 /* end of program.c */