error checking
[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       GNUNET_break (0 == close (fd_stdout_read));
237       if (-1 == dup2(fd_stdout_write, 1))
238         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
239       GNUNET_break (0 == close (fd_stdout_write));
240     }
241
242   if (pipe_stdin != NULL)
243     {
244
245       GNUNET_break (0 == close (fd_stdin_write));
246       if (-1 == dup2(fd_stdin_read, 0))
247         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
248       GNUNET_break (0 == close (fd_stdin_read));
249     }
250   execvp (filename, argv);
251   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
252   _exit (1);
253 #else
254   char *arg;
255   unsigned int cmdlen;
256   char *cmd, *idx;
257   STARTUPINFO start;
258   PROCESS_INFORMATION proc;
259 #if NILS
260   HANDLE stdin_handle;
261   HANDLE stdout_handle;
262 #endif
263   char *fn;
264   int len;
265   char path[MAX_PATH + 1];
266
267   cmdlen = 0;
268   va_start (ap, filename);
269   while (NULL != (arg = va_arg (ap, char *)))
270       cmdlen = cmdlen + strlen (arg) + 3;
271   va_end (ap);
272
273   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
274   va_start (ap, filename);
275   while (NULL != (arg = va_arg (ap, char *)))
276       idx += sprintf (idx, "\"%s\" ", arg);
277   va_end (ap);
278
279   memset (&start, 0, sizeof (start));
280   start.cb = sizeof (start);
281
282 #if NILS
283   if ((pipe_stdin != NULL) || (pipe_stdout != NULL))
284     start.dwFlags |= STARTF_USESTDHANDLES;
285
286   if (pipe_stdin != NULL)
287     {
288       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &stdin_handle, sizeof (HANDLE));
289       start.hStdInput = stdin_handle;
290     }
291
292   if (pipe_stdout != NULL)
293     {
294       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &stdout_handle, sizeof (HANDLE));
295       start.hStdOutput = stdout_handle;
296     }
297 #endif
298   if (FindExecutable(filename, NULL, path) <= 32)
299     {
300       SetErrnoFromWinError (GetLastError ());
301       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "FindExecutable", fn);
302       return -1;
303     }
304
305   if (!CreateProcess
306       (path, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
307        &proc))
308     {
309       SetErrnoFromWinError (GetLastError ());
310       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", fn);
311       return -1;
312     }
313
314   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
315
316   if (fn != filename)
317     GNUNET_free (fn);
318   CloseHandle (proc.hThread);
319
320   GNUNET_free (cmd);
321
322   return proc.dwProcessId;
323 #endif
324
325 }
326
327
328
329 /**
330  * Start a process.
331  *
332  * @param filename name of the binary
333  * @param argv NULL-terminated list of arguments to the process
334  * @return process ID of the new process, -1 on error
335  */
336 pid_t
337 GNUNET_OS_start_process_v (const char *filename, char *const argv[])
338 {
339 #ifndef MINGW
340   pid_t ret;
341
342 #if HAVE_WORKING_VFORK
343   ret = vfork ();
344 #else
345   ret = fork ();
346 #endif
347   if (ret != 0)
348     {
349       if (ret == -1)
350         {
351           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
352         }
353       else
354         {
355 #if HAVE_WORKING_VFORK
356           /* let's hope vfork actually works; for some extreme cases (including
357              a testcase) we need 'execvp' to have run before we return, since
358              we may send a signal to the process next and we don't want it
359              to be caught by OUR signal handler (but either by the default
360              handler or the actual handler as installed by the process itself). */
361 #else
362           /* let's give the child process a chance to run execvp, 1s should
363              be plenty in practice */
364           sleep (1);
365 #endif
366         }
367       return ret;
368     }
369   execvp (filename, argv);
370   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
371   _exit (1);
372 #else
373   char **arg;
374   unsigned int cmdlen;
375   char *cmd, *idx;
376   STARTUPINFO start;
377   PROCESS_INFORMATION proc;
378
379   cmdlen = 0;
380   arg = argv;
381   while (*arg)
382     {
383       cmdlen = cmdlen + strlen (*arg) + 3;
384       arg++;
385     }
386
387   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
388   arg = argv;
389   while (*arg)
390     {
391       idx += sprintf (idx, "\"%s\" ", *arg);
392       arg++;
393     }
394
395   memset (&start, 0, sizeof (start));
396   start.cb = sizeof (start);
397
398   if (!CreateProcess
399       (filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
400        &proc))
401     {
402       SetErrnoFromWinError (GetLastError ());
403       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
404       return -1;
405     }
406
407   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
408
409   CloseHandle (proc.hThread);
410   GNUNET_free (cmd);
411
412   return proc.dwProcessId;
413 #endif
414 }
415
416 /**
417  * Retrieve the status of a process
418  * @param proc process ID
419  * @param type status type
420  * @param code return code/signal number
421  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
422  */
423 int
424 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
425                           unsigned long *code)
426 {
427 #ifndef MINGW
428   int status;
429   int ret;
430
431   GNUNET_assert (0 != proc);
432   ret = waitpid (proc, &status, WNOHANG);
433   if (0 == ret)
434     {
435       *type = GNUNET_OS_PROCESS_RUNNING;
436       *code = 0;
437       return GNUNET_NO;
438     }
439   if (proc != ret)
440     {
441       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
442       return GNUNET_SYSERR;
443     }
444   if (WIFEXITED (status))
445     {
446       *type = GNUNET_OS_PROCESS_EXITED;
447       *code = WEXITSTATUS (status);
448     }
449   else if (WIFSIGNALED (status))
450     {
451       *type = GNUNET_OS_PROCESS_SIGNALED;
452       *code = WTERMSIG (status);
453     }
454   else if (WIFSTOPPED (status))
455     {
456       *type = GNUNET_OS_PROCESS_SIGNALED;
457       *code = WSTOPSIG (status);
458     }
459 #ifdef WIFCONTINUED
460   else if (WIFCONTINUED (status))
461     {
462       *type = GNUNET_OS_PROCESS_RUNNING;
463       *code = 0;
464     }
465 #endif
466   else
467     {
468       *type = GNUNET_OS_PROCESS_UNKNOWN;
469       *code = 0;
470     }
471 #else
472   HANDLE h;
473   DWORD c;
474
475   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
476   if (INVALID_HANDLE_VALUE == h)
477     {
478       SetErrnoFromWinError (GetLastError ());
479       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
480       return GNUNET_SYSERR;
481     }
482
483   c = GetExitCodeProcess (proc, &c);
484   if (STILL_ACTIVE == c)
485     {
486       *type = GNUNET_OS_PROCESS_RUNNING;
487       *code = 0;
488       CloseHandle (h);
489       return GNUNET_NO;
490     }
491   *type = GNUNET_OS_PROCESS_EXITED;
492   *code = c;
493   CloseHandle (h);
494 #endif
495
496   return GNUNET_OK;
497 }
498
499 /**
500  * Wait for a process
501  * @param proc process ID to wait for
502  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
503  */
504 int
505 GNUNET_OS_process_wait (pid_t proc)
506 {
507 #ifndef MINGW
508   if (proc != waitpid (proc, NULL, 0))
509     return GNUNET_SYSERR;
510
511   return GNUNET_OK;
512 #else
513   HANDLE h;
514   DWORD c;
515   int ret;
516
517   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
518   if (INVALID_HANDLE_VALUE == h)
519     {
520       SetErrnoFromWinError (GetLastError ());
521       return GNUNET_SYSERR;
522     }
523
524   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
525     {
526       SetErrnoFromWinError (GetLastError ());
527       ret = GNUNET_SYSERR;
528     }
529   else
530     ret = GNUNET_OK;
531
532   CloseHandle (h);
533
534   return ret;
535 #endif
536 }
537
538
539 /* end of os_priority.c */