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>
17 XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh);
19 xbt_dynar_t bg_jobs = NULL;
25 static void join_it(void*t) {
26 xbt_thread_t th = *(xbt_thread_t*)t;
27 VERB1("Join thread %p which were running a background cmd",th);
28 xbt_thread_join(th,NULL);
31 void rctx_init(void) {
32 bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it);
35 void rctx_exit(void) {
36 xbt_dynar_free(&bg_jobs);
39 void rctx_wait_bg(void) {
40 xbt_dynar_free(&bg_jobs);
41 bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it);
48 void rctx_empty(rctx_t rc) {
53 rc->is_background = 0;
55 rc->output = e_output_check;
58 buff_empty(rc->input);
59 buff_empty(rc->output_wanted);
60 buff_empty(rc->output_got);
64 rctx_t res = xbt_new0(s_rctx_t,1);
66 res->input=buff_new();
67 res->output_wanted=buff_new();
68 res->output_got=buff_new();
73 void rctx_free(rctx_t rctx) {
74 DEBUG1("RCTX: Free %p", rctx);
75 rctx_dump(rctx,"free");
81 buff_free(rctx->input);
82 buff_free(rctx->output_got);
83 buff_free(rctx->output_wanted);
87 void rctx_dump(rctx_t rctx, const char *str) {
88 DEBUG9("%s RCTX %p={in%p={%d,%10s}, want={%d,%10s}, out={%d,%10s}}",
90 rctx->input, rctx->input->used, rctx->input->data,
91 rctx->output_wanted->used,rctx->output_wanted->data,
92 rctx->output_got->used, rctx->output_got->data);
93 DEBUG5("%s RCTX %p=[cmd%p=%10s, pid=%d]",
94 str,rctx,rctx->cmd,rctx->cmd,rctx->pid);
99 * Getting instructions from the file
102 void rctx_pushline(const char* filepos, char kind, char *line) {
108 if (!rctx->is_empty) {
109 ERROR2("[%s] More than one command in this chunk of lines (previous: %s).\n"
110 " Dunno which input/output belongs to which command.",
115 VERB1("[%s] More than one command in this chunk of lines",filepos);
118 rctx->is_background = 1;
120 rctx->is_background = 0;
122 rctx->cmd = xbt_strdup(line);
123 INFO3("[%s] %s%s",filepos,line,
124 ((rctx->is_background)?" (background command)":""));
130 buff_append(rctx->input,line);
131 buff_append(rctx->input,"\n");
136 buff_append(rctx->output_wanted,line);
137 buff_append(rctx->output_wanted,"\n");
144 if (!strncmp(line,"set timeout ",strlen("set timeout "))) {
145 timeout_value=atoi(line+strlen("set timeout"));
146 VERB2("[%s] (new timeout value: %d)",
147 filepos,timeout_value);
149 } else if (!strncmp(line,"expect signal ",strlen("expect signal "))) {
150 rctx->expected_signal = strdup(line + strlen("expect signal "));
151 xbt_str_trim(rctx->expected_signal," \n");
152 VERB2("[%s] (next command must raise signal %s)",
153 filepos, rctx->expected_signal);
155 } else if (!strncmp(line,"expect return ",strlen("expect return "))) {
156 rctx->expected_return = atoi(line+strlen("expect return "));
157 VERB2("[%s] (next command must return code %d)",
158 filepos, rctx->expected_return);
160 } else if (!strncmp(line,"output ignore",strlen("output ignore"))) {
161 rctx->output = e_output_ignore;
162 VERB1("[%s] (ignore output of next command)", filepos);
164 } else if (!strncmp(line,"output display",strlen("output display"))) {
165 rctx->output = e_output_display;
166 VERB1("[%s] (ignore output of next command)", filepos);
169 ERROR2("%s: Malformed metacommand: %s",filepos,line);
177 * Actually doing the job
180 /* The IO of the childs are handled by the two following threads
181 (one pair per child) */
183 static void* thread_writer(void *r) {
185 rctx_t rctx = (rctx_t)r;
186 for (posw=0; posw<rctx->input->used && !rctx->brokenpipe; ) {
188 DEBUG1("Still %d chars to write",rctx->input->used-posw);
189 got=write(rctx->child_to,rctx->input->data+posw,rctx->input->used-posw);
193 if (errno == EPIPE) {
194 rctx->brokenpipe = 1;
195 } else if (errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) {
196 perror("Error while writing input to child");
200 DEBUG1("written %d chars so far",posw);
205 rctx->input->data[0]='\0';
207 close(rctx->child_to);
211 static void *thread_reader(void *r) {
212 rctx_t rctx = (rctx_t)r;
213 char *buffout=malloc(4096);
217 posr=read(rctx->child_from,buffout,4095);
218 if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
219 perror("Error while reading output of child");
224 buff_append(rctx->output_got,buffout);
228 } while (!rctx->timeout && posr!=0);
231 /* let this thread wait for the child so that the main thread can detect the timeout without blocking on the wait */
232 got_pid = waitpid(rctx->pid,&rctx->status,0);
233 if (got_pid != rctx->pid) {
234 perror(bprintf("Cannot wait for the child %s",rctx->cmd));
238 rctx->reader_done = 1;
242 /* Start a new child, plug the pipes as expected and fire up the
243 helping threads. Is also waits for the child to end if this is a
244 foreground job, or fire up a thread to wait otherwise. */
246 void rctx_start(void) {
250 VERB2("Start %s %s",rctx->cmd,(rctx->is_background?"(background job)":""));
251 if (pipe(child_in) || pipe(child_out)) {
252 perror("Cannot open the pipes");
258 perror("Cannot fork the command");
262 if (rctx->pid) { /* father */
264 rctx->child_to = child_in[1];
267 rctx->child_from = child_out[0];
269 rctx->end_time = time(NULL) + timeout_value;
271 rctx->reader_done = 0;
272 rctx->reader = xbt_thread_create(thread_reader,(void*)rctx);
273 rctx->writer = xbt_thread_create(thread_writer,(void*)rctx);
282 dup2(child_out[1],1);
283 dup2(child_out[1],2);
286 execlp ("/bin/sh", "sh", "-c", rctx->cmd, NULL);
289 rctx->is_stoppable = 1;
291 if (!rctx->is_background) {
294 /* Damn. Copy the rctx and launch a thread to handle it */
299 DEBUG2("RCTX: new bg=%p, new fg=%p",old,rctx);
301 DEBUG2("Launch a thread to wait for %s %d",old->cmd,old->pid);
302 runner = xbt_thread_create(rctx_wait,(void*)old);
303 VERB3("Launched thread %p to wait for %s %d",
304 runner,old->cmd, old->pid);
305 xbt_dynar_push(bg_jobs,&runner);
309 /* Waits for the child to end (or to timeout), and check its
310 ending conditions. This is launched from rctx_start but either in main
311 thread (for foreground jobs) or in a separate one for background jobs.
312 That explains the prototype, forced by xbt_thread_create. */
314 void *rctx_wait(void* r) {
315 rctx_t rctx = (rctx_t)r;
317 int now = time(NULL);
319 rctx_dump(rctx,"wait");
321 if (!rctx->is_stoppable)
322 THROW1(unknown_error,0,"Cmd '%s' not started yet. Cannot wait it",
326 /* Wait for the child to die or the timeout to happen */
327 while (!rctx->reader_done && rctx->end_time >= now) {
332 if (rctx->end_time < now) {
333 INFO2("Child '%s' %d timeouted. Kill it",rctx->cmd,rctx->pid);
335 kill(rctx->pid,SIGTERM);
337 kill(rctx->pid,SIGKILL);
340 /* Make sure helper threads die.
341 Cannot block since they wait for the child we just killed
342 if not already dead. */
343 xbt_thread_join(rctx->writer,NULL);
344 xbt_thread_join(rctx->reader,NULL);
346 /* Check for broken pipe */
347 if (rctx->brokenpipe)
348 VERB0("Warning: Child did not consume all its input (I got broken pipe)");
350 /* Check for timeouts */
352 ERROR1("Child timeouted (waited %d sec)",timeout_value);
356 DEBUG2("RCTX=%p (pid=%d)",rctx,rctx->pid);
357 DEBUG3("Status(%s|%d)=%d",rctx->cmd,rctx->pid,rctx->status);
359 if (WIFSIGNALED(rctx->status) && !rctx->expected_signal) {
360 ERROR2("Child \"%s\" got signal %s.", rctx->cmd,
361 signal_name(WTERMSIG(rctx->status),NULL));
362 errcode = WTERMSIG(rctx->status)+4;
365 if (WIFSIGNALED(rctx->status) && rctx->expected_signal &&
366 strcmp(signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
367 rctx->expected_signal)) {
368 ERROR3("Child \"%s\" got signal %s instead of signal %s", rctx->cmd,
369 signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
370 rctx->expected_signal);
371 errcode = WTERMSIG(rctx->status)+4;
374 if (!WIFSIGNALED(rctx->status) && rctx->expected_signal) {
375 ERROR2("Child \"%s\" didn't got expected signal %s",
376 rctx->cmd, rctx->expected_signal);
380 if (WIFEXITED(rctx->status) && WEXITSTATUS(rctx->status) != rctx->expected_return ) {
381 if (rctx->expected_return)
382 ERROR3("Child \"%s\" returned code %d instead of %d", rctx->cmd,
383 WEXITSTATUS(rctx->status), rctx->expected_return);
385 ERROR2("Child \"%s\" returned code %d", rctx->cmd, WEXITSTATUS(rctx->status));
386 errcode = 40+WEXITSTATUS(rctx->status);
388 rctx->expected_return = 0;
390 if(rctx->expected_signal){
391 free(rctx->expected_signal);
392 rctx->expected_signal = NULL;
395 buff_chomp(rctx->output_got);
396 buff_chomp(rctx->output_wanted);
397 buff_trim(rctx->output_got);
398 buff_trim(rctx->output_wanted);
400 if ( rctx->output == e_output_check
401 && ( rctx->output_got->used != rctx->output_wanted->used
402 || strcmp(rctx->output_got->data, rctx->output_wanted->data))) {
403 char *diff= xbt_str_diff(rctx->output_wanted->data,rctx->output_got->data);
404 if (XBT_LOG_ISENABLED(tesh,xbt_log_priority_info))
405 ERROR1("Child's output don't match expectations. Here is a diff between expected and got output:\n%s",
408 ERROR0("Child's output don't match expectations");
411 } else if (rctx->output == e_output_ignore) {
412 INFO0("(ignoring the output as requested)");
413 } else if (rctx->output == e_output_display) {
414 xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
415 char *out = xbt_str_join(a,"\n||");
417 INFO1("Here is the (ignored) command output: \n||%s",out);
421 if (rctx->is_background)
426 if (rctx->output == e_output_check)
427 INFO1("Here is the child's output:\n%s",rctx->output_got->data);