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