fix mem issues, add ".exe" extension to path (fixes #1570, thanks to LRN on #gnunet)
[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;
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, **non_const_argv;
374   unsigned int cmdlen;
375   char *cmd, *idx;
376   STARTUPINFO start;
377   PROCESS_INFORMATION proc;
378   int argcount = 0;
379   char *non_const_filename = NULL;
380   int filenamelen = 0;
381
382   /* Count the number of arguments */
383   arg = argv;
384   while (*arg)
385     {
386       arg++;
387       argcount++;
388     }
389
390   /* Allocate a copy argv */
391   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
392
393   /* Copy all argv strings */
394   argcount = 0;
395   arg = argv;
396   while (*arg)
397     {
398       non_const_argv[argcount] = GNUNET_strdup (*arg);
399       arg++;
400       argcount++;
401     }
402   non_const_argv[argcount] = NULL;
403
404   /* Fix .exe extension */
405   filenamelen = strlen (filename);
406   if (filenamelen <= 4 || stricmp (&filename[filenamelen - 4], ".exe") != 0)
407   {
408     non_const_filename = GNUNET_malloc (sizeof (char) * (filenamelen + 4 + 1));
409     non_const_filename = strcpy (non_const_filename, non_const_argv[0]);
410     strcat (non_const_filename, ".exe");
411     GNUNET_free (non_const_argv[0]);
412     non_const_argv[0] = non_const_filename;
413   }
414   else
415     non_const_filename = non_const_argv[0];
416
417   /* Count cmd len */
418   cmdlen = 1;
419   arg = non_const_argv;
420   while (*arg)
421     {
422       cmdlen = cmdlen + strlen (*arg) + 3;
423       arg++;
424     }
425
426   /* Allocate and create cmd */
427   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
428   arg = non_const_argv;
429   while (*arg)
430     {
431       idx += sprintf (idx, "\"%s\" ", *arg);
432       arg++;
433     }
434
435   memset (&start, 0, sizeof (start));
436   start.cb = sizeof (start);
437
438   if (!CreateProcess
439       (non_const_filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
440        &proc))
441     {
442       SetErrnoFromWinError (GetLastError ());
443       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
444       return -1;
445     }
446
447   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
448
449   CloseHandle (proc.hThread);
450   GNUNET_free (cmd);
451
452   while (argcount > 0)
453     GNUNET_free (non_const_argv[--argcount]);
454   GNUNET_free (non_const_argv);
455
456   return proc.dwProcessId;
457 #endif
458 }
459
460 /**
461  * Retrieve the status of a process
462  * @param proc process ID
463  * @param type status type
464  * @param code return code/signal number
465  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
466  */
467 int
468 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
469                           unsigned long *code)
470 {
471 #ifndef MINGW
472   int status;
473   int ret;
474
475   GNUNET_assert (0 != proc);
476   ret = waitpid (proc, &status, WNOHANG);
477   if (ret < 0)
478     {
479       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
480       return GNUNET_SYSERR;
481     }
482   if (0 == ret)
483     {
484       *type = GNUNET_OS_PROCESS_RUNNING;
485       *code = 0;
486       return GNUNET_NO;
487     }
488   if (proc != ret)
489     {
490       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
491       return GNUNET_SYSERR;
492     }
493   if (WIFEXITED (status))
494     {
495       *type = GNUNET_OS_PROCESS_EXITED;
496       *code = WEXITSTATUS (status);
497     }
498   else if (WIFSIGNALED (status))
499     {
500       *type = GNUNET_OS_PROCESS_SIGNALED;
501       *code = WTERMSIG (status);
502     }
503   else if (WIFSTOPPED (status))
504     {
505       *type = GNUNET_OS_PROCESS_SIGNALED;
506       *code = WSTOPSIG (status);
507     }
508 #ifdef WIFCONTINUED
509   else if (WIFCONTINUED (status))
510     {
511       *type = GNUNET_OS_PROCESS_RUNNING;
512       *code = 0;
513     }
514 #endif
515   else
516     {
517       *type = GNUNET_OS_PROCESS_UNKNOWN;
518       *code = 0;
519     }
520 #else
521   HANDLE h;
522   DWORD c;
523
524   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
525   if (INVALID_HANDLE_VALUE == h)
526     {
527       SetErrnoFromWinError (GetLastError ());
528       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
529       return GNUNET_SYSERR;
530     }
531
532   c = GetExitCodeProcess (proc, &c);
533   if (STILL_ACTIVE == c)
534     {
535       *type = GNUNET_OS_PROCESS_RUNNING;
536       *code = 0;
537       CloseHandle (h);
538       return GNUNET_NO;
539     }
540   *type = GNUNET_OS_PROCESS_EXITED;
541   *code = c;
542   CloseHandle (h);
543 #endif
544
545   return GNUNET_OK;
546 }
547
548 /**
549  * Wait for a process
550  * @param proc process ID to wait for
551  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
552  */
553 int
554 GNUNET_OS_process_wait (pid_t proc)
555 {
556 #ifndef MINGW
557   if (proc != waitpid (proc, NULL, 0))
558     return GNUNET_SYSERR;
559
560   return GNUNET_OK;
561 #else
562   HANDLE h;
563   DWORD c;
564   int ret;
565
566   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
567   if (INVALID_HANDLE_VALUE == h)
568     {
569       SetErrnoFromWinError (GetLastError ());
570       return GNUNET_SYSERR;
571     }
572
573   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
574     {
575       SetErrnoFromWinError (GetLastError ());
576       ret = GNUNET_SYSERR;
577     }
578   else
579     ret = GNUNET_OK;
580
581   CloseHandle (h);
582
583   return ret;
584 #endif
585 }
586
587
588 /* end of os_priority.c */