anastasis-cli-redux.c (11574B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020,2021,2022 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Anastasis 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file cli/anastasis-cli-redux.c 18 * @brief command line tool for our reducer 19 * @author Christian Grothoff 20 * @author Dennis Neufeld 21 * @author Dominik Meister 22 */ 23 24 #include "platform.h" 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "anastasis_redux.h" 28 #include <taler/taler_util.h> 29 #include <taler/taler_error_codes.h> 30 #include <taler/taler_json_lib.h> 31 #include "anastasis_util_lib.h" 32 #include "anastasis-cli-common.h" 33 34 /** 35 * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). 36 */ 37 static struct GNUNET_CURL_RescheduleContext *rc; 38 39 /** 40 * Curl context for communication with anastasis backend 41 */ 42 static struct GNUNET_CURL_Context *ctx; 43 44 /** 45 * Application ID to include in the user attributes. 46 * (-a option). 47 */ 48 char *application_id; 49 50 /** 51 * -b option given. 52 */ 53 static int b_flag; 54 55 /** 56 * -r option given. 57 */ 58 static int r_flag; 59 60 /** 61 * Input to -a option given. 62 */ 63 static char *input; 64 65 /** 66 * Output filename, if given. 67 */ 68 static char *output_filename; 69 70 /** 71 * JSON containing previous state 72 */ 73 static json_t *prev_state; 74 75 /** 76 * JSON containing arguments for action 77 */ 78 static json_t *arguments; 79 80 /** 81 * Handle to an ongoing action. 82 */ 83 static struct ANASTASIS_ReduxAction *ra; 84 85 /** 86 * Return value from main. Defaults to failure so that every path which shuts 87 * down without an action having run to completion — a parse error, a bad 88 * command line, or a signal — is reported to the caller's shell. 89 */ 90 static int global_ret = 1; 91 92 93 /** 94 * Persist a json state, report errors. 95 * 96 * @param state to persist 97 * @param filename where to write the state to, NULL for stdout 98 * @return #GNUNET_OK if the state was written out 99 */ 100 static enum GNUNET_GenericReturnValue 101 persist_new_state (json_t *state, 102 const char *filename) 103 { 104 if (NULL != filename) 105 { 106 if (0 != 107 json_dump_file (state, 108 filename, 109 JSON_COMPACT)) 110 { 111 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 112 "Could not dump state to `%s'\n", 113 filename); 114 return GNUNET_SYSERR; 115 } 116 return GNUNET_OK; 117 } 118 { 119 char *state_str = json_dumps (state, 120 JSON_COMPACT); 121 122 if (NULL == state_str) 123 { 124 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 125 "Could not serialize state to JSON\n"); 126 return GNUNET_SYSERR; 127 } 128 if (-1 >= 129 fprintf (stdout, 130 "%s", 131 state_str)) 132 { 133 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 134 "Could not dump state to stdout\n"); 135 GNUNET_free (state_str); 136 return GNUNET_SYSERR; 137 } 138 GNUNET_free (state_str); 139 /* stdio may only report a full output device at flush time, and the 140 implicit flush at exit has nowhere left to report it to. */ 141 if (0 != fflush (stdout)) 142 { 143 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 144 "fflush"); 145 return GNUNET_SYSERR; 146 } 147 } 148 return GNUNET_OK; 149 } 150 151 152 /** 153 * Function called with the results of #ANASTASIS_redux_action(). 154 * 155 * @param cls closure 156 * @param error_code Error code 157 * @param result_state new state as result 158 */ 159 static void 160 action_cb (void *cls, 161 enum TALER_ErrorCode error_code, 162 json_t *result_state) 163 { 164 bool persisted = true; 165 166 (void) cls; 167 ra = NULL; 168 if (NULL != result_state) 169 persisted = (GNUNET_OK == 170 persist_new_state (result_state, 171 output_filename)); 172 /* A state we could not write out is a failure of this tool even when the 173 reduction itself succeeded, so both conditions must hold. */ 174 if ( (TALER_EC_NONE == error_code) && 175 persisted) 176 global_ret = 0; 177 if (TALER_EC_NONE != error_code) 178 { 179 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 180 "Redux failed with error %d: %s\n", 181 error_code, 182 TALER_ErrorCode_get_hint (error_code)); 183 json_dumpf (result_state, 184 stderr, 185 JSON_INDENT (2)); 186 } 187 GNUNET_SCHEDULER_shutdown (); 188 } 189 190 191 /** 192 * @brief Shutdown the application. 193 * 194 * @param cls closure 195 */ 196 static void 197 shutdown_task (void *cls) 198 { 199 (void) cls; 200 201 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 202 "Shutdown initiated\n"); 203 if (NULL != ra) 204 { 205 ANASTASIS_redux_action_cancel (ra); 206 ra = NULL; 207 } 208 ANASTASIS_redux_done (); 209 if (NULL != ctx) 210 { 211 GNUNET_CURL_fini (ctx); 212 ctx = NULL; 213 } 214 if (NULL != rc) 215 { 216 GNUNET_CURL_gnunet_rc_destroy (rc); 217 rc = NULL; 218 } 219 json_decref (prev_state); 220 prev_state = NULL; 221 json_decref (arguments); 222 arguments = NULL; 223 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 224 "Shutdown complete\n"); 225 } 226 227 228 /** 229 * @brief Start the application 230 * 231 * @param cls closure 232 * @param args arguments left 233 * @param cfgfile config file name 234 * @param cfg handle for the configuration file 235 */ 236 static void 237 run (void *cls, 238 char *const *args, 239 const char *cfgfile, 240 const struct GNUNET_CONFIGURATION_Handle *cfg) 241 { 242 json_error_t error; 243 244 (void) cls; 245 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 246 "Starting anastasis-reducer\n"); 247 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 248 NULL); 249 if (b_flag && r_flag) 250 { 251 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 252 "We cannot start backup and recovery at the same time!\n"); 253 global_ret = 1; 254 GNUNET_SCHEDULER_shutdown (); 255 return; 256 } 257 if (r_flag) 258 { 259 json_t *init_state; 260 261 init_state = ANASTASIS_recovery_start (cfg); 262 if (NULL == init_state) 263 { 264 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 265 "Failed to create an initial recovery state!\n"); 266 global_ret = 1; 267 GNUNET_SCHEDULER_shutdown (); 268 return; 269 } 270 if (GNUNET_OK == 271 persist_new_state (init_state, 272 args[0])) 273 global_ret = 0; 274 json_decref (init_state); 275 GNUNET_SCHEDULER_shutdown (); 276 return; 277 } 278 if (b_flag) 279 { 280 json_t *init_state; 281 282 init_state = ANASTASIS_backup_start (cfg); 283 if (NULL == init_state) 284 { 285 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 286 "Failed to create an initial backup state!\n"); 287 global_ret = 1; 288 GNUNET_SCHEDULER_shutdown (); 289 return; 290 } 291 if (GNUNET_OK == 292 persist_new_state (init_state, 293 args[0])) 294 global_ret = 0; 295 json_decref (init_state); 296 GNUNET_SCHEDULER_shutdown (); 297 return; 298 } 299 300 /* action processing */ 301 { 302 const char *action = args[0]; 303 304 if (NULL == action) 305 { 306 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 307 "You must specify an action as the first argument (or `-b' or `-r')\n"); 308 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 309 "Example: anastasis-reducer back\n"); 310 global_ret = 1; 311 GNUNET_SCHEDULER_shutdown (); 312 return; 313 } 314 args++; 315 if (NULL != input) 316 { 317 arguments = json_loads (input, 318 JSON_DECODE_ANY, 319 &error); 320 if (NULL == arguments) 321 { 322 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 323 "Failed to parse arguments on line %u:%u: %s!\n", 324 error.line, 325 error.column, 326 error.text); 327 global_ret = 1; 328 GNUNET_SCHEDULER_shutdown (); 329 return; 330 } 331 } 332 if (NULL != args[0]) 333 { 334 prev_state = json_load_file (args[0], 335 JSON_DECODE_ANY, 336 &error); 337 args++; 338 } 339 else 340 { 341 prev_state = ANASTASIS_CLI_json_load_capped (stdin, 342 &error); 343 } 344 if (NULL == prev_state) 345 { 346 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 347 "Failed to parse initial state on line %u:%u: %s!\n", 348 error.line, 349 error.column, 350 error.text); 351 global_ret = 1; 352 GNUNET_SCHEDULER_shutdown (); 353 return; 354 } 355 output_filename = args[0]; 356 /* initialize HTTP client event loop */ 357 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 358 &rc); 359 rc = GNUNET_CURL_gnunet_rc_create (ctx); 360 ANASTASIS_redux_init (ctx); 361 /* Expand identity_attributes if -a is given explicitly and we 362 are at the respective step of the reduction */ 363 if ( (0 == strcasecmp (action, 364 "enter_user_attributes")) && 365 (NULL != application_id) && 366 (NULL != arguments) ) 367 { 368 json_t *attr = json_object_get (arguments, 369 "identity_attributes"); 370 if (NULL != attr) 371 GNUNET_assert (0 == 372 json_object_set_new (attr, 373 "application-id", 374 json_string (application_id))); 375 } 376 ra = ANASTASIS_redux_action (prev_state, 377 action, 378 arguments, 379 &action_cb, 380 cls); 381 } 382 } 383 384 385 int 386 main (int argc, 387 char *const *argv) 388 { 389 /* the available command line options */ 390 struct GNUNET_GETOPT_CommandLineOption options[] = { 391 GNUNET_GETOPT_option_string ('A', 392 "application", 393 "ID", 394 "set the application ID", 395 &application_id), 396 GNUNET_GETOPT_option_string ('a', 397 "arguments", 398 "JSON", 399 "pass a JSON string containing arguments to reducer", 400 &input), 401 GNUNET_GETOPT_option_flag ('b', 402 "backup", 403 "use reducer to handle states for backup process", 404 &b_flag), 405 GNUNET_GETOPT_option_flag ('r', 406 "restore", 407 "use reducer to handle states for restore process", 408 &r_flag), 409 GNUNET_GETOPT_OPTION_END 410 }; 411 enum GNUNET_GenericReturnValue ret; 412 413 ret = GNUNET_PROGRAM_run (ANASTASIS_project_data (), 414 argc, 415 argv, 416 "anastasis-reducer", 417 "This is an application for using Anastasis to handle the states.\n", 418 options, 419 &run, 420 NULL); 421 GNUNET_free (application_id); 422 if (GNUNET_SYSERR == ret) 423 return 3; 424 if (GNUNET_NO == ret) 425 return 0; 426 return global_ret; 427 } 428 429 430 /* end of anastasis-cli-redux.c */