possible start for windows passing of stdin and stdout to 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_stdout_read;
138   int fd_stdin_read;
139   int fd_stdin_write;
140
141   argc = 0;
142   va_start (ap, filename);
143   while (NULL != va_arg (ap, char *))
144       argc++;
145   va_end (ap);
146   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
147   argc = 0;
148   va_start (ap, filename);
149   while (NULL != (argv[argc] = va_arg (ap, char *)))
150     argc++;
151   va_end (ap);
152   if (pipe_stdout != NULL)
153     {
154       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
155       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_READ), &fd_stdout_read, sizeof (int));
156     }
157   if (pipe_stdin != NULL)
158     {
159       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &fd_stdin_read, sizeof (int));
160       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_WRITE), &fd_stdin_write, sizeof (int));
161     }
162
163 #if HAVE_WORKING_VFORK
164   ret = vfork ();
165 #else
166   ret = fork ();
167 #endif
168   if (ret != 0)
169     {
170       if (ret == -1)
171         {
172           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
173         }
174       else
175         {
176
177 #if HAVE_WORKING_VFORK
178           /* let's hope vfork actually works; for some extreme cases (including
179              a testcase) we need 'execvp' to have run before we return, since
180              we may send a signal to the process next and we don't want it
181              to be caught by OUR signal handler (but either by the default
182              handler or the actual handler as installed by the process itself). */
183 #else
184           /* let's give the child process a chance to run execvp, 1s should
185              be plenty in practice */
186           if (pipe_stdout != NULL)
187             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
188           if (pipe_stdin != NULL)
189             GNUNET_DISK_pipe_close_end(pipe_stdin, GNUNET_DISK_PIPE_END_READ);
190           sleep (1);
191 #endif
192         }
193       GNUNET_free (argv);
194       return ret;
195     }
196
197   if (pipe_stdout != NULL)
198     {
199       dup2(fd_stdout_write, 1);
200       close (fd_stdout_write);
201       close (fd_stdout_read);
202     }
203
204   if (pipe_stdin != NULL)
205     {
206
207       dup2(fd_stdin_read, 0);
208       close (fd_stdin_read);
209       close (fd_stdin_write);
210     }
211
212   execvp (filename, argv);
213   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
214   _exit (1);
215 #else
216   char *arg;
217   unsigned int cmdlen;
218   char *cmd, *idx;
219   STARTUPINFO start;
220   PROCESS_INFORMATION proc;
221 #if NILS
222   HANDLE stdin_handle;
223   HANDLE stdout_handle;
224 #endif
225   char *fn;
226   int len;
227
228   cmdlen = 0;
229   va_start (ap, filename);
230   while (NULL != (arg = va_arg (ap, char *)))
231       cmdlen = cmdlen + strlen (arg) + 3;
232   va_end (ap);
233
234   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
235   va_start (ap, filename);
236   while (NULL != (arg = va_arg (ap, char *)))
237       idx += sprintf (idx, "\"%s\" ", arg);
238   va_end (ap);
239
240   memset (&start, 0, sizeof (start));
241   start.cb = sizeof (start);
242
243 #if NILS
244   if (pipe_stdin != NULL)
245     {
246       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &stdin_handle, sizeof (HANDLE));
247       start.hStdInput = stdin_handle;
248     }
249
250   if (pipe_stdout != NULL)
251     {
252       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &stdout_handle, sizeof (HANDLE));
253       start.hStdOutput = stdout_handle;
254     }
255 #endif
256   len = strlen (filename);
257   if (strnicmp (filename + len - 4, ".exe", 4) == 0)
258     fn = filename;
259   else
260     GNUNET_asprintf (&fn, "%s.exe", filename);
261
262   if (!CreateProcess
263       (fn, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
264        &proc))
265     {
266       SetErrnoFromWinError (GetLastError ());
267       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", fn);
268       return -1;
269     }
270   if (fn != filename)
271     GNUNET_free (fn);
272   CloseHandle (proc.hProcess);
273   CloseHandle (proc.hThread);
274
275   GNUNET_free (cmd);
276
277   return proc.dwProcessId;
278 #endif
279
280 }
281
282
283
284 /**
285  * Start a process.
286  *
287  * @param filename name of the binary
288  * @param argv NULL-terminated list of arguments to the process
289  * @return process ID of the new process, -1 on error
290  */
291 pid_t
292 GNUNET_OS_start_process_v (const char *filename, char *const argv[])
293 {
294 #ifndef MINGW
295   pid_t ret;
296
297 #if HAVE_WORKING_VFORK
298   ret = vfork ();
299 #else
300   ret = fork ();
301 #endif
302   if (ret != 0)
303     {
304       if (ret == -1)
305         {
306           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
307         }
308       else
309         {
310 #if HAVE_WORKING_VFORK
311           /* let's hope vfork actually works; for some extreme cases (including
312              a testcase) we need 'execvp' to have run before we return, since
313              we may send a signal to the process next and we don't want it
314              to be caught by OUR signal handler (but either by the default
315              handler or the actual handler as installed by the process itself). */
316 #else
317           /* let's give the child process a chance to run execvp, 1s should
318              be plenty in practice */
319           sleep (1);
320 #endif
321         }
322       return ret;
323     }
324   execvp (filename, argv);
325   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
326   _exit (1);
327 #else
328   char **arg;
329   unsigned int cmdlen;
330   char *cmd, *idx;
331   STARTUPINFO start;
332   PROCESS_INFORMATION proc;
333
334   cmdlen = 0;
335   arg = argv;
336   while (*arg)
337     {
338       cmdlen = cmdlen + strlen (*arg) + 3;
339       arg++;
340     }
341
342   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
343   arg = argv;
344   while (*arg)
345     {
346       idx += sprintf (idx, "\"%s\" ", *arg);
347       arg++;
348     }
349
350   memset (&start, 0, sizeof (start));
351   start.cb = sizeof (start);
352
353   if (!CreateProcess
354       (filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
355        &proc))
356     {
357       SetErrnoFromWinError (GetLastError ());
358       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
359       return -1;
360     }
361   CloseHandle (proc.hProcess);
362   CloseHandle (proc.hThread);
363
364   GNUNET_free (cmd);
365
366   return proc.dwProcessId;
367 #endif
368 }
369
370 /**
371  * Retrieve the status of a process
372  * @param proc process ID
373  * @param type status type
374  * @param code return code/signal number
375  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
376  */
377 int
378 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
379                           unsigned long *code)
380 {
381 #ifndef MINGW
382   int status;
383   int ret;
384
385   GNUNET_assert (0 != proc);
386   ret = waitpid (proc, &status, WNOHANG);
387   if (0 == ret)
388     {
389       *type = GNUNET_OS_PROCESS_RUNNING;
390       *code = 0;
391       return GNUNET_NO;
392     }
393   if (proc != ret)
394     {
395       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
396       return GNUNET_SYSERR;
397     }
398   if (WIFEXITED (status))
399     {
400       *type = GNUNET_OS_PROCESS_EXITED;
401       *code = WEXITSTATUS (status);
402     }
403   else if (WIFSIGNALED (status))
404     {
405       *type = GNUNET_OS_PROCESS_SIGNALED;
406       *code = WTERMSIG (status);
407     }
408   else if (WIFSTOPPED (status))
409     {
410       *type = GNUNET_OS_PROCESS_SIGNALED;
411       *code = WSTOPSIG (status);
412     }
413 #ifdef WIFCONTINUED
414   else if (WIFCONTINUED (status))
415     {
416       *type = GNUNET_OS_PROCESS_RUNNING;
417       *code = 0;
418     }
419 #endif
420   else
421     {
422       *type = GNUNET_OS_PROCESS_UNKNOWN;
423       *code = 0;
424     }
425 #else
426   HANDLE h;
427   DWORD c;
428
429   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
430   if (INVALID_HANDLE_VALUE == h)
431     {
432       SetErrnoFromWinError (GetLastError ());
433       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
434       return GNUNET_SYSERR;
435     }
436
437   c = GetExitCodeProcess (proc, &c);
438   if (STILL_ACTIVE == c)
439     {
440       *type = GNUNET_OS_PROCESS_RUNNING;
441       *code = 0;
442       CloseHandle (h);
443       return GNUNET_NO;
444     }
445   *type = GNUNET_OS_PROCESS_EXITED;
446   *code = c;
447   CloseHandle (h);
448 #endif
449
450   return GNUNET_OK;
451 }
452
453 /**
454  * Wait for a process
455  * @param proc process ID to wait for
456  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
457  */
458 int
459 GNUNET_OS_process_wait (pid_t proc)
460 {
461 #ifndef MINGW
462   if (proc != waitpid (proc, NULL, 0))
463     return GNUNET_SYSERR;
464
465   return GNUNET_OK;
466 #else
467   HANDLE h;
468   DWORD c;
469   int ret;
470
471   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
472   if (INVALID_HANDLE_VALUE == h)
473     {
474       SetErrnoFromWinError (GetLastError ());
475       return GNUNET_SYSERR;
476     }
477
478   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
479     {
480       SetErrnoFromWinError (GetLastError ());
481       ret = GNUNET_SYSERR;
482     }
483   else
484     ret = GNUNET_OK;
485
486   CloseHandle (h);
487
488   return ret;
489 #endif
490 }
491
492
493 /* end of os_priority.c */