testing_api_cmd_system_start.c (9889B)
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 "taler/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_signatures.h" 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 * Current input buffer, 0-terminated. Contains the last 15 bytes of input 76 * so we can search them again for the "<<READY>>" tag. 77 */ 78 char ibuf[16]; 79 80 /** 81 * Did we find the ready tag? 82 */ 83 bool ready; 84 85 /** 86 * Is the child process still running? 87 */ 88 bool active; 89 }; 90 91 92 /** 93 * Defines a GNUNET_ChildCompletedCallback which is sent back 94 * upon death or completion of a child process. 95 * 96 * @param cls our `struct SystemState *` 97 * @param type type of the process 98 * @param exit_code status code of the process 99 */ 100 static void 101 setup_terminated (void *cls, 102 enum GNUNET_OS_ProcessStatusType type, 103 long unsigned int exit_code) 104 { 105 struct SystemState *as = cls; 106 107 as->cwh = NULL; 108 as->active = false; 109 if (NULL != as->reader) 110 { 111 GNUNET_SCHEDULER_cancel (as->reader); 112 as->reader = NULL; 113 } 114 if (! as->ready) 115 { 116 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 117 "Launching Taler system failed: %d/%llu\n", 118 (int) type, 119 (unsigned long long) exit_code); 120 TALER_TESTING_interpreter_fail (as->is); 121 return; 122 } 123 } 124 125 126 /** 127 * Start helper to read from stdout of child. 128 * 129 * @param as our system state 130 */ 131 static void 132 start_reader (struct SystemState *as); 133 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 * 10]; 141 ssize_t ret; 142 size_t off = 0; 143 144 as->reader = NULL; 145 strcpy (buf, 146 as->ibuf); 147 off = strlen (buf); 148 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 149 GNUNET_DISK_PIPE_END_READ); 150 ret = GNUNET_DISK_file_read (fh, 151 &buf[off], 152 sizeof (buf) - off); 153 if (-1 == ret) 154 { 155 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 156 "read"); 157 TALER_TESTING_interpreter_fail (as->is); 158 return; 159 } 160 if (0 == ret) 161 { 162 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 163 "Child closed stdout\n"); 164 return; 165 } 166 /* forward log, except single '.' outputs */ 167 if ( (1 != ret) || 168 ('.' != buf[off]) ) 169 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 170 "TUS: %.*s\n", 171 (int) ret, 172 &buf[off]); 173 start_reader (as); 174 off += ret; 175 if (as->ready) 176 { 177 /* already done */ 178 return; 179 } 180 if (NULL != 181 memmem (buf, 182 off, 183 "\n<<READY>>\n", 184 strlen ("\n<<READY>>\n"))) 185 { 186 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 187 "Taler system UP\n"); 188 as->ready = true; 189 TALER_TESTING_interpreter_next (as->is); 190 return; 191 } 192 193 { 194 size_t mcpy; 195 196 mcpy = GNUNET_MIN (off, 197 sizeof (as->ibuf) - 1); 198 memcpy (as->ibuf, 199 &buf[off - mcpy], 200 mcpy); 201 as->ibuf[mcpy] = '\0'; 202 } 203 } 204 205 206 static void 207 start_reader (struct SystemState *as) 208 { 209 const struct GNUNET_DISK_FileHandle *fh; 210 211 GNUNET_assert (NULL == as->reader); 212 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 213 GNUNET_DISK_PIPE_END_READ); 214 as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 215 fh, 216 &read_stdout, 217 as); 218 } 219 220 221 /** 222 * Run the command. Use the `taler-unified-setup.sh` program. 223 * 224 * @param cls closure. 225 * @param cmd command being run. 226 * @param is interpreter state. 227 */ 228 static void 229 system_run (void *cls, 230 const struct TALER_TESTING_Command *cmd, 231 struct TALER_TESTING_Interpreter *is) 232 { 233 struct SystemState *as = cls; 234 235 (void) cmd; 236 as->is = is; 237 as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 238 GNUNET_assert (NULL != as->pipe_in); 239 as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE); 240 GNUNET_assert (NULL != as->pipe_out); 241 as->system_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 242 GNUNET_assert (GNUNET_OK == 243 GNUNET_process_set_options ( 244 as->system_proc, 245 GNUNET_process_option_inherit_rpipe (as->pipe_in, 246 STDIN_FILENO), 247 GNUNET_process_option_inherit_wpipe (as->pipe_out, 248 STDOUT_FILENO))); 249 if (GNUNET_OK != 250 GNUNET_process_run_command_argv (as->system_proc, 251 "taler-unified-setup.sh", 252 (const char **) as->args)) 253 { 254 GNUNET_break (0); 255 GNUNET_process_destroy (as->system_proc); 256 as->system_proc = NULL; 257 TALER_TESTING_interpreter_fail (is); 258 return; 259 } 260 as->active = true; 261 start_reader (as); 262 as->cwh = GNUNET_wait_child (as->system_proc, 263 &setup_terminated, 264 as); 265 } 266 267 268 /** 269 * Free the state of a "system" CMD, and possibly kill its 270 * process if it did not terminate correctly. 271 * 272 * @param cls closure. 273 * @param cmd the command being freed. 274 */ 275 static void 276 system_cleanup (void *cls, 277 const struct TALER_TESTING_Command *cmd) 278 { 279 struct SystemState *as = cls; 280 281 (void) cmd; 282 if (NULL != as->cwh) 283 { 284 GNUNET_wait_child_cancel (as->cwh); 285 as->cwh = NULL; 286 } 287 if (NULL != as->reader) 288 { 289 GNUNET_SCHEDULER_cancel (as->reader); 290 as->reader = NULL; 291 } 292 if (NULL != as->system_proc) 293 { 294 if (as->active) 295 { 296 GNUNET_break (0 == 297 GNUNET_process_kill (as->system_proc, 298 SIGTERM)); 299 GNUNET_process_wait (as->system_proc, 300 true, 301 NULL, 302 NULL); 303 } 304 GNUNET_process_destroy (as->system_proc); 305 as->system_proc = NULL; 306 } 307 if (NULL != as->pipe_in) 308 { 309 GNUNET_break (GNUNET_OK == 310 GNUNET_DISK_pipe_close (as->pipe_in)); 311 as->pipe_in = NULL; 312 } 313 if (NULL != as->pipe_out) 314 { 315 GNUNET_break (GNUNET_OK == 316 GNUNET_DISK_pipe_close (as->pipe_out)); 317 as->pipe_out = NULL; 318 } 319 320 for (unsigned int i = 0; NULL != as->args[i]; i++) 321 GNUNET_free (as->args[i]); 322 GNUNET_free (as->args); 323 GNUNET_free (as); 324 } 325 326 327 /** 328 * Offer "system" CMD internal data to other commands. 329 * 330 * @param cls closure. 331 * @param[out] ret result. 332 * @param trait name of the trait. 333 * @param index index number of the object to offer. 334 * @return #GNUNET_OK on success 335 */ 336 static enum GNUNET_GenericReturnValue 337 system_traits (void *cls, 338 const void **ret, 339 const char *trait, 340 unsigned int index) 341 { 342 struct SystemState *as = cls; 343 struct TALER_TESTING_Trait traits[] = { 344 TALER_TESTING_make_trait_process (&as->system_proc), 345 TALER_TESTING_trait_end () 346 }; 347 348 return TALER_TESTING_get_trait (traits, 349 ret, 350 trait, 351 index); 352 } 353 354 355 struct TALER_TESTING_Command 356 TALER_TESTING_cmd_system_start ( 357 const char *label, 358 const char *config_file, 359 ...) 360 { 361 struct SystemState *as; 362 va_list ap; 363 const char *arg; 364 unsigned int cnt; 365 366 as = GNUNET_new (struct SystemState); 367 cnt = 4; /* 0-2 reserved, +1 for NULL termination */ 368 va_start (ap, 369 config_file); 370 while (NULL != (arg = va_arg (ap, 371 const char *))) 372 { 373 cnt++; 374 } 375 va_end (ap); 376 as->args = GNUNET_new_array (cnt, 377 char *); 378 as->args[0] = GNUNET_strdup ("taler-unified-setup"); 379 as->args[1] = GNUNET_strdup ("-c"); 380 as->args[2] = GNUNET_strdup (config_file); 381 cnt = 3; 382 va_start (ap, 383 config_file); 384 while (NULL != (arg = va_arg (ap, 385 const char *))) 386 { 387 as->args[cnt++] = GNUNET_strdup (arg); 388 } 389 va_end (ap); 390 391 { 392 struct TALER_TESTING_Command cmd = { 393 .cls = as, 394 .label = label, 395 .run = &system_run, 396 .cleanup = &system_cleanup, 397 .traits = &system_traits 398 }; 399 400 return cmd; 401 } 402 } 403 404 405 /* end of testing_api_cmd_system_start.c */