add stdin functionality for 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   /* FIXME:  Make this work with stdin as well as stdout! */
131   va_list ap;
132
133 #ifndef MINGW
134   pid_t ret;
135   char **argv;
136   int argc;
137   int fd_stdout_write;
138   int fd_stdin_read;
139
140   argc = 0;
141   va_start (ap, filename);
142   while (NULL != va_arg (ap, char *))
143       argc++;
144   va_end (ap);
145   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
146   argc = 0;
147   va_start (ap, filename);
148   while (NULL != (argv[argc] = va_arg (ap, char *)))
149     argc++;
150   va_end (ap);
151   if (pipe_stdout != NULL)
152     GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
153   if (pipe_stdin != NULL)
154     GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_READ), &fd_stdin_read, sizeof (int));
155
156 #if HAVE_WORKING_VFORK
157   ret = vfork ();
158 #else
159   ret = fork ();
160 #endif
161   if (ret != 0)
162     {
163       if (ret == -1)
164         {
165           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
166         }
167       else
168         {
169 #if HAVE_WORKING_VFORK
170           /* let's hope vfork actually works; for some extreme cases (including
171              a testcase) we need 'execvp' to have run before we return, since
172              we may send a signal to the process next and we don't want it
173              to be caught by OUR signal handler (but either by the default
174              handler or the actual handler as installed by the process itself). */
175 #else
176           /* let's give the child process a chance to run execvp, 1s should
177              be plenty in practice */
178           sleep (1);
179           if (pipe_stdout != NULL)
180             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
181           if (pipe_stdin != NULL)
182             GNUNET_DISK_pipe_close_end(pipe_stdin, GNUNET_DISK_PIPE_END_READ);
183 #endif
184         }
185       GNUNET_free (argv);
186       return ret;
187     }
188
189   if (pipe_stdout != NULL)
190     {
191       dup2(fd_stdout_write, 1);
192       close (fd_stdout_write);
193     }
194
195   if (pipe_stdout != NULL)
196     {
197       dup2(fd_stdin_read, 0);
198       close (fd_stdin_read);
199     }
200
201   execvp (filename, argv);
202   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
203   _exit (1);
204 #else
205   char *arg;
206   unsigned int cmdlen;
207   char *cmd, *idx;
208   STARTUPINFO start;
209   PROCESS_INFORMATION proc;
210   char *fn;
211   int len;
212
213   cmdlen = 0;
214   va_start (ap, filename);
215   while (NULL != (arg = va_arg (ap, char *)))
216       cmdlen = cmdlen + strlen (arg) + 3;
217   va_end (ap);
218
219   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
220   va_start (ap, filename);
221   while (NULL != (arg = va_arg (ap, char *)))
222       idx += sprintf (idx, "\"%s\" ", arg);
223   va_end (ap);
224
225   memset (&start, 0, sizeof (start));
226   start.cb = sizeof (start);
227
228   len = strlen (filename);
229   if (strnicmp (filename + len - 4, ".exe", 4) == 0)
230     fn = filename;
231   else
232     GNUNET_asprintf (&fn, "%s.exe", filename);
233
234   if (!CreateProcess
235       (fn, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
236        &proc))
237     {
238       SetErrnoFromWinError (GetLastError ());
239       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", fn);
240       return -1;
241     }
242   if (fn != filename)
243     GNUNET_free (fn);
244   CloseHandle (proc.hProcess);
245   CloseHandle (proc.hThread);
246
247   GNUNET_free (cmd);
248
249   return proc.dwProcessId;
250 #endif
251
252 }
253
254
255
256 /**
257  * Start a process.
258  *
259  * @param filename name of the binary
260  * @param argv NULL-terminated list of arguments to the process
261  * @return process ID of the new process, -1 on error
262  */
263 pid_t
264 GNUNET_OS_start_process_v (const char *filename, char *const argv[])
265 {
266 #ifndef MINGW
267   pid_t ret;
268
269 #if HAVE_WORKING_VFORK
270   ret = vfork ();
271 #else
272   ret = fork ();
273 #endif
274   if (ret != 0)
275     {
276       if (ret == -1)
277         {
278           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
279         }
280       else
281         {
282 #if HAVE_WORKING_VFORK
283           /* let's hope vfork actually works; for some extreme cases (including
284              a testcase) we need 'execvp' to have run before we return, since
285              we may send a signal to the process next and we don't want it
286              to be caught by OUR signal handler (but either by the default
287              handler or the actual handler as installed by the process itself). */
288 #else
289           /* let's give the child process a chance to run execvp, 1s should
290              be plenty in practice */
291           sleep (1);
292 #endif
293         }
294       return ret;
295     }
296   execvp (filename, argv);
297   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
298   _exit (1);
299 #else
300   char **arg;
301   unsigned int cmdlen;
302   char *cmd, *idx;
303   STARTUPINFO start;
304   PROCESS_INFORMATION proc;
305
306   cmdlen = 0;
307   arg = argv;
308   while (*arg)
309     {
310       cmdlen = cmdlen + strlen (*arg) + 3;
311       arg++;
312     }
313
314   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
315   arg = argv;
316   while (*arg)
317     {
318       idx += sprintf (idx, "\"%s\" ", *arg);
319       arg++;
320     }
321
322   memset (&start, 0, sizeof (start));
323   start.cb = sizeof (start);
324
325   if (!CreateProcess
326       (filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
327        &proc))
328     {
329       SetErrnoFromWinError (GetLastError ());
330       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
331       return -1;
332     }
333   CloseHandle (proc.hProcess);
334   CloseHandle (proc.hThread);
335
336   GNUNET_free (cmd);
337
338   return proc.dwProcessId;
339 #endif
340 }
341
342 /**
343  * Retrieve the status of a process
344  * @param proc process ID
345  * @param type status type
346  * @param code return code/signal number
347  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
348  */
349 int
350 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
351                           unsigned long *code)
352 {
353 #ifndef MINGW
354   int status;
355   int ret;
356
357   GNUNET_assert (0 != proc);
358   ret = waitpid (proc, &status, WNOHANG);
359   if (0 == ret)
360     {
361       *type = GNUNET_OS_PROCESS_RUNNING;
362       *code = 0;
363       return GNUNET_NO;
364     }
365   if (proc != ret)
366     {
367       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
368       return GNUNET_SYSERR;
369     }
370   if (WIFEXITED (status))
371     {
372       *type = GNUNET_OS_PROCESS_EXITED;
373       *code = WEXITSTATUS (status);
374     }
375   else if (WIFSIGNALED (status))
376     {
377       *type = GNUNET_OS_PROCESS_SIGNALED;
378       *code = WTERMSIG (status);
379     }
380   else if (WIFSTOPPED (status))
381     {
382       *type = GNUNET_OS_PROCESS_SIGNALED;
383       *code = WSTOPSIG (status);
384     }
385 #ifdef WIFCONTINUED
386   else if (WIFCONTINUED (status))
387     {
388       *type = GNUNET_OS_PROCESS_RUNNING;
389       *code = 0;
390     }
391 #endif
392   else
393     {
394       *type = GNUNET_OS_PROCESS_UNKNOWN;
395       *code = 0;
396     }
397 #else
398   HANDLE h;
399   DWORD c;
400
401   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
402   if (INVALID_HANDLE_VALUE == h)
403     {
404       SetErrnoFromWinError (GetLastError ());
405       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
406       return GNUNET_SYSERR;
407     }
408
409   c = GetExitCodeProcess (proc, &c);
410   if (STILL_ACTIVE == c)
411     {
412       *type = GNUNET_OS_PROCESS_RUNNING;
413       *code = 0;
414       CloseHandle (h);
415       return GNUNET_NO;
416     }
417   *type = GNUNET_OS_PROCESS_EXITED;
418   *code = c;
419   CloseHandle (h);
420 #endif
421
422   return GNUNET_OK;
423 }
424
425 /**
426  * Wait for a process
427  * @param proc process ID to wait for
428  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
429  */
430 int
431 GNUNET_OS_process_wait (pid_t proc)
432 {
433 #ifndef MINGW
434   if (proc != waitpid (proc, NULL, 0))
435     return GNUNET_SYSERR;
436
437   return GNUNET_OK;
438 #else
439   HANDLE h;
440   DWORD c;
441   int ret;
442
443   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
444   if (INVALID_HANDLE_VALUE == h)
445     {
446       SetErrnoFromWinError (GetLastError ());
447       return GNUNET_SYSERR;
448     }
449
450   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
451     {
452       SetErrnoFromWinError (GetLastError ());
453       ret = GNUNET_SYSERR;
454     }
455   else
456     ret = GNUNET_OK;
457
458   CloseHandle (h);
459
460   return ret;
461 #endif
462 }
463
464
465 /* end of os_priority.c */