merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-report-generator.c (25894B)


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