fix warnings
[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   return 0;
51 }
52 #endif
53
54 /**
55  * Set process priority
56  *
57  * @param proc id of the process
58  * @param prio priority value
59  * @return GNUNET_OK on success, GNUNET_SYSERR on error
60  */
61 int
62 GNUNET_OS_set_process_priority (pid_t proc,
63                                 enum GNUNET_SCHEDULER_Priority prio)
64 {
65   int rprio;
66
67   GNUNET_assert (prio < GNUNET_SCHEDULER_PRIORITY_COUNT);
68   if (prio == GNUNET_SCHEDULER_PRIORITY_KEEP)
69     return GNUNET_OK;
70   /* convert to MINGW/Unix values */
71   switch (prio)
72     {
73     case GNUNET_SCHEDULER_PRIORITY_UI:
74     case GNUNET_SCHEDULER_PRIORITY_URGENT:
75 #ifdef MINGW
76       rprio = HIGH_PRIORITY_CLASS;
77 #else
78       rprio = 0;
79 #endif
80       break;
81
82     case GNUNET_SCHEDULER_PRIORITY_HIGH:
83 #ifdef MINGW
84       rprio = ABOVE_NORMAL_PRIORITY_CLASS;
85 #else
86       rprio = 5;
87 #endif
88       break;
89
90     case GNUNET_SCHEDULER_PRIORITY_DEFAULT:
91 #ifdef MINGW
92       rprio = NORMAL_PRIORITY_CLASS;
93 #else
94       rprio = 7;
95 #endif
96       break;
97
98     case GNUNET_SCHEDULER_PRIORITY_BACKGROUND:
99 #ifdef MINGW
100       rprio = BELOW_NORMAL_PRIORITY_CLASS;
101 #else
102       rprio = 10;
103 #endif
104       break;
105
106     case GNUNET_SCHEDULER_PRIORITY_IDLE:
107 #ifdef MINGW
108       rprio = IDLE_PRIORITY_CLASS;
109 #else
110       rprio = 19;
111 #endif
112       break;
113     default:
114       GNUNET_assert (0);
115       return GNUNET_SYSERR;
116     }
117   /* Set process priority */
118 #ifdef MINGW
119   SetPriorityClass (GetCurrentProcess (), rprio);
120 #elif LINUX 
121   if ( (0 == proc) ||
122        (proc == getpid () ) )
123     {
124       int have = nice (0);
125       int delta = rprio - have;
126       errno = 0;
127       if ( (delta != 0) &&
128            (rprio == nice (delta)) && 
129            (errno != 0) )
130         {
131           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
132                                GNUNET_ERROR_TYPE_BULK, "nice");
133           return GNUNET_SYSERR;
134         }
135     }
136   else
137     {
138       if (0 != setpriority (PRIO_PROCESS, proc, rprio))
139
140         {
141           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING |
142                                GNUNET_ERROR_TYPE_BULK, "setpriority");
143           return GNUNET_SYSERR;
144         }
145     }
146 #else
147   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
148               "Priority management not availabe for this platform\n");
149 #endif
150   return GNUNET_OK;
151 }
152
153 /**
154  * Start a process.
155  *
156  * @param pipe_stdin pipe to use to send input to child process (or NULL)
157  * @param pipe_stdout pipe to use to get output from child process (or NULL)
158  * @param filename name of the binary
159  * @param ... NULL-terminated list of arguments to the process
160  * @return process ID of the new process, -1 on error
161  */
162 pid_t
163 GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, 
164                          struct GNUNET_DISK_PipeHandle *pipe_stdout,
165                          const char *filename, ...)
166 {
167   /* FIXME:  Make this work on windows!!! */
168   va_list ap;
169
170 #ifndef MINGW
171   pid_t ret;
172   char **argv;
173   int argc;
174   int fd_stdout_write;
175   int fd_stdout_read;
176   int fd_stdin_read;
177   int fd_stdin_write;
178
179   argc = 0;
180   va_start (ap, filename);
181   while (NULL != va_arg (ap, char *))
182       argc++;
183   va_end (ap);
184   argv = GNUNET_malloc (sizeof (char *) * (argc + 1));
185   argc = 0;
186   va_start (ap, filename);
187   while (NULL != (argv[argc] = va_arg (ap, char *)))
188     argc++;
189   va_end (ap);
190   if (pipe_stdout != NULL)
191     {
192       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &fd_stdout_write, sizeof (int));
193       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_READ), &fd_stdout_read, sizeof (int));
194     }
195   if (pipe_stdin != NULL)
196     {
197       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &fd_stdin_read, sizeof (int));
198       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_WRITE), &fd_stdin_write, sizeof (int));
199     }
200
201 #if HAVE_WORKING_VFORK
202   ret = vfork ();
203 #else
204   ret = fork ();
205 #endif
206   if (ret != 0)
207     {
208       if (ret == -1)
209         {
210           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
211         }
212       else
213         {
214
215 #if HAVE_WORKING_VFORK
216           /* let's hope vfork actually works; for some extreme cases (including
217              a testcase) we need 'execvp' to have run before we return, since
218              we may send a signal to the process next and we don't want it
219              to be caught by OUR signal handler (but either by the default
220              handler or the actual handler as installed by the process itself). */
221 #else
222           /* let's give the child process a chance to run execvp, 1s should
223              be plenty in practice */
224           if (pipe_stdout != NULL)
225             GNUNET_DISK_pipe_close_end(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE);
226           if (pipe_stdin != NULL)
227             GNUNET_DISK_pipe_close_end(pipe_stdin, GNUNET_DISK_PIPE_END_READ);
228           sleep (1);
229 #endif
230         }
231       GNUNET_free (argv);
232       return ret;
233     }
234
235   if (pipe_stdout != NULL)
236     {
237       GNUNET_break (0 == close (fd_stdout_read));
238       if (-1 == dup2(fd_stdout_write, 1))
239         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
240       GNUNET_break (0 == close (fd_stdout_write));
241     }
242
243   if (pipe_stdin != NULL)
244     {
245
246       GNUNET_break (0 == close (fd_stdin_write));
247       if (-1 == dup2(fd_stdin_read, 0))
248         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup2");  
249       GNUNET_break (0 == close (fd_stdin_read));
250     }
251   execvp (filename, argv);
252   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
253   _exit (1);
254 #else
255   char *arg;
256   unsigned int cmdlen;
257   char *cmd, *idx;
258   int findresult;
259   STARTUPINFO start;
260   PROCESS_INFORMATION proc;
261
262   HANDLE stdin_handle;
263   HANDLE stdout_handle;
264
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 + 1));
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 ((pipe_stdin != NULL) || (pipe_stdout != NULL))
283     start.dwFlags |= STARTF_USESTDHANDLES;
284
285   if (pipe_stdin != NULL)
286     {
287       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdin, GNUNET_DISK_PIPE_END_READ), &stdin_handle, sizeof (HANDLE));
288       start.hStdInput = stdin_handle;
289     }
290
291   if (pipe_stdout != NULL)
292     {
293       GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle(pipe_stdout, GNUNET_DISK_PIPE_END_WRITE), &stdout_handle, sizeof (HANDLE));
294       start.hStdOutput = stdout_handle;
295     }
296
297   findresult = (int) FindExecutableA (filename, NULL, path);
298   if (findresult <= 32) 
299     {
300       SetErrnoFromWinError (GetLastError ());
301       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "FindExecutable", filename);
302       return -1;
303     }
304
305   if (!CreateProcessA
306       (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &start,
307        &proc))
308     {
309       SetErrnoFromWinError (GetLastError ());
310       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", path);
311       return -1;
312     }
313
314   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
315
316   CloseHandle (proc.hThread);
317
318   GNUNET_free (cmd);
319
320   return proc.dwProcessId;
321 #endif
322
323 }
324
325
326
327 /**
328  * Start a process.
329  *
330  * @param lsocks array of listen sockets to dup systemd-style (or NULL);
331  *         must be NULL on platforms where dup is not supported
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 int *lsocks,
338                            const char *filename, char *const argv[])
339 {
340 #ifndef MINGW
341   pid_t ret;
342   char lpid[16];
343   char fds[16];
344   int i;
345   int j;
346   int k;
347   int tgt;
348   int flags;
349   int *lscp;
350   unsigned int ls;    
351
352   lscp = NULL;
353   ls = 0;
354   if (lsocks != NULL)
355     {
356       i = 0;
357       while (-1 != (k = lsocks[i++]))
358         {
359           flags = fcntl (k, F_GETFD);
360           GNUNET_assert (flags >= 0);
361           flags &= ~FD_CLOEXEC;
362           (void) fcntl (k, F_SETFD, flags);
363           GNUNET_array_append (lscp, ls, k);
364         }
365       GNUNET_array_append (lscp, ls, -1);
366     }
367 #if HAVE_WORKING_VFORK
368   ret = vfork ();
369 #else
370   ret = fork ();
371 #endif
372   if (ret != 0)
373     {
374       if (ret == -1)
375         {
376           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
377         }
378       else
379         {
380 #if HAVE_WORKING_VFORK
381           /* let's hope vfork actually works; for some extreme cases (including
382              a testcase) we need 'execvp' to have run before we return, since
383              we may send a signal to the process next and we don't want it
384              to be caught by OUR signal handler (but either by the default
385              handler or the actual handler as installed by the process itself). */
386 #else
387           /* let's give the child process a chance to run execvp, 1s should
388              be plenty in practice */
389           sleep (1);
390 #endif
391         }
392       GNUNET_array_grow (lscp, ls, 0);
393       return ret;
394     }
395   if (lscp != NULL)
396     {
397       /* read systemd documentation... */
398       GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
399       setenv ("LISTEN_PID", lpid, 1);      
400       i = 0;
401       tgt = 3;
402       while (-1 != lscp[i])
403         {
404           j = i + 1;
405           while (-1 != lscp[j])
406             {
407               if (lscp[j] == tgt)
408                 {
409                   /* dup away */
410                   k = dup (lscp[j]);
411                   GNUNET_assert (-1 != k);
412                   GNUNET_assert (0 == close (lscp[j]));
413                   lscp[j] = k;
414                   break;
415                 }
416               j++;
417             }
418           if (lscp[i] != tgt)
419             {
420               /* Bury any existing FD, no matter what; they should all be closed
421                  on exec anyway and the important onces have been dup'ed away */
422               (void) close (tgt);             
423               GNUNET_assert (-1 != dup2 (lscp[i], tgt));
424             }
425           /* set close-on-exec flag */
426           flags = fcntl (tgt, F_GETFD);
427           GNUNET_assert (flags >= 0);
428           flags &= ~FD_CLOEXEC;
429           (void) fcntl (tgt, F_SETFD, flags);
430           tgt++;
431           i++;
432         }
433       GNUNET_snprintf (fds, sizeof (fds), "%u", i);
434       setenv ("LISTEN_FDS", fds, 1); 
435     }
436   GNUNET_array_grow (lscp, ls, 0);
437   execvp (filename, argv);
438   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
439   _exit (1);
440 #else
441   char **arg, **non_const_argv;
442   unsigned int cmdlen;
443   char *cmd, *idx;
444   STARTUPINFO start;
445   PROCESS_INFORMATION proc;
446   int argcount = 0;
447   char *non_const_filename = NULL;
448   int filenamelen = 0;
449
450   GNUNET_assert (lsocks == NULL);
451   /* Count the number of arguments */
452   arg = (char **) argv;
453   while (*arg)
454     {
455       arg++;
456       argcount++;
457     }
458
459   /* Allocate a copy argv */
460   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
461
462   /* Copy all argv strings */
463   argcount = 0;
464   arg = (char **) argv;
465   while (*arg)
466     {
467       non_const_argv[argcount] = GNUNET_strdup (*arg);
468       arg++;
469       argcount++;
470     }
471   non_const_argv[argcount] = NULL;
472
473   /* Fix .exe extension */
474   filenamelen = strlen (filename);
475   if (filenamelen <= 4 || stricmp (&filename[filenamelen - 4], ".exe") != 0)
476   {
477     non_const_filename = GNUNET_malloc (sizeof (char) * (filenamelen + 4 + 1));
478     non_const_filename = strcpy (non_const_filename, non_const_argv[0]);
479     strcat (non_const_filename, ".exe");
480     GNUNET_free (non_const_argv[0]);
481     non_const_argv[0] = non_const_filename;
482   }
483   else
484     non_const_filename = non_const_argv[0];
485
486   /* Count cmd len */
487   cmdlen = 1;
488   arg = non_const_argv;
489   while (*arg)
490     {
491       cmdlen = cmdlen + strlen (*arg) + 3;
492       arg++;
493     }
494
495   /* Allocate and create cmd */
496   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
497   arg = non_const_argv;
498   while (*arg)
499     {
500       idx += sprintf (idx, "\"%s\" ", *arg);
501       arg++;
502     }
503
504   memset (&start, 0, sizeof (start));
505   start.cb = sizeof (start);
506
507   if (!CreateProcess
508       (non_const_filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
509        &proc))
510     {
511       SetErrnoFromWinError (GetLastError ());
512       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
513       return -1;
514     }
515
516   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
517
518   CloseHandle (proc.hThread);
519   GNUNET_free (cmd);
520
521   while (argcount > 0)
522     GNUNET_free (non_const_argv[--argcount]);
523   GNUNET_free (non_const_argv);
524
525   return proc.dwProcessId;
526 #endif
527 }
528
529 /**
530  * Retrieve the status of a process
531  * @param proc process ID
532  * @param type status type
533  * @param code return code/signal number
534  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
535  */
536 int
537 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
538                           unsigned long *code)
539 {
540 #ifndef MINGW
541   int status;
542   int ret;
543
544   GNUNET_assert (0 != proc);
545   ret = waitpid (proc, &status, WNOHANG);
546   if (ret < 0)
547     {
548       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
549       return GNUNET_SYSERR;
550     }
551   if (0 == ret)
552     {
553       *type = GNUNET_OS_PROCESS_RUNNING;
554       *code = 0;
555       return GNUNET_NO;
556     }
557   if (proc != ret)
558     {
559       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
560       return GNUNET_SYSERR;
561     }
562   if (WIFEXITED (status))
563     {
564       *type = GNUNET_OS_PROCESS_EXITED;
565       *code = WEXITSTATUS (status);
566     }
567   else if (WIFSIGNALED (status))
568     {
569       *type = GNUNET_OS_PROCESS_SIGNALED;
570       *code = WTERMSIG (status);
571     }
572   else if (WIFSTOPPED (status))
573     {
574       *type = GNUNET_OS_PROCESS_SIGNALED;
575       *code = WSTOPSIG (status);
576     }
577 #ifdef WIFCONTINUED
578   else if (WIFCONTINUED (status))
579     {
580       *type = GNUNET_OS_PROCESS_RUNNING;
581       *code = 0;
582     }
583 #endif
584   else
585     {
586       *type = GNUNET_OS_PROCESS_UNKNOWN;
587       *code = 0;
588     }
589 #else
590   HANDLE h;
591   DWORD c;
592
593   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
594   if (INVALID_HANDLE_VALUE == h)
595     {
596       SetErrnoFromWinError (GetLastError ());
597       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
598       return GNUNET_SYSERR;
599     }
600
601   c = GetExitCodeProcess ((HANDLE) proc, &c);
602   if (STILL_ACTIVE == c)
603     {
604       *type = GNUNET_OS_PROCESS_RUNNING;
605       *code = 0;
606       CloseHandle (h);
607       return GNUNET_NO;
608     }
609   *type = GNUNET_OS_PROCESS_EXITED;
610   *code = c;
611   CloseHandle (h);
612 #endif
613
614   return GNUNET_OK;
615 }
616
617 /**
618  * Wait for a process
619  * @param proc process ID to wait for
620  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
621  */
622 int
623 GNUNET_OS_process_wait (pid_t proc)
624 {
625 #ifndef MINGW
626   if (proc != waitpid (proc, NULL, 0))
627     return GNUNET_SYSERR;
628
629   return GNUNET_OK;
630 #else
631   HANDLE h;
632   int ret;
633
634   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
635   if (INVALID_HANDLE_VALUE == h)
636     {
637       SetErrnoFromWinError (GetLastError ());
638       return GNUNET_SYSERR;
639     }
640
641   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
642     {
643       SetErrnoFromWinError (GetLastError ());
644       ret = GNUNET_SYSERR;
645     }
646   else
647     ret = GNUNET_OK;
648
649   CloseHandle (h);
650
651   return ret;
652 #endif
653 }
654
655
656 /* end of os_priority.c */