start process change to allow stdout to be sent from child process
[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 "disk.h"
31
32 /**
33  * Set process priority
34  *
35  * @param proc id of the process
36  * @param prio priority value
37  * @return GNUNET_OK on success, GNUNET_SYSERR on error
38  */
39 int
40 GNUNET_OS_set_process_priority (pid_t proc,
41                                 enum GNUNET_SCHEDULER_Priority prio)
42 {
43   int rprio = 0;
44
45   GNUNET_assert (prio < GNUNET_SCHEDULER_PRIORITY_COUNT);
46   if (prio == GNUNET_SCHEDULER_PRIORITY_KEEP)
47     return GNUNET_OK;
48   /* convert to MINGW/Unix values */
49   switch (prio)
50     {
51     case GNUNET_SCHEDULER_PRIORITY_DEFAULT:
52 #ifdef MINGW
53       rprio = NORMAL_PRIORITY_CLASS;
54 #else
55       rprio = 0;
56 #endif
57       break;
58     case GNUNET_SCHEDULER_PRIORITY_HIGH:
59 #ifdef MINGW
60       rprio = ABOVE_NORMAL_PRIORITY_CLASS;
61 #else
62       rprio = -5;
63 #endif
64       break;
65     case GNUNET_SCHEDULER_PRIORITY_BACKGROUND:
66 #ifdef MINGW
67       rprio = BELOW_NORMAL_PRIORITY_CLASS;
68 #else
69       rprio = 10;
70 #endif
71       break;
72     case GNUNET_SCHEDULER_PRIORITY_UI:
73     case GNUNET_SCHEDULER_PRIORITY_URGENT:
74 #ifdef MINGW
75       rprio = HIGH_PRIORITY_CLASS;
76 #else
77       rprio = -10;
78 #endif
79       break;
80     case GNUNET_SCHEDULER_PRIORITY_IDLE:
81 #ifdef MINGW
82       rprio = IDLE_PRIORITY_CLASS;
83 #else
84       rprio = 19;
85 #endif
86       break;
87     default:
88       GNUNET_assert (0);
89       return GNUNET_SYSERR;
90     }
91   /* Set process priority */
92 #ifdef MINGW
93   SetPriorityClass (GetCurrentProcess (), rprio);
94 #else
95   if (proc == getpid ())
96     {
97       errno = 0;
98       if ((-1 == nice (rprio)) && (errno != 0))
99         {
100           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
101                                GNUNET_ERROR_TYPE_BULK, "nice");
102           return GNUNET_SYSERR;
103         }
104     }
105   else
106     {
107       if (0 != setpriority (PRIO_PROCESS, proc, rprio))
108
109         {
110           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
111                                GNUNET_ERROR_TYPE_BULK, "setpriority");
112           return GNUNET_SYSERR;
113         }
114     }
115 #endif
116   return GNUNET_OK;
117 }
118
119 /**
120  * Start a process.
121  *
122  * @param filename name of the binary
123  * @param ... NULL-terminated list of arguments to the process
124  * @return process ID of the new process, -1 on error
125  */
126 pid_t
127 GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, struct GNUNET_DISK_PipeHandle *pipe_stdout, const char *filename, ...)
128 {
129   /* FIXME:  Make this work on windows!!! */
130   va_list ap;
131
132 #ifndef MINGW
133   pid_t ret;
134   char **argv;
135   int argc;
136   int fd_stdout_write;
137   int fd_stdin_read;
138
139   argc = 0;
140   va_start (ap, filename);
141   while (NULL != va_arg (ap, char *))
142       argc++;
143   va_end (ap);
144   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
145   argc = 0;
146   va_start (ap, filename);
147   while (NULL != (argv[argc] = va_arg (ap, char *)))
148     argc++;
149   va_end (ap);
150   if (pipe_stdout != NULL)
151     GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
152
153 #if HAVE_WORKING_VFORK
154   ret = vfork ();
155 #else
156   ret = fork ();
157 #endif
158   if (ret != 0)
159     {
160       if (ret == -1)
161         {
162           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
163         }
164       else
165         {
166 #if HAVE_WORKING_VFORK
167           /* let's hope vfork actually works; for some extreme cases (including
168              a testcase) we need 'execvp' to have run before we return, since
169              we may send a signal to the process next and we don't want it
170              to be caught by OUR signal handler (but either by the default
171              handler or the actual handler as installed by the process itself). */
172 #else
173           /* let's give the child process a chance to run execvp, 1s should
174              be plenty in practice */
175           sleep (1);
176           if (pipe_stdout != NULL)
177             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
178 #endif
179         }
180       GNUNET_free (argv);
181       return ret;
182     }
183   if (pipe_stdout != NULL)
184     {
185       dup2(fd_stdout_write, 1);
186       close (fd_stdout_write);
187     }
188
189   execvp (filename, argv);
190   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
191   _exit (1);
192 #else
193   char *arg;
194   unsigned int cmdlen;
195   char *cmd, *idx;
196   STARTUPINFO start;
197   PROCESS_INFORMATION proc;
198   char *fn;
199   int len;
200
201   cmdlen = 0;
202   va_start (ap, filename);
203   while (NULL != (arg = va_arg (ap, char *)))
204       cmdlen = cmdlen + strlen (arg) + 3;
205   va_end (ap);
206
207   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
208   va_start (ap, filename);
209   while (NULL != (arg = va_arg (ap, char *)))
210       idx += sprintf (idx, "\"%s\" ", arg);
211   va_end (ap);
212
213   memset (&start, 0, sizeof (start));
214   start.cb = sizeof (start);
215
216   len = strlen (filename);
217   if (strnicmp (filename + len - 4, ".exe", 4) == 0)
218     fn = filename;
219   else
220     GNUNET_asprintf (&fn, "%s.exe", filename);
221
222   if (!CreateProcess
223       (fn, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
224        &proc))
225     {
226       SetErrnoFromWinError (GetLastError ());
227       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", fn);
228       return -1;
229     }
230   if (fn != filename)
231     GNUNET_free (fn);
232   CloseHandle (proc.hProcess);
233   CloseHandle (proc.hThread);
234
235   GNUNET_free (cmd);
236
237   return proc.dwProcessId;
238 #endif
239
240 }
241
242
243
244 /**
245  * Start a process.
246  *
247  * @param filename name of the binary
248  * @param argv NULL-terminated list of arguments to the process
249  * @return process ID of the new process, -1 on error
250  */
251 pid_t
252 GNUNET_OS_start_process_v (const char *filename, char *const argv[])
253 {
254 #ifndef MINGW
255   pid_t ret;
256
257 #if HAVE_WORKING_VFORK
258   ret = vfork ();
259 #else
260   ret = fork ();
261 #endif
262   if (ret != 0)
263     {
264       if (ret == -1)
265         {
266           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
267         }
268       else
269         {
270 #if HAVE_WORKING_VFORK
271           /* let's hope vfork actually works; for some extreme cases (including
272              a testcase) we need 'execvp' to have run before we return, since
273              we may send a signal to the process next and we don't want it
274              to be caught by OUR signal handler (but either by the default
275              handler or the actual handler as installed by the process itself). */
276 #else
277           /* let's give the child process a chance to run execvp, 1s should
278              be plenty in practice */
279           sleep (1);
280 #endif
281         }
282       return ret;
283     }
284   execvp (filename, argv);
285   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
286   _exit (1);
287 #else
288   char **arg;
289   unsigned int cmdlen;
290   char *cmd, *idx;
291   STARTUPINFO start;
292   PROCESS_INFORMATION proc;
293
294   cmdlen = 0;
295   arg = argv;
296   while (*arg)
297     {
298       cmdlen = cmdlen + strlen (*arg) + 3;
299       arg++;
300     }
301
302   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
303   arg = argv;
304   while (*arg)
305     {
306       idx += sprintf (idx, "\"%s\" ", *arg);
307       arg++;
308     }
309
310   memset (&start, 0, sizeof (start));
311   start.cb = sizeof (start);
312
313   if (!CreateProcess
314       (filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
315        &proc))
316     {
317       SetErrnoFromWinError (GetLastError ());
318       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
319       return -1;
320     }
321   CloseHandle (proc.hProcess);
322   CloseHandle (proc.hThread);
323
324   GNUNET_free (cmd);
325
326   return proc.dwProcessId;
327 #endif
328 }
329
330 /**
331  * Retrieve the status of a process
332  * @param proc process ID
333  * @param type status type
334  * @param code return code/signal number
335  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
336  */
337 int
338 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
339                           unsigned long *code)
340 {
341 #ifndef MINGW
342   int status;
343   int ret;
344
345   GNUNET_assert (0 != proc);
346   ret = waitpid (proc, &status, WNOHANG);
347   if (0 == ret)
348     {
349       *type = GNUNET_OS_PROCESS_RUNNING;
350       *code = 0;
351       return GNUNET_NO;
352     }
353   if (proc != ret)
354     {
355       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
356       return GNUNET_SYSERR;
357     }
358   if (WIFEXITED (status))
359     {
360       *type = GNUNET_OS_PROCESS_EXITED;
361       *code = WEXITSTATUS (status);
362     }
363   else if (WIFSIGNALED (status))
364     {
365       *type = GNUNET_OS_PROCESS_SIGNALED;
366       *code = WTERMSIG (status);
367     }
368   else if (WIFSTOPPED (status))
369     {
370       *type = GNUNET_OS_PROCESS_SIGNALED;
371       *code = WSTOPSIG (status);
372     }
373 #ifdef WIFCONTINUED
374   else if (WIFCONTINUED (status))
375     {
376       *type = GNUNET_OS_PROCESS_RUNNING;
377       *code = 0;
378     }
379 #endif
380   else
381     {
382       *type = GNUNET_OS_PROCESS_UNKNOWN;
383       *code = 0;
384     }
385 #else
386   HANDLE h;
387   DWORD c;
388
389   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
390   if (INVALID_HANDLE_VALUE == h)
391     {
392       SetErrnoFromWinError (GetLastError ());
393       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
394       return GNUNET_SYSERR;
395     }
396
397   c = GetExitCodeProcess (proc, &c);
398   if (STILL_ACTIVE == c)
399     {
400       *type = GNUNET_OS_PROCESS_RUNNING;
401       *code = 0;
402       CloseHandle (h);
403       return GNUNET_NO;
404     }
405   *type = GNUNET_OS_PROCESS_EXITED;
406   *code = c;
407   CloseHandle (h);
408 #endif
409
410   return GNUNET_OK;
411 }
412
413 /**
414  * Wait for a process
415  * @param proc process ID to wait for
416  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
417  */
418 int
419 GNUNET_OS_process_wait (pid_t proc)
420 {
421 #ifndef MINGW
422   if (proc != waitpid (proc, NULL, 0))
423     return GNUNET_SYSERR;
424
425   return GNUNET_OK;
426 #else
427   HANDLE h;
428   DWORD c;
429   int ret;
430
431   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
432   if (INVALID_HANDLE_VALUE == h)
433     {
434       SetErrnoFromWinError (GetLastError ());
435       return GNUNET_SYSERR;
436     }
437
438   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
439     {
440       SetErrnoFromWinError (GetLastError ());
441       ret = GNUNET_SYSERR;
442     }
443   else
444     ret = GNUNET_OK;
445
446   CloseHandle (h);
447
448   return ret;
449 #endif
450 }
451
452
453 /* end of os_priority.c */