3 /* run_context -- stuff in which TESH runs a command */
5 /* Copyright (c) 2007 Martin Quinson. */
6 /* All rights reserved. */
8 /* This program is free software; you can redistribute it and/or modify it
9 * under the terms of the license (GNU LGPL) which comes with this package. */
13 #include <sys/types.h>
16 XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh);
18 xbt_dynar_t bg_jobs = NULL;
19 rctx_t armageddon_initiator = NULL;
20 xbt_mutex_t armageddon_mutex = NULL;
26 static void kill_it(void*r) {
27 rctx_t rctx = *(rctx_t*)r;
29 VERB1("Join thread %p which were running a background cmd",rctx->runner);
30 xbt_thread_join(rctx->runner,NULL);
34 void rctx_init(void) {
35 bg_jobs = xbt_dynar_new(sizeof(rctx_t),kill_it);
36 armageddon_mutex = xbt_mutex_init();
37 armageddon_initiator = NULL;
40 void rctx_exit(void) {
42 xbt_dynar_free(&bg_jobs);
43 xbt_mutex_destroy(armageddon_mutex);
46 void rctx_wait_bg(void) {
47 xbt_dynar_free(&bg_jobs);
48 bg_jobs = xbt_dynar_new(sizeof(rctx_t),kill_it);
51 void rctx_armageddon(rctx_t initiator, int exitcode) {
55 xbt_mutex_lock(armageddon_mutex);
56 if (armageddon_initiator != NULL) {
57 VERB0("Armageddon already started. Let it go");
60 armageddon_initiator = initiator;
61 xbt_mutex_unlock(armageddon_mutex);
64 /* Kill any background commands */
65 xbt_dynar_foreach(bg_jobs,cpt,rctx) {
66 if (rctx != initiator) {
67 xbt_mutex_lock(rctx->interruption);
68 rctx->interrupted = 1;
69 INFO2("Kill <%s> because <%s> failed",rctx->filepos,initiator->filepos);
70 if (!rctx->reader_done) {
71 kill(rctx->pid,SIGTERM);
73 kill(rctx->pid,SIGKILL);
75 xbt_mutex_unlock(rctx->interruption);
79 /* Remove myself from the tasks */
80 if (xbt_dynar_member(bg_jobs, &initiator)) {
81 int mypos = xbt_dynar_search(bg_jobs, &initiator);
83 xbt_dynar_remove_at(bg_jobs,mypos,&myself);
87 /* Cleanup the place */
88 xbt_dynar_free(&bg_jobs);
97 void rctx_empty(rctx_t rc) {
105 rc->is_background = 0;
106 rc->is_stoppable = 0;
107 rc->output = e_output_check;
111 buff_empty(rc->input);
112 buff_empty(rc->output_wanted);
113 buff_empty(rc->output_got);
117 rctx_t res = xbt_new0(s_rctx_t,1);
119 res->input=buff_new();
120 res->output_wanted=buff_new();
121 res->output_got=buff_new();
122 res->interruption = xbt_mutex_init();
127 void rctx_free(rctx_t rctx) {
128 DEBUG1("RCTX: Free %p", rctx);
129 rctx_dump(rctx,"free");
137 xbt_mutex_destroy(rctx->interruption);
138 buff_free(rctx->input);
139 buff_free(rctx->output_got);
140 buff_free(rctx->output_wanted);
144 void rctx_dump(rctx_t rctx, const char *str) {
145 DEBUG9("%s RCTX %p={in%p={%d,%10s}, want={%d,%10s}, out={%d,%10s}}",
147 rctx->input, rctx->input->used, rctx->input->data,
148 rctx->output_wanted->used,rctx->output_wanted->data,
149 rctx->output_got->used, rctx->output_got->data);
150 DEBUG5("%s RCTX %p=[cmd%p=%10s, pid=%d]",
151 str,rctx,rctx->cmd,rctx->cmd,rctx->pid);
156 * Getting instructions from the file
159 void rctx_pushline(const char* filepos, char kind, char *line) {
165 if (!rctx->is_empty) {
166 ERROR2("[%s] More than one command in this chunk of lines (previous: %s).\n"
167 " Dunno which input/output belongs to which command.",
169 ERROR1("Test suite `%s': NOK (syntax error)",testsuite_name);
170 rctx_armageddon(rctx,1);
173 VERB1("[%s] More than one command in this chunk of lines",filepos);
176 rctx->is_background = 1;
178 rctx->is_background = 0;
180 rctx->cmd = xbt_strdup(line);
181 rctx->filepos = xbt_strdup(filepos);
182 INFO3("[%s] %s%s",filepos,rctx->cmd,
183 ((rctx->is_background)?" (background command)":""));
189 buff_append(rctx->input,line);
190 buff_append(rctx->input,"\n");
195 buff_append(rctx->output_wanted,line);
196 buff_append(rctx->output_wanted,"\n");
203 if (!strncmp(line,"set timeout ",strlen("set timeout "))) {
204 timeout_value=atoi(line+strlen("set timeout"));
205 VERB2("[%s] (new timeout value: %d)",
206 filepos,timeout_value);
208 } else if (!strncmp(line,"expect signal ",strlen("expect signal "))) {
209 rctx->expected_signal = strdup(line + strlen("expect signal "));
210 xbt_str_trim(rctx->expected_signal," \n");
211 VERB2("[%s] (next command must raise signal %s)",
212 filepos, rctx->expected_signal);
214 } else if (!strncmp(line,"expect return ",strlen("expect return "))) {
215 rctx->expected_return = atoi(line+strlen("expect return "));
216 VERB2("[%s] (next command must return code %d)",
217 filepos, rctx->expected_return);
219 } else if (!strncmp(line,"output ignore",strlen("output ignore"))) {
220 rctx->output = e_output_ignore;
221 VERB1("[%s] (ignore output of next command)", filepos);
223 } else if (!strncmp(line,"output display",strlen("output display"))) {
224 rctx->output = e_output_display;
225 VERB1("[%s] (ignore output of next command)", filepos);
228 ERROR2("%s: Malformed metacommand: %s",filepos,line);
229 ERROR1("Test suite `%s': NOK (syntax error)",testsuite_name);
230 rctx_armageddon(rctx,1);
237 * Actually doing the job
240 /* The IO of the childs are handled by the two following threads
241 (one pair per child) */
243 static void* thread_writer(void *r) {
245 rctx_t rctx = (rctx_t)r;
246 for (posw=0; posw<rctx->input->used && !rctx->brokenpipe; ) {
248 DEBUG1("Still %d chars to write",rctx->input->used-posw);
249 got=write(rctx->child_to,rctx->input->data+posw,rctx->input->used-posw);
253 if (errno == EPIPE) {
254 rctx->brokenpipe = 1;
255 } else if (errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) {
256 perror("Error while writing input to child");
257 ERROR1("Test suite `%s': NOK (system error)",testsuite_name);
258 rctx_armageddon(rctx,4);
261 DEBUG1("written %d chars so far",posw);
266 rctx->input->data[0]='\0';
268 close(rctx->child_to);
272 static void *thread_reader(void *r) {
273 rctx_t rctx = (rctx_t)r;
274 char *buffout=malloc(4096);
278 posr=read(rctx->child_from,buffout,4095);
279 if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
280 perror("Error while reading output of child");
281 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
282 rctx_armageddon(rctx,4);
286 buff_append(rctx->output_got,buffout);
290 } while (!rctx->timeout && posr!=0);
293 /* let this thread wait for the child so that the main thread can detect the timeout without blocking on the wait */
294 got_pid = waitpid(rctx->pid,&rctx->status,0);
295 if (got_pid != rctx->pid) {
296 perror(bprintf("Cannot wait for the child %s",rctx->cmd));
297 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
298 rctx_armageddon(rctx,4);
301 rctx->reader_done = 1;
305 /* Start a new child, plug the pipes as expected and fire up the
306 helping threads. Is also waits for the child to end if this is a
307 foreground job, or fire up a thread to wait otherwise. */
309 void rctx_start(void) {
313 VERB2("Start %s %s",rctx->cmd,(rctx->is_background?"(background job)":""));
314 if (pipe(child_in) || pipe(child_out)) {
315 perror("Cannot open the pipes");
316 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
317 rctx_armageddon(rctx,4);
322 perror("Cannot fork the command");
323 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
324 rctx_armageddon(rctx,4);
327 if (rctx->pid) { /* father */
329 rctx->child_to = child_in[1];
332 rctx->child_from = child_out[0];
334 rctx->end_time = time(NULL) + timeout_value;
336 rctx->reader_done = 0;
337 rctx->reader = xbt_thread_create(thread_reader,(void*)rctx);
338 rctx->writer = xbt_thread_create(thread_writer,(void*)rctx);
347 dup2(child_out[1],1);
348 dup2(child_out[1],2);
351 execlp ("/bin/sh", "sh", "-c", rctx->cmd, NULL);
354 rctx->is_stoppable = 1;
356 if (!rctx->is_background) {
359 /* Damn. Copy the rctx and launch a thread to handle it */
364 DEBUG2("RCTX: new bg=%p, new fg=%p",old,rctx);
366 DEBUG2("Launch a thread to wait for %s %d",old->cmd,old->pid);
367 runner = xbt_thread_create(rctx_wait,(void*)old);
368 old->runner = runner;
369 VERB3("Launched thread %p to wait for %s %d",
370 runner,old->cmd, old->pid);
371 xbt_dynar_push(bg_jobs,&old);
375 /* Waits for the child to end (or to timeout), and check its
376 ending conditions. This is launched from rctx_start but either in main
377 thread (for foreground jobs) or in a separate one for background jobs.
378 That explains the prototype, forced by xbt_thread_create. */
380 void *rctx_wait(void* r) {
381 rctx_t rctx = (rctx_t)r;
383 int now = time(NULL);
385 rctx_dump(rctx,"wait");
387 if (!rctx->is_stoppable)
388 THROW1(unknown_error,0,"Cmd '%s' not started yet. Cannot wait it",
391 /* Wait for the child to die or the timeout to happen (or an armageddon to happen) */
392 while (!rctx->interrupted && !rctx->reader_done && rctx->end_time >= now) {
397 xbt_mutex_lock(rctx->interruption);
399 if (!rctx->interrupted && rctx->end_time < now) {
400 INFO1("<%s> timeouted. Kill the process.",rctx->filepos);
402 kill(rctx->pid,SIGTERM);
404 kill(rctx->pid,SIGKILL);
405 rctx->reader_done = 1;
408 /* Make sure helper threads die.
409 Cannot block since they wait for the child we just killed
410 if not already dead. */
411 xbt_thread_join(rctx->writer,NULL);
412 xbt_thread_join(rctx->reader,NULL);
414 /* xbt_mutex_unlock(rctx->interruption);
415 if (rctx->interrupted)
417 xbt_mutex_lock(rctx->interruption);*/
419 buff_chomp(rctx->output_got);
420 buff_chomp(rctx->output_wanted);
421 buff_trim(rctx->output_got);
422 buff_trim(rctx->output_wanted);
424 /* Check for broken pipe */
425 if (rctx->brokenpipe)
426 VERB0("Warning: Child did not consume all its input (I got broken pipe)");
428 /* Check for timeouts */
430 if (rctx->output_got->data[0])
431 INFO2("<%s> Output on timeout:\n%s",
432 rctx->filepos,rctx->output_got->data);
434 INFO1("<%s> No output before timeout",
436 ERROR3("Test suite `%s': NOK (<%s> timeout after %d sec)",
437 testsuite_name,rctx->filepos,timeout_value);
438 if (!rctx->interrupted)
439 rctx_armageddon(rctx, 3);
442 DEBUG2("RCTX=%p (pid=%d)",rctx,rctx->pid);
443 DEBUG3("Status(%s|%d)=%d",rctx->cmd,rctx->pid,rctx->status);
445 if (!rctx->interrupted) {
446 if (WIFSIGNALED(rctx->status) && !rctx->expected_signal) {
447 ERROR3("Test suite `%s': NOK (<%s> got signal %s)",
448 testsuite_name, rctx->filepos,
449 signal_name(WTERMSIG(rctx->status),NULL));
450 errcode = WTERMSIG(rctx->status)+4;
453 if (WIFSIGNALED(rctx->status) && rctx->expected_signal &&
454 strcmp(signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
455 rctx->expected_signal)) {
456 ERROR4("Test suite `%s': NOK (%s got signal %s instead of %s)",
457 testsuite_name, rctx->filepos,
458 signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
459 rctx->expected_signal);
460 errcode = WTERMSIG(rctx->status)+4;
463 if (!WIFSIGNALED(rctx->status) && rctx->expected_signal) {
464 ERROR3("Test suite `%s': NOK (child %s expected signal %s)",
465 testsuite_name, rctx->filepos,
466 rctx->expected_signal);
470 if (WIFEXITED(rctx->status) && WEXITSTATUS(rctx->status) != rctx->expected_return ) {
471 if (rctx->expected_return)
472 ERROR4("Test suite `%s': NOK (<%s> returned code %d instead of %d)",
473 testsuite_name, rctx->filepos,
474 WEXITSTATUS(rctx->status), rctx->expected_return);
476 ERROR3("Test suite `%s': NOK (<%s> returned code %d)",
477 testsuite_name, rctx->filepos, WEXITSTATUS(rctx->status));
478 errcode = 40+WEXITSTATUS(rctx->status);
481 rctx->expected_return = 0;
483 if(rctx->expected_signal){
484 free(rctx->expected_signal);
485 rctx->expected_signal = NULL;
489 if ( rctx->output == e_output_check
490 && ( rctx->output_got->used != rctx->output_wanted->used
491 || strcmp(rctx->output_got->data, rctx->output_wanted->data))) {
492 if (XBT_LOG_ISENABLED(tesh,xbt_log_priority_info)) {
493 char *diff= xbt_str_diff(rctx->output_wanted->data,rctx->output_got->data);
494 ERROR1("Output mismatch:\n%s",
498 ERROR2("Test suite `%s': NOK (<%s> output mismatch)",
499 testsuite_name,rctx->filepos);
502 } else if (rctx->output == e_output_ignore) {
503 INFO0("(ignoring the output as requested)");
504 } else if (rctx->output == e_output_display) {
505 xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
506 char *out = xbt_str_join(a,"\n||");
508 INFO1("Here is the (ignored) command output: \n||%s",out);
510 } else if (errcode || rctx->interrupted) {
511 /* checking output, and matching */
512 xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
513 char *out = xbt_str_join(a,"\n||");
515 INFO2("Output of <%s> so far: \n||%s",rctx->filepos,out);
519 if (!rctx->is_background) {
523 if (!rctx->interrupted)
524 rctx_armageddon(rctx, errcode);
526 xbt_mutex_unlock(rctx->interruption);