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