Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-auditor-sync.c
18 : * @brief Tool used by the auditor to make a 'safe' copy of the exchanges' database.
19 : * @author Christian Grothoff
20 : */
21 : #include <platform.h>
22 : #include "taler_exchangedb_lib.h"
23 :
24 :
25 : /**
26 : * Handle to access the exchange's source database.
27 : */
28 : static struct TALER_EXCHANGEDB_Plugin *src;
29 :
30 : /**
31 : * Handle to access the exchange's destination database.
32 : */
33 : static struct TALER_EXCHANGEDB_Plugin *dst;
34 :
35 : /**
36 : * Return value from #main().
37 : */
38 : static int global_ret;
39 :
40 : /**
41 : * Main task to do synchronization.
42 : */
43 : static struct GNUNET_SCHEDULER_Task *sync_task;
44 :
45 : /**
46 : * What is our target transaction size (number of records)?
47 : */
48 : static unsigned int transaction_size = 512;
49 :
50 : /**
51 : * Number of records copied in this transaction.
52 : */
53 : static unsigned long long actual_size;
54 :
55 : /**
56 : * Terminate once synchronization is achieved.
57 : */
58 : static int exit_if_synced;
59 :
60 :
61 : /**
62 : * Information we track per replicated table.
63 : */
64 : struct Table
65 : {
66 : /**
67 : * Which table is this record about?
68 : */
69 : enum TALER_EXCHANGEDB_ReplicatedTable rt;
70 :
71 : /**
72 : * Up to which record is the destination table synchronized.
73 : */
74 : uint64_t start_serial;
75 :
76 : /**
77 : * Highest serial in the source table.
78 : */
79 : uint64_t end_serial;
80 :
81 : /**
82 : * Marker for the end of the list of #tables.
83 : */
84 : bool end;
85 : };
86 :
87 :
88 : /**
89 : * Information about replicated tables.
90 : */
91 : static struct Table tables[] = {
92 : { .rt = TALER_EXCHANGEDB_RT_DENOMINATIONS},
93 : { .rt = TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS},
94 : { .rt = TALER_EXCHANGEDB_RT_KYC_TARGETS},
95 : { .rt = TALER_EXCHANGEDB_RT_WIRE_TARGETS},
96 : { .rt = TALER_EXCHANGEDB_RT_LEGITIMIZATION_MEASURES},
97 : { .rt = TALER_EXCHANGEDB_RT_LEGITIMIZATION_OUTCOMES},
98 : { .rt = TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES},
99 : { .rt = TALER_EXCHANGEDB_RT_RESERVES},
100 : { .rt = TALER_EXCHANGEDB_RT_RESERVES_IN},
101 : { .rt = TALER_EXCHANGEDB_RT_RESERVES_CLOSE},
102 : { .rt = TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS},
103 : { .rt = TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS},
104 : { .rt = TALER_EXCHANGEDB_RT_WITHDRAW},
105 : { .rt = TALER_EXCHANGEDB_RT_AUDITORS},
106 : { .rt = TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS},
107 : { .rt = TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS},
108 : { .rt = TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS},
109 : { .rt = TALER_EXCHANGEDB_RT_KNOWN_COINS},
110 : { .rt = TALER_EXCHANGEDB_RT_REFRESH},
111 : { .rt = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS},
112 : { .rt = TALER_EXCHANGEDB_RT_COIN_DEPOSITS},
113 : { .rt = TALER_EXCHANGEDB_RT_REFUNDS},
114 : { .rt = TALER_EXCHANGEDB_RT_WIRE_OUT},
115 : { .rt = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING},
116 : { .rt = TALER_EXCHANGEDB_RT_WIRE_FEE},
117 : { .rt = TALER_EXCHANGEDB_RT_GLOBAL_FEE},
118 : { .rt = TALER_EXCHANGEDB_RT_RECOUP},
119 : { .rt = TALER_EXCHANGEDB_RT_RECOUP_REFRESH },
120 : { .rt = TALER_EXCHANGEDB_RT_EXTENSIONS},
121 : { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS },
122 : { .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS },
123 : { .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS},
124 : { .rt = TALER_EXCHANGEDB_RT_PURSE_DECISION},
125 : { .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES},
126 : { .rt = TALER_EXCHANGEDB_RT_PURSE_DEPOSITS},
127 : { .rt = TALER_EXCHANGEDB_RT_ACCOUNT_MERGES},
128 : { .rt = TALER_EXCHANGEDB_RT_HISTORY_REQUESTS},
129 : { .rt = TALER_EXCHANGEDB_RT_CLOSE_REQUESTS},
130 : { .rt = TALER_EXCHANGEDB_RT_WADS_OUT},
131 : { .rt = TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES},
132 : { .rt = TALER_EXCHANGEDB_RT_WADS_IN},
133 : { .rt = TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES},
134 : { .rt = TALER_EXCHANGEDB_RT_PROFIT_DRAINS},
135 : { .end = true }
136 : };
137 :
138 :
139 : /**
140 : * Closure for #do_insert.
141 : */
142 : struct InsertContext
143 : {
144 : /**
145 : * Table we are replicating.
146 : */
147 : struct Table *table;
148 :
149 : /**
150 : * Set to error if insertion created an error.
151 : */
152 : enum GNUNET_DB_QueryStatus qs;
153 : };
154 :
155 :
156 : /**
157 : * Function called on data to replicate in the auditor's database.
158 : *
159 : * @param cls closure, a `struct InsertContext`
160 : * @param td record from an exchange table
161 : * @return #GNUNET_OK to continue to iterate,
162 : * #GNUNET_SYSERR to fail with an error
163 : */
164 : static enum GNUNET_GenericReturnValue
165 882 : do_insert (void *cls,
166 : const struct TALER_EXCHANGEDB_TableData *td)
167 : {
168 882 : struct InsertContext *ctx = cls;
169 : enum GNUNET_DB_QueryStatus qs;
170 :
171 882 : if (0 >= ctx->qs)
172 0 : return GNUNET_SYSERR;
173 882 : qs = dst->insert_records_by_table (dst->cls,
174 : td);
175 882 : if (0 >= qs)
176 : {
177 0 : switch (qs)
178 : {
179 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
180 0 : GNUNET_assert (0);
181 : break;
182 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
183 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
184 : "Failed to insert record into table %d: no change\n",
185 : td->table);
186 0 : break;
187 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
188 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
189 : "Serialization error inserting record into table %d (will retry)\n",
190 : td->table);
191 0 : break;
192 0 : case GNUNET_DB_STATUS_HARD_ERROR:
193 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
194 : "Failed to insert record into table %d: hard error\n",
195 : td->table);
196 0 : break;
197 : }
198 0 : ctx->qs = qs;
199 0 : return GNUNET_SYSERR;
200 : }
201 882 : actual_size++;
202 882 : ctx->table->start_serial = td->serial;
203 882 : return GNUNET_OK;
204 : }
205 :
206 :
207 : /**
208 : * Run one replication transaction.
209 : *
210 : * @return #GNUNET_OK on success, #GNUNET_SYSERR to rollback
211 : */
212 : static enum GNUNET_GenericReturnValue
213 2 : transact (void)
214 : {
215 2 : struct InsertContext ctx = {
216 : .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
217 : };
218 :
219 2 : if (0 >
220 2 : src->start (src->cls,
221 : "lookup src serials"))
222 0 : return GNUNET_SYSERR;
223 88 : for (unsigned int i = 0; ! tables[i].end; i++)
224 86 : src->lookup_serial_by_table (src->cls,
225 : tables[i].rt,
226 : &tables[i].end_serial);
227 2 : src->rollback (src->cls);
228 2 : if (GNUNET_OK !=
229 2 : dst->start (dst->cls,
230 : "lookup dst serials"))
231 0 : return GNUNET_SYSERR;
232 88 : for (unsigned int i = 0; ! tables[i].end; i++)
233 86 : dst->lookup_serial_by_table (dst->cls,
234 : tables[i].rt,
235 : &tables[i].start_serial);
236 2 : dst->rollback (dst->cls);
237 88 : for (unsigned int i = 0; ! tables[i].end; i++)
238 : {
239 86 : struct Table *table = &tables[i];
240 :
241 86 : if (table->start_serial == table->end_serial)
242 70 : continue;
243 16 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
244 : "Replicating table %d from %llu to %llu\n",
245 : i,
246 : (unsigned long long) table->start_serial,
247 : (unsigned long long) table->end_serial);
248 16 : ctx.table = table;
249 32 : while (table->start_serial < table->end_serial)
250 : {
251 : enum GNUNET_DB_QueryStatus qs;
252 :
253 16 : if (GNUNET_OK !=
254 16 : src->start (src->cls,
255 : "copy table (src)"))
256 0 : return GNUNET_SYSERR;
257 16 : if (GNUNET_OK !=
258 16 : dst->start (dst->cls,
259 : "copy table (dst)"))
260 0 : return GNUNET_SYSERR;
261 16 : qs = src->lookup_records_by_table (src->cls,
262 : table->rt,
263 : table->start_serial,
264 : &do_insert,
265 : &ctx);
266 16 : if (ctx.qs < 0)
267 0 : qs = ctx.qs;
268 16 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
269 : {
270 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
271 : "Failed to lookup records from table %d: hard error\n",
272 : i);
273 0 : global_ret = EXIT_FAILURE;
274 0 : return GNUNET_SYSERR;
275 : }
276 16 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
277 : {
278 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
279 : "Serialization error looking up records from table %d (will retry)\n",
280 : i);
281 0 : return GNUNET_SYSERR; /* will retry */
282 : }
283 16 : if (0 == qs)
284 : {
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 : "Failed to lookup records from table %d: no results\n",
287 : i);
288 0 : GNUNET_break (0); /* should be impossible */
289 0 : global_ret = EXIT_FAILURE;
290 0 : return GNUNET_SYSERR;
291 : }
292 16 : if (0 == ctx.qs)
293 0 : return GNUNET_SYSERR; /* insertion failed, maybe record existed? try again */
294 16 : src->rollback (src->cls);
295 16 : qs = dst->commit (dst->cls);
296 16 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
297 : {
298 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
299 : "Serialization error committing transaction on table %d (will retry)\n",
300 : i);
301 0 : continue;
302 : }
303 16 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
304 : {
305 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
306 : "Hard error committing transaction on table %d\n",
307 : i);
308 0 : global_ret = EXIT_FAILURE;
309 0 : return GNUNET_SYSERR;
310 : }
311 : }
312 : }
313 : /* we do not care about conflicting UPDATEs to src table, so safe to just rollback */
314 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
315 : "Sync pass completed successfully with %llu updates\n",
316 : actual_size);
317 2 : return GNUNET_OK;
318 : }
319 :
320 :
321 : /**
322 : * Task to do the actual synchronization work.
323 : *
324 : * @param cls NULL, unused
325 : */
326 : static void
327 2 : do_sync (void *cls)
328 : {
329 : static struct GNUNET_TIME_Relative delay;
330 :
331 : (void) cls;
332 2 : sync_task = NULL;
333 2 : actual_size = 0;
334 2 : if (GNUNET_SYSERR ==
335 2 : src->preflight (src->cls))
336 : {
337 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
338 : "Failed to begin transaction with data source. Exiting\n");
339 0 : return;
340 : }
341 2 : if (GNUNET_SYSERR ==
342 2 : dst->preflight (dst->cls))
343 : {
344 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
345 : "Failed to begin transaction with data destination. Exiting\n");
346 0 : return;
347 : }
348 2 : if (GNUNET_OK != transact ())
349 : {
350 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
351 : "Transaction failed, rolling back\n");
352 0 : src->rollback (src->cls);
353 0 : dst->rollback (dst->cls);
354 : }
355 2 : if (0 != global_ret)
356 : {
357 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
358 : "Transaction failed permanently, exiting\n");
359 0 : return;
360 : }
361 2 : if ( (0 == actual_size) &&
362 : (exit_if_synced) )
363 : {
364 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
365 : "Databases are synchronized. Exiting\n");
366 1 : return;
367 : }
368 1 : if (actual_size < transaction_size / 2)
369 : {
370 0 : delay = GNUNET_TIME_STD_BACKOFF (delay);
371 : }
372 1 : else if (actual_size >= transaction_size)
373 : {
374 1 : delay = GNUNET_TIME_UNIT_ZERO;
375 : }
376 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
377 : "Next sync pass in %s\n",
378 : GNUNET_STRINGS_relative_time_to_string (delay,
379 : GNUNET_YES));
380 1 : sync_task = GNUNET_SCHEDULER_add_delayed (delay,
381 : &do_sync,
382 : NULL);
383 : }
384 :
385 :
386 : /**
387 : * Set an option of type 'char *' from the command line with
388 : * filename expansion a la #GNUNET_STRINGS_filename_expand().
389 : *
390 : * @param ctx command line processing context
391 : * @param scls additional closure (will point to the `char *`,
392 : * which will be allocated)
393 : * @param option name of the option
394 : * @param value actual value of the option (a string)
395 : * @return #GNUNET_OK
396 : */
397 : static enum GNUNET_GenericReturnValue
398 2 : set_filename (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx,
399 : void *scls,
400 : const char *option,
401 : const char *value)
402 : {
403 2 : char **val = scls;
404 :
405 : (void) ctx;
406 : (void) option;
407 2 : GNUNET_assert (NULL != value);
408 2 : GNUNET_free (*val);
409 2 : *val = GNUNET_STRINGS_filename_expand (value);
410 2 : return GNUNET_OK;
411 : }
412 :
413 :
414 : /**
415 : * Allow user to specify configuration file name (-s option)
416 : *
417 : * @param[out] fn set to the name of the configuration file
418 : */
419 : static struct GNUNET_GETOPT_CommandLineOption
420 1 : option_cfgfile_src (char **fn)
421 : {
422 1 : struct GNUNET_GETOPT_CommandLineOption clo = {
423 : .shortName = 's',
424 : .name = "source-configuration",
425 : .argumentHelp = "FILENAME",
426 : .description = gettext_noop (
427 : "use configuration file FILENAME for the SOURCE database"),
428 : .require_argument = 1,
429 : .processor = &set_filename,
430 : .scls = (void *) fn
431 : };
432 :
433 1 : return clo;
434 : }
435 :
436 :
437 : /**
438 : * Allow user to specify configuration file name (-d option)
439 : *
440 : * @param[out] fn set to the name of the configuration file
441 : */
442 : static struct GNUNET_GETOPT_CommandLineOption
443 1 : option_cfgfile_dst (char **fn)
444 : {
445 1 : struct GNUNET_GETOPT_CommandLineOption clo = {
446 : .shortName = 'd',
447 : .name = "destination-configuration",
448 : .argumentHelp = "FILENAME",
449 : .description = gettext_noop (
450 : "use configuration file FILENAME for the DESTINATION database"),
451 : .require_argument = 1,
452 : .processor = &set_filename,
453 : .scls = (void *) fn
454 : };
455 :
456 1 : return clo;
457 : }
458 :
459 :
460 : static struct GNUNET_CONFIGURATION_Handle *
461 2 : load_config (const char *cfgfile)
462 : {
463 : struct GNUNET_CONFIGURATION_Handle *cfg;
464 :
465 2 : cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ());
466 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
467 : "Loading config file: %s\n",
468 : cfgfile);
469 2 : if (GNUNET_SYSERR ==
470 2 : GNUNET_CONFIGURATION_load (cfg,
471 : cfgfile))
472 : {
473 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
474 : "Malformed configuration file `%s', exit ...\n",
475 : cfgfile);
476 0 : GNUNET_CONFIGURATION_destroy (cfg);
477 0 : return NULL;
478 : }
479 2 : return cfg;
480 : }
481 :
482 :
483 : /**
484 : * Shutdown task.
485 : *
486 : * @param cls NULL, unused
487 : */
488 : static void
489 1 : do_shutdown (void *cls)
490 : {
491 : (void) cls;
492 1 : if (NULL != sync_task)
493 : {
494 0 : GNUNET_SCHEDULER_cancel (sync_task);
495 0 : sync_task = NULL;
496 : }
497 1 : }
498 :
499 :
500 : /**
501 : * Initial task.
502 : *
503 : * @param cls NULL, unused
504 : */
505 : static void
506 1 : run (void *cls)
507 : {
508 : (void) cls;
509 :
510 1 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
511 : NULL);
512 1 : sync_task = GNUNET_SCHEDULER_add_now (&do_sync,
513 : NULL);
514 1 : }
515 :
516 :
517 : /**
518 : * Setup plugins in #src and #dst and #run() the main
519 : * logic with those plugins.
520 : */
521 : static void
522 1 : setup (struct GNUNET_CONFIGURATION_Handle *src_cfg,
523 : struct GNUNET_CONFIGURATION_Handle *dst_cfg)
524 : {
525 1 : src = TALER_EXCHANGEDB_plugin_load (src_cfg,
526 : false);
527 1 : if (NULL == src)
528 : {
529 0 : global_ret = EXIT_NOTINSTALLED;
530 0 : return;
531 : }
532 1 : dst = TALER_EXCHANGEDB_plugin_load (dst_cfg,
533 : false);
534 1 : if (NULL == dst)
535 : {
536 0 : global_ret = EXIT_NOTINSTALLED;
537 0 : TALER_EXCHANGEDB_plugin_unload (src);
538 0 : src = NULL;
539 0 : return;
540 : }
541 1 : GNUNET_SCHEDULER_run (&run,
542 : NULL);
543 1 : TALER_EXCHANGEDB_plugin_unload (src);
544 1 : src = NULL;
545 1 : TALER_EXCHANGEDB_plugin_unload (dst);
546 1 : dst = NULL;
547 : }
548 :
549 :
550 : /**
551 : * The main function of the taler-auditor-exchange tool. This tool is used
552 : * to add (or remove) an exchange's master key and base URL to the auditor's
553 : * database.
554 : *
555 : * @param argc number of arguments from the command line
556 : * @param argv command line arguments
557 : * @return 0 ok, non-zero on error
558 : */
559 : int
560 1 : main (int argc,
561 : char *const *argv)
562 : {
563 1 : char *src_cfgfile = NULL;
564 1 : char *dst_cfgfile = NULL;
565 1 : char *level = GNUNET_strdup ("WARNING");
566 : struct GNUNET_CONFIGURATION_Handle *src_cfg;
567 : struct GNUNET_CONFIGURATION_Handle *dst_cfg;
568 1 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
569 1 : GNUNET_GETOPT_option_mandatory (
570 : option_cfgfile_src (&src_cfgfile)),
571 1 : GNUNET_GETOPT_option_mandatory (
572 : option_cfgfile_dst (&dst_cfgfile)),
573 1 : GNUNET_GETOPT_option_help (
574 : TALER_AUDITOR_project_data (),
575 : gettext_noop ("Make a safe copy of an exchange database")),
576 1 : GNUNET_GETOPT_option_uint (
577 : 'b',
578 : "batch",
579 : "SIZE",
580 : gettext_noop (
581 : "target SIZE for a the number of records to copy in one transaction"),
582 : &transaction_size),
583 1 : GNUNET_GETOPT_option_flag (
584 : 't',
585 : "terminate-when-synchronized",
586 : gettext_noop (
587 : "terminate as soon as the databases are synchronized"),
588 : &exit_if_synced),
589 1 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
590 1 : GNUNET_GETOPT_option_loglevel (&level),
591 : GNUNET_GETOPT_OPTION_END
592 : };
593 :
594 1 : TALER_gcrypt_init (); /* must trigger initialization manually at this point! */
595 : {
596 : int ret;
597 :
598 1 : ret = GNUNET_GETOPT_run ("taler-auditor-sync",
599 : options,
600 : argc, argv);
601 1 : if (GNUNET_NO == ret)
602 0 : return EXIT_SUCCESS;
603 1 : if (GNUNET_SYSERR == ret)
604 0 : return EXIT_INVALIDARGUMENT;
605 : }
606 1 : GNUNET_assert (GNUNET_OK ==
607 : GNUNET_log_setup ("taler-auditor-sync",
608 : level,
609 : NULL));
610 1 : GNUNET_free (level);
611 : /* suppress compiler warnings... */
612 1 : GNUNET_assert (NULL != src_cfgfile);
613 1 : GNUNET_assert (NULL != dst_cfgfile);
614 1 : if (0 == strcmp (src_cfgfile,
615 : dst_cfgfile))
616 : {
617 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
618 : "Source and destination configuration files must differ!\n");
619 0 : return EXIT_INVALIDARGUMENT;
620 : }
621 1 : src_cfg = load_config (src_cfgfile);
622 1 : if (NULL == src_cfg)
623 : {
624 0 : GNUNET_free (src_cfgfile);
625 0 : GNUNET_free (dst_cfgfile);
626 0 : return EXIT_NOTCONFIGURED;
627 : }
628 1 : dst_cfg = load_config (dst_cfgfile);
629 1 : if (NULL == dst_cfg)
630 : {
631 0 : GNUNET_CONFIGURATION_destroy (src_cfg);
632 0 : GNUNET_free (src_cfgfile);
633 0 : GNUNET_free (dst_cfgfile);
634 0 : return EXIT_NOTCONFIGURED;
635 : }
636 1 : setup (src_cfg,
637 : dst_cfg);
638 1 : GNUNET_CONFIGURATION_destroy (src_cfg);
639 1 : GNUNET_CONFIGURATION_destroy (dst_cfg);
640 1 : GNUNET_free (src_cfgfile);
641 1 : GNUNET_free (dst_cfgfile);
642 :
643 1 : return global_ret;
644 : }
645 :
646 :
647 : /* end of taler-auditor-sync.c */
|