testing_api_cmd_system_start.c (10209B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_system_start.c 21 * @brief run taler-benchmark-setup.sh command 22 * @author Christian Grothoff 23 */ 24 #include "platform.h" /* UNNECESSARY? */ 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_signatures.h" /* UNNECESSARY? */ 28 #include "taler/taler_testing_lib.h" 29 30 31 /** 32 * State for a "system" CMD. 33 */ 34 struct SystemState 35 { 36 37 /** 38 * System process. 39 */ 40 struct GNUNET_Process *system_proc; 41 42 /** 43 * Input pipe to @e system_proc, used to keep the 44 * process alive until we are done. 45 */ 46 struct GNUNET_DISK_PipeHandle *pipe_in; 47 48 /** 49 * Output pipe to @e system_proc, used to find out 50 * when the services are ready. 51 */ 52 struct GNUNET_DISK_PipeHandle *pipe_out; 53 54 /** 55 * Task reading from @e pipe_in. 56 */ 57 struct GNUNET_SCHEDULER_Task *reader; 58 59 /** 60 * Waiting for child to die. 61 */ 62 struct GNUNET_ChildWaitHandle *cwh; 63 64 /** 65 * Our interpreter state. 66 */ 67 struct TALER_TESTING_Interpreter *is; 68 69 /** 70 * NULL-terminated array of command-line arguments. 71 */ 72 char **args; 73 74 /** 75 * Input buffer for the stdin of the test setup helper. 76 */ 77 struct GNUNET_Buffer ibuf; 78 79 /** 80 * Did we find the ready tag? 81 */ 82 bool ready; 83 84 /** 85 * Is the child process still running? 86 */ 87 bool active; 88 }; 89 90 91 /** 92 * Defines a GNUNET_ChildCompletedCallback which is sent back 93 * upon death or completion of a child process. 94 * 95 * @param cls our `struct SystemState *` 96 * @param type type of the process 97 * @param exit_code status code of the process 98 */ 99 static void 100 setup_terminated (void *cls, 101 enum GNUNET_OS_ProcessStatusType type, 102 long unsigned int exit_code) 103 { 104 struct SystemState *as = cls; 105 106 as->cwh = NULL; 107 as->active = false; 108 if (NULL != as->reader) 109 { 110 GNUNET_SCHEDULER_cancel (as->reader); 111 as->reader = NULL; 112 } 113 if (! as->ready) 114 { 115 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 116 "Launching Taler system failed: %d/%llu\n", 117 (int) type, 118 (unsigned long long) exit_code); 119 TALER_TESTING_interpreter_fail (as->is); 120 return; 121 } 122 } 123 124 125 /** 126 * Start helper to read from stdout of child. 127 * 128 * @param as our system state 129 */ 130 static void 131 start_reader (struct SystemState *as); 132 133 #define READY_MARKER "READY:" 134 135 static void 136 read_stdout (void *cls) 137 { 138 struct SystemState *as = cls; 139 const struct GNUNET_DISK_FileHandle *fh; 140 char buf[1024]; 141 ssize_t ret; 142 size_t off = 0; 143 char *testroot = NULL; 144 145 as->reader = NULL; 146 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 147 GNUNET_DISK_PIPE_END_READ); 148 ret = GNUNET_DISK_file_read (fh, 149 buf, 150 sizeof (buf) - 1); 151 if (-1 == ret) 152 { 153 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 154 "read"); 155 TALER_TESTING_interpreter_fail (as->is); 156 return; 157 } 158 if (0 == ret) 159 { 160 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 161 "Child closed stdout\n"); 162 return; 163 } 164 GNUNET_buffer_write (&as->ibuf, 165 buf, 166 ret); 167 if ( (0 == strncmp (as->ibuf.mem, 168 READY_MARKER, 169 strlen (READY_MARKER))) && 170 (NULL != (testroot = memchr (as->ibuf.mem, 171 '\n', 172 as->ibuf.position))) ) 173 { 174 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 175 "Got test root %.*s\n", 176 (int) (as->ibuf.position - (testroot - as->ibuf.mem)), 177 testroot); 178 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 179 "Taler system UP\n"); 180 as->ready = true; 181 TALER_TESTING_interpreter_next (as->is); 182 return; 183 } 184 if ( (NULL != memchr (as->ibuf.mem, 185 '\n', 186 as->ibuf.position)) || 187 (as->ibuf.position > 4096) ) 188 { 189 TALER_TESTING_interpreter_fail (as->is); 190 /* Only commands are allowed! */ 191 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 192 "Unexpected stdout of test setup helper: %.*s\n", 193 (int) ret, 194 &buf[off]); 195 } 196 197 start_reader (as); 198 } 199 200 201 static void 202 start_reader (struct SystemState *as) 203 { 204 const struct GNUNET_DISK_FileHandle *fh; 205 206 GNUNET_assert (NULL == as->reader); 207 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 208 GNUNET_DISK_PIPE_END_READ); 209 as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 210 fh, 211 &read_stdout, 212 as); 213 } 214 215 216 /** 217 * Run the command. Use the `taler-unified-setup.sh` program. 218 * 219 * @param cls closure. 220 * @param cmd command being run. 221 * @param is interpreter state. 222 */ 223 static void 224 system_run (void *cls, 225 const struct TALER_TESTING_Command *cmd, 226 struct TALER_TESTING_Interpreter *is) 227 { 228 struct SystemState *as = cls; 229 230 (void) cmd; 231 as->is = is; 232 as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 233 GNUNET_assert (NULL != as->pipe_in); 234 as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE); 235 GNUNET_assert (NULL != as->pipe_out); 236 as->system_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 237 GNUNET_assert (GNUNET_OK == 238 GNUNET_process_set_options ( 239 as->system_proc, 240 GNUNET_process_option_inherit_rpipe (as->pipe_in, 241 STDIN_FILENO), 242 GNUNET_process_option_inherit_wpipe (as->pipe_out, 243 STDOUT_FILENO))); 244 if (GNUNET_OK != 245 GNUNET_process_run_command_argv (as->system_proc, 246 "taler-unified-setup.sh", 247 (const char **) as->args)) 248 { 249 GNUNET_break (0); 250 GNUNET_process_destroy (as->system_proc); 251 as->system_proc = NULL; 252 TALER_TESTING_interpreter_fail (is); 253 return; 254 } 255 as->active = true; 256 start_reader (as); 257 as->cwh = GNUNET_wait_child (as->system_proc, 258 &setup_terminated, 259 as); 260 } 261 262 263 /** 264 * Free the state of a "system" CMD, and possibly kill its 265 * process if it did not terminate correctly. 266 * 267 * @param cls closure. 268 * @param cmd the command being freed. 269 */ 270 static void 271 system_cleanup (void *cls, 272 const struct TALER_TESTING_Command *cmd) 273 { 274 struct SystemState *as = cls; 275 276 (void) cmd; 277 if (NULL != as->cwh) 278 { 279 GNUNET_wait_child_cancel (as->cwh); 280 as->cwh = NULL; 281 } 282 if (NULL != as->reader) 283 { 284 GNUNET_SCHEDULER_cancel (as->reader); 285 as->reader = NULL; 286 } 287 GNUNET_buffer_clear (&as->ibuf); 288 if (NULL != as->system_proc) 289 { 290 if (as->active) 291 { 292 GNUNET_break (GNUNET_OK == 293 GNUNET_process_kill (as->system_proc, 294 SIGTERM)); 295 GNUNET_process_wait (as->system_proc, 296 true, 297 NULL, 298 NULL); 299 } 300 GNUNET_process_destroy (as->system_proc); 301 as->system_proc = NULL; 302 } 303 if (NULL != as->pipe_in) 304 { 305 GNUNET_break (GNUNET_OK == 306 GNUNET_DISK_pipe_close (as->pipe_in)); 307 as->pipe_in = NULL; 308 } 309 if (NULL != as->pipe_out) 310 { 311 GNUNET_break (GNUNET_OK == 312 GNUNET_DISK_pipe_close (as->pipe_out)); 313 as->pipe_out = NULL; 314 } 315 316 for (unsigned int i = 0; NULL != as->args[i]; i++) 317 GNUNET_free (as->args[i]); 318 GNUNET_free (as->args); 319 GNUNET_free (as); 320 } 321 322 323 /** 324 * Offer "system" CMD internal data to other commands. 325 * 326 * @param cls closure. 327 * @param[out] ret result. 328 * @param trait name of the trait. 329 * @param index index number of the object to offer. 330 * @return #GNUNET_OK on success 331 */ 332 static enum GNUNET_GenericReturnValue 333 system_traits (void *cls, 334 const void **ret, 335 const char *trait, 336 unsigned int index) 337 { 338 struct SystemState *as = cls; 339 struct TALER_TESTING_Trait traits[] = { 340 TALER_TESTING_make_trait_process (&as->system_proc), 341 TALER_TESTING_trait_end () 342 }; 343 344 return TALER_TESTING_get_trait (traits, 345 ret, 346 trait, 347 index); 348 } 349 350 351 struct TALER_TESTING_Command 352 TALER_TESTING_cmd_system_start ( 353 const char *label, 354 const char *config_file, 355 ...) 356 { 357 struct SystemState *as; 358 va_list ap; 359 const char *arg; 360 unsigned int cnt; 361 362 as = GNUNET_new (struct SystemState); 363 cnt = 4; /* 0-2 reserved, +1 for NULL termination */ 364 va_start (ap, 365 config_file); 366 while (NULL != (arg = va_arg (ap, 367 const char *))) 368 { 369 cnt++; 370 } 371 va_end (ap); 372 as->args = GNUNET_new_array (cnt, 373 char *); 374 as->args[0] = GNUNET_strdup ("taler-unified-setup"); 375 as->args[1] = GNUNET_strdup ("-c"); 376 as->args[2] = GNUNET_strdup (config_file); 377 cnt = 3; 378 va_start (ap, 379 config_file); 380 while (NULL != (arg = va_arg (ap, 381 const char *))) 382 { 383 as->args[cnt++] = GNUNET_strdup (arg); 384 } 385 va_end (ap); 386 387 { 388 struct TALER_TESTING_Command cmd = { 389 .cls = as, 390 .label = label, 391 .run = &system_run, 392 .cleanup = &system_cleanup, 393 .traits = &system_traits 394 }; 395 396 return cmd; 397 } 398 } 399 400 401 /* end of testing_api_cmd_system_start.c */