Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
parameter "--trace" to control whether to trace masterslave examples
[simgrid.git] / tools / tesh / run_context.c
1 /* $Id$ */
2
3 /* run_context -- stuff in which TESH runs a command                        */
4
5 /* Copyright (c) 2007 Martin Quinson.                                       */
6 /* All rights reserved.                                                     */
7
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. */
10
11 #include "tesh.h"
12
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh);
19
20 xbt_dynar_t bg_jobs = NULL;
21 rctx_t armageddon_initiator = NULL;
22 xbt_os_mutex_t armageddon_mutex = NULL;
23
24 /*
25  * Module management
26  */
27
28 static void kill_it(void *r)
29 {
30   rctx_t rctx = *(rctx_t *) r;
31
32   VERB2("Join thread %p which were running background cmd <%s>", rctx->runner,
33         rctx->filepos);
34   xbt_os_thread_join(rctx->runner, NULL);
35   rctx_free(rctx);
36 }
37
38 void rctx_init(void)
39 {
40   bg_jobs = xbt_dynar_new_sync(sizeof(rctx_t), kill_it);
41   armageddon_mutex = xbt_os_mutex_init();
42   armageddon_initiator = NULL;
43 }
44
45 void rctx_exit(void)
46 {
47   if (bg_jobs) {
48     /* Do not use xbt_dynar_free or it will lock the dynar, preventing armageddon from working */
49     while (xbt_dynar_length(bg_jobs)) {
50       rctx_t rctx;
51       xbt_dynar_pop(bg_jobs, &rctx);
52       kill_it(&rctx);
53     }
54     xbt_dynar_free(&bg_jobs);
55   }
56   xbt_os_mutex_destroy(armageddon_mutex);
57 }
58
59 void rctx_wait_bg(void)
60 {
61   if (bg_jobs) {
62     /* Do not use xbt_dynar_free or it will lock the dynar, preventing armageddon from working */
63     while (xbt_dynar_length(bg_jobs)) {
64       rctx_t rctx;
65       xbt_dynar_pop(bg_jobs, &rctx);
66       kill_it(&rctx);
67     }
68     xbt_dynar_free(&bg_jobs);
69   }
70   bg_jobs = xbt_dynar_new_sync(sizeof(rctx_t), kill_it);
71 }
72
73 void rctx_armageddon(rctx_t initiator, int exitcode)
74 {
75   rctx_t rctx;
76
77   DEBUG2("Armageddon request by <%s> (exit=%d)", initiator->filepos,
78          exitcode);
79   xbt_os_mutex_acquire(armageddon_mutex);
80   if (armageddon_initiator != NULL) {
81     VERB0("Armageddon already started. Let it go");
82     xbt_os_mutex_release(initiator->interruption);
83     xbt_os_mutex_release(armageddon_mutex);
84     return;
85   }
86   DEBUG1("Armageddon request by <%s> got the lock. Let's go amok",
87          initiator->filepos);
88   armageddon_initiator = initiator;
89   xbt_os_mutex_release(armageddon_mutex);
90
91   /* Kill any background commands */
92   while (xbt_dynar_length(bg_jobs)) {
93     xbt_dynar_pop(bg_jobs, &rctx);
94     if (rctx != initiator) {
95       INFO2("Kill <%s> because <%s> failed", rctx->filepos,
96             initiator->filepos);
97       xbt_os_mutex_acquire(rctx->interruption);
98       rctx->interrupted = 1;
99       xbt_os_mutex_release(rctx->interruption);
100       if (!rctx->reader_done) {
101         kill(rctx->pid, SIGTERM);
102         usleep(100);
103         kill(rctx->pid, SIGKILL);
104       }
105     }
106   }
107
108   VERB0("Shut everything down!");
109   exit(exitcode);
110 }
111
112 /*
113  * Memory management
114  */
115
116 void rctx_empty(rctx_t rc)
117 {
118   int i;
119   char **env_it = environ;
120
121   if (rc->cmd)
122     free(rc->cmd);
123   rc->cmd = NULL;
124   if (rc->filepos)
125     free(rc->filepos);
126   if (rc->env)
127     free(rc->env);
128
129   for (i = 0; *env_it; i++, env_it++);
130   i++;
131   rc->env_size = i;
132   rc->env = malloc(i * sizeof(char *));
133   memcpy(rc->env, environ, i * sizeof(char *));
134
135   rc->filepos = NULL;
136   rc->is_empty = 1;
137   rc->is_background = 0;
138   rc->is_stoppable = 0;
139   rc->output = e_output_check;
140   rc->brokenpipe = 0;
141   rc->timeout = 0;
142   rc->interrupted = 0;
143   xbt_strbuff_empty(rc->input);
144   xbt_strbuff_empty(rc->output_wanted);
145   xbt_strbuff_empty(rc->output_got);
146 }
147
148
149 rctx_t rctx_new()
150 {
151   rctx_t res = xbt_new0(s_rctx_t, 1);
152
153   res->input = xbt_strbuff_new();
154   res->output_wanted = xbt_strbuff_new();
155   res->output_got = xbt_strbuff_new();
156   res->interruption = xbt_os_mutex_init();
157   rctx_empty(res);
158   return res;
159 }
160
161 void rctx_free(rctx_t rctx)
162 {
163   DEBUG1("RCTX: Free %p", rctx);
164   rctx_dump(rctx, "free");
165   if (!rctx)
166     return;
167
168   if (rctx->cmd)
169     free(rctx->cmd);
170   if (rctx->filepos)
171     free(rctx->filepos);
172   if (rctx->env)
173     free(rctx->env);
174   xbt_os_mutex_destroy(rctx->interruption);
175   xbt_strbuff_free(rctx->input);
176   xbt_strbuff_free(rctx->output_got);
177   xbt_strbuff_free(rctx->output_wanted);
178   free(rctx);
179 }
180
181 void rctx_dump(rctx_t rctx, const char *str)
182 {
183   DEBUG9("%s RCTX %p={in%p={%d,%10s}, want={%d,%10s}, out={%d,%10s}}",
184          str, rctx,
185          rctx->input, rctx->input->used, rctx->input->data,
186          rctx->output_wanted->used, rctx->output_wanted->data,
187          rctx->output_got->used, rctx->output_got->data);
188   DEBUG5("%s RCTX %p=[cmd%p=%10s, pid=%d]",
189          str, rctx, rctx->cmd, rctx->cmd, rctx->pid);
190
191 }
192
193 /*
194  * Getting instructions from the file
195  */
196
197 void rctx_pushline(const char *filepos, char kind, char *line)
198 {
199
200   switch (kind) {
201   case '$':
202   case '&':
203     if (rctx->cmd) {
204       if (!rctx->is_empty) {
205         ERROR2
206           ("[%s] More than one command in this chunk of lines (previous: %s).\n"
207            " Cannot guess which input/output belongs to which command.",
208            filepos, rctx->cmd);
209         ERROR1("Test suite `%s': NOK (syntax error)", testsuite_name);
210         rctx_armageddon(rctx, 1);
211         return;
212       }
213       rctx_start();
214       VERB1("[%s] More than one command in this chunk of lines", filepos);
215     }
216     if (kind == '&')
217       rctx->is_background = 1;
218     else
219       rctx->is_background = 0;
220
221     rctx->cmd = xbt_strdup(line);
222     rctx->filepos = xbt_strdup(filepos);
223     INFO3("[%s] %s%s", filepos, rctx->cmd,
224           ((rctx->is_background) ? " (background command)" : ""));
225
226     break;
227
228   case '<':
229     rctx->is_empty = 0;
230     xbt_strbuff_append(rctx->input, line);
231     xbt_strbuff_append(rctx->input, "\n");
232     break;
233
234   case '>':
235     rctx->is_empty = 0;
236     xbt_strbuff_append(rctx->output_wanted, line);
237     xbt_strbuff_append(rctx->output_wanted, "\n");
238     break;
239
240   case '!':
241     if (rctx->cmd)
242       rctx_start();
243
244     if (!strncmp(line, "timeout no", strlen("timeout no"))) {
245       VERB1("[%s] (disable timeout)", filepos);
246       timeout_value = -1;
247     } else if (!strncmp(line, "timeout ", strlen("timeout "))) {
248       timeout_value = atoi(line + strlen("timeout"));
249       VERB2("[%s] (new timeout value: %d)", filepos, timeout_value);
250
251     } else if (!strncmp(line, "expect signal ", strlen("expect signal "))) {
252       rctx->expected_signal = strdup(line + strlen("expect signal "));
253       xbt_str_trim(rctx->expected_signal, " \n");
254       VERB2("[%s] (next command must raise signal %s)",
255             filepos, rctx->expected_signal);
256
257     } else if (!strncmp(line, "expect return ", strlen("expect return "))) {
258       rctx->expected_return = atoi(line + strlen("expect return "));
259       VERB2("[%s] (next command must return code %d)",
260             filepos, rctx->expected_return);
261
262     } else if (!strncmp(line, "output ignore", strlen("output ignore"))) {
263       rctx->output = e_output_ignore;
264       VERB1("[%s] (ignore output of next command)", filepos);
265
266     } else if (!strncmp(line, "output display", strlen("output display"))) {
267       rctx->output = e_output_display;
268       VERB1("[%s] (ignore output of next command)", filepos);
269
270     } else if (!strncmp(line, "setenv ", strlen("setenv "))) {
271       int len = strlen("setenv ");
272       char *eq = strchr(line + len, '=');
273       char *key = bprintf("%.*s", (int) (eq - line - len), line + len);
274       xbt_dict_set(env, key, xbt_strdup(eq + 1), xbt_free_f);
275
276       rctx->env = realloc(rctx->env, ++(rctx->env_size) * sizeof(char *));
277       rctx->env[rctx->env_size - 2] = xbt_strdup(line + len);
278       rctx->env[rctx->env_size - 1] = NULL;
279       VERB2("[%s] setenv %s", filepos, line + len);
280
281     } else {
282       ERROR2("%s: Malformed metacommand: %s", filepos, line);
283       ERROR1("Test suite `%s': NOK (syntax error)", testsuite_name);
284       rctx_armageddon(rctx, 1);
285       return;
286     }
287     break;
288   }
289 }
290
291 /*
292  * Actually doing the job
293  */
294
295 /* The IO of the childs are handled by the two following threads
296    (one pair per child) */
297
298 static void *thread_writer(void *r)
299 {
300   int posw;
301   rctx_t rctx = (rctx_t) r;
302   for (posw = 0; posw < rctx->input->used && !rctx->brokenpipe;) {
303     int got;
304     DEBUG1("Still %d chars to write", rctx->input->used - posw);
305     got =
306       write(rctx->child_to, rctx->input->data + posw,
307             rctx->input->used - posw);
308     if (got > 0)
309       posw += got;
310     if (got < 0) {
311       if (errno == EPIPE) {
312         rctx->brokenpipe = 1;
313       } else if (errno != EINTR && errno != EAGAIN && errno != EPIPE) {
314         perror("Error while writing input to child");
315         ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
316         rctx_armageddon(rctx, 4);
317         return NULL;
318       }
319     }
320     DEBUG1("written %d chars so far", posw);
321
322     if (got <= 0)
323       usleep(100);
324   }
325   rctx->input->data[0] = '\0';
326   rctx->input->used = 0;
327   close(rctx->child_to);
328
329   return NULL;
330 }
331
332 static void *thread_reader(void *r)
333 {
334   rctx_t rctx = (rctx_t) r;
335   char *buffout = malloc(4096);
336   int posr, got_pid;
337
338   do {
339     posr = read(rctx->child_from, buffout, 4095);
340     if (posr < 0 && errno != EINTR && errno != EAGAIN) {
341       perror("Error while reading output of child");
342       ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
343       rctx_armageddon(rctx, 4);
344       return NULL;
345     }
346     if (posr > 0) {
347       buffout[posr] = '\0';
348       xbt_strbuff_append(rctx->output_got, buffout);
349     } else {
350       usleep(100);
351     }
352   } while (!rctx->timeout && posr != 0);
353   free(buffout);
354
355   /* let this thread wait for the child so that the main thread can detect the timeout without blocking on the wait */
356   got_pid = waitpid(rctx->pid, &rctx->status, 0);
357   if (got_pid != rctx->pid) {
358     perror(bprintf("Cannot wait for the child %s", rctx->cmd));
359     ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
360     rctx_armageddon(rctx, 4);
361     return NULL;
362   }
363
364   rctx->reader_done = 1;
365   return NULL;
366 }
367
368 /* Special command: mkfile is a building creating a file with the input data as content */
369 static void rctx_mkfile(void)
370 {
371   char *filename = xbt_strdup(rctx->cmd + strlen("mkfile "));
372   FILE *OUT;
373   xbt_str_trim(filename, NULL);
374   OUT = fopen(filename, "w");
375   if (!OUT) {
376     free(filename);
377     THROW3(system_error, errno, "%s: Cannot create file %s: %s",
378            rctx->filepos, filename, strerror(errno));
379   }
380   fprintf(OUT, "%s", rctx->input->data);
381   fclose(OUT);
382 }
383
384 /* function to be called from the child to start the actual process */
385 static void start_command(rctx_t rctx)
386 {
387   xbt_dynar_t cmd = xbt_str_split_quoted(rctx->cmd);
388   char *binary_name = NULL;
389   unsigned int it;
390   char *str;
391   xbt_dynar_get_cpy(cmd, 0, &binary_name);
392   char **args = xbt_new(char *, xbt_dynar_length(cmd) + 1);
393   int errcode;
394
395   if (!strncmp(rctx->cmd, "mkfile ", strlen("mkfile "))) {
396     rctx_mkfile();
397     exit(0);                    /* end the working child */
398   }
399
400   xbt_dynar_foreach(cmd, it, str) {
401     args[it] = xbt_strdup(str);
402   }
403   args[it] = NULL;
404
405   /* To search for the right executable path when not trivial */
406   struct stat stat_buf;
407
408   /* build the command line */
409   if (stat(binary_name, &stat_buf)) {
410     /* Damn. binary not in current dir. We'll have to dig the PATH to find it */
411     int i;
412
413     for (i = 0; environ[i]; i++) {
414       if (!strncmp("PATH=", environ[i], 5)) {
415         xbt_dynar_t path = xbt_str_split(environ[i] + 5, ":");
416
417         xbt_dynar_foreach(path, it, str) {
418           if (binary_name)
419             free(binary_name);
420           binary_name = bprintf("%s/%s", str, args[0]);
421           if (!stat(binary_name, &stat_buf)) {
422             /* Found. */
423             DEBUG1("Looked in the PATH for the binary. Found %s",
424                    binary_name);
425             xbt_dynar_free(&path);
426             break;
427           }
428         }
429         xbt_dynar_free(&path);
430         if (stat(binary_name, &stat_buf)) {
431           /* not found */
432           printf("TESH_ERROR Command %s not found\n", args[0]);
433           exit(127);
434         }
435         break;
436       }
437     }
438   } else {
439     binary_name = xbt_strdup(args[0]);
440   }
441
442   errcode = execve(binary_name, args, rctx->env);
443   printf("TESH_ERROR %s: Cannot start %s: %s\n", rctx->filepos, rctx->cmd,
444          strerror(errcode));
445   exit(127);
446 }
447
448 /* Start a new child, plug the pipes as expected and fire up the
449    helping threads. Is also waits for the child to end if this is a
450    foreground job, or fire up a thread to wait otherwise. */
451 void rctx_start(void)
452 {
453   int child_in[2];
454   int child_out[2];
455
456   DEBUG1("Cmd before rewriting %s", rctx->cmd);
457   rctx->cmd = xbt_str_varsubst(rctx->cmd, env);
458   VERB2("Start %s %s", rctx->cmd,
459         (rctx->is_background ? "(background job)" : ""));
460   if (pipe(child_in) || pipe(child_out)) {
461     perror("Cannot open the pipes");
462     ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
463     rctx_armageddon(rctx, 4);
464   }
465
466   rctx->pid = fork();
467   if (rctx->pid < 0) {
468     perror("Cannot fork the command");
469     ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
470     rctx_armageddon(rctx, 4);
471     return;
472   }
473
474   if (rctx->pid) {              /* father */
475     close(child_in[0]);
476     rctx->child_to = child_in[1];
477
478     close(child_out[1]);
479     rctx->child_from = child_out[0];
480
481     if (timeout_value > 0)
482       rctx->end_time = time(NULL) + timeout_value;
483     else
484       rctx->end_time = -1;
485
486     rctx->reader_done = 0;
487     rctx->reader =
488       xbt_os_thread_create("reader", thread_reader, (void *) rctx);
489     rctx->writer =
490       xbt_os_thread_create("writer", thread_writer, (void *) rctx);
491
492   } else {                      /* child */
493
494     close(child_in[1]);
495     dup2(child_in[0], 0);
496     close(child_in[0]);
497
498     close(child_out[0]);
499     dup2(child_out[1], 1);
500     dup2(child_out[1], 2);
501     close(child_out[1]);
502
503     start_command(rctx);
504   }
505
506   rctx->is_stoppable = 1;
507
508   if (!rctx->is_background) {
509     rctx_wait(rctx);
510   } else {
511     /* Damn. Copy the rctx and launch a thread to handle it */
512     rctx_t old = rctx;
513     xbt_os_thread_t runner;
514
515     rctx = rctx_new();
516     DEBUG2("RCTX: new bg=%p, new fg=%p", old, rctx);
517
518     DEBUG2("Launch a thread to wait for %s %d", old->cmd, old->pid);
519     runner = xbt_os_thread_create(old->cmd, rctx_wait, (void *) old);
520     old->runner = runner;
521     VERB3("Launched thread %p to wait for %s %d", runner, old->cmd, old->pid);
522     xbt_dynar_push(bg_jobs, &old);
523   }
524 }
525
526 /* Waits for the child to end (or to timeout), and check its
527    ending conditions. This is launched from rctx_start but either in main
528    thread (for foreground jobs) or in a separate one for background jobs.
529    That explains the prototype, forced by xbt_os_thread_create. */
530
531 void *rctx_wait(void *r)
532 {
533   rctx_t rctx = (rctx_t) r;
534   int errcode = 0;
535   int now = time(NULL);
536
537   rctx_dump(rctx, "wait");
538
539   if (!rctx->is_stoppable)
540     THROW1(unknown_error, 0, "Cmd '%s' not started yet. Cannot wait it",
541            rctx->cmd);
542
543   /* Wait for the child to die or the timeout to happen (or an armageddon to happen) */
544   while (!rctx->interrupted && !rctx->reader_done
545          && (rctx->end_time < 0 || rctx->end_time >= now)) {
546     usleep(100);
547     now = time(NULL);
548   }
549
550   xbt_os_mutex_acquire(rctx->interruption);
551   if (!rctx->interrupted && rctx->end_time > 0 && rctx->end_time < now) {
552     INFO1("<%s> timeouted. Kill the process.", rctx->filepos);
553     rctx->timeout = 1;
554     kill(rctx->pid, SIGTERM);
555     usleep(100);
556     kill(rctx->pid, SIGKILL);
557     rctx->reader_done = 1;
558   }
559
560   /* Make sure helper threads die.
561      Cannot block since they wait for the child we just killed
562      if not already dead. */
563   xbt_os_thread_join(rctx->writer, NULL);
564   xbt_os_thread_join(rctx->reader, NULL);
565
566   /*  xbt_os_mutex_release(rctx->interruption);
567      if (rctx->interrupted)
568      return NULL;
569      xbt_os_mutex_acquire(rctx->interruption); */
570
571   xbt_strbuff_chomp(rctx->output_got);
572   xbt_strbuff_chomp(rctx->output_wanted);
573   xbt_strbuff_trim(rctx->output_got);
574   xbt_strbuff_trim(rctx->output_wanted);
575
576   /* Check for broken pipe */
577   if (rctx->brokenpipe)
578     VERB0("Warning: Child did not consume all its input (I got broken pipe)");
579
580   /* Check for timeouts */
581   if (rctx->timeout) {
582     if (rctx->output_got->data[0])
583       INFO2("<%s> Output on timeout:\n%s",
584             rctx->filepos, rctx->output_got->data);
585     else
586       INFO1("<%s> No output before timeout", rctx->filepos);
587     ERROR3("Test suite `%s': NOK (<%s> timeout after %d sec)",
588            testsuite_name, rctx->filepos, timeout_value);
589     DEBUG2("<%s> Interrupted = %d", rctx->filepos, rctx->interrupted);
590     if (!rctx->interrupted) {
591       rctx_armageddon(rctx, 3);
592       return NULL;
593     }
594   }
595
596   DEBUG2("RCTX=%p (pid=%d)", rctx, rctx->pid);
597   DEBUG3("Status(%s|%d)=%d", rctx->cmd, rctx->pid, rctx->status);
598
599   if (!rctx->interrupted) {
600     if (WIFSIGNALED(rctx->status) && !rctx->expected_signal) {
601       ERROR3("Test suite `%s': NOK (<%s> got signal %s)",
602              testsuite_name, rctx->filepos,
603              signal_name(WTERMSIG(rctx->status), NULL));
604       errcode = WTERMSIG(rctx->status) + 4;
605     }
606
607     if (WIFSIGNALED(rctx->status) && rctx->expected_signal &&
608         strcmp(signal_name(WTERMSIG(rctx->status), rctx->expected_signal),
609                rctx->expected_signal)) {
610       ERROR4("Test suite `%s': NOK (%s got signal %s instead of %s)",
611              testsuite_name, rctx->filepos,
612              signal_name(WTERMSIG(rctx->status), rctx->expected_signal),
613              rctx->expected_signal);
614       errcode = WTERMSIG(rctx->status) + 4;
615     }
616
617     if (!WIFSIGNALED(rctx->status) && rctx->expected_signal) {
618       ERROR3("Test suite `%s': NOK (child %s expected signal %s)",
619              testsuite_name, rctx->filepos, rctx->expected_signal);
620       errcode = 5;
621     }
622
623     if (WIFEXITED(rctx->status)
624         && WEXITSTATUS(rctx->status) != rctx->expected_return) {
625       if (rctx->expected_return)
626         ERROR4("Test suite `%s': NOK (<%s> returned code %d instead of %d)",
627                testsuite_name, rctx->filepos,
628                WEXITSTATUS(rctx->status), rctx->expected_return);
629       else
630         ERROR3("Test suite `%s': NOK (<%s> returned code %d)",
631                testsuite_name, rctx->filepos, WEXITSTATUS(rctx->status));
632       errcode = 40 + WEXITSTATUS(rctx->status);
633
634     }
635     rctx->expected_return = 0;
636
637     if (rctx->expected_signal) {
638       free(rctx->expected_signal);
639       rctx->expected_signal = NULL;
640     }
641   }
642   while (rctx->output_got->used
643          && !strncmp(rctx->output_got->data, "TESH_ERROR ",
644                      strlen("TESH_ERROR "))) {
645     int marklen = strlen("TESH_ERROR ");
646     char *endline = strchr(rctx->output_got->data, '\n');
647
648     CRITICAL2("%.*s", (int) (endline - rctx->output_got->data - marklen),
649               rctx->output_got->data + marklen);
650     memmove(rctx->output_got->data, rctx->output_got->data + marklen,
651             rctx->output_got->used - marklen);
652     rctx->output_got->used -= endline - rctx->output_got->data + 1;
653     rctx->output_got->data[rctx->output_got->used] = '\0';
654     errcode = 1;
655   }
656
657   if (rctx->output == e_output_check
658       && (rctx->output_got->used != rctx->output_wanted->used
659           || strcmp(rctx->output_got->data, rctx->output_wanted->data))) {
660     if (XBT_LOG_ISENABLED(tesh, xbt_log_priority_info)) {
661       char *diff =
662         xbt_str_diff(rctx->output_wanted->data, rctx->output_got->data);
663       ERROR2("Output of <%s> mismatch:\n%s", rctx->filepos, diff);
664       free(diff);
665     }
666     ERROR2("Test suite `%s': NOK (<%s> output mismatch)",
667            testsuite_name, rctx->filepos);
668
669     errcode = 2;
670   } else if (rctx->output == e_output_ignore) {
671     INFO1("(ignoring the output of <%s> as requested)", rctx->filepos);
672   } else if (rctx->output == e_output_display) {
673     xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
674     char *out = xbt_str_join(a, "\n||");
675     xbt_dynar_free(&a);
676     INFO1("Here is the (ignored) command output: \n||%s", out);
677     free(out);
678   } else if ((errcode && errcode != 1) || rctx->interrupted) {
679     /* checking output, and matching */
680     xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
681     char *out = xbt_str_join(a, "\n||");
682     xbt_dynar_free(&a);
683     INFO2("Output of <%s> so far: \n||%s", rctx->filepos, out);
684     free(out);
685   }
686
687   if (!rctx->is_background) {
688     rctx_empty(rctx);
689   }
690   if (errcode) {
691     if (!rctx->interrupted) {
692       rctx_armageddon(rctx, errcode);
693       return NULL;
694     }
695   }
696
697   xbt_os_mutex_release(rctx->interruption);
698   return NULL;
699 }