doxygen
[oweals/gnunet.git] / src / util / os_priority.c
1 /*
2      This file is part of GNUnet
3      (C) 2002, 2003, 2004, 2005, 2006 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet 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 GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file util/os_priority.c
23  * @brief Methods to set process priority
24  * @author Nils Durner
25  */
26
27 #include "platform.h"
28 #include "gnunet_common.h"
29 #include "gnunet_os_lib.h"
30 #include "gnunet_scheduler_lib.h"
31 #include "disk.h"
32
33 #define GNUNET_OS_CONTROL_PIPE "GNUNET_OS_CONTROL_PIPE"
34
35 struct GNUNET_OS_Process
36 {
37   pid_t pid;
38 #if WINDOWS
39   HANDLE handle;
40 #endif
41   int sig;
42   struct GNUNET_DISK_FileHandle *control_pipe;
43 };
44
45 static struct GNUNET_OS_Process current_process;
46
47
48 /**
49  * This handler is called when there are control data to be read on the pipe
50  */
51 void
52 GNUNET_OS_parent_control_handler (void *cls,
53                                   const struct
54                                   GNUNET_SCHEDULER_TaskContext * tc)
55 {
56   struct GNUNET_DISK_FileHandle *control_pipe = (struct GNUNET_DISK_FileHandle *) cls;
57   int sig;
58
59   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' invoked because of %d\n", __FUNCTION__, tc->reason);
60
61   if (tc->reason & (GNUNET_SCHEDULER_REASON_SHUTDOWN | GNUNET_SCHEDULER_REASON_TIMEOUT | GNUNET_SCHEDULER_REASON_PREREQ_DONE))
62   {
63     GNUNET_DISK_npipe_close (control_pipe);
64   }
65   else
66   {
67     if (GNUNET_DISK_file_read (control_pipe, &sig, sizeof (sig)) != sizeof (sig))
68     {
69       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "GNUNET_DISK_file_read");
70       GNUNET_DISK_npipe_close (control_pipe);
71     }
72     else
73     {
74       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got control code %d from parent\n", sig);
75       raise (sig);
76       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Re-scheduling the parent control handler pipe\n");
77       GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, control_pipe, GNUNET_OS_parent_control_handler, control_pipe);
78     }
79   }
80 }
81
82 /**
83  * Connects this process to its parent via pipe
84  */
85 void
86 GNUNET_OS_install_parent_control_handler (void *cls,
87                                           const struct
88                                           GNUNET_SCHEDULER_TaskContext * tc)
89 {
90   char *env_buf;
91   struct GNUNET_DISK_FileHandle *control_pipe = NULL;
92
93   env_buf = getenv (GNUNET_OS_CONTROL_PIPE);
94   if (env_buf == NULL || strlen (env_buf) <= 0)
95   {
96     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Not installing a handler because %s=%s\n", GNUNET_OS_CONTROL_PIPE, env_buf);
97     return;
98   }
99
100   control_pipe = GNUNET_DISK_npipe_open (env_buf, GNUNET_DISK_OPEN_READ,
101         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
102   if (control_pipe == NULL)
103   {
104     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to open the pipe `%s'\n", env_buf);
105     return;
106   }
107
108   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding parent control handler pipe `%s' to the scheduler\n", env_buf);
109   GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, control_pipe, GNUNET_OS_parent_control_handler, control_pipe);
110 }
111
112 /**
113  * Get process structure for current process
114  *
115  * The pointer it returns points to static memory location and must not be
116  * deallocated/closed
117  *
118  * @return pointer to the process sturcutre for this process
119  */
120 struct GNUNET_OS_Process *
121 GNUNET_OS_process_current ()
122 {
123 #if WINDOWS
124   current_process.pid = GetCurrentProcessId ();
125   current_process.handle = GetCurrentProcess ();
126 #else
127   current_process.pid = 0;
128 #endif
129   return &current_process;
130 }
131
132 int
133 GNUNET_OS_process_kill (struct GNUNET_OS_Process *proc, int sig)
134 {
135 #if ENABLE_WINDOWS_WORKAROUNDS
136   int res;
137   int ret;
138
139   ret = GNUNET_DISK_file_write (proc->control_pipe, &sig, sizeof(sig));
140   if (ret != sizeof(sig))
141   {
142     if (errno == ECOMM)
143       /* Child process is not controllable via pipe */
144       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
145           "Child process is not controllable, will kill it directly\n");
146     else
147       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
148           "Failed to write into control pipe , errno is %d\n", errno);
149     res = PLIBC_KILL (proc->pid, sig);
150   }
151   else
152   {
153         struct GNUNET_NETWORK_FDSet *rfds;
154     struct GNUNET_NETWORK_FDSet *efds;
155
156     rfds = GNUNET_NETWORK_fdset_create ();
157     efds = GNUNET_NETWORK_fdset_create ();
158
159     GNUNET_NETWORK_fdset_handle_set (rfds, proc->control_pipe);
160     GNUNET_NETWORK_fdset_handle_set (efds, proc->control_pipe);
161
162  read_next:
163         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
164             "Wrote control code into control pipe, now waiting\n");
165
166         ret = GNUNET_NETWORK_socket_select (rfds, NULL, efds,
167             GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_get_unit (),
168                 5000));
169
170         if (ret < 1 || GNUNET_NETWORK_fdset_handle_isset (efds,
171             proc->control_pipe))
172           {
173             /* Just to be sure */
174             PLIBC_KILL (proc->pid, sig);
175             res = 0;
176           }
177         else
178           {
179             if (GNUNET_DISK_file_read (proc->control_pipe, &ret,
180                 sizeof(ret)) != GNUNET_OK)
181               res = PLIBC_KILL (proc->pid, sig);
182
183             /* Child signaled shutdown is in progress */
184             goto read_next;
185           }
186       }
187
188     return res;
189 #else
190   return kill (proc->pid, sig);
191 #endif
192 }
193
194 /**
195  * Get the pid of the process in question
196  *
197  * @param proc the process to get the pid of
198  *
199  * @return the current process id
200  */
201 pid_t
202 GNUNET_OS_process_get_pid (struct GNUNET_OS_Process *proc)
203 {
204   return proc->pid;
205 }
206
207 void
208 GNUNET_OS_process_close (struct GNUNET_OS_Process *proc)
209 {
210 #if ENABLE_WINDOWS_WORKAROUNDS
211   if (proc->control_pipe)
212     GNUNET_DISK_npipe_close (proc->control_pipe);
213 #endif
214 // FIXME NILS
215 #ifdef WINDOWS
216   if (proc->handle != NULL)
217     CloseHandle (proc->handle);
218 #endif
219   GNUNET_free (proc);
220 }
221
222 // FIXME NILS
223 #if WINDOWS
224 #include "gnunet_signal_lib.h"
225
226 extern GNUNET_SIGNAL_Handler w32_sigchld_handler;
227
228 /**
229  * Make seaspider happy.
230  */
231 #define DWORD_WINAPI DWORD WINAPI
232
233 /**
234  * @brief Waits for a process to terminate and invokes the SIGCHLD handler
235  * @param proc pointer to process structure
236  */
237 static DWORD_WINAPI
238 ChildWaitThread (void *arg)
239 {
240   struct GNUNET_OS_Process *proc = (struct GNUNET_OS_Process *) arg;
241   WaitForSingleObject (proc->handle, INFINITE);
242
243   if (w32_sigchld_handler)
244     w32_sigchld_handler ();
245
246   return 0;
247 }
248 #endif
249
250 /**
251  * Set process priority
252  *
253  * @param proc pointer to process structure
254  * @param prio priority value
255  * @return GNUNET_OK on success, GNUNET_SYSERR on error
256  */
257 int
258 GNUNET_OS_set_process_priority (struct GNUNET_OS_Process *proc,
259                                 enum GNUNET_SCHEDULER_Priority prio)
260 {
261   int rprio;
262
263   GNUNET_assert (prio < GNUNET_SCHEDULER_PRIORITY_COUNT);
264   if (prio == GNUNET_SCHEDULER_PRIORITY_KEEP)
265     return GNUNET_OK;
266
267   /* convert to MINGW/Unix values */
268   switch (prio)
269     {
270     case GNUNET_SCHEDULER_PRIORITY_UI:
271     case GNUNET_SCHEDULER_PRIORITY_URGENT:
272 #ifdef MINGW
273       rprio = HIGH_PRIORITY_CLASS;
274 #else
275       rprio = 0;
276 #endif
277       break;
278
279     case GNUNET_SCHEDULER_PRIORITY_HIGH:
280 #ifdef MINGW
281       rprio = ABOVE_NORMAL_PRIORITY_CLASS;
282 #else
283       rprio = 5;
284 #endif
285       break;
286
287     case GNUNET_SCHEDULER_PRIORITY_DEFAULT:
288 #ifdef MINGW
289       rprio = NORMAL_PRIORITY_CLASS;
290 #else
291       rprio = 7;
292 #endif
293       break;
294
295     case GNUNET_SCHEDULER_PRIORITY_BACKGROUND:
296 #ifdef MINGW
297       rprio = BELOW_NORMAL_PRIORITY_CLASS;
298 #else
299       rprio = 10;
300 #endif
301       break;
302
303     case GNUNET_SCHEDULER_PRIORITY_IDLE:
304 #ifdef MINGW
305       rprio = IDLE_PRIORITY_CLASS;
306 #else
307       rprio = 19;
308 #endif
309       break;
310     default:
311       GNUNET_assert (0);
312       return GNUNET_SYSERR;
313     }
314
315   /* Set process priority */
316 #ifdef MINGW
317   {
318     HANDLE h = proc->handle;
319     GNUNET_assert (h != NULL);
320     SetPriorityClass (h, rprio);
321   }
322 #elif LINUX 
323   pid_t pid;
324
325   pid = proc->pid;
326   if ( (0 == pid) ||
327        (pid == getpid () ) )
328     {
329       int have = nice (0);
330       int delta = rprio - have;
331       errno = 0;
332       if ( (delta != 0) &&
333            (rprio == nice (delta)) && 
334            (errno != 0) )
335         {
336           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
337                                GNUNET_ERROR_TYPE_BULK, "nice");
338           return GNUNET_SYSERR;
339         }
340     }
341   else
342     {
343       if (0 != setpriority (PRIO_PROCESS, pid, rprio))
344         {
345           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
346                                GNUNET_ERROR_TYPE_BULK, "setpriority");
347           return GNUNET_SYSERR;
348         }
349     }
350 #else
351   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
352               "Priority management not availabe for this platform\n");
353 #endif
354   return GNUNET_OK;
355 }
356
357 #if MINGW
358 static char *
359 CreateCustomEnvTable (char **vars)
360 {
361   char *win32_env_table, *ptr, **var_ptr, *result, *result_ptr;
362   size_t tablesize = 0;
363   size_t items_count = 0;
364   size_t n_found = 0, n_var;
365   char *index = NULL;
366   size_t c;
367   size_t var_len;
368   char *var;
369   char *val;
370   win32_env_table = GetEnvironmentStringsA ();
371   if (win32_env_table == NULL)
372     return NULL;
373   for (c = 0, var_ptr = vars; *var_ptr; var_ptr += 2, c++);
374   n_var = c;
375   index = GNUNET_malloc (n_var);
376   for (c = 0; c < n_var; c++)
377     index[c] = 0;
378   for (items_count = 0, ptr = win32_env_table; ptr[0] != 0; items_count++)
379   {
380     size_t len = strlen (ptr);
381     int found = 0;
382     for (var_ptr = vars; *var_ptr; var_ptr++)
383     {
384       var = *var_ptr++;
385       val = *var_ptr;
386       var_len = strlen (var);
387       if (strncmp (var, ptr, var_len) == 0)
388       {
389         found = 1;
390         index[c] = 1;
391         tablesize += var_len + strlen (val) + 1;
392         break;
393       }
394     }
395     if (!found)
396       tablesize += len + 1;
397     ptr += len + 1; 
398   }
399   for (n_found = 0, c = 0, var_ptr = vars; *var_ptr; var_ptr++, c++)
400   {
401     var = *var_ptr++;
402     val = *var_ptr;
403     if (index[c] != 1)
404       n_found += strlen (var) + strlen (val) + 1;
405   }
406   result = GNUNET_malloc (tablesize + n_found + 1);
407   for (result_ptr = result, ptr = win32_env_table; ptr[0] != 0;)
408   {
409     size_t len = strlen (ptr);
410     int found = 0;
411     for (c = 0, var_ptr = vars; *var_ptr; var_ptr++, c++)
412     {
413       var = *var_ptr++;
414       val = *var_ptr;
415       var_len = strlen (var);
416       if (strncmp (var, ptr, var_len) == 0)
417       {
418         found = 1;
419         break;
420       }
421     }
422     if (!found)
423     {
424       strcpy (result_ptr, ptr);
425       result_ptr += len + 1;
426     }
427     else
428     {
429       strcpy (result_ptr, var);
430       result_ptr += var_len;
431       strcpy (result_ptr, val);
432       result_ptr += strlen (val) + 1;
433     }
434     ptr += len + 1;
435   }
436   for (c = 0, var_ptr = vars; *var_ptr; var_ptr++, c++)
437   {
438     var = *var_ptr++;
439     val = *var_ptr;
440     var_len = strlen (var);
441     if (index[c] != 1)
442     {
443       strcpy (result_ptr, var);
444       result_ptr += var_len;
445       strcpy (result_ptr, val);
446       result_ptr += strlen (val) + 1;
447     }
448   }
449   FreeEnvironmentStrings (win32_env_table);
450   GNUNET_free (index);
451   *result_ptr = 0;
452   return result;
453 }
454 #endif
455
456 /**
457  * Start a process.
458  *
459  * @param pipe_stdin pipe to use to send input to child process (or NULL)
460  * @param pipe_stdout pipe to use to get output from child process (or NULL)
461  * @param filename name of the binary
462  * @param ... NULL-terminated list of arguments to the process
463  * @return pointer to process structure of the new process, NULL on error
464  */
465 struct GNUNET_OS_Process *
466 GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, 
467                          struct GNUNET_DISK_PipeHandle *pipe_stdout,
468                          const char *filename, ...)
469 {
470   va_list ap;
471 #if ENABLE_WINDOWS_WORKAROUNDS
472   char *childpipename = NULL;
473   struct GNUNET_DISK_FileHandle *control_pipe = NULL;
474 #endif
475   struct GNUNET_OS_Process *gnunet_proc = NULL;
476
477 #ifndef MINGW
478   pid_t ret;
479   char **argv;
480   int argc;
481   int fd_stdout_write;
482   int fd_stdout_read;
483   int fd_stdin_read;
484   int fd_stdin_write;
485
486 #if ENABLE_WINDOWS_WORKAROUNDS
487   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
488       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
489       GNUNET_DISK_PERM_USER_WRITE);
490   if (control_pipe == NULL)
491     return NULL;
492 #endif
493
494   argc = 0;
495   va_start (ap, filename);
496   while (NULL != va_arg (ap, char *))
497       argc++;
498   va_end (ap);
499   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
500   argc = 0;
501   va_start (ap, filename);
502   while (NULL != (argv[argc] = va_arg (ap, char *)))
503     argc++;
504   va_end (ap);
505   if (pipe_stdout != NULL)
506     {
507       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
508       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_READ), &fd_stdout_read, sizeof (int));
509     }
510   if (pipe_stdin != NULL)
511     {
512       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &fd_stdin_read, sizeof (int));
513       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_WRITE), &fd_stdin_write, sizeof (int));
514     }
515
516 #if HAVE_WORKING_VFORK
517   ret = vfork ();
518 #else
519   ret = fork ();
520 #endif
521   if (ret != 0)
522     {
523       if (ret == -1)
524         {
525           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
526 #if ENABLE_WINDOWS_WORKAROUNDS
527           GNUNET_DISK_npipe_close (control_pipe);
528 #endif
529         }
530       else
531         {
532
533 #if HAVE_WORKING_VFORK
534           /* let's hope vfork actually works; for some extreme cases (including
535              a testcase) we need 'execvp' to have run before we return, since
536              we may send a signal to the process next and we don't want it
537              to be caught by OUR signal handler (but either by the default
538              handler or the actual handler as installed by the process itself). */
539 #else
540           /* let's give the child process a chance to run execvp, 1s should
541              be plenty in practice */
542           if (pipe_stdout != NULL)
543             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
544           if (pipe_stdin != NULL)
545             GNUNET_DISK_pipe_close_end(pipe_stdin, GNUNET_DISK_PIPE_END_READ);
546           sleep (1);
547 #endif
548           gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
549           gnunet_proc->pid = ret;
550 #if ENABLE_WINDOWS_WORKAROUNDS
551           gnunet_proc->control_pipe = control_pipe;
552 #endif
553         }
554       GNUNET_free (argv);
555 #if ENABLE_WINDOWS_WORKAROUNDS
556       GNUNET_free (childpipename);
557 #endif
558       return gnunet_proc;
559     }
560
561 #if ENABLE_WINDOWS_WORKAROUNDS
562   setenv (GNUNET_OS_CONTROL_PIPE, childpipename, 1);
563   GNUNET_free (childpipename);
564 #endif
565
566   if (pipe_stdout != NULL)
567     {
568       GNUNET_break (0 == close (fd_stdout_read));
569       if (-1 == dup2(fd_stdout_write, 1))
570         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
571       GNUNET_break (0 == close (fd_stdout_write));
572     }
573
574   if (pipe_stdin != NULL)
575     {
576
577       GNUNET_break (0 == close (fd_stdin_write));
578       if (-1 == dup2(fd_stdin_read, 0))
579         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
580       GNUNET_break (0 == close (fd_stdin_read));
581     }
582   execvp (filename, argv);
583   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
584   _exit (1);
585 #else
586   char *arg;
587   unsigned int cmdlen;
588   char *cmd, *idx;
589   STARTUPINFO start;
590   PROCESS_INFORMATION proc;
591
592   HANDLE stdin_handle;
593   HANDLE stdout_handle;
594
595   char path[MAX_PATH + 1];
596
597   char *our_env[3] = { NULL, NULL, NULL };
598   char *env_block = NULL;
599   char *pathbuf;
600   DWORD pathbuf_len, alloc_len;
601   char *self_prefix;
602   char *bindir;
603   char *libdir;
604   char *ptr;
605   char *non_const_filename;
606
607   /* Search in prefix dir (hopefully - the directory from which
608    * the current module was loaded), bindir and libdir, then in PATH
609    */
610   self_prefix = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_SELF_PREFIX);
611   bindir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
612   libdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
613
614   pathbuf_len = GetEnvironmentVariableA ("PATH", (char *) &pathbuf, 0);
615
616   alloc_len = pathbuf_len + 1 + strlen (self_prefix) + 1 + strlen (bindir) + 1 + strlen (libdir);
617
618   pathbuf = GNUNET_malloc (alloc_len * sizeof (char));
619
620   ptr = pathbuf;
621   ptr += sprintf (pathbuf, "%s;%s;%s;", self_prefix, bindir, libdir);
622   GNUNET_free (self_prefix);
623   GNUNET_free (bindir);
624   GNUNET_free (libdir);
625
626   alloc_len = GetEnvironmentVariableA ("PATH", ptr, pathbuf_len);
627   GNUNET_assert (alloc_len == (pathbuf_len - 1));
628
629   cmdlen = strlen (filename);
630   if (cmdlen < 5 || strcmp (&filename[cmdlen - 4], ".exe") != 0)
631     GNUNET_asprintf (&non_const_filename, "%s.exe", filename);
632   else
633     GNUNET_asprintf (&non_const_filename, "%s", filename);
634
635   /* Check that this is the full path. If it isn't, search. */
636   if (non_const_filename[1] == ':')
637     snprintf (path, sizeof (path) / sizeof (char), "%s", non_const_filename);
638   else if (!SearchPathA (pathbuf, non_const_filename, NULL, sizeof (path) / sizeof (char), path, NULL))
639     {
640       SetErrnoFromWinError (GetLastError ());
641       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "SearchPath", non_const_filename);
642       GNUNET_free (non_const_filename);
643       GNUNET_free (pathbuf);
644       return NULL;
645     }
646   GNUNET_free (pathbuf);
647   GNUNET_free (non_const_filename);
648  
649   cmdlen = 0;
650   va_start (ap, filename);
651   while (NULL != (arg = va_arg (ap, char *)))
652   {
653       if (cmdlen == 0)
654         cmdlen = cmdlen + strlen (path) + 3;
655       else
656         cmdlen = cmdlen + strlen (arg) + 3;
657   }
658   va_end (ap);
659
660   cmd = idx = GNUNET_malloc (sizeof (char) * (cmdlen + 1));
661   va_start (ap, filename);
662   while (NULL != (arg = va_arg (ap, char *)))
663   {
664       if (idx == cmd)
665         idx += sprintf (idx, "\"%s\" ", path);
666       else
667         idx += sprintf (idx, "\"%s\" ", arg);
668   }
669   va_end (ap);
670
671   memset (&start, 0, sizeof (start));
672   start.cb = sizeof (start);
673
674   if ((pipe_stdin != NULL) || (pipe_stdout != NULL))
675     start.dwFlags |= STARTF_USESTDHANDLES;
676
677   if (pipe_stdin != NULL)
678     {
679       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &stdin_handle, sizeof (HANDLE));
680       start.hStdInput = stdin_handle;
681     }
682
683   if (pipe_stdout != NULL)
684     {
685       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &stdout_handle, sizeof (HANDLE));
686       start.hStdOutput = stdout_handle;
687     }
688
689   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
690       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
691       GNUNET_DISK_PERM_USER_WRITE);
692   if (control_pipe == NULL)
693   {
694     GNUNET_free (cmd);
695     GNUNET_free (path);
696     return NULL;
697   }
698
699   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", childpipename);
700
701   GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE);
702   GNUNET_asprintf (&our_env[1], "%s", childpipename);
703   our_env[2] = NULL;
704   env_block = CreateCustomEnvTable (our_env);
705   GNUNET_free (our_env[0]);
706   GNUNET_free (our_env[1]);
707
708   if (!CreateProcessA
709       (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS | CREATE_SUSPENDED,
710        env_block, NULL, &start, &proc))
711     {
712       SetErrnoFromWinError (GetLastError ());
713       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", path);
714       GNUNET_free (env_block);
715       GNUNET_free (cmd);
716       return NULL;
717     }
718
719   GNUNET_free (env_block);
720
721   gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
722   gnunet_proc->pid = proc.dwProcessId;
723   gnunet_proc->handle = proc.hProcess;
724   gnunet_proc->control_pipe = control_pipe;
725
726   CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL);
727
728   ResumeThread (proc.hThread);
729   CloseHandle (proc.hThread);
730
731   GNUNET_free (cmd);
732
733   return gnunet_proc;
734 #endif
735
736 }
737
738
739
740 /**
741  * Start a process.
742  *
743  * @param lsocks array of listen sockets to dup systemd-style (or NULL);
744  *         must be NULL on platforms where dup is not supported
745  * @param filename name of the binary
746  * @param argv NULL-terminated list of arguments to the process
747  * @return process ID of the new process, -1 on error
748  */
749 struct GNUNET_OS_Process *
750 GNUNET_OS_start_process_v (const int *lsocks,
751                            const char *filename, char *const argv[])
752 {
753 #if ENABLE_WINDOWS_WORKAROUNDS
754   struct GNUNET_DISK_FileHandle *control_pipe = NULL;
755   char *childpipename = NULL;
756 #endif
757
758 #ifndef MINGW
759   pid_t ret;
760   char lpid[16];
761   char fds[16];
762   struct GNUNET_OS_Process *gnunet_proc = NULL;
763   int i;
764   int j;
765   int k;
766   int tgt;
767   int flags;
768   int *lscp;
769   unsigned int ls;    
770
771 #if ENABLE_WINDOWS_WORKAROUNDS
772   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
773       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
774       GNUNET_DISK_PERM_USER_WRITE);
775   if (control_pipe == NULL)
776     return NULL;
777 #endif
778
779   lscp = NULL;
780   ls = 0;
781   if (lsocks != NULL)
782     {
783       i = 0;
784       while (-1 != (k = lsocks[i++]))
785         GNUNET_array_append (lscp, ls, k);      
786       GNUNET_array_append (lscp, ls, -1);
787     }
788 #if HAVE_WORKING_VFORK
789   ret = vfork ();
790 #else
791   ret = fork ();
792 #endif
793   if (ret != 0)
794     {
795       if (ret == -1)
796         {
797           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
798 #if ENABLE_WINDOWS_WORKAROUNDS
799           GNUNET_DISK_npipe_close (control_pipe);
800 #endif
801         }
802       else
803         {
804 #if HAVE_WORKING_VFORK
805           /* let's hope vfork actually works; for some extreme cases (including
806              a testcase) we need 'execvp' to have run before we return, since
807              we may send a signal to the process next and we don't want it
808              to be caught by OUR signal handler (but either by the default
809              handler or the actual handler as installed by the process itself). */
810 #else
811           /* let's give the child process a chance to run execvp, 1s should
812              be plenty in practice */
813           sleep (1);
814 #endif
815           gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
816           gnunet_proc->pid = ret;
817 #if ENABLE_WINDOWS_WORKAROUNDS
818           gnunet_proc->control_pipe = control_pipe;
819
820 #endif
821         }
822       GNUNET_array_grow (lscp, ls, 0);
823 #if ENABLE_WINDOWS_WORKAROUNDS
824       GNUNET_free (childpipename);
825 #endif
826       return gnunet_proc;
827     }
828
829 #if ENABLE_WINDOWS_WORKAROUNDS
830         setenv (GNUNET_OS_CONTROL_PIPE, childpipename, 1);
831         GNUNET_free (childpipename);
832 #endif
833
834   if (lscp != NULL)
835     {
836       /* read systemd documentation... */
837       GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
838       setenv ("LISTEN_PID", lpid, 1);      
839       i = 0;
840       tgt = 3;
841       while (-1 != lscp[i])
842         {
843           j = i + 1;
844           while (-1 != lscp[j])
845             {
846               if (lscp[j] == tgt)
847                 {
848                   /* dup away */
849                   k = dup (lscp[j]);
850                   GNUNET_assert (-1 != k);
851                   GNUNET_assert (0 == close (lscp[j]));
852                   lscp[j] = k;
853                   break;
854                 }
855               j++;
856             }
857           if (lscp[i] != tgt)
858             {
859               /* Bury any existing FD, no matter what; they should all be closed
860                  on exec anyway and the important onces have been dup'ed away */
861               (void) close (tgt);             
862               GNUNET_assert (-1 != dup2 (lscp[i], tgt));
863             }
864           /* unset close-on-exec flag */
865           flags = fcntl (tgt, F_GETFD);
866           GNUNET_assert (flags >= 0);
867           flags &= ~FD_CLOEXEC;
868           fflush (stderr);
869           (void) fcntl (tgt, F_SETFD, flags);
870           tgt++;
871           i++;
872         }
873       GNUNET_snprintf (fds, sizeof (fds), "%u", i);
874       setenv ("LISTEN_FDS", fds, 1); 
875     }
876   GNUNET_array_grow (lscp, ls, 0);
877   execvp (filename, argv);
878   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
879   _exit (1);
880 #else
881   char **arg, **non_const_argv;
882   unsigned int cmdlen;
883   char *cmd, *idx;
884   STARTUPINFO start;
885   PROCESS_INFORMATION proc;
886   int argcount = 0;
887   struct GNUNET_OS_Process *gnunet_proc = NULL;
888
889   char path[MAX_PATH + 1];
890
891   char *our_env[3] = { NULL, NULL, NULL };
892   char *env_block = NULL;
893   char *pathbuf;
894   DWORD pathbuf_len, alloc_len;
895   char *self_prefix;
896   char *bindir;
897   char *libdir;
898   char *ptr;
899   char *non_const_filename;
900
901   GNUNET_assert (lsocks == NULL);
902
903   /* Search in prefix dir (hopefully - the directory from which
904    * the current module was loaded), bindir and libdir, then in PATH
905    */
906   self_prefix = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_SELF_PREFIX);
907   bindir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
908   libdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
909
910   pathbuf_len = GetEnvironmentVariableA ("PATH", (char *) &pathbuf, 0);
911
912   alloc_len = pathbuf_len + 1 + strlen (self_prefix) + 1 + strlen (bindir) + 1 + strlen (libdir);
913
914   pathbuf = GNUNET_malloc (alloc_len * sizeof (char));
915
916   ptr = pathbuf;
917   ptr += sprintf (pathbuf, "%s;%s;%s;", self_prefix, bindir, libdir);
918   GNUNET_free (self_prefix);
919   GNUNET_free (bindir);
920   GNUNET_free (libdir);
921
922   alloc_len = GetEnvironmentVariableA ("PATH", ptr, pathbuf_len);
923   if (alloc_len != pathbuf_len - 1)
924   {
925     GNUNET_free (pathbuf);
926     errno = ENOSYS; /* PATH changed on the fly. What kind of error is that? */
927     return NULL;
928   }
929
930   cmdlen = strlen (filename);
931   if (cmdlen < 5 || strcmp (&filename[cmdlen - 4], ".exe") != 0)
932     GNUNET_asprintf (&non_const_filename, "%s.exe", filename);
933   else
934     GNUNET_asprintf (&non_const_filename, "%s", filename);
935
936   /* Check that this is the full path. If it isn't, search. */
937   if (non_const_filename[1] == ':')
938     snprintf (path, sizeof (path) / sizeof (char), "%s", non_const_filename);
939   else if (!SearchPathA (pathbuf, non_const_filename, NULL, sizeof (path) / sizeof (char), path, NULL))
940     {
941       SetErrnoFromWinError (GetLastError ());
942       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "SearchPath", non_const_filename);
943       GNUNET_free (non_const_filename);
944       GNUNET_free (pathbuf);
945       return NULL;
946     }
947   GNUNET_free (pathbuf);
948   GNUNET_free (non_const_filename);
949
950   /* Count the number of arguments */
951   arg = (char **) argv;
952   while (*arg)
953     {
954       arg++;
955       argcount++;
956     }
957
958   /* Allocate a copy argv */
959   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
960
961   /* Copy all argv strings */
962   argcount = 0;
963   arg = (char **) argv;
964   while (*arg)
965     {
966       if (arg == argv)
967         non_const_argv[argcount] = GNUNET_strdup (path);
968       else
969         non_const_argv[argcount] = GNUNET_strdup (*arg);
970       arg++;
971       argcount++;
972     }
973   non_const_argv[argcount] = NULL;
974
975   /* Count cmd len */
976   cmdlen = 1;
977   arg = non_const_argv;
978   while (*arg)
979     {
980       cmdlen = cmdlen + strlen (*arg) + 3;
981       arg++;
982     }
983
984   /* Allocate and create cmd */
985   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
986   arg = non_const_argv;
987   while (*arg)
988     {
989       idx += sprintf (idx, "\"%s\" ", *arg);
990       arg++;
991     }
992
993   while (argcount > 0)
994     GNUNET_free (non_const_argv[--argcount]);
995   GNUNET_free (non_const_argv);
996
997   memset (&start, 0, sizeof (start));
998   start.cb = sizeof (start);
999
1000   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
1001       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
1002       GNUNET_DISK_PERM_USER_WRITE);
1003   if (control_pipe == NULL)
1004   {
1005     GNUNET_free (cmd);
1006     GNUNET_free (path);
1007     return NULL;
1008   }
1009
1010   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", childpipename);
1011
1012   GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE);
1013   GNUNET_asprintf (&our_env[1], "%s", childpipename);
1014   our_env[2] = NULL;
1015   env_block = CreateCustomEnvTable (our_env);
1016   GNUNET_free (our_env[0]);
1017   GNUNET_free (our_env[1]);
1018
1019   if (!CreateProcess
1020       (path, cmd, NULL, NULL, FALSE, DETACHED_PROCESS | CREATE_SUSPENDED,
1021        env_block, NULL, &start, &proc))
1022     {
1023       SetErrnoFromWinError (GetLastError ());
1024       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "CreateProcess");
1025       GNUNET_free (env_block);
1026       GNUNET_free (cmd);
1027       return NULL;
1028     }
1029
1030   GNUNET_free (env_block);
1031
1032   gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
1033   gnunet_proc->pid = proc.dwProcessId;
1034   gnunet_proc->handle = proc.hProcess;
1035   gnunet_proc->control_pipe = control_pipe;
1036
1037   CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL);
1038
1039   ResumeThread (proc.hThread);
1040   CloseHandle (proc.hThread);
1041   GNUNET_free (cmd);
1042
1043   return gnunet_proc;
1044 #endif
1045 }
1046
1047 /**
1048  * Retrieve the status of a process
1049  * @param proc process ID
1050  * @param type status type
1051  * @param code return code/signal number
1052  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
1053  */
1054 int
1055 GNUNET_OS_process_status (struct GNUNET_OS_Process *proc, 
1056                           enum GNUNET_OS_ProcessStatusType *type,
1057                           unsigned long *code)
1058 {
1059 #ifndef MINGW
1060   int status;
1061   int ret;
1062
1063   GNUNET_assert (0 != proc);
1064   ret = waitpid (proc->pid, &status, WNOHANG);
1065   if (ret < 0)
1066     {
1067       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
1068       return GNUNET_SYSERR;
1069     }
1070   if (0 == ret)
1071     {
1072       *type = GNUNET_OS_PROCESS_RUNNING;
1073       *code = 0;
1074       return GNUNET_NO;
1075     }
1076   if (proc->pid != ret)
1077     {
1078       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
1079       return GNUNET_SYSERR;
1080     }
1081   if (WIFEXITED (status))
1082     {
1083       *type = GNUNET_OS_PROCESS_EXITED;
1084       *code = WEXITSTATUS (status);
1085     }
1086   else if (WIFSIGNALED (status))
1087     {
1088       *type = GNUNET_OS_PROCESS_SIGNALED;
1089       *code = WTERMSIG (status);
1090     }
1091   else if (WIFSTOPPED (status))
1092     {
1093       *type = GNUNET_OS_PROCESS_SIGNALED;
1094       *code = WSTOPSIG (status);
1095     }
1096 #ifdef WIFCONTINUED
1097   else if (WIFCONTINUED (status))
1098     {
1099       *type = GNUNET_OS_PROCESS_RUNNING;
1100       *code = 0;
1101     }
1102 #endif
1103   else
1104     {
1105       *type = GNUNET_OS_PROCESS_UNKNOWN;
1106       *code = 0;
1107     }
1108 #else
1109   HANDLE h;
1110   DWORD c, error_code, ret;
1111
1112   h = proc->handle;
1113   ret = proc->pid;
1114   if (h == NULL || ret == 0)
1115     {
1116       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid process information {%d, %08X}\n", ret, h);
1117       return GNUNET_SYSERR;
1118     }
1119   if (h == NULL)
1120     h = GetCurrentProcess ();
1121
1122   SetLastError (0);
1123   ret = GetExitCodeProcess (h, &c);
1124   error_code = GetLastError ();
1125   if (ret == 0 || error_code != NO_ERROR)
1126   {
1127       SetErrnoFromWinError (error_code);
1128       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "GetExitCodeProcess");
1129       return GNUNET_SYSERR;
1130   }
1131   if (STILL_ACTIVE == c)
1132     {
1133       *type = GNUNET_OS_PROCESS_RUNNING;
1134       *code = 0;
1135       return GNUNET_NO;
1136     }
1137   *type = GNUNET_OS_PROCESS_EXITED;
1138   *code = c;
1139 #endif
1140
1141   return GNUNET_OK;
1142 }
1143
1144 /**
1145  * Wait for a process
1146  * @param proc pointer to process structure
1147  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1148  */
1149 int
1150 GNUNET_OS_process_wait (struct GNUNET_OS_Process *proc)
1151 {
1152
1153 #ifndef MINGW
1154   pid_t pid = proc->pid;
1155   if (pid != waitpid (pid, NULL, 0))
1156     return GNUNET_SYSERR;
1157   return GNUNET_OK;
1158 #else
1159   HANDLE h;
1160   int ret;
1161
1162   h = proc->handle;
1163   if (NULL == h)
1164     {
1165       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
1166                   "Invalid process information {%d, %08X}\n", 
1167                   proc->pid, 
1168                   h);
1169       return GNUNET_SYSERR;
1170     }
1171   if (h == NULL)
1172     h = GetCurrentProcess ();
1173
1174   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
1175     {
1176       SetErrnoFromWinError (GetLastError ());
1177       ret = GNUNET_SYSERR;
1178     }
1179   else
1180     ret = GNUNET_OK;
1181
1182   return ret;
1183 #endif
1184 }
1185
1186
1187 /* end of os_priority.c */