merchant

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

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


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