testing_api_cmd_system_start.c (9271B)
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_OS_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-exchange-system' 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 242 = GNUNET_OS_start_process_vap ( 243 GNUNET_OS_INHERIT_STD_ERR, 244 as->pipe_in, as->pipe_out, NULL, 245 "taler-unified-setup.sh", 246 as->args); 247 if (NULL == as->system_proc) 248 { 249 GNUNET_break (0); 250 TALER_TESTING_interpreter_fail (is); 251 return; 252 } 253 as->active = true; 254 start_reader (as); 255 as->cwh = GNUNET_wait_child (as->system_proc, 256 &setup_terminated, 257 as); 258 } 259 260 261 /** 262 * Free the state of a "system" CMD, and possibly kill its 263 * process if it did not terminate correctly. 264 * 265 * @param cls closure. 266 * @param cmd the command being freed. 267 */ 268 static void 269 system_cleanup (void *cls, 270 const struct TALER_TESTING_Command *cmd) 271 { 272 struct SystemState *as = cls; 273 274 (void) cmd; 275 if (NULL != as->cwh) 276 { 277 GNUNET_wait_child_cancel (as->cwh); 278 as->cwh = NULL; 279 } 280 if (NULL != as->reader) 281 { 282 GNUNET_SCHEDULER_cancel (as->reader); 283 as->reader = NULL; 284 } 285 if (NULL != as->system_proc) 286 { 287 if (as->active) 288 { 289 GNUNET_break (0 == 290 GNUNET_OS_process_kill (as->system_proc, 291 SIGTERM)); 292 GNUNET_OS_process_wait (as->system_proc); 293 } 294 GNUNET_OS_process_destroy (as->system_proc); 295 as->system_proc = NULL; 296 } 297 if (NULL != as->pipe_in) 298 { 299 GNUNET_break (GNUNET_OK == 300 GNUNET_DISK_pipe_close (as->pipe_in)); 301 as->pipe_in = NULL; 302 } 303 if (NULL != as->pipe_out) 304 { 305 GNUNET_break (GNUNET_OK == 306 GNUNET_DISK_pipe_close (as->pipe_out)); 307 as->pipe_out = NULL; 308 } 309 310 for (unsigned int i = 0; NULL != as->args[i]; i++) 311 GNUNET_free (as->args[i]); 312 GNUNET_free (as->args); 313 GNUNET_free (as); 314 } 315 316 317 /** 318 * Offer "system" CMD internal data to other commands. 319 * 320 * @param cls closure. 321 * @param[out] ret result. 322 * @param trait name of the trait. 323 * @param index index number of the object to offer. 324 * @return #GNUNET_OK on success 325 */ 326 static enum GNUNET_GenericReturnValue 327 system_traits (void *cls, 328 const void **ret, 329 const char *trait, 330 unsigned int index) 331 { 332 struct SystemState *as = cls; 333 struct TALER_TESTING_Trait traits[] = { 334 TALER_TESTING_make_trait_process (&as->system_proc), 335 TALER_TESTING_trait_end () 336 }; 337 338 return TALER_TESTING_get_trait (traits, 339 ret, 340 trait, 341 index); 342 } 343 344 345 struct TALER_TESTING_Command 346 TALER_TESTING_cmd_system_start ( 347 const char *label, 348 const char *config_file, 349 ...) 350 { 351 struct SystemState *as; 352 va_list ap; 353 const char *arg; 354 unsigned int cnt; 355 356 as = GNUNET_new (struct SystemState); 357 cnt = 4; /* 0-2 reserved, +1 for NULL termination */ 358 va_start (ap, 359 config_file); 360 while (NULL != (arg = va_arg (ap, 361 const char *))) 362 { 363 cnt++; 364 } 365 va_end (ap); 366 as->args = GNUNET_new_array (cnt, 367 char *); 368 as->args[0] = GNUNET_strdup ("taler-unified-setup"); 369 as->args[1] = GNUNET_strdup ("-c"); 370 as->args[2] = GNUNET_strdup (config_file); 371 cnt = 3; 372 va_start (ap, 373 config_file); 374 while (NULL != (arg = va_arg (ap, 375 const char *))) 376 { 377 as->args[cnt++] = GNUNET_strdup (arg); 378 } 379 va_end (ap); 380 381 { 382 struct TALER_TESTING_Command cmd = { 383 .cls = as, 384 .label = label, 385 .run = &system_run, 386 .cleanup = &system_cleanup, 387 .traits = &system_traits 388 }; 389 390 return cmd; 391 } 392 } 393 394 395 /* end of testing_api_cmd_system_start.c */