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