Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-wirewatch.c
18 : * @brief Process that imports information about incoming bank transfers into the merchant backend
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "microhttpd.h"
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <jansson.h>
25 : #include <pthread.h>
26 : #include <taler/taler_dbevents.h>
27 : #include "taler_merchant_util.h"
28 : #include "taler_merchant_bank_lib.h"
29 : #include "taler_merchantdb_lib.h"
30 : #include "taler_merchantdb_plugin.h"
31 :
32 : /**
33 : * Timeout for the bank interaction. Rather long as we should do long-polling
34 : * and do not want to wake up too often.
35 : */
36 : #define BANK_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \
37 : 5)
38 :
39 :
40 : /**
41 : * Information about a watch job.
42 : */
43 : struct Watch
44 : {
45 : /**
46 : * Kept in a DLL.
47 : */
48 : struct Watch *next;
49 :
50 : /**
51 : * Kept in a DLL.
52 : */
53 : struct Watch *prev;
54 :
55 : /**
56 : * Next task to run, if any.
57 : */
58 : struct GNUNET_SCHEDULER_Task *task;
59 :
60 : /**
61 : * Dynamically adjusted long polling time-out.
62 : */
63 : struct GNUNET_TIME_Relative bank_timeout;
64 :
65 : /**
66 : * For which instance are we importing bank transfers?
67 : */
68 : char *instance_id;
69 :
70 : /**
71 : * For which account are we importing bank transfers?
72 : */
73 : struct TALER_FullPayto payto_uri;
74 :
75 : /**
76 : * Bank history request.
77 : */
78 : struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh;
79 :
80 : /**
81 : * Start row for the bank interaction. Exclusive.
82 : */
83 : uint64_t start_row;
84 :
85 : /**
86 : * Artificial delay to use between API calls. Used to
87 : * throttle on failures.
88 : */
89 : struct GNUNET_TIME_Relative delay;
90 :
91 : /**
92 : * When did we start our last HTTP request?
93 : */
94 : struct GNUNET_TIME_Absolute start_time;
95 :
96 : /**
97 : * How long should long-polling take at least?
98 : */
99 : struct GNUNET_TIME_Absolute long_poll_timeout;
100 :
101 : /**
102 : * Login data for the bank.
103 : */
104 : struct TALER_MERCHANT_BANK_AuthenticationData ad;
105 :
106 : /**
107 : * Set to true if we found a transaction in the last iteration.
108 : */
109 : bool found;
110 :
111 : };
112 :
113 :
114 : /**
115 : * Head of active watches.
116 : */
117 : static struct Watch *w_head;
118 :
119 : /**
120 : * Tail of active watches.
121 : */
122 : static struct Watch *w_tail;
123 :
124 : /**
125 : * The merchant's configuration.
126 : */
127 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
128 :
129 : /**
130 : * Our database plugin.
131 : */
132 : static struct TALER_MERCHANTDB_Plugin *db_plugin;
133 :
134 : /**
135 : * Handle to the context for interacting with the bank.
136 : */
137 : static struct GNUNET_CURL_Context *ctx;
138 :
139 : /**
140 : * Scheduler context for running the @e ctx.
141 : */
142 : static struct GNUNET_CURL_RescheduleContext *rc;
143 :
144 : /**
145 : * Event handler to learn that the configuration changed
146 : * and we should shutdown (to be restarted).
147 : */
148 : static struct GNUNET_DB_EventHandler *eh;
149 :
150 : /**
151 : * Value to return from main(). 0 on success, non-zero on errors.
152 : */
153 : static int global_ret;
154 :
155 : /**
156 : * How many transactions should we fetch at most per batch?
157 : */
158 : static unsigned int batch_size = 32;
159 :
160 : /**
161 : * #GNUNET_YES if we are in test mode and should exit when idle.
162 : */
163 : static int test_mode;
164 :
165 : /**
166 : * #GNUNET_YES if we are in persistent mode and do
167 : * not exit on #config_changed.
168 : */
169 : static int persist_mode;
170 :
171 : /**
172 : * Set to true if we are shutting down due to a
173 : * configuration change.
174 : */
175 : static bool config_changed_flag;
176 :
177 : /**
178 : * Save progress in DB.
179 : */
180 : static void
181 4 : save (struct Watch *w)
182 : {
183 : enum GNUNET_DB_QueryStatus qs;
184 :
185 4 : qs = db_plugin->update_wirewatch_progress (db_plugin->cls,
186 4 : w->instance_id,
187 : w->payto_uri,
188 : w->start_row);
189 4 : if (qs < 0)
190 : {
191 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
192 : "Failed to persist wirewatch progress for %s/%s (%d)\n",
193 : w->instance_id,
194 : w->payto_uri.full_payto,
195 : qs);
196 0 : GNUNET_SCHEDULER_shutdown ();
197 0 : global_ret = EXIT_FAILURE;
198 : }
199 4 : }
200 :
201 :
202 : /**
203 : * Free resources of @a w.
204 : *
205 : * @param w watch job to terminate
206 : */
207 : static void
208 2 : end_watch (struct Watch *w)
209 : {
210 2 : if (NULL != w->task)
211 : {
212 0 : GNUNET_SCHEDULER_cancel (w->task);
213 0 : w->task = NULL;
214 : }
215 2 : if (NULL != w->hh)
216 : {
217 0 : TALER_MERCHANT_BANK_credit_history_cancel (w->hh);
218 0 : w->hh = NULL;
219 : }
220 2 : GNUNET_free (w->instance_id);
221 2 : GNUNET_free (w->payto_uri.full_payto);
222 2 : TALER_MERCHANT_BANK_auth_free (&w->ad);
223 2 : GNUNET_CONTAINER_DLL_remove (w_head,
224 : w_tail,
225 : w);
226 2 : GNUNET_free (w);
227 2 : }
228 :
229 :
230 : /**
231 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
232 : *
233 : * @param cls closure
234 : */
235 : static void
236 2 : shutdown_task (void *cls)
237 : {
238 : (void) cls;
239 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
240 : "Running shutdown\n");
241 2 : while (NULL != w_head)
242 : {
243 0 : struct Watch *w = w_head;
244 :
245 0 : save (w);
246 0 : end_watch (w);
247 : }
248 2 : if (NULL != eh)
249 : {
250 2 : db_plugin->event_listen_cancel (eh);
251 2 : eh = NULL;
252 : }
253 2 : TALER_MERCHANTDB_plugin_unload (db_plugin);
254 2 : db_plugin = NULL;
255 2 : cfg = NULL;
256 2 : if (NULL != ctx)
257 : {
258 2 : GNUNET_CURL_fini (ctx);
259 2 : ctx = NULL;
260 : }
261 2 : if (NULL != rc)
262 : {
263 2 : GNUNET_CURL_gnunet_rc_destroy (rc);
264 2 : rc = NULL;
265 : }
266 2 : }
267 :
268 :
269 : /**
270 : * Parse @a subject from wire transfer into @a wtid and @a exchange_url.
271 : *
272 : * @param subject wire transfer subject to parse;
273 : * format is "$WTID $URL"
274 : * @param[out] wtid wire transfer ID to extract
275 : * @param[out] exchange_url set to exchange URL
276 : * @return #GNUNET_OK on success
277 : */
278 : static enum GNUNET_GenericReturnValue
279 4 : parse_subject (const char *subject,
280 : struct TALER_WireTransferIdentifierRawP *wtid,
281 : char **exchange_url)
282 : {
283 : const char *space;
284 :
285 4 : space = strchr (subject, ' ');
286 4 : if (NULL == space)
287 0 : return GNUNET_NO;
288 4 : if (GNUNET_OK !=
289 4 : GNUNET_STRINGS_string_to_data (subject,
290 4 : space - subject,
291 : wtid,
292 : sizeof (*wtid)))
293 0 : return GNUNET_NO;
294 4 : space++;
295 4 : if (! TALER_url_valid_charset (space))
296 0 : return GNUNET_NO;
297 4 : if ( (0 != strncasecmp ("http://",
298 : space,
299 0 : strlen ("http://"))) &&
300 0 : (0 != strncasecmp ("https://",
301 : space,
302 : strlen ("https://"))) )
303 0 : return GNUNET_NO;
304 4 : *exchange_url = GNUNET_strdup (space);
305 4 : return GNUNET_OK;
306 : }
307 :
308 :
309 : /**
310 : * Run next iteration.
311 : *
312 : * @param cls a `struct Watch *`
313 : */
314 : static void
315 : do_work (void *cls);
316 :
317 :
318 : /**
319 : * Callbacks of this type are used to serve the result of asking
320 : * the bank for the credit transaction history.
321 : *
322 : * @param cls a `struct Watch *`
323 : * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
324 : * 0 if the bank's reply is bogus (fails to follow the protocol),
325 : * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
326 : * last callback is always of this status (even if `abs(num_results)` were
327 : * already returned).
328 : * @param ec detailed error code
329 : * @param serial_id monotonically increasing counter corresponding to the transaction
330 : * @param details details about the wire transfer
331 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
332 : */
333 : static enum GNUNET_GenericReturnValue
334 8 : credit_cb (
335 : void *cls,
336 : unsigned int http_status,
337 : enum TALER_ErrorCode ec,
338 : uint64_t serial_id,
339 : const struct TALER_MERCHANT_BANK_CreditDetails *details)
340 : {
341 8 : struct Watch *w = cls;
342 :
343 8 : switch (http_status)
344 : {
345 0 : case 0:
346 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
347 : "Invalid HTTP response (HTTP status: 0, %d) from bank\n",
348 : ec);
349 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
350 0 : break;
351 4 : case MHD_HTTP_OK:
352 : {
353 : enum GNUNET_DB_QueryStatus qs;
354 : char *exchange_url;
355 : struct TALER_WireTransferIdentifierRawP wtid;
356 :
357 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
358 : "Received wire transfer `%s' over %s\n",
359 : details->wire_subject,
360 : TALER_amount2s (&details->amount));
361 4 : w->found = true;
362 4 : if (GNUNET_OK !=
363 4 : parse_subject (details->wire_subject,
364 : &wtid,
365 : &exchange_url))
366 : {
367 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
368 : "Skipping transfer %llu (%s): not from exchange\n",
369 : (unsigned long long) serial_id,
370 : details->wire_subject);
371 0 : w->start_row = serial_id;
372 0 : return GNUNET_OK;
373 : }
374 : /* FIXME-Performance-Optimization: consider grouping multiple inserts
375 : into one bigger transaction with just one notify. */
376 4 : qs = db_plugin->insert_transfer (db_plugin->cls,
377 4 : w->instance_id,
378 : exchange_url,
379 : &wtid,
380 : &details->amount,
381 : details->credit_account_uri,
382 : true /* confirmed */);
383 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
384 : {
385 : struct TALER_Amount total;
386 : struct TALER_Amount wfee;
387 : struct TALER_Amount eamount;
388 : struct GNUNET_TIME_Timestamp timestamp;
389 : bool have_esig;
390 : bool verified;
391 :
392 2 : qs = db_plugin->lookup_transfer (db_plugin->cls,
393 2 : w->instance_id,
394 : exchange_url,
395 : &wtid,
396 : &total,
397 : &wfee,
398 : &eamount,
399 : ×tamp,
400 : &have_esig,
401 : &verified);
402 2 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
403 : {
404 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
405 : "Inserting transfer for %s into database failed. Is the credit account %s configured correctly?\n",
406 : w->instance_id,
407 : details->credit_account_uri.full_payto);
408 : }
409 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
410 : {
411 2 : if (0 !=
412 2 : TALER_amount_cmp (&total,
413 : &details->amount))
414 : {
415 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
416 : "Inserting transfer for %s into database failed. An entry exists for a different transfer amount (%s)!\n",
417 : w->instance_id,
418 : TALER_amount2s (&total));
419 : }
420 : else
421 : {
422 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
423 : "Inserting transfer for %s into database failed. An equivalent entry already exists.\n",
424 : w->instance_id);
425 : }
426 : }
427 : }
428 4 : GNUNET_free (exchange_url);
429 4 : if (qs < 0)
430 : {
431 0 : GNUNET_break (0);
432 0 : GNUNET_SCHEDULER_shutdown ();
433 0 : w->hh = NULL;
434 0 : return GNUNET_SYSERR;
435 : }
436 : /* Success => reset back-off timer! */
437 4 : w->delay = GNUNET_TIME_UNIT_ZERO;
438 : {
439 4 : struct GNUNET_DB_EventHeaderP es = {
440 4 : .size = htons (sizeof (es)),
441 4 : .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
442 : };
443 :
444 4 : db_plugin->event_notify (db_plugin->cls,
445 : &es,
446 : NULL,
447 : 0);
448 : }
449 : }
450 4 : w->start_row = serial_id;
451 4 : return GNUNET_OK;
452 4 : case MHD_HTTP_NO_CONTENT:
453 4 : save (w);
454 : /* Delay artificially if server returned before long-poll timeout */
455 4 : if (! w->found)
456 2 : w->delay = GNUNET_TIME_absolute_get_remaining (w->long_poll_timeout);
457 4 : break;
458 0 : case MHD_HTTP_NOT_FOUND:
459 : /* configuration likely wrong, wait at least 1 minute, backoff up to 15 minutes! */
460 0 : w->delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MINUTES,
461 : GNUNET_TIME_STD_BACKOFF (w->delay));
462 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
463 : "Bank claims account is unknown, waiting for %s before trying again\n",
464 : GNUNET_TIME_relative2s (w->delay,
465 : true));
466 0 : break;
467 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
468 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
469 : "Gateway timeout, adjusting long polling threshold\n");
470 : /* Limit new timeout at request delay */
471 : w->bank_timeout
472 0 : = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_duration (
473 : w->start_time),
474 : w->bank_timeout);
475 : /* set the timeout a bit earlier */
476 : w->bank_timeout
477 0 : = GNUNET_TIME_relative_subtract (w->bank_timeout,
478 : GNUNET_TIME_UNIT_SECONDS);
479 : /* do not allow it to go to zero */
480 : w->bank_timeout
481 0 : = GNUNET_TIME_relative_max (w->bank_timeout,
482 : GNUNET_TIME_UNIT_SECONDS);
483 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
484 0 : break;
485 0 : default:
486 : /* Something went wrong, try again, but with back-off */
487 0 : w->delay = GNUNET_TIME_STD_BACKOFF (w->delay);
488 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
489 : "Unexpected HTTP status code %u(%d) from bank\n",
490 : http_status,
491 : ec);
492 0 : break;
493 : }
494 4 : w->hh = NULL;
495 4 : if (test_mode && (! w->found))
496 : {
497 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
498 : "No transactions found and in test mode. Ending watch!\n");
499 2 : end_watch (w);
500 2 : if (NULL == w_head)
501 2 : GNUNET_SCHEDULER_shutdown ();
502 2 : return GNUNET_OK;
503 : }
504 2 : w->task = GNUNET_SCHEDULER_add_delayed (w->delay,
505 : &do_work,
506 : w);
507 2 : return GNUNET_OK;
508 : }
509 :
510 :
511 : static void
512 4 : do_work (void *cls)
513 : {
514 4 : struct Watch *w = cls;
515 :
516 4 : w->task = NULL;
517 4 : w->found = false;
518 : w->long_poll_timeout
519 4 : = GNUNET_TIME_relative_to_absolute (w->bank_timeout);
520 : w->start_time
521 4 : = GNUNET_TIME_absolute_get ();
522 4 : w->hh = TALER_MERCHANT_BANK_credit_history (ctx,
523 4 : &w->ad,
524 : w->start_row,
525 : batch_size,
526 : test_mode
527 4 : ? GNUNET_TIME_UNIT_ZERO
528 : : w->bank_timeout,
529 : &credit_cb,
530 : w);
531 4 : if (NULL == w->hh)
532 : {
533 0 : GNUNET_break (0);
534 0 : GNUNET_SCHEDULER_shutdown ();
535 0 : return;
536 : }
537 : }
538 :
539 :
540 : /**
541 : * Function called with information about a accounts
542 : * the wirewatcher should monitor.
543 : *
544 : * @param cls closure (NULL)
545 : * @param instance instance that owns the account
546 : * @param payto_uri account URI
547 : * @param credit_facade_url URL for the credit facade
548 : * @param credit_facade_credentials account access credentials
549 : * @param last_serial last transaction serial (inclusive) we have seen from this account
550 : */
551 : static void
552 2 : start_watch (
553 : void *cls,
554 : const char *instance,
555 : struct TALER_FullPayto payto_uri,
556 : const char *credit_facade_url,
557 : const json_t *credit_facade_credentials,
558 : uint64_t last_serial)
559 : {
560 2 : struct Watch *w = GNUNET_new (struct Watch);
561 :
562 : (void) cls;
563 2 : w->bank_timeout = BANK_TIMEOUT;
564 2 : if (GNUNET_OK !=
565 2 : TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
566 : credit_facade_url,
567 : &w->ad))
568 : {
569 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
570 : "Failed to parse authentication data of `%s/%s'\n",
571 : instance,
572 : payto_uri.full_payto);
573 0 : GNUNET_free (w);
574 0 : GNUNET_SCHEDULER_shutdown ();
575 0 : global_ret = EXIT_NOTCONFIGURED;
576 0 : return;
577 : }
578 :
579 2 : GNUNET_CONTAINER_DLL_insert (w_head,
580 : w_tail,
581 : w);
582 2 : w->instance_id = GNUNET_strdup (instance);
583 2 : w->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto);
584 2 : w->start_row = last_serial;
585 2 : w->task = GNUNET_SCHEDULER_add_now (&do_work,
586 : w);
587 : }
588 :
589 :
590 : /**
591 : * Function called on configuration change events received from Postgres. We
592 : * shutdown (and systemd should restart us).
593 : *
594 : * @param cls closure (NULL)
595 : * @param extra additional event data provided
596 : * @param extra_size number of bytes in @a extra
597 : */
598 : static void
599 0 : config_changed (void *cls,
600 : const void *extra,
601 : size_t extra_size)
602 : {
603 : (void) cls;
604 : (void) extra;
605 : (void) extra_size;
606 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
607 : "Configuration changed, %s\n",
608 : 0 == persist_mode
609 : ? "restarting"
610 : : "reinitializing");
611 0 : config_changed_flag = true;
612 0 : GNUNET_SCHEDULER_shutdown ();
613 0 : }
614 :
615 :
616 : /**
617 : * First task.
618 : *
619 : * @param cls closure, NULL
620 : * @param args remaining command-line arguments
621 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
622 : * @param c configuration
623 : */
624 : static void
625 2 : run (void *cls,
626 : char *const *args,
627 : const char *cfgfile,
628 : const struct GNUNET_CONFIGURATION_Handle *c)
629 : {
630 : (void) args;
631 : (void) cfgfile;
632 :
633 2 : cfg = c;
634 2 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
635 : NULL);
636 2 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
637 : &rc);
638 2 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
639 2 : if (NULL == ctx)
640 : {
641 0 : GNUNET_break (0);
642 0 : GNUNET_SCHEDULER_shutdown ();
643 0 : global_ret = EXIT_FAILURE;
644 0 : return;
645 : }
646 2 : if (NULL ==
647 2 : (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
648 : {
649 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
650 : "Failed to initialize DB subsystem\n");
651 0 : GNUNET_SCHEDULER_shutdown ();
652 0 : global_ret = EXIT_NOTCONFIGURED;
653 0 : return;
654 : }
655 2 : if (GNUNET_OK !=
656 2 : db_plugin->connect (db_plugin->cls))
657 : {
658 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
659 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
660 0 : GNUNET_SCHEDULER_shutdown ();
661 0 : global_ret = EXIT_FAILURE;
662 0 : return;
663 : }
664 : {
665 2 : struct GNUNET_DB_EventHeaderP es = {
666 2 : .size = htons (sizeof (es)),
667 2 : .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
668 : };
669 :
670 4 : eh = db_plugin->event_listen (db_plugin->cls,
671 : &es,
672 2 : GNUNET_TIME_UNIT_FOREVER_REL,
673 : &config_changed,
674 : NULL);
675 : }
676 : {
677 : enum GNUNET_DB_QueryStatus qs;
678 :
679 2 : qs = db_plugin->select_wirewatch_accounts (db_plugin->cls,
680 : &start_watch,
681 : NULL);
682 2 : if (qs < 0)
683 : {
684 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
685 : "Failed to obtain wirewatch accounts from database\n");
686 0 : GNUNET_SCHEDULER_shutdown ();
687 0 : global_ret = EXIT_NO_RESTART;
688 0 : return;
689 : }
690 2 : if ( (NULL == w_head) &&
691 0 : (GNUNET_YES == test_mode) )
692 : {
693 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
694 : "No active wirewatch accounts in database and in test mode. Exiting.\n");
695 0 : GNUNET_SCHEDULER_shutdown ();
696 0 : global_ret = EXIT_SUCCESS;
697 0 : return;
698 : }
699 : }
700 : }
701 :
702 :
703 : /**
704 : * The main function of taler-merchant-wirewatch
705 : *
706 : * @param argc number of arguments from the command line
707 : * @param argv command line arguments
708 : * @return 0 ok, 1 on error
709 : */
710 : int
711 2 : main (int argc,
712 : char *const *argv)
713 : {
714 2 : struct GNUNET_GETOPT_CommandLineOption options[] = {
715 2 : GNUNET_GETOPT_option_flag ('p',
716 : "persist",
717 : "run in persist mode and do not exit on configuration changes",
718 : &persist_mode),
719 2 : GNUNET_GETOPT_option_timetravel ('T',
720 : "timetravel"),
721 2 : GNUNET_GETOPT_option_flag ('t',
722 : "test",
723 : "run in test mode and exit when idle",
724 : &test_mode),
725 2 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
726 : GNUNET_GETOPT_OPTION_END
727 : };
728 : enum GNUNET_GenericReturnValue ret;
729 :
730 : do {
731 2 : config_changed_flag = false;
732 2 : ret = GNUNET_PROGRAM_run (
733 : TALER_MERCHANT_project_data (),
734 : argc, argv,
735 : "taler-merchant-wirewatch",
736 : gettext_noop (
737 : "background process that watches for incoming wire transfers to the merchant bank account"),
738 : options,
739 : &run, NULL);
740 2 : } while ( (1 == persist_mode) &&
741 : config_changed_flag);
742 2 : if (GNUNET_SYSERR == ret)
743 0 : return EXIT_INVALIDARGUMENT;
744 2 : if (GNUNET_NO == ret)
745 0 : return EXIT_SUCCESS;
746 2 : return global_ret;
747 : }
748 :
749 :
750 : /* end of taler-exchange-wirewatch.c */
|