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