Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2025 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 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 : /**
18 : * @file src/backend/taler-merchant-report-generator.c
19 : * @brief Service for fetching and transmitting merchant reports
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <gnunet/gnunet_db_lib.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include <taler/taler_merchant_util.h>
27 : #include <taler/taler_curl_lib.h>
28 : #include <taler/taler_dbevents.h>
29 : #include <taler/taler_error_codes.h>
30 : #include "merchantdb_lib.h"
31 : #include "merchantdb_lib.h"
32 : #include "taler/taler_merchant_service.h"
33 : #include <microhttpd.h>
34 : #include <curl/curl.h>
35 : #include "merchant-database/delete_report.h"
36 : #include "merchant-database/iterate_pending_reports.h"
37 : #include "merchant-database/update_report_status.h"
38 : #include "merchant-database/set_instance.h"
39 : #include "merchant-database/event_listen.h"
40 :
41 :
42 : /**
43 : * Information about an active reporting activity.
44 : */
45 : struct ReportActivity
46 : {
47 :
48 : /**
49 : * Kept in a DLL.
50 : */
51 : struct ReportActivity *next;
52 :
53 : /**
54 : * Kept in a DLL.
55 : */
56 : struct ReportActivity *prev;
57 :
58 : /**
59 : * Transmission program that is running.
60 : */
61 : struct GNUNET_Process *proc;
62 :
63 : /**
64 : * Handle to wait for @e proc to terminate.
65 : */
66 : struct GNUNET_ChildWaitHandle *cwh;
67 :
68 : /**
69 : * Minor context that holds body and headers.
70 : */
71 : struct TALER_CURL_PostContext post_ctx;
72 :
73 : /**
74 : * CURL easy handle for the HTTP request.
75 : */
76 : CURL *eh;
77 :
78 : /**
79 : * Job handle for the HTTP request.
80 : */
81 : struct GNUNET_CURL_Job *job;
82 :
83 : /**
84 : * ID of the instance we are working on.
85 : */
86 : char *instance_id;
87 :
88 : /**
89 : * URL where we request the report from.
90 : */
91 : char *url;
92 :
93 : /**
94 : * Report program section.
95 : */
96 : char *report_program_section;
97 :
98 : /**
99 : * Report description.
100 : */
101 : char *report_description;
102 :
103 : /**
104 : * Target address for transmission.
105 : */
106 : char *target_address;
107 :
108 : /**
109 : * MIME type of the report.
110 : */
111 : char *mime_type;
112 :
113 : /**
114 : * Report we are working on.
115 : */
116 : uint64_t report_id;
117 :
118 : /**
119 : * Next transmission time, already calculated.
120 : */
121 : struct GNUNET_TIME_Absolute next_transmission;
122 :
123 : /**
124 : * HTTP response code.
125 : */
126 : long response_code;
127 :
128 : /**
129 : * Set to true if this is a one-shot report.
130 : */
131 : bool one_shot;
132 :
133 : };
134 :
135 :
136 : /**
137 : * Global return value.
138 : */
139 : static int global_ret;
140 :
141 : /**
142 : * #GNUNET_YES if we are in test mode and should exit when idle.
143 : */
144 : static int test_mode;
145 :
146 : /**
147 : * Base URL of the merchant backend.
148 : */
149 : static char *base_url;
150 :
151 : /**
152 : * Our configuration.
153 : */
154 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
155 :
156 : /**
157 : * Database connection.
158 : */
159 : static struct TALER_MERCHANTDB_PostgresContext *pg;
160 :
161 : /**
162 : * Event handler for database change notifications.
163 : */
164 : static struct GNUNET_DB_EventHandler *eh;
165 :
166 : /**
167 : * Task for checking pending reports.
168 : */
169 : static struct GNUNET_SCHEDULER_Task *report_task;
170 :
171 : /**
172 : * When is the current report_task scheduled to run?
173 : */
174 : static struct GNUNET_TIME_Absolute report_task_due;
175 :
176 : /**
177 : * Context for CURL operations.
178 : */
179 : static struct GNUNET_CURL_Context *curl_ctx;
180 :
181 : /**
182 : * Reschedule context for CURL.
183 : */
184 : static struct GNUNET_CURL_RescheduleContext *curl_rc;
185 :
186 : /**
187 : * Head of DLL of active report activities.
188 : */
189 : static struct ReportActivity *ra_head;
190 :
191 : /**
192 : * Tail of DLL of active report activities.
193 : */
194 : static struct ReportActivity *ra_tail;
195 :
196 :
197 : /**
198 : * Free a report activity structure.
199 : *
200 : * @param[in] ra report activity to free
201 : */
202 : static void
203 1 : free_ra (struct ReportActivity *ra)
204 : {
205 1 : if (NULL != ra->cwh)
206 : {
207 0 : GNUNET_wait_child_cancel (ra->cwh);
208 0 : ra->cwh = NULL;
209 : }
210 1 : if (NULL != ra->proc)
211 : {
212 0 : GNUNET_break (GNUNET_OK ==
213 : GNUNET_process_kill (ra->proc,
214 : SIGKILL));
215 0 : GNUNET_break (GNUNET_OK ==
216 : GNUNET_process_wait (ra->proc,
217 : true,
218 : NULL,
219 : NULL));
220 0 : GNUNET_process_destroy (ra->proc);
221 0 : ra->proc = NULL;
222 : }
223 1 : if (NULL != ra->job)
224 : {
225 0 : GNUNET_CURL_job_cancel (ra->job);
226 0 : ra->job = NULL;
227 : }
228 1 : if (NULL != ra->eh)
229 : {
230 0 : curl_easy_cleanup (ra->eh);
231 0 : ra->eh = NULL;
232 : }
233 1 : TALER_curl_easy_post_finished (&ra->post_ctx);
234 1 : GNUNET_CONTAINER_DLL_remove (ra_head,
235 : ra_tail,
236 : ra);
237 1 : GNUNET_free (ra->instance_id);
238 1 : GNUNET_free (ra->report_program_section);
239 1 : GNUNET_free (ra->report_description);
240 1 : GNUNET_free (ra->target_address);
241 1 : GNUNET_free (ra->mime_type);
242 1 : GNUNET_free (ra->url);
243 1 : GNUNET_free (ra);
244 1 : }
245 :
246 :
247 : /**
248 : * Check for pending reports and process them.
249 : *
250 : * @param cls closure (unused)
251 : */
252 : static void
253 : check_pending_reports (void *cls);
254 :
255 :
256 : /**
257 : * Finish transmission of a report and update database.
258 : *
259 : * @param[in] ra report activity to finish
260 : * @param ec error code (#TALER_EC_NONE on success)
261 : * @param error_details human-readable error details (NULL on success)
262 : */
263 : static void
264 1 : finish_transmission (struct ReportActivity *ra,
265 : enum TALER_ErrorCode ec,
266 : const char *error_details)
267 : {
268 : enum GNUNET_DB_QueryStatus qs;
269 : struct GNUNET_TIME_Timestamp next_ts;
270 :
271 1 : next_ts = GNUNET_TIME_absolute_to_timestamp (ra->next_transmission);
272 1 : qs = TALER_MERCHANTDB_set_instance (pg,
273 1 : ra->instance_id);
274 1 : if (qs <= 0)
275 : {
276 0 : free_ra (ra);
277 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
278 : "Failed to set instance to update report status: %d\n",
279 : qs);
280 0 : global_ret = EXIT_FAILURE;
281 0 : GNUNET_SCHEDULER_shutdown ();
282 1 : return;
283 : }
284 1 : if ( (TALER_EC_NONE == ec) &&
285 0 : (ra->one_shot) )
286 : {
287 0 : qs = TALER_MERCHANTDB_delete_report (pg,
288 0 : ra->instance_id,
289 : ra->report_id);
290 : }
291 : else
292 : {
293 1 : qs = TALER_MERCHANTDB_update_report_status (pg,
294 1 : ra->instance_id,
295 : ra->report_id,
296 : next_ts,
297 : ec,
298 : error_details);
299 : }
300 1 : if (qs < 0)
301 : {
302 0 : free_ra (ra);
303 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
304 : "Failed to update report status: %d\n",
305 : qs);
306 0 : global_ret = EXIT_FAILURE;
307 0 : GNUNET_SCHEDULER_shutdown ();
308 0 : return;
309 : }
310 1 : if ( (NULL == report_task) ||
311 0 : (GNUNET_TIME_absolute_cmp (report_task_due,
312 : >,
313 : ra->next_transmission)) )
314 : {
315 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
316 : "Scheduling next report for %s\n",
317 : GNUNET_TIME_absolute2s (ra->next_transmission));
318 1 : if (NULL != report_task)
319 0 : GNUNET_SCHEDULER_cancel (report_task);
320 1 : report_task_due = ra->next_transmission;
321 1 : report_task = GNUNET_SCHEDULER_add_at (ra->next_transmission,
322 : &check_pending_reports,
323 : NULL);
324 : }
325 1 : free_ra (ra);
326 2 : if (test_mode &&
327 1 : GNUNET_TIME_absolute_is_future (report_task_due) &&
328 1 : (NULL == ra_head))
329 : {
330 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
331 : "Test mode, exiting because of going idle\n");
332 1 : GNUNET_SCHEDULER_shutdown ();
333 1 : return;
334 : }
335 : }
336 :
337 :
338 : /**
339 : * Callback invoked when the child process terminates.
340 : *
341 : * @param cls closure, a `struct ReportActivity *`
342 : * @param type type of the process
343 : * @param exit_code exit code of the process
344 : */
345 : static void
346 0 : child_completed_cb (void *cls,
347 : enum GNUNET_OS_ProcessStatusType type,
348 : long unsigned int exit_code)
349 : {
350 0 : struct ReportActivity *ra = cls;
351 : enum TALER_ErrorCode ec;
352 0 : char *error_details = NULL;
353 :
354 0 : ra->cwh = NULL;
355 0 : GNUNET_process_destroy (ra->proc);
356 0 : ra->proc = NULL;
357 0 : if ( (GNUNET_OS_PROCESS_EXITED != type) ||
358 : (0 != exit_code) )
359 : {
360 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
361 : "Report transmission program failed with status %d/%lu\n",
362 : (int) type,
363 : exit_code);
364 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
365 0 : GNUNET_asprintf (&error_details,
366 : "Report transmission program exited with status %d/%lu",
367 : (int) type,
368 : exit_code);
369 : }
370 : else
371 : {
372 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
373 : "Report transmitted successfully\n");
374 0 : ec = TALER_EC_NONE;
375 : }
376 0 : finish_transmission (ra,
377 : ec,
378 : error_details);
379 0 : GNUNET_free (error_details);
380 0 : }
381 :
382 :
383 : /**
384 : * Transmit a report using the respective report program.
385 : *
386 : * @param[in,out] ra which report activity are we working on
387 : * @param report_len length of @a report
388 : * @param report binary report data to transmit
389 : */
390 : static void
391 0 : transmit_report (struct ReportActivity *ra,
392 : size_t report_len,
393 : const void *report)
394 : {
395 : const char *binary;
396 : struct GNUNET_DISK_FileHandle *stdin_handle;
397 :
398 : {
399 : char *section;
400 :
401 0 : GNUNET_asprintf (§ion,
402 : "report-generator-%s",
403 : ra->report_program_section);
404 0 : if (GNUNET_OK !=
405 0 : GNUNET_CONFIGURATION_get_value_string (cfg,
406 : section,
407 : "BINARY",
408 : (char **) &binary))
409 : {
410 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
411 : section,
412 : "BINARY");
413 0 : finish_transmission (ra,
414 : TALER_EC_MERCHANT_GENERIC_REPORT_GENERATOR_UNCONFIGURED,
415 : section);
416 0 : GNUNET_free (section);
417 0 : return;
418 : }
419 0 : GNUNET_free (section);
420 : }
421 :
422 : {
423 : struct GNUNET_DISK_PipeHandle *stdin_pipe;
424 :
425 0 : stdin_pipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
426 0 : if (NULL == stdin_pipe)
427 : {
428 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
429 : "pipe");
430 0 : finish_transmission (ra,
431 : TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE,
432 : "pipe");
433 0 : return;
434 : }
435 :
436 0 : ra->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
437 0 : GNUNET_assert (GNUNET_OK ==
438 : GNUNET_process_set_options (
439 : ra->proc,
440 : GNUNET_process_option_inherit_rpipe (stdin_pipe,
441 : STDIN_FILENO)));
442 0 : if (GNUNET_OK !=
443 0 : GNUNET_process_run_command_va (ra->proc,
444 : binary,
445 : binary,
446 : "-d",
447 : ra->report_description,
448 : "-m",
449 : ra->mime_type,
450 : "-t",
451 : ra->target_address,
452 : NULL))
453 : {
454 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
455 : "exec",
456 : binary);
457 0 : GNUNET_process_destroy (ra->proc);
458 0 : ra->proc = NULL;
459 0 : GNUNET_DISK_pipe_close (stdin_pipe);
460 0 : finish_transmission (ra,
461 : TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
462 : "Could not execute report generator binary");
463 0 : return;
464 : }
465 :
466 : /* Write report data to stdin of child process */
467 0 : stdin_handle = GNUNET_DISK_pipe_detach_end (stdin_pipe,
468 : GNUNET_DISK_PIPE_END_WRITE);
469 0 : GNUNET_DISK_pipe_close (stdin_pipe);
470 : }
471 :
472 : {
473 0 : size_t off = 0;
474 :
475 0 : while (off < report_len)
476 : {
477 : ssize_t wrote;
478 :
479 0 : wrote = GNUNET_DISK_file_write (stdin_handle,
480 : report + off,
481 : report_len - off);
482 0 : if (wrote <= 0)
483 0 : break;
484 0 : off += (size_t) wrote;
485 : }
486 0 : GNUNET_DISK_file_close (stdin_handle);
487 :
488 0 : if (off != report_len)
489 : {
490 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
491 : "Failed to write report data to child process stdin\n");
492 0 : finish_transmission (ra,
493 : TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
494 : "Failed to write to transmission program");
495 0 : return;
496 : }
497 : }
498 :
499 : /* Wait for child to complete */
500 0 : ra->cwh = GNUNET_wait_child (ra->proc,
501 : &child_completed_cb,
502 : ra);
503 : }
504 :
505 :
506 : /**
507 : * Callback invoked when CURL request completes.
508 : *
509 : * @param cls closure, a `struct ReportActivity *`
510 : * @param response_code HTTP response code
511 : * @param body http body of the response
512 : * @param body_size number of bytes in @a body
513 : */
514 : static void
515 1 : curl_completed_cb (void *cls,
516 : long response_code,
517 : const void *body,
518 : size_t body_size)
519 : {
520 1 : struct ReportActivity *ra = cls;
521 :
522 1 : ra->job = NULL;
523 1 : ra->response_code = response_code;
524 1 : if (MHD_HTTP_OK != response_code)
525 : {
526 : char *error_details;
527 :
528 1 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
529 : "Failed to fetch report data: HTTP %ld\n",
530 : response_code);
531 1 : GNUNET_asprintf (&error_details,
532 : "HTTP request failed with status %ld from `%s'",
533 : response_code,
534 : ra->url);
535 1 : finish_transmission (ra,
536 : TALER_EC_MERCHANT_REPORT_FETCH_FAILED,
537 : error_details);
538 1 : GNUNET_free (error_details);
539 1 : return;
540 : }
541 0 : transmit_report (ra,
542 : body_size,
543 : body);
544 : }
545 :
546 :
547 : /**
548 : * Function to fetch data from @a data_source at @a instance_id
549 : * and to send it to the @a target_address
550 : *
551 : * @param[in,out] ra which report activity are we working on
552 : * @param mime_type mime type to request from @a data_source
553 : * @param report_token token to get access to the report
554 : */
555 : static void
556 1 : fetch_and_transmit (
557 : struct ReportActivity *ra,
558 : const char *mime_type,
559 : const struct TALER_MERCHANT_ReportToken *report_token)
560 : {
561 1 : GNUNET_asprintf (&ra->url,
562 : "%sreports/%llu",
563 : base_url,
564 1 : (unsigned long long) ra->report_id);
565 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
566 : "Fetching report from %s\n",
567 : ra->url);
568 1 : ra->eh = curl_easy_init ();
569 1 : if (NULL == ra->eh)
570 : {
571 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
572 : "Failed to initialize CURL handle\n");
573 0 : finish_transmission (ra,
574 : TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
575 : "curl_easy_init");
576 0 : return;
577 : }
578 :
579 : {
580 : char *accept_header;
581 :
582 1 : GNUNET_asprintf (&accept_header,
583 : "Accept: %s",
584 : mime_type);
585 1 : ra->post_ctx.headers = curl_slist_append (ra->post_ctx.headers,
586 : accept_header);
587 1 : GNUNET_free (accept_header);
588 : }
589 1 : GNUNET_assert (CURLE_OK ==
590 : curl_easy_setopt (ra->eh,
591 : CURLOPT_URL,
592 : ra->url));
593 : {
594 : json_t *req;
595 :
596 1 : req = GNUNET_JSON_PACK (
597 : GNUNET_JSON_pack_data_auto ("report_token",
598 : report_token));
599 1 : if (GNUNET_OK !=
600 1 : TALER_curl_easy_post (&ra->post_ctx,
601 : ra->eh,
602 : req))
603 : {
604 0 : GNUNET_break (0);
605 0 : json_decref (req);
606 0 : finish_transmission (ra,
607 : TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
608 : "TALER_curl_easy_post");
609 0 : return;
610 : }
611 1 : json_decref (req);
612 : }
613 2 : ra->job = GNUNET_CURL_job_add_raw (curl_ctx,
614 : ra->eh,
615 1 : ra->post_ctx.headers,
616 : &curl_completed_cb,
617 : ra);
618 : /* On both success and failure ownership of the easy handle has been
619 : taken over by GNUNET_CURL_job_add_raw() (on failure it already calls
620 : curl_easy_cleanup()), so we must drop our reference to avoid a
621 : double-free in free_ra(). */
622 1 : ra->eh = NULL;
623 1 : if (NULL == ra->job)
624 : {
625 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
626 : "Failed to start the curl job for report #%llu\n",
627 : (unsigned long long) ra->report_id);
628 0 : finish_transmission (ra,
629 : TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
630 : "GNUNET_CURL_job_add_raw");
631 0 : return;
632 : }
633 : }
634 :
635 :
636 : /**
637 : * Callback invoked for each pending report.
638 : *
639 : * @param cls closure
640 : * @param instance_id name of the instance
641 : * @param report_id serial number of the report
642 : * @param report_program_section configuration section of program
643 : * for report generation
644 : * @param report_description text describing the report
645 : * @param mime_type mime type to request
646 : * @param report_token token to authorize access to the data source
647 : * @param target_address where to send report data
648 : * @param frequency report frequency
649 : * @param frequency_shift how much to shift the report time from a
650 : * multiple of the report @a frequency
651 : * @param next_transmission when is the next transmission of this report
652 : * due
653 : * @param one_shot true if the report should be removed from the
654 : * list after generation instead of being repeated
655 : */
656 : static void
657 1 : process_pending_report (
658 : void *cls,
659 : const char *instance_id,
660 : uint64_t report_id,
661 : const char *report_program_section,
662 : const char *report_description,
663 : const char *mime_type,
664 : const struct TALER_MERCHANT_ReportToken *report_token,
665 : const char *target_address,
666 : struct GNUNET_TIME_Relative frequency,
667 : struct GNUNET_TIME_Relative frequency_shift,
668 : struct GNUNET_TIME_Absolute next_transmission,
669 : bool one_shot)
670 : {
671 1 : struct GNUNET_TIME_Absolute *next = cls;
672 : struct ReportActivity *ra;
673 :
674 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
675 : "Next report %llu is pending at %s\n",
676 : (unsigned long long) report_id,
677 : GNUNET_TIME_absolute2s (next_transmission));
678 1 : *next = next_transmission;
679 1 : if (GNUNET_TIME_absolute_is_future (next_transmission))
680 0 : return;
681 1 : *next = GNUNET_TIME_UNIT_ZERO_ABS; /* there might be more! */
682 2 : if ( (one_shot) ||
683 1 : (GNUNET_TIME_relative_is_zero (frequency)) )
684 0 : {
685 0 : next_transmission = GNUNET_TIME_UNIT_FOREVER_ABS;
686 : }
687 : else
688 : {
689 : next_transmission =
690 1 : GNUNET_TIME_absolute_add (
691 : GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
692 : frequency),
693 : GNUNET_TIME_relative_add (frequency,
694 : frequency_shift));
695 : }
696 1 : if (! GNUNET_TIME_absolute_is_future (next_transmission))
697 : {
698 : /* frequency near-zero!? */
699 0 : GNUNET_break (0);
700 0 : next_transmission = GNUNET_TIME_relative_to_absolute (
701 : GNUNET_TIME_UNIT_MINUTES);
702 : }
703 1 : ra = GNUNET_new (struct ReportActivity);
704 1 : ra->instance_id = GNUNET_strdup (instance_id);
705 1 : ra->report_id = report_id;
706 1 : ra->next_transmission = next_transmission;
707 1 : ra->report_program_section = GNUNET_strdup (report_program_section);
708 1 : ra->report_description = GNUNET_strdup (report_description);
709 1 : ra->target_address = GNUNET_strdup (target_address);
710 1 : ra->mime_type = GNUNET_strdup (mime_type);
711 1 : ra->one_shot = one_shot;
712 1 : GNUNET_CONTAINER_DLL_insert (ra_head,
713 : ra_tail,
714 : ra);
715 1 : fetch_and_transmit (ra,
716 : mime_type,
717 : report_token);
718 : }
719 :
720 :
721 : static void
722 1 : check_pending_reports (void *cls)
723 : {
724 : enum GNUNET_DB_QueryStatus qs;
725 : struct GNUNET_TIME_Absolute next;
726 :
727 : (void) cls;
728 1 : report_task = NULL;
729 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
730 : "Checking for pending reports...\n");
731 1 : next = GNUNET_TIME_UNIT_FOREVER_ABS;
732 1 : qs = TALER_MERCHANTDB_iterate_pending_reports (pg,
733 : &process_pending_report,
734 : &next);
735 1 : if (qs < 0)
736 : {
737 0 : GNUNET_break (0);
738 0 : global_ret = EXIT_FAILURE;
739 0 : GNUNET_SCHEDULER_shutdown ();
740 1 : return;
741 : }
742 1 : if (NULL != ra_head)
743 1 : return; /* wait for completion */
744 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
745 : "Found %d reports pending, next at %s\n",
746 : (int) qs,
747 : GNUNET_TIME_absolute2s (next));
748 0 : GNUNET_assert (NULL == report_task);
749 0 : if (test_mode &&
750 0 : GNUNET_TIME_absolute_is_future (next) &&
751 0 : (NULL == ra_head))
752 : {
753 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
754 : "Test mode, existing because of going idle\n");
755 0 : GNUNET_SCHEDULER_shutdown ();
756 0 : return;
757 : }
758 0 : report_task_due = next;
759 0 : report_task = GNUNET_SCHEDULER_add_at (next,
760 : &check_pending_reports,
761 : NULL);
762 : }
763 :
764 :
765 : /**
766 : * Callback invoked when a MERCHANT_REPORT_UPDATE event is received.
767 : *
768 : * @param cls closure (unused)
769 : * @param extra additional event data (unused)
770 : * @param extra_size size of @a extra
771 : */
772 : static void
773 0 : report_update_cb (void *cls,
774 : const void *extra,
775 : size_t extra_size)
776 : {
777 : (void) cls;
778 : (void) extra;
779 : (void) extra_size;
780 :
781 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
782 : "Received MERCHANT_REPORT_UPDATE event\n");
783 : /* Cancel any pending check and schedule immediate execution */
784 0 : if (NULL != report_task)
785 0 : GNUNET_SCHEDULER_cancel (report_task);
786 0 : report_task_due = GNUNET_TIME_UNIT_ZERO_ABS;
787 0 : report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
788 : NULL);
789 0 : }
790 :
791 :
792 : /**
793 : * Shutdown the service cleanly.
794 : *
795 : * @param cls closure (unused)
796 : */
797 : static void
798 1 : do_shutdown (void *cls)
799 : {
800 : struct ReportActivity *ra;
801 :
802 : (void) cls;
803 :
804 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
805 : "Shutting down report generator service\n");
806 :
807 1 : while (NULL != (ra = ra_head))
808 0 : free_ra (ra);
809 :
810 1 : if (NULL != report_task)
811 : {
812 1 : GNUNET_SCHEDULER_cancel (report_task);
813 1 : report_task = NULL;
814 : }
815 1 : if (NULL != curl_rc)
816 : {
817 1 : GNUNET_CURL_gnunet_rc_destroy (curl_rc);
818 1 : curl_rc = NULL;
819 : }
820 1 : if (NULL != curl_ctx)
821 : {
822 1 : GNUNET_CURL_fini (curl_ctx);
823 1 : curl_ctx = NULL;
824 : }
825 1 : if (NULL != eh)
826 : {
827 1 : TALER_MERCHANTDB_event_listen_cancel (eh);
828 1 : eh = NULL;
829 : }
830 1 : if (NULL != pg)
831 : {
832 1 : TALER_MERCHANTDB_disconnect (pg);
833 1 : pg = NULL;
834 : }
835 1 : GNUNET_free (base_url);
836 1 : base_url = NULL;
837 1 : }
838 :
839 :
840 : /**
841 : * Main function for the report generator service.
842 : *
843 : * @param cls closure
844 : * @param args remaining command-line arguments
845 : * @param cfgfile name of the configuration file used
846 : * @param config configuration
847 : */
848 : static void
849 1 : run (void *cls,
850 : char *const *args,
851 : const char *cfgfile,
852 : const struct GNUNET_CONFIGURATION_Handle *config)
853 : {
854 : (void) cls;
855 : (void) args;
856 : (void) cfgfile;
857 :
858 1 : cfg = config;
859 1 : if (GNUNET_OK !=
860 1 : GNUNET_CONFIGURATION_get_value_string (cfg,
861 : "merchant",
862 : "BASE_URL",
863 : &base_url))
864 : {
865 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
866 : "merchant",
867 : "BASE_URL");
868 0 : global_ret = EXIT_NOTCONFIGURED;
869 0 : return;
870 : }
871 1 : if (! TALER_is_web_url (base_url))
872 : {
873 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
874 : "merchant",
875 : "BASE_URL",
876 : "Not a Web URL");
877 0 : global_ret = EXIT_NOTCONFIGURED;
878 0 : return;
879 : }
880 :
881 : /* Ensure base_url ends with '/' */
882 1 : if ('/' != base_url[strlen (base_url) - 1])
883 : {
884 : char *tmp;
885 :
886 0 : GNUNET_asprintf (&tmp,
887 : "%s/",
888 : base_url);
889 0 : GNUNET_free (base_url);
890 0 : base_url = tmp;
891 : }
892 :
893 1 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
894 : NULL);
895 :
896 1 : curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
897 : &curl_rc);
898 1 : if (NULL == curl_ctx)
899 : {
900 0 : GNUNET_break (0);
901 0 : global_ret = EXIT_FAILURE;
902 0 : GNUNET_SCHEDULER_shutdown ();
903 0 : return;
904 : }
905 1 : curl_rc = GNUNET_CURL_gnunet_rc_create (curl_ctx);
906 :
907 1 : pg = TALER_MERCHANTDB_connect (cfg);
908 1 : if (NULL == pg)
909 : {
910 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
911 : "Failed to connect to database. Consider running taler-merchant-dbconfig!\n");
912 0 : global_ret = EXIT_NOTINSTALLED;
913 0 : GNUNET_SCHEDULER_shutdown ();
914 0 : return;
915 : }
916 : {
917 1 : struct GNUNET_DB_EventHeaderP ev = {
918 1 : .size = htons (sizeof (ev)),
919 1 : .type = htons (TALER_DBEVENT_MERCHANT_REPORT_UPDATE)
920 : };
921 :
922 2 : eh = TALER_MERCHANTDB_event_listen (pg,
923 : &ev,
924 1 : GNUNET_TIME_UNIT_FOREVER_REL,
925 : &report_update_cb,
926 : NULL);
927 1 : if (NULL == eh)
928 : {
929 0 : GNUNET_break (0);
930 0 : global_ret = EXIT_FAILURE;
931 0 : GNUNET_SCHEDULER_shutdown ();
932 0 : return;
933 : }
934 : }
935 1 : report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
936 : NULL);
937 : }
938 :
939 :
940 : /**
941 : * The main function of the report generator service.
942 : *
943 : * @param argc number of arguments from the command line
944 : * @param argv command line arguments
945 : * @return 0 ok, 1 on error
946 : */
947 : int
948 1 : main (int argc,
949 : char *const *argv)
950 : {
951 1 : struct GNUNET_GETOPT_CommandLineOption options[] = {
952 1 : GNUNET_GETOPT_option_flag ('t',
953 : "test",
954 : "run in test mode and exit when idle",
955 : &test_mode),
956 1 : GNUNET_GETOPT_option_timetravel ('T',
957 : "timetravel"),
958 1 : GNUNET_GETOPT_option_version (VERSION),
959 : GNUNET_GETOPT_OPTION_END
960 : };
961 : enum GNUNET_GenericReturnValue ret;
962 :
963 1 : ret = GNUNET_PROGRAM_run (
964 : TALER_MERCHANT_project_data (),
965 : argc, argv,
966 : "taler-merchant-report-generator",
967 : "Fetch and transmit periodic merchant reports",
968 : options,
969 : &run,
970 : NULL);
971 1 : if (GNUNET_SYSERR == ret)
972 0 : return EXIT_INVALIDARGUMENT;
973 1 : if (GNUNET_NO == ret)
974 0 : return EXIT_SUCCESS;
975 1 : return global_ret;
976 : }
977 :
978 :
979 : /* end of taler-merchant-report-generator.c */
|