merchant

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

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


      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/lookup_reports_pending.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 free_ra (struct ReportActivity *ra)
    204 {
    205   if (NULL != ra->cwh)
    206   {
    207     GNUNET_wait_child_cancel (ra->cwh);
    208     ra->cwh = NULL;
    209   }
    210   if (NULL != ra->proc)
    211   {
    212     GNUNET_break (GNUNET_OK ==
    213                   GNUNET_process_kill (ra->proc,
    214                                        SIGKILL));
    215     GNUNET_break (GNUNET_OK ==
    216                   GNUNET_process_wait (ra->proc,
    217                                        true,
    218                                        NULL,
    219                                        NULL));
    220     GNUNET_process_destroy (ra->proc);
    221     ra->proc = NULL;
    222   }
    223   TALER_curl_easy_post_finished (&ra->post_ctx);
    224   if (NULL != ra->eh)
    225   {
    226     curl_easy_cleanup (ra->eh);
    227     ra->eh = NULL;
    228   }
    229   if (NULL != ra->job)
    230   {
    231     GNUNET_CURL_job_cancel (ra->job);
    232     ra->job = NULL;
    233   }
    234   GNUNET_CONTAINER_DLL_remove (ra_head,
    235                                ra_tail,
    236                                ra);
    237   GNUNET_free (ra->instance_id);
    238   GNUNET_free (ra->report_program_section);
    239   GNUNET_free (ra->report_description);
    240   GNUNET_free (ra->target_address);
    241   GNUNET_free (ra->mime_type);
    242   GNUNET_free (ra->url);
    243   GNUNET_free (ra);
    244 }
    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 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   next_ts = GNUNET_TIME_absolute_to_timestamp (ra->next_transmission);
    272   qs = TALER_MERCHANTDB_set_instance (pg,
    273                                       ra->instance_id);
    274   if (qs <= 0)
    275   {
    276     free_ra (ra);
    277     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    278                 "Failed to set instance to update report status: %d\n",
    279                 qs);
    280     global_ret = EXIT_FAILURE;
    281     GNUNET_SCHEDULER_shutdown ();
    282     return;
    283   }
    284   if ( (TALER_EC_NONE == ec) &&
    285        (ra->one_shot) )
    286   {
    287     qs = TALER_MERCHANTDB_delete_report (pg,
    288                                          ra->instance_id,
    289                                          ra->report_id);
    290   }
    291   else
    292   {
    293     qs = TALER_MERCHANTDB_update_report_status (pg,
    294                                                 ra->instance_id,
    295                                                 ra->report_id,
    296                                                 next_ts,
    297                                                 ec,
    298                                                 error_details);
    299   }
    300   if (qs < 0)
    301   {
    302     free_ra (ra);
    303     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    304                 "Failed to update report status: %d\n",
    305                 qs);
    306     global_ret = EXIT_FAILURE;
    307     GNUNET_SCHEDULER_shutdown ();
    308     return;
    309   }
    310   if ( (NULL == report_task) ||
    311        (GNUNET_TIME_absolute_cmp (report_task_due,
    312                                   >,
    313                                   ra->next_transmission)) )
    314   {
    315     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    316                 "Scheduling next report for %s\n",
    317                 GNUNET_TIME_absolute2s (ra->next_transmission));
    318     if (NULL != report_task)
    319       GNUNET_SCHEDULER_cancel (report_task);
    320     report_task_due = ra->next_transmission;
    321     report_task = GNUNET_SCHEDULER_add_at (ra->next_transmission,
    322                                            &check_pending_reports,
    323                                            NULL);
    324   }
    325   free_ra (ra);
    326   if (test_mode &&
    327       GNUNET_TIME_absolute_is_future (report_task_due) &&
    328       (NULL == ra_head))
    329   {
    330     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    331                 "Test mode, exiting because of going idle\n");
    332     GNUNET_SCHEDULER_shutdown ();
    333     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 child_completed_cb (void *cls,
    347                     enum GNUNET_OS_ProcessStatusType type,
    348                     long unsigned int exit_code)
    349 {
    350   struct ReportActivity *ra = cls;
    351   enum TALER_ErrorCode ec;
    352   char *error_details = NULL;
    353 
    354   ra->cwh = NULL;
    355   GNUNET_process_destroy (ra->proc);
    356   ra->proc = NULL;
    357   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
    358        (0 != exit_code) )
    359   {
    360     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    361                 "Report transmission program failed with status %d/%lu\n",
    362                 (int) type,
    363                 exit_code);
    364     ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    365     GNUNET_asprintf (&error_details,
    366                      "Report transmission program exited with status %d/%lu",
    367                      (int) type,
    368                      exit_code);
    369   }
    370   else
    371   {
    372     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    373                 "Report transmitted successfully\n");
    374     ec = TALER_EC_NONE;
    375   }
    376   finish_transmission (ra,
    377                        ec,
    378                        error_details);
    379   GNUNET_free (error_details);
    380 }
    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 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     GNUNET_asprintf (&section,
    402                      "report-generator-%s",
    403                      ra->report_program_section);
    404     if (GNUNET_OK !=
    405         GNUNET_CONFIGURATION_get_value_string (cfg,
    406                                                section,
    407                                                "BINARY",
    408                                                (char **) &binary))
    409     {
    410       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    411                                  section,
    412                                  "BINARY");
    413       finish_transmission (ra,
    414                            TALER_EC_MERCHANT_GENERIC_REPORT_GENERATOR_UNCONFIGURED,
    415                            section);
    416       GNUNET_free (section);
    417       return;
    418     }
    419     GNUNET_free (section);
    420   }
    421 
    422   {
    423     struct GNUNET_DISK_PipeHandle *stdin_pipe;
    424 
    425     stdin_pipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
    426     if (NULL == stdin_pipe)
    427     {
    428       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    429                            "pipe");
    430       finish_transmission (ra,
    431                            TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE,
    432                            "pipe");
    433       return;
    434     }
    435 
    436     ra->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    437     GNUNET_assert (GNUNET_OK ==
    438                    GNUNET_process_set_options (
    439                      ra->proc,
    440                      GNUNET_process_option_inherit_rpipe (stdin_pipe,
    441                                                           STDIN_FILENO)));
    442     if (GNUNET_OK !=
    443         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       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    455                                 "exec",
    456                                 binary);
    457       GNUNET_process_destroy (ra->proc);
    458       ra->proc = NULL;
    459       GNUNET_DISK_pipe_close (stdin_pipe);
    460       finish_transmission (ra,
    461                            TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
    462                            "Could not execute report generator binary");
    463       return;
    464     }
    465 
    466     /* Write report data to stdin of child process */
    467     stdin_handle = GNUNET_DISK_pipe_detach_end (stdin_pipe,
    468                                                 GNUNET_DISK_PIPE_END_WRITE);
    469     GNUNET_DISK_pipe_close (stdin_pipe);
    470   }
    471 
    472   {
    473     size_t off = 0;
    474 
    475     while (off < report_len)
    476     {
    477       ssize_t wrote;
    478 
    479       wrote = GNUNET_DISK_file_write (stdin_handle,
    480                                       report,
    481                                       report_len);
    482       if (wrote <= 0)
    483         break;
    484       off += (size_t) wrote;
    485     }
    486     GNUNET_DISK_file_close (stdin_handle);
    487 
    488     if (off != report_len)
    489     {
    490       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    491                   "Failed to write report data to child process stdin\n");
    492       finish_transmission (ra,
    493                            TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
    494                            "Failed to write to transmission program");
    495       return;
    496     }
    497   }
    498 
    499   /* Wait for child to complete */
    500   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 curl_completed_cb (void *cls,
    516                    long response_code,
    517                    const void *body,
    518                    size_t body_size)
    519 {
    520   struct ReportActivity *ra = cls;
    521 
    522   ra->job = NULL;
    523   ra->response_code = response_code;
    524   if (MHD_HTTP_OK != response_code)
    525   {
    526     char *error_details;
    527 
    528     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    529                 "Failed to fetch report data: HTTP %ld\n",
    530                 response_code);
    531     GNUNET_asprintf (&error_details,
    532                      "HTTP request failed with status %ld from `%s'",
    533                      response_code,
    534                      ra->url);
    535     finish_transmission (ra,
    536                          TALER_EC_MERCHANT_REPORT_FETCH_FAILED,
    537                          error_details);
    538     GNUNET_free (error_details);
    539     return;
    540   }
    541   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 fetch_and_transmit (
    557   struct ReportActivity *ra,
    558   const char *mime_type,
    559   const struct TALER_MERCHANT_ReportToken *report_token)
    560 {
    561   GNUNET_asprintf (&ra->url,
    562                    "%sreports/%llu",
    563                    base_url,
    564                    (unsigned long long) ra->report_id);
    565   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    566               "Fetching report from %s\n",
    567               ra->url);
    568   ra->eh = curl_easy_init ();
    569   if (NULL == ra->eh)
    570   {
    571     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    572                 "Failed to initialize CURL handle\n");
    573     finish_transmission (ra,
    574                          TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
    575                          "curl_easy_init");
    576     return;
    577   }
    578 
    579   {
    580     char *accept_header;
    581 
    582     GNUNET_asprintf (&accept_header,
    583                      "Accept: %s",
    584                      mime_type);
    585     ra->post_ctx.headers = curl_slist_append (ra->post_ctx.headers,
    586                                               accept_header);
    587     GNUNET_free (accept_header);
    588   }
    589   GNUNET_assert (CURLE_OK ==
    590                  curl_easy_setopt (ra->eh,
    591                                    CURLOPT_URL,
    592                                    ra->url));
    593   {
    594     json_t *req;
    595 
    596     req = GNUNET_JSON_PACK (
    597       GNUNET_JSON_pack_data_auto ("report_token",
    598                                   report_token));
    599     if (GNUNET_OK !=
    600         TALER_curl_easy_post (&ra->post_ctx,
    601                               ra->eh,
    602                               req))
    603     {
    604       GNUNET_break (0);
    605       json_decref (req);
    606       finish_transmission (ra,
    607                            TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
    608                            "TALER_curl_easy_post");
    609       return;
    610     }
    611     json_decref (req);
    612   }
    613   ra->job = GNUNET_CURL_job_add_raw (curl_ctx,
    614                                      ra->eh,
    615                                      ra->post_ctx.headers,
    616                                      &curl_completed_cb,
    617                                      ra);
    618   ra->eh = NULL;
    619 }
    620 
    621 
    622 /**
    623  * Callback invoked for each pending report.
    624  *
    625  * @param cls closure
    626  * @param instance_id name of the instance
    627  * @param report_id serial number of the report
    628  * @param report_program_section configuration section of program
    629  *   for report generation
    630  * @param report_description text describing the report
    631  * @param mime_type mime type to request
    632  * @param report_token token to authorize access to the data source
    633  * @param target_address where to send report data
    634  * @param frequency report frequency
    635  * @param frequency_shift how much to shift the report time from a
    636  *   multiple of the report @a frequency
    637  * @param next_transmission when is the next transmission of this report
    638  *   due
    639  * @param one_shot true if the report should be removed from the
    640  *   list after generation instead of being repeated
    641  */
    642 static void
    643 process_pending_report (
    644   void *cls,
    645   const char *instance_id,
    646   uint64_t report_id,
    647   const char *report_program_section,
    648   const char *report_description,
    649   const char *mime_type,
    650   const struct TALER_MERCHANT_ReportToken *report_token,
    651   const char *target_address,
    652   struct GNUNET_TIME_Relative frequency,
    653   struct GNUNET_TIME_Relative frequency_shift,
    654   struct GNUNET_TIME_Absolute next_transmission,
    655   bool one_shot)
    656 {
    657   struct GNUNET_TIME_Absolute *next = cls;
    658   struct ReportActivity *ra;
    659 
    660   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    661               "Next report %llu is pending at %s\n",
    662               (unsigned long long) report_id,
    663               GNUNET_TIME_absolute2s (next_transmission));
    664   *next = next_transmission;
    665   if (GNUNET_TIME_absolute_is_future (next_transmission))
    666     return;
    667   *next = GNUNET_TIME_UNIT_ZERO_ABS; /* there might be more! */
    668   if ( (one_shot) ||
    669        (GNUNET_TIME_relative_is_zero (frequency)) )
    670   {
    671     next_transmission = GNUNET_TIME_UNIT_FOREVER_ABS;
    672   }
    673   else
    674   {
    675     next_transmission =
    676       GNUNET_TIME_absolute_add (
    677         GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
    678                                          frequency),
    679         GNUNET_TIME_relative_add (frequency,
    680                                   frequency_shift));
    681   }
    682   if (! GNUNET_TIME_absolute_is_future (next_transmission))
    683   {
    684     /* frequency near-zero!? */
    685     GNUNET_break (0);
    686     next_transmission = GNUNET_TIME_relative_to_absolute (
    687       GNUNET_TIME_UNIT_MINUTES);
    688   }
    689   ra = GNUNET_new (struct ReportActivity);
    690   ra->instance_id = GNUNET_strdup (instance_id);
    691   ra->report_id = report_id;
    692   ra->next_transmission = next_transmission;
    693   ra->report_program_section = GNUNET_strdup (report_program_section);
    694   ra->report_description = GNUNET_strdup (report_description);
    695   ra->target_address = GNUNET_strdup (target_address);
    696   ra->mime_type = GNUNET_strdup (mime_type);
    697   ra->one_shot = one_shot;
    698   GNUNET_CONTAINER_DLL_insert (ra_head,
    699                                ra_tail,
    700                                ra);
    701   fetch_and_transmit (ra,
    702                       mime_type,
    703                       report_token);
    704 }
    705 
    706 
    707 static void
    708 check_pending_reports (void *cls)
    709 {
    710   enum GNUNET_DB_QueryStatus qs;
    711   struct GNUNET_TIME_Absolute next;
    712 
    713   (void) cls;
    714   report_task = NULL;
    715   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    716               "Checking for pending reports...\n");
    717   next = GNUNET_TIME_UNIT_FOREVER_ABS;
    718   qs = TALER_MERCHANTDB_lookup_reports_pending (pg,
    719                                                 &process_pending_report,
    720                                                 &next);
    721   if (qs < 0)
    722   {
    723     GNUNET_break (0);
    724     global_ret = EXIT_FAILURE;
    725     GNUNET_SCHEDULER_shutdown ();
    726     return;
    727   }
    728   if (NULL != ra_head)
    729     return; /* wait for completion */
    730   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    731               "Found %d reports pending, next at %s\n",
    732               (int) qs,
    733               GNUNET_TIME_absolute2s (next));
    734   GNUNET_assert (NULL == report_task);
    735   if (test_mode &&
    736       GNUNET_TIME_absolute_is_future (next) &&
    737       (NULL == ra_head))
    738   {
    739     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    740                 "Test mode, existing because of going idle\n");
    741     GNUNET_SCHEDULER_shutdown ();
    742     return;
    743   }
    744   report_task_due = next;
    745   report_task = GNUNET_SCHEDULER_add_at (next,
    746                                          &check_pending_reports,
    747                                          NULL);
    748 }
    749 
    750 
    751 /**
    752  * Callback invoked when a MERCHANT_REPORT_UPDATE event is received.
    753  *
    754  * @param cls closure (unused)
    755  * @param extra additional event data (unused)
    756  * @param extra_size size of @a extra
    757  */
    758 static void
    759 report_update_cb (void *cls,
    760                   const void *extra,
    761                   size_t extra_size)
    762 {
    763   (void) cls;
    764   (void) extra;
    765   (void) extra_size;
    766 
    767   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    768               "Received MERCHANT_REPORT_UPDATE event\n");
    769   /* Cancel any pending check and schedule immediate execution */
    770   if (NULL != report_task)
    771     GNUNET_SCHEDULER_cancel (report_task);
    772   report_task_due = GNUNET_TIME_UNIT_ZERO_ABS;
    773   report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
    774                                           NULL);
    775 }
    776 
    777 
    778 /**
    779  * Shutdown the service cleanly.
    780  *
    781  * @param cls closure (unused)
    782  */
    783 static void
    784 do_shutdown (void *cls)
    785 {
    786   struct ReportActivity *ra;
    787 
    788   (void) cls;
    789 
    790   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    791               "Shutting down report generator service\n");
    792 
    793   while (NULL != (ra = ra_head))
    794     free_ra (ra);
    795 
    796   if (NULL != report_task)
    797   {
    798     GNUNET_SCHEDULER_cancel (report_task);
    799     report_task = NULL;
    800   }
    801   if (NULL != curl_rc)
    802   {
    803     GNUNET_CURL_gnunet_rc_destroy (curl_rc);
    804     curl_rc = NULL;
    805   }
    806   if (NULL != curl_ctx)
    807   {
    808     GNUNET_CURL_fini (curl_ctx);
    809     curl_ctx = NULL;
    810   }
    811   if (NULL != eh)
    812   {
    813     TALER_MERCHANTDB_event_listen_cancel (eh);
    814     eh = NULL;
    815   }
    816   if (NULL != pg)
    817   {
    818     TALER_MERCHANTDB_disconnect (pg);
    819     pg = NULL;
    820   }
    821   GNUNET_free (base_url);
    822   base_url = NULL;
    823 }
    824 
    825 
    826 /**
    827  * Main function for the report generator service.
    828  *
    829  * @param cls closure
    830  * @param args remaining command-line arguments
    831  * @param cfgfile name of the configuration file used
    832  * @param config configuration
    833  */
    834 static void
    835 run (void *cls,
    836      char *const *args,
    837      const char *cfgfile,
    838      const struct GNUNET_CONFIGURATION_Handle *config)
    839 {
    840   (void) cls;
    841   (void) args;
    842   (void) cfgfile;
    843 
    844   cfg = config;
    845   if (GNUNET_OK !=
    846       GNUNET_CONFIGURATION_get_value_string (cfg,
    847                                              "merchant",
    848                                              "BASE_URL",
    849                                              &base_url))
    850   {
    851     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    852                                "merchant",
    853                                "BASE_URL");
    854     global_ret = EXIT_NOTCONFIGURED;
    855     return;
    856   }
    857   if (! TALER_is_web_url (base_url))
    858   {
    859     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    860                                "merchant",
    861                                "BASE_URL",
    862                                "Not a Web URL");
    863     global_ret = EXIT_NOTCONFIGURED;
    864     return;
    865   }
    866 
    867   /* Ensure base_url ends with '/' */
    868   if ('/' != base_url[strlen (base_url) - 1])
    869   {
    870     char *tmp;
    871 
    872     GNUNET_asprintf (&tmp,
    873                      "%s/",
    874                      base_url);
    875     GNUNET_free (base_url);
    876     base_url = tmp;
    877   }
    878 
    879   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    880                                  NULL);
    881 
    882   curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    883                                &curl_rc);
    884   if (NULL == curl_ctx)
    885   {
    886     GNUNET_break (0);
    887     global_ret = EXIT_FAILURE;
    888     GNUNET_SCHEDULER_shutdown ();
    889     return;
    890   }
    891   curl_rc = GNUNET_CURL_gnunet_rc_create (curl_ctx);
    892 
    893   pg = TALER_MERCHANTDB_connect (cfg);
    894   if (NULL == pg)
    895   {
    896     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    897                 "Failed to connect to database. Consider running taler-merchant-dbconfig!\n");
    898     global_ret = EXIT_NOTINSTALLED;
    899     GNUNET_SCHEDULER_shutdown ();
    900     return;
    901   }
    902   {
    903     struct GNUNET_DB_EventHeaderP ev = {
    904       .size = htons (sizeof (ev)),
    905       .type = htons (TALER_DBEVENT_MERCHANT_REPORT_UPDATE)
    906     };
    907 
    908     eh = TALER_MERCHANTDB_event_listen (pg,
    909                                         &ev,
    910                                         GNUNET_TIME_UNIT_FOREVER_REL,
    911                                         &report_update_cb,
    912                                         NULL);
    913     if (NULL == eh)
    914     {
    915       GNUNET_break (0);
    916       global_ret = EXIT_FAILURE;
    917       GNUNET_SCHEDULER_shutdown ();
    918       return;
    919     }
    920   }
    921   report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
    922                                           NULL);
    923 }
    924 
    925 
    926 /**
    927  * The main function of the report generator service.
    928  *
    929  * @param argc number of arguments from the command line
    930  * @param argv command line arguments
    931  * @return 0 ok, 1 on error
    932  */
    933 int
    934 main (int argc,
    935       char *const *argv)
    936 {
    937   struct GNUNET_GETOPT_CommandLineOption options[] = {
    938     GNUNET_GETOPT_option_flag ('t',
    939                                "test",
    940                                "run in test mode and exit when idle",
    941                                &test_mode),
    942     GNUNET_GETOPT_option_timetravel ('T',
    943                                      "timetravel"),
    944     GNUNET_GETOPT_option_version (VERSION),
    945     GNUNET_GETOPT_OPTION_END
    946   };
    947   enum GNUNET_GenericReturnValue ret;
    948 
    949   ret = GNUNET_PROGRAM_run (
    950     TALER_MERCHANT_project_data (),
    951     argc, argv,
    952     "taler-merchant-report-generator",
    953     "Fetch and transmit periodic merchant reports",
    954     options,
    955     &run,
    956     NULL);
    957   if (GNUNET_SYSERR == ret)
    958     return EXIT_INVALIDARGUMENT;
    959   if (GNUNET_NO == ret)
    960     return EXIT_SUCCESS;
    961   return global_ret;
    962 }
    963 
    964 
    965 /* end of taler-merchant-report-generator.c */