LCOV - code coverage report
Current view: top level - backend - taler-merchant-report-generator.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 51.2 % 281 144
Test Date: 2026-09-04 23:42:01 Functions: 75.0 % 12 9

            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 (&section,
     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 */
        

Generated by: LCOV version 2.0-1