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