oops
[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  *
464  * @return pointer to process structure of the new process, NULL on error
465  *
466  */
467 struct GNUNET_OS_Process *
468 GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, 
469                          struct GNUNET_DISK_PipeHandle *pipe_stdout,
470                          const char *filename, ...)
471 {
472   va_list ap;
473 #if ENABLE_WINDOWS_WORKAROUNDS
474   char *childpipename = NULL;
475   struct GNUNET_DISK_FileHandle *control_pipe = NULL;
476 #endif
477   struct GNUNET_OS_Process *gnunet_proc = NULL;
478
479 #ifndef MINGW
480   pid_t ret;
481   char **argv;
482   int argc;
483   int fd_stdout_write;
484   int fd_stdout_read;
485   int fd_stdin_read;
486   int fd_stdin_write;
487
488 #if ENABLE_WINDOWS_WORKAROUNDS
489   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
490       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
491       GNUNET_DISK_PERM_USER_WRITE);
492   if (control_pipe == NULL)
493     return NULL;
494 #endif
495
496   argc = 0;
497   va_start (ap, filename);
498   while (NULL != va_arg (ap, char *))
499       argc++;
500   va_end (ap);
501   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
502   argc = 0;
503   va_start (ap, filename);
504   while (NULL != (argv[argc] = va_arg (ap, char *)))
505     argc++;
506   va_end (ap);
507   if (pipe_stdout != NULL)
508     {
509       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
510       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_READ), &fd_stdout_read, sizeof (int));
511     }
512   if (pipe_stdin != NULL)
513     {
514       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &fd_stdin_read, sizeof (int));
515       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_WRITE), &fd_stdin_write, sizeof (int));
516     }
517
518 #if HAVE_WORKING_VFORK
519   ret = vfork ();
520 #else
521   ret = fork ();
522 #endif
523   if (ret != 0)
524     {
525       if (ret == -1)
526         {
527           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
528 #if ENABLE_WINDOWS_WORKAROUNDS
529           GNUNET_DISK_npipe_close (control_pipe);
530 #endif
531         }
532       else
533         {
534
535 #if HAVE_WORKING_VFORK
536           /* let's hope vfork actually works; for some extreme cases (including
537              a testcase) we need 'execvp' to have run before we return, since
538              we may send a signal to the process next and we don't want it
539              to be caught by OUR signal handler (but either by the default
540              handler or the actual handler as installed by the process itself). */
541 #else
542           /* let's give the child process a chance to run execvp, 1s should
543              be plenty in practice */
544           if (pipe_stdout != NULL)
545             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
546           if (pipe_stdin != NULL)
547             GNUNET_DISK_pipe_close_end(pipe_stdin, GNUNET_DISK_PIPE_END_READ);
548           sleep (1);
549 #endif
550           gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
551           gnunet_proc->pid = ret;
552 #if ENABLE_WINDOWS_WORKAROUNDS
553           gnunet_proc->control_pipe = control_pipe;
554 #endif
555         }
556       GNUNET_free (argv);
557 #if ENABLE_WINDOWS_WORKAROUNDS
558       GNUNET_free (childpipename);
559 #endif
560       return gnunet_proc;
561     }
562
563 #if ENABLE_WINDOWS_WORKAROUNDS
564   setenv (GNUNET_OS_CONTROL_PIPE, childpipename, 1);
565   GNUNET_free (childpipename);
566 #endif
567
568   if (pipe_stdout != NULL)
569     {
570       GNUNET_break (0 == close (fd_stdout_read));
571       if (-1 == dup2(fd_stdout_write, 1))
572         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
573       GNUNET_break (0 == close (fd_stdout_write));
574     }
575
576   if (pipe_stdin != NULL)
577     {
578
579       GNUNET_break (0 == close (fd_stdin_write));
580       if (-1 == dup2(fd_stdin_read, 0))
581         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
582       GNUNET_break (0 == close (fd_stdin_read));
583     }
584   execvp (filename, argv);
585   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
586   _exit (1);
587 #else
588   char *arg;
589   unsigned int cmdlen;
590   char *cmd, *idx;
591   STARTUPINFO start;
592   PROCESS_INFORMATION proc;
593
594   HANDLE stdin_handle;
595   HANDLE stdout_handle;
596
597   char path[MAX_PATH + 1];
598
599   char *our_env[3] = { NULL, NULL, NULL };
600   char *env_block = NULL;
601   char *pathbuf;
602   DWORD pathbuf_len, alloc_len;
603   char *self_prefix;
604   char *bindir;
605   char *libdir;
606   char *ptr;
607   char *non_const_filename;
608
609   /* Search in prefix dir (hopefully - the directory from which
610    * the current module was loaded), bindir and libdir, then in PATH
611    */
612   self_prefix = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_SELF_PREFIX);
613   bindir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
614   libdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
615
616   pathbuf_len = GetEnvironmentVariableA ("PATH", (char *) &pathbuf, 0);
617
618   alloc_len = pathbuf_len + 1 + strlen (self_prefix) + 1 + strlen (bindir) + 1 + strlen (libdir);
619
620   pathbuf = GNUNET_malloc (alloc_len * sizeof (char));
621
622   ptr = pathbuf;
623   ptr += sprintf (pathbuf, "%s;%s;%s;", self_prefix, bindir, libdir);
624   GNUNET_free (self_prefix);
625   GNUNET_free (bindir);
626   GNUNET_free (libdir);
627
628   alloc_len = GetEnvironmentVariableA ("PATH", ptr, pathbuf_len);
629   GNUNET_assert (alloc_len == (pathbuf_len - 1));
630
631   cmdlen = strlen (filename);
632   if (cmdlen < 5 || strcmp (&filename[cmdlen - 4], ".exe") != 0)
633     GNUNET_asprintf (&non_const_filename, "%s.exe", filename);
634   else
635     GNUNET_asprintf (&non_const_filename, "%s", filename);
636
637   /* Check that this is the full path. If it isn't, search. */
638   if (non_const_filename[1] == ':')
639     snprintf (path, sizeof (path) / sizeof (char), "%s", non_const_filename);
640   else if (!SearchPathA (pathbuf, non_const_filename, NULL, sizeof (path) / sizeof (char), path, NULL))
641     {
642       SetErrnoFromWinError (GetLastError ());
643       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "SearchPath", non_const_filename);
644       GNUNET_free (non_const_filename);
645       GNUNET_free (pathbuf);
646       return NULL;
647     }
648   GNUNET_free (pathbuf);
649   GNUNET_free (non_const_filename);
650  
651   cmdlen = 0;
652   va_start (ap, filename);
653   while (NULL != (arg = va_arg (ap, char *)))
654   {
655       if (cmdlen == 0)
656         cmdlen = cmdlen + strlen (path) + 3;
657       else
658         cmdlen = cmdlen + strlen (arg) + 3;
659   }
660   va_end (ap);
661
662   cmd = idx = GNUNET_malloc (sizeof (char) * (cmdlen + 1));
663   va_start (ap, filename);
664   while (NULL != (arg = va_arg (ap, char *)))
665   {
666       if (idx == cmd)
667         idx += sprintf (idx, "\"%s\" ", path);
668       else
669         idx += sprintf (idx, "\"%s\" ", arg);
670   }
671   va_end (ap);
672
673   memset (&start, 0, sizeof (start));
674   start.cb = sizeof (start);
675
676   if ((pipe_stdin != NULL) || (pipe_stdout != NULL))
677     start.dwFlags |= STARTF_USESTDHANDLES;
678
679   if (pipe_stdin != NULL)
680     {
681       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &stdin_handle, sizeof (HANDLE));
682       start.hStdInput = stdin_handle;
683     }
684
685   if (pipe_stdout != NULL)
686     {
687       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &stdout_handle, sizeof (HANDLE));
688       start.hStdOutput = stdout_handle;
689     }
690
691   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
692       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
693       GNUNET_DISK_PERM_USER_WRITE);
694   if (control_pipe == NULL)
695   {
696     GNUNET_free (cmd);
697     GNUNET_free (path);
698     return NULL;
699   }
700
701   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", childpipename);
702
703   GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE);
704   GNUNET_asprintf (&our_env[1], "%s", childpipename);
705   our_env[2] = NULL;
706   env_block = CreateCustomEnvTable (our_env);
707   GNUNET_free (our_env[0]);
708   GNUNET_free (our_env[1]);
709
710   if (!CreateProcessA
711       (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS | CREATE_SUSPENDED,
712        env_block, NULL, &start, &proc))
713     {
714       SetErrnoFromWinError (GetLastError ());
715       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", path);
716       GNUNET_free (env_block);
717       GNUNET_free (cmd);
718       return NULL;
719     }
720
721   GNUNET_free (env_block);
722
723   gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
724   gnunet_proc->pid = proc.dwProcessId;
725   gnunet_proc->handle = proc.hProcess;
726   gnunet_proc->control_pipe = control_pipe;
727
728   CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL);
729
730   ResumeThread (proc.hThread);
731   CloseHandle (proc.hThread);
732
733   GNUNET_free (cmd);
734
735   return gnunet_proc;
736 #endif
737
738 }
739
740
741
742 /**
743  * Start a process.
744  *
745  * @param lsocks array of listen sockets to dup systemd-style (or NULL);
746  *         must be NULL on platforms where dup is not supported
747  * @param filename name of the binary
748  * @param argv NULL-terminated list of arguments to the process
749  * @return process ID of the new process, -1 on error
750  */
751 struct GNUNET_OS_Process *
752 GNUNET_OS_start_process_v (const int *lsocks,
753                            const char *filename, char *const argv[])
754 {
755 #if ENABLE_WINDOWS_WORKAROUNDS
756   struct GNUNET_DISK_FileHandle *control_pipe = NULL;
757   char *childpipename = NULL;
758 #endif
759
760 #ifndef MINGW
761   pid_t ret;
762   char lpid[16];
763   char fds[16];
764   struct GNUNET_OS_Process *gnunet_proc = NULL;
765   int i;
766   int j;
767   int k;
768   int tgt;
769   int flags;
770   int *lscp;
771   unsigned int ls;    
772
773 #if ENABLE_WINDOWS_WORKAROUNDS
774   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
775       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
776       GNUNET_DISK_PERM_USER_WRITE);
777   if (control_pipe == NULL)
778     return NULL;
779 #endif
780
781   lscp = NULL;
782   ls = 0;
783   if (lsocks != NULL)
784     {
785       i = 0;
786       while (-1 != (k = lsocks[i++]))
787         GNUNET_array_append (lscp, ls, k);      
788       GNUNET_array_append (lscp, ls, -1);
789     }
790 #if HAVE_WORKING_VFORK
791   ret = vfork ();
792 #else
793   ret = fork ();
794 #endif
795   if (ret != 0)
796     {
797       if (ret == -1)
798         {
799           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
800 #if ENABLE_WINDOWS_WORKAROUNDS
801           GNUNET_DISK_npipe_close (control_pipe);
802 #endif
803         }
804       else
805         {
806 #if HAVE_WORKING_VFORK
807           /* let's hope vfork actually works; for some extreme cases (including
808              a testcase) we need 'execvp' to have run before we return, since
809              we may send a signal to the process next and we don't want it
810              to be caught by OUR signal handler (but either by the default
811              handler or the actual handler as installed by the process itself). */
812 #else
813           /* let's give the child process a chance to run execvp, 1s should
814              be plenty in practice */
815           sleep (1);
816 #endif
817           gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
818           gnunet_proc->pid = ret;
819 #if ENABLE_WINDOWS_WORKAROUNDS
820           gnunet_proc->control_pipe = control_pipe;
821
822 #endif
823         }
824       GNUNET_array_grow (lscp, ls, 0);
825 #if ENABLE_WINDOWS_WORKAROUNDS
826       GNUNET_free (childpipename);
827 #endif
828       return gnunet_proc;
829     }
830
831 #if ENABLE_WINDOWS_WORKAROUNDS
832         setenv (GNUNET_OS_CONTROL_PIPE, childpipename, 1);
833         GNUNET_free (childpipename);
834 #endif
835
836   if (lscp != NULL)
837     {
838       /* read systemd documentation... */
839       GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
840       setenv ("LISTEN_PID", lpid, 1);      
841       i = 0;
842       tgt = 3;
843       while (-1 != lscp[i])
844         {
845           j = i + 1;
846           while (-1 != lscp[j])
847             {
848               if (lscp[j] == tgt)
849                 {
850                   /* dup away */
851                   k = dup (lscp[j]);
852                   GNUNET_assert (-1 != k);
853                   GNUNET_assert (0 == close (lscp[j]));
854                   lscp[j] = k;
855                   break;
856                 }
857               j++;
858             }
859           if (lscp[i] != tgt)
860             {
861               /* Bury any existing FD, no matter what; they should all be closed
862                  on exec anyway and the important onces have been dup'ed away */
863               (void) close (tgt);             
864               GNUNET_assert (-1 != dup2 (lscp[i], tgt));
865             }
866           /* unset close-on-exec flag */
867           flags = fcntl (tgt, F_GETFD);
868           GNUNET_assert (flags >= 0);
869           flags &= ~FD_CLOEXEC;
870           fflush (stderr);
871           (void) fcntl (tgt, F_SETFD, flags);
872           tgt++;
873           i++;
874         }
875       GNUNET_snprintf (fds, sizeof (fds), "%u", i);
876       setenv ("LISTEN_FDS", fds, 1); 
877     }
878   GNUNET_array_grow (lscp, ls, 0);
879   execvp (filename, argv);
880   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
881   _exit (1);
882 #else
883   char **arg, **non_const_argv;
884   unsigned int cmdlen;
885   char *cmd, *idx;
886   STARTUPINFO start;
887   PROCESS_INFORMATION proc;
888   int argcount = 0;
889   struct GNUNET_OS_Process *gnunet_proc = NULL;
890
891   char path[MAX_PATH + 1];
892
893   char *our_env[3] = { NULL, NULL, NULL };
894   char *env_block = NULL;
895   char *pathbuf;
896   DWORD pathbuf_len, alloc_len;
897   char *self_prefix;
898   char *bindir;
899   char *libdir;
900   char *ptr;
901   char *non_const_filename;
902
903   GNUNET_assert (lsocks == NULL);
904
905   /* Search in prefix dir (hopefully - the directory from which
906    * the current module was loaded), bindir and libdir, then in PATH
907    */
908   self_prefix = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_SELF_PREFIX);
909   bindir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
910   libdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
911
912   pathbuf_len = GetEnvironmentVariableA ("PATH", (char *) &pathbuf, 0);
913
914   alloc_len = pathbuf_len + 1 + strlen (self_prefix) + 1 + strlen (bindir) + 1 + strlen (libdir);
915
916   pathbuf = GNUNET_malloc (alloc_len * sizeof (char));
917
918   ptr = pathbuf;
919   ptr += sprintf (pathbuf, "%s;%s;%s;", self_prefix, bindir, libdir);
920   GNUNET_free (self_prefix);
921   GNUNET_free (bindir);
922   GNUNET_free (libdir);
923
924   alloc_len = GetEnvironmentVariableA ("PATH", ptr, pathbuf_len);
925   if (alloc_len != pathbuf_len - 1)
926   {
927     GNUNET_free (pathbuf);
928     errno = ENOSYS; /* PATH changed on the fly. What kind of error is that? */
929     return NULL;
930   }
931
932   cmdlen = strlen (filename);
933   if (cmdlen < 5 || strcmp (&filename[cmdlen - 4], ".exe") != 0)
934     GNUNET_asprintf (&non_const_filename, "%s.exe", filename);
935   else
936     GNUNET_asprintf (&non_const_filename, "%s", filename);
937
938   /* Check that this is the full path. If it isn't, search. */
939   if (non_const_filename[1] == ':')
940     snprintf (path, sizeof (path) / sizeof (char), "%s", non_const_filename);
941   else if (!SearchPathA (pathbuf, non_const_filename, NULL, sizeof (path) / sizeof (char), path, NULL))
942     {
943       SetErrnoFromWinError (GetLastError ());
944       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "SearchPath", non_const_filename);
945       GNUNET_free (non_const_filename);
946       GNUNET_free (pathbuf);
947       return NULL;
948     }
949   GNUNET_free (pathbuf);
950   GNUNET_free (non_const_filename);
951
952   /* Count the number of arguments */
953   arg = (char **) argv;
954   while (*arg)
955     {
956       arg++;
957       argcount++;
958     }
959
960   /* Allocate a copy argv */
961   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
962
963   /* Copy all argv strings */
964   argcount = 0;
965   arg = (char **) argv;
966   while (*arg)
967     {
968       if (arg == argv)
969         non_const_argv[argcount] = GNUNET_strdup (path);
970       else
971         non_const_argv[argcount] = GNUNET_strdup (*arg);
972       arg++;
973       argcount++;
974     }
975   non_const_argv[argcount] = NULL;
976
977   /* Count cmd len */
978   cmdlen = 1;
979   arg = non_const_argv;
980   while (*arg)
981     {
982       cmdlen = cmdlen + strlen (*arg) + 3;
983       arg++;
984     }
985
986   /* Allocate and create cmd */
987   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
988   arg = non_const_argv;
989   while (*arg)
990     {
991       idx += sprintf (idx, "\"%s\" ", *arg);
992       arg++;
993     }
994
995   while (argcount > 0)
996     GNUNET_free (non_const_argv[--argcount]);
997   GNUNET_free (non_const_argv);
998
999   memset (&start, 0, sizeof (start));
1000   start.cb = sizeof (start);
1001
1002   control_pipe = GNUNET_DISK_npipe_create (&childpipename,
1003       GNUNET_DISK_OPEN_WRITE, GNUNET_DISK_PERM_USER_READ |
1004       GNUNET_DISK_PERM_USER_WRITE);
1005   if (control_pipe == NULL)
1006   {
1007     GNUNET_free (cmd);
1008     GNUNET_free (path);
1009     return NULL;
1010   }
1011
1012   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", childpipename);
1013
1014   GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE);
1015   GNUNET_asprintf (&our_env[1], "%s", childpipename);
1016   our_env[2] = NULL;
1017   env_block = CreateCustomEnvTable (our_env);
1018   GNUNET_free (our_env[0]);
1019   GNUNET_free (our_env[1]);
1020
1021   if (!CreateProcess
1022       (path, cmd, NULL, NULL, FALSE, DETACHED_PROCESS | CREATE_SUSPENDED,
1023        env_block, NULL, &start, &proc))
1024     {
1025       SetErrnoFromWinError (GetLastError ());
1026       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "CreateProcess");
1027       GNUNET_free (env_block);
1028       GNUNET_free (cmd);
1029       return NULL;
1030     }
1031
1032   GNUNET_free (env_block);
1033
1034   gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process));
1035   gnunet_proc->pid = proc.dwProcessId;
1036   gnunet_proc->handle = proc.hProcess;
1037   gnunet_proc->control_pipe = control_pipe;
1038
1039   CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL);
1040
1041   ResumeThread (proc.hThread);
1042   CloseHandle (proc.hThread);
1043   GNUNET_free (cmd);
1044
1045   return gnunet_proc;
1046 #endif
1047 }
1048
1049 /**
1050  * Retrieve the status of a process
1051  * @param proc process ID
1052  * @param type status type
1053  * @param code return code/signal number
1054  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
1055  */
1056 int
1057 GNUNET_OS_process_status (struct GNUNET_OS_Process *proc, 
1058                           enum GNUNET_OS_ProcessStatusType *type,
1059                           unsigned long *code)
1060 {
1061 #ifndef MINGW
1062   int status;
1063   int ret;
1064
1065   GNUNET_assert (0 != proc);
1066   ret = waitpid (proc->pid, &status, WNOHANG);
1067   if (ret < 0)
1068     {
1069       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
1070       return GNUNET_SYSERR;
1071     }
1072   if (0 == ret)
1073     {
1074       *type = GNUNET_OS_PROCESS_RUNNING;
1075       *code = 0;
1076       return GNUNET_NO;
1077     }
1078   if (proc->pid != ret)
1079     {
1080       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
1081       return GNUNET_SYSERR;
1082     }
1083   if (WIFEXITED (status))
1084     {
1085       *type = GNUNET_OS_PROCESS_EXITED;
1086       *code = WEXITSTATUS (status);
1087     }
1088   else if (WIFSIGNALED (status))
1089     {
1090       *type = GNUNET_OS_PROCESS_SIGNALED;
1091       *code = WTERMSIG (status);
1092     }
1093   else if (WIFSTOPPED (status))
1094     {
1095       *type = GNUNET_OS_PROCESS_SIGNALED;
1096       *code = WSTOPSIG (status);
1097     }
1098 #ifdef WIFCONTINUED
1099   else if (WIFCONTINUED (status))
1100     {
1101       *type = GNUNET_OS_PROCESS_RUNNING;
1102       *code = 0;
1103     }
1104 #endif
1105   else
1106     {
1107       *type = GNUNET_OS_PROCESS_UNKNOWN;
1108       *code = 0;
1109     }
1110 #else
1111   HANDLE h;
1112   DWORD c, error_code, ret;
1113
1114   h = proc->handle;
1115   ret = proc->pid;
1116   if (h == NULL || ret == 0)
1117     {
1118       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid process information {%d, %08X}\n", ret, h);
1119       return GNUNET_SYSERR;
1120     }
1121   if (h == NULL)
1122     h = GetCurrentProcess ();
1123
1124   SetLastError (0);
1125   ret = GetExitCodeProcess (h, &c);
1126   error_code = GetLastError ();
1127   if (ret == 0 || error_code != NO_ERROR)
1128   {
1129       SetErrnoFromWinError (error_code);
1130       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "GetExitCodeProcess");
1131       return GNUNET_SYSERR;
1132   }
1133   if (STILL_ACTIVE == c)
1134     {
1135       *type = GNUNET_OS_PROCESS_RUNNING;
1136       *code = 0;
1137       return GNUNET_NO;
1138     }
1139   *type = GNUNET_OS_PROCESS_EXITED;
1140   *code = c;
1141 #endif
1142
1143   return GNUNET_OK;
1144 }
1145
1146 /**
1147  * Wait for a process
1148  * @param proc pointer to process structure
1149  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1150  */
1151 int
1152 GNUNET_OS_process_wait (struct GNUNET_OS_Process *proc)
1153 {
1154
1155 #ifndef MINGW
1156   pid_t pid = proc->pid;
1157   if (pid != waitpid (pid, NULL, 0))
1158     return GNUNET_SYSERR;
1159   return GNUNET_OK;
1160 #else
1161   HANDLE h;
1162   int ret;
1163
1164   h = proc->handle;
1165   if (NULL == h)
1166     {
1167       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
1168                   "Invalid process information {%d, %08X}\n", 
1169                   proc->pid, 
1170                   h);
1171       return GNUNET_SYSERR;
1172     }
1173   if (h == NULL)
1174     h = GetCurrentProcess ();
1175
1176   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
1177     {
1178       SetErrnoFromWinError (GetLastError ());
1179       ret = GNUNET_SYSERR;
1180     }
1181   else
1182     ret = GNUNET_OK;
1183
1184   return ret;
1185 #endif
1186 }
1187
1188
1189 /* end of os_priority.c */