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