merchant

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

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


      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 + off,
    481                                       report_len - off);
    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   /* 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   ra->eh = NULL;
    623   if (NULL == ra->job)
    624   {
    625     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     finish_transmission (ra,
    629                          TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
    630                          "GNUNET_CURL_job_add_raw");
    631     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 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   struct GNUNET_TIME_Absolute *next = cls;
    672   struct ReportActivity *ra;
    673 
    674   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   *next = next_transmission;
    679   if (GNUNET_TIME_absolute_is_future (next_transmission))
    680     return;
    681   *next = GNUNET_TIME_UNIT_ZERO_ABS; /* there might be more! */
    682   if ( (one_shot) ||
    683        (GNUNET_TIME_relative_is_zero (frequency)) )
    684   {
    685     next_transmission = GNUNET_TIME_UNIT_FOREVER_ABS;
    686   }
    687   else
    688   {
    689     next_transmission =
    690       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   if (! GNUNET_TIME_absolute_is_future (next_transmission))
    697   {
    698     /* frequency near-zero!? */
    699     GNUNET_break (0);
    700     next_transmission = GNUNET_TIME_relative_to_absolute (
    701       GNUNET_TIME_UNIT_MINUTES);
    702   }
    703   ra = GNUNET_new (struct ReportActivity);
    704   ra->instance_id = GNUNET_strdup (instance_id);
    705   ra->report_id = report_id;
    706   ra->next_transmission = next_transmission;
    707   ra->report_program_section = GNUNET_strdup (report_program_section);
    708   ra->report_description = GNUNET_strdup (report_description);
    709   ra->target_address = GNUNET_strdup (target_address);
    710   ra->mime_type = GNUNET_strdup (mime_type);
    711   ra->one_shot = one_shot;
    712   GNUNET_CONTAINER_DLL_insert (ra_head,
    713                                ra_tail,
    714                                ra);
    715   fetch_and_transmit (ra,
    716                       mime_type,
    717                       report_token);
    718 }
    719 
    720 
    721 static void
    722 check_pending_reports (void *cls)
    723 {
    724   enum GNUNET_DB_QueryStatus qs;
    725   struct GNUNET_TIME_Absolute next;
    726 
    727   (void) cls;
    728   report_task = NULL;
    729   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    730               "Checking for pending reports...\n");
    731   next = GNUNET_TIME_UNIT_FOREVER_ABS;
    732   qs = TALER_MERCHANTDB_lookup_reports_pending (pg,
    733                                                 &process_pending_report,
    734                                                 &next);
    735   if (qs < 0)
    736   {
    737     GNUNET_break (0);
    738     global_ret = EXIT_FAILURE;
    739     GNUNET_SCHEDULER_shutdown ();
    740     return;
    741   }
    742   if (NULL != ra_head)
    743     return; /* wait for completion */
    744   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    745               "Found %d reports pending, next at %s\n",
    746               (int) qs,
    747               GNUNET_TIME_absolute2s (next));
    748   GNUNET_assert (NULL == report_task);
    749   if (test_mode &&
    750       GNUNET_TIME_absolute_is_future (next) &&
    751       (NULL == ra_head))
    752   {
    753     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    754                 "Test mode, existing because of going idle\n");
    755     GNUNET_SCHEDULER_shutdown ();
    756     return;
    757   }
    758   report_task_due = next;
    759   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 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   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    782               "Received MERCHANT_REPORT_UPDATE event\n");
    783   /* Cancel any pending check and schedule immediate execution */
    784   if (NULL != report_task)
    785     GNUNET_SCHEDULER_cancel (report_task);
    786   report_task_due = GNUNET_TIME_UNIT_ZERO_ABS;
    787   report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
    788                                           NULL);
    789 }
    790 
    791 
    792 /**
    793  * Shutdown the service cleanly.
    794  *
    795  * @param cls closure (unused)
    796  */
    797 static void
    798 do_shutdown (void *cls)
    799 {
    800   struct ReportActivity *ra;
    801 
    802   (void) cls;
    803 
    804   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    805               "Shutting down report generator service\n");
    806 
    807   while (NULL != (ra = ra_head))
    808     free_ra (ra);
    809 
    810   if (NULL != report_task)
    811   {
    812     GNUNET_SCHEDULER_cancel (report_task);
    813     report_task = NULL;
    814   }
    815   if (NULL != curl_rc)
    816   {
    817     GNUNET_CURL_gnunet_rc_destroy (curl_rc);
    818     curl_rc = NULL;
    819   }
    820   if (NULL != curl_ctx)
    821   {
    822     GNUNET_CURL_fini (curl_ctx);
    823     curl_ctx = NULL;
    824   }
    825   if (NULL != eh)
    826   {
    827     TALER_MERCHANTDB_event_listen_cancel (eh);
    828     eh = NULL;
    829   }
    830   if (NULL != pg)
    831   {
    832     TALER_MERCHANTDB_disconnect (pg);
    833     pg = NULL;
    834   }
    835   GNUNET_free (base_url);
    836   base_url = NULL;
    837 }
    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 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   cfg = config;
    859   if (GNUNET_OK !=
    860       GNUNET_CONFIGURATION_get_value_string (cfg,
    861                                              "merchant",
    862                                              "BASE_URL",
    863                                              &base_url))
    864   {
    865     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    866                                "merchant",
    867                                "BASE_URL");
    868     global_ret = EXIT_NOTCONFIGURED;
    869     return;
    870   }
    871   if (! TALER_is_web_url (base_url))
    872   {
    873     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    874                                "merchant",
    875                                "BASE_URL",
    876                                "Not a Web URL");
    877     global_ret = EXIT_NOTCONFIGURED;
    878     return;
    879   }
    880 
    881   /* Ensure base_url ends with '/' */
    882   if ('/' != base_url[strlen (base_url) - 1])
    883   {
    884     char *tmp;
    885 
    886     GNUNET_asprintf (&tmp,
    887                      "%s/",
    888                      base_url);
    889     GNUNET_free (base_url);
    890     base_url = tmp;
    891   }
    892 
    893   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    894                                  NULL);
    895 
    896   curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    897                                &curl_rc);
    898   if (NULL == curl_ctx)
    899   {
    900     GNUNET_break (0);
    901     global_ret = EXIT_FAILURE;
    902     GNUNET_SCHEDULER_shutdown ();
    903     return;
    904   }
    905   curl_rc = GNUNET_CURL_gnunet_rc_create (curl_ctx);
    906 
    907   pg = TALER_MERCHANTDB_connect (cfg);
    908   if (NULL == pg)
    909   {
    910     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    911                 "Failed to connect to database. Consider running taler-merchant-dbconfig!\n");
    912     global_ret = EXIT_NOTINSTALLED;
    913     GNUNET_SCHEDULER_shutdown ();
    914     return;
    915   }
    916   {
    917     struct GNUNET_DB_EventHeaderP ev = {
    918       .size = htons (sizeof (ev)),
    919       .type = htons (TALER_DBEVENT_MERCHANT_REPORT_UPDATE)
    920     };
    921 
    922     eh = TALER_MERCHANTDB_event_listen (pg,
    923                                         &ev,
    924                                         GNUNET_TIME_UNIT_FOREVER_REL,
    925                                         &report_update_cb,
    926                                         NULL);
    927     if (NULL == eh)
    928     {
    929       GNUNET_break (0);
    930       global_ret = EXIT_FAILURE;
    931       GNUNET_SCHEDULER_shutdown ();
    932       return;
    933     }
    934   }
    935   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 main (int argc,
    949       char *const *argv)
    950 {
    951   struct GNUNET_GETOPT_CommandLineOption options[] = {
    952     GNUNET_GETOPT_option_flag ('t',
    953                                "test",
    954                                "run in test mode and exit when idle",
    955                                &test_mode),
    956     GNUNET_GETOPT_option_timetravel ('T',
    957                                      "timetravel"),
    958     GNUNET_GETOPT_option_version (VERSION),
    959     GNUNET_GETOPT_OPTION_END
    960   };
    961   enum GNUNET_GenericReturnValue ret;
    962 
    963   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   if (GNUNET_SYSERR == ret)
    972     return EXIT_INVALIDARGUMENT;
    973   if (GNUNET_NO == ret)
    974     return EXIT_SUCCESS;
    975   return global_ret;
    976 }
    977 
    978 
    979 /* end of taler-merchant-report-generator.c */