Allow redirection of STDERR when starting processes.
[oweals/gnunet.git] / src / testbed / gnunet_testbed_mpi_spawn.c
1 #include "platform.h"
2 #include "gnunet_util_lib.h"
3 #include "gnunet_testbed_service.h"
4
5
6 /**
7  * Generic logging shorthand
8  */
9 #define LOG(kind,...)                           \
10   GNUNET_log (kind, __VA_ARGS__)
11
12 /**
13  * Debug logging shorthand
14  */
15 #define LOG_DEBUG(...)                          \
16   LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
17
18 /**
19  * Global result
20  */
21 static int ret;
22
23 /**
24  * The child process we spawn
25  */
26 static struct GNUNET_OS_Process *child;
27
28 /**
29  * The arguments including the binary to spawn
30  */
31 static char **argv2;
32
33 /**
34  * Pipe used to communicate shutdown via signal.
35  */
36 static struct GNUNET_DISK_PipeHandle *sigpipe;
37
38 /**
39  * Filename of the unique file
40  */
41 static char *fn;
42
43 /**
44  * Handle to the unique file
45  */
46 static int fh;
47
48 /**
49  * The return code of the binary
50  */
51 static unsigned long child_exit_code;
52
53 /**
54  * The process status of the child
55  */
56 static enum GNUNET_OS_ProcessStatusType child_status;
57
58 /**
59  * The shutdown task
60  */
61 static GNUNET_SCHEDULER_TaskIdentifier shutdown_task_id;
62
63 /**
64  * Task to kill the child
65  */
66 static GNUNET_SCHEDULER_TaskIdentifier terminate_task_id;
67
68 /**
69  * Task to kill the child
70  */
71 static GNUNET_SCHEDULER_TaskIdentifier child_death_task_id;
72
73 /**
74  * The shutdown task
75  */
76 static void
77 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
78 {
79   shutdown_task_id = GNUNET_SCHEDULER_NO_TASK;
80   if (0 != child_exit_code)
81   {
82     LOG (GNUNET_ERROR_TYPE_WARNING, "Child exited with error code: %lu\n",
83          child_exit_code);
84     ret = 128 + (int) child_exit_code;
85   }
86   if (0 != fh)
87   {
88     close (fh);
89   }
90   if ((NULL != fn) && (0 != unlink (fn)))
91   {
92     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "open");
93     ret = GNUNET_SYSERR;
94   }
95 }
96
97
98 static void
99 terminate_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
100 {
101   static int hard_kill;
102
103   GNUNET_assert (NULL != child);
104   terminate_task_id =
105       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
106                                     &terminate_task, NULL);
107   if (0 != hard_kill)
108   {
109     switch (hard_kill)
110     {
111     case 1:
112     case 2:
113       LOG (GNUNET_ERROR_TYPE_WARNING,
114            "%d more interrupts needed to send SIGKILL to the child\n",
115            3 - hard_kill);
116       hard_kill++;
117       return;
118     case 3:
119       GNUNET_break (0 == GNUNET_OS_process_kill (child, SIGKILL));
120       return;
121     }
122   }
123   hard_kill++;
124   GNUNET_break (0 == GNUNET_OS_process_kill (child, GNUNET_TERM_SIG));
125   LOG (GNUNET_ERROR_TYPE_INFO, _("Waiting for child to exit.\n"));
126 }
127
128
129 /**
130  * Task triggered whenever we receive a SIGCHLD (child
131  * process died).
132  *
133  * @param cls closure, NULL if we need to self-restart
134  * @param tc context
135  */
136 static void
137 child_death_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
138 {
139   const struct GNUNET_DISK_FileHandle *pr;
140   char c[16];
141
142   pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
143   child_death_task_id = GNUNET_SCHEDULER_NO_TASK;
144   if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
145   {
146     child_death_task_id =
147         GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
148                                         pr, &child_death_task, NULL);
149     return;
150   }
151   /* consume the signal */
152   GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
153   LOG_DEBUG ("Child died\n");
154   GNUNET_SCHEDULER_cancel (terminate_task_id);
155   terminate_task_id = GNUNET_SCHEDULER_NO_TASK;
156   GNUNET_assert (GNUNET_OK == GNUNET_OS_process_status (child, &child_status,
157                                                         &child_exit_code));
158   GNUNET_OS_process_destroy (child);
159   child = NULL;
160   shutdown_task_id = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
161 }
162
163
164 static void
165 destroy_hosts(struct GNUNET_TESTBED_Host **hosts, unsigned int nhosts)
166 {
167   unsigned int host;
168
169   GNUNET_assert (NULL != hosts);
170   for (host = 0; host < nhosts; host++)
171     if (NULL != hosts[host])
172       GNUNET_TESTBED_host_destroy (hosts[host]);
173   GNUNET_free (hosts);
174   hosts = NULL;
175 }
176
177
178 /**
179  * The main scheduler run task
180  *
181  * @param cls NULL
182  * @param tc scheduler task context
183  */
184 static void
185 run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
186 {
187   struct GNUNET_TESTBED_Host **hosts;
188   const struct GNUNET_CONFIGURATION_Handle *null_cfg;
189   char *tmpdir;
190   char *hostname;
191   size_t hostname_len;
192   unsigned int nhosts;
193
194   null_cfg = GNUNET_CONFIGURATION_create ();
195   nhosts = GNUNET_TESTBED_hosts_load_from_loadleveler (null_cfg, &hosts);
196   if (0 == nhosts)
197   {
198     GNUNET_break (0);
199     ret = GNUNET_SYSERR;
200     return;
201   }
202   hostname_len = GNUNET_OS_get_hostname_max_length ();
203   hostname = GNUNET_malloc (hostname_len);
204   if (0 != gethostname (hostname, hostname_len))
205   {
206     LOG (GNUNET_ERROR_TYPE_ERROR, "Cannot get hostname.  Exiting\n");
207     GNUNET_free (hostname);
208     destroy_hosts (hosts, nhosts);
209     ret = GNUNET_SYSERR;
210     return;
211   }
212   if (NULL == strstr (GNUNET_TESTBED_host_get_hostname (hosts[0]), hostname))
213   {
214     LOG_DEBUG ("Exiting as `%s' is not the lowest host\n", hostname);
215     GNUNET_free (hostname);
216     ret = GNUNET_OK;
217     return;
218   }
219   LOG_DEBUG ("Will be executing `%s' on host `%s'\n", argv2[0], hostname);
220   GNUNET_free (hostname);
221   destroy_hosts (hosts, nhosts);
222   tmpdir = getenv ("TMPDIR");
223   if (NULL == tmpdir)
224     tmpdir = getenv ("TMP");
225   if (NULL == tmpdir)
226     tmpdir = getenv ("TEMP");
227   if (NULL == tmpdir)
228     tmpdir = "/tmp";
229   (void) GNUNET_asprintf (&fn, "%s/gnunet-testbed-spawn.lock", tmpdir);
230   /* Open the unique file; we can create it then we can spawn the child process
231      else we exit */
232   fh = open (fn, O_CREAT | O_EXCL | O_CLOEXEC,
233              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
234   if (-1 == fh)
235   {
236     if (EEXIST == errno)
237     {
238       LOG_DEBUG ("Lock file already created by other process.  Exiting\n");
239       ret = GNUNET_OK;
240       return;
241     }
242     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "open");
243     ret = GNUNET_SYSERR;
244     return;
245   }
246   /* Spawn the new process here */
247   LOG (GNUNET_ERROR_TYPE_INFO, _("Spawning process `%s'\n"), argv2[0]);
248   child = GNUNET_OS_start_process_vap (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL,
249                                        NULL, NULL,
250                                        argv2[0], argv2);
251   if (NULL == child)
252   {
253     GNUNET_break (0);
254     ret = GNUNET_SYSERR;
255     shutdown_task_id = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
256     return;
257   }
258   ret = GNUNET_OK;
259   terminate_task_id =
260       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
261                                     &terminate_task, NULL);
262   child_death_task_id =
263     GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
264                                     GNUNET_DISK_pipe_handle (sigpipe,
265                                                              GNUNET_DISK_PIPE_END_READ),
266                                     &child_death_task, NULL);
267 }
268
269
270 /**
271  * Signal handler called for SIGCHLD.
272  */
273 static void
274 sighandler_child_death ()
275 {
276   static char c;
277   int old_errno = errno;        /* back-up errno */
278
279   GNUNET_break (1 ==
280                 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
281                                         (sigpipe, GNUNET_DISK_PIPE_END_WRITE),
282                                         &c, sizeof (c)));
283   errno = old_errno;            /* restore errno */
284 }
285
286
287 /**
288  * Execution start point
289  */
290 int
291 main (int argc, char *argv[])
292 {
293   struct GNUNET_SIGNAL_Context *shc_chld;
294   unsigned int cnt;
295
296   ret = -1;
297   if (argc < 2)
298   {
299     printf ("Need arguments: gnunet-testbed-mpi-spawn <cmd> <cmd_args>");
300     return 1;
301   }
302   if (GNUNET_OK != GNUNET_log_setup ("gnunet-testbed-spawn", NULL, NULL))
303   {
304     GNUNET_break (0);
305     return 1;
306   }
307   if (NULL == (sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO,
308                                            GNUNET_NO, GNUNET_NO)))
309   {
310     GNUNET_break (0);
311     ret = GNUNET_SYSERR;
312     return 1;
313   }
314   shc_chld =
315       GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death);
316   if (NULL == shc_chld)
317   {
318     LOG (GNUNET_ERROR_TYPE_ERROR, "Cannot install a signal handler\n");
319     return 1;
320   }
321   argv2 = GNUNET_malloc (sizeof (char *) * argc);
322   for (cnt = 1; cnt < argc; cnt++)
323     argv2[cnt - 1] = argv[cnt];
324   GNUNET_SCHEDULER_run (run, NULL);
325   GNUNET_free (argv2);
326   GNUNET_SIGNAL_handler_uninstall (shc_chld);
327   shc_chld = NULL;
328   GNUNET_DISK_pipe_close (sigpipe);
329   GNUNET_free_non_null (fn);
330   if (GNUNET_OK != ret)
331     return ret;
332   return 0;
333 }