leak
[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   STARTUPINFO start;
259   PROCESS_INFORMATION proc;
260 #if NILS
261   HANDLE stdin_handle;
262   HANDLE stdout_handle;
263 #endif
264   char *fn = NULL;
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 ((int) 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 lsocks array of listen sockets to dup systemd-style (or NULL);
333  *         must be NULL on platforms where dup is not supported
334  * @param filename name of the binary
335  * @param argv NULL-terminated list of arguments to the process
336  * @return process ID of the new process, -1 on error
337  */
338 pid_t
339 GNUNET_OS_start_process_v (const int *lsocks,
340                            const char *filename, char *const argv[])
341 {
342 #ifndef MINGW
343   pid_t ret;
344   char lpid[16];
345   char fds[16];
346   int i;
347   int j;
348   int k;
349   int tgt;
350   int flags;
351   int *lscp;
352   unsigned int ls;    
353
354   lscp = NULL;
355   ls = 0;
356   if (lsocks != NULL)
357     {
358       i = 0;
359       while (-1 != (k = lsocks[i++]))
360         {
361           flags = fcntl (k, F_GETFD);
362           GNUNET_assert (flags >= 0);
363           flags &= ~FD_CLOEXEC;
364           (void) fcntl (k, F_SETFD, flags);
365           GNUNET_array_append (lscp, ls, k);
366         }
367       GNUNET_array_append (lscp, ls, -1);
368     }
369 #if HAVE_WORKING_VFORK
370   ret = vfork ();
371 #else
372   ret = fork ();
373 #endif
374   if (ret != 0)
375     {
376       if (ret == -1)
377         {
378           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
379         }
380       else
381         {
382 #if HAVE_WORKING_VFORK
383           /* let's hope vfork actually works; for some extreme cases (including
384              a testcase) we need 'execvp' to have run before we return, since
385              we may send a signal to the process next and we don't want it
386              to be caught by OUR signal handler (but either by the default
387              handler or the actual handler as installed by the process itself). */
388 #else
389           /* let's give the child process a chance to run execvp, 1s should
390              be plenty in practice */
391           sleep (1);
392 #endif
393         }
394       GNUNET_array_grow (lscp, ls, 0);
395       return ret;
396     }
397   if (lscp != NULL)
398     {
399       /* read systemd documentation... */
400       GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
401       setenv ("LISTEN_PID", lpid, 1);      
402       i = 0;
403       tgt = 3;
404       while (-1 != lscp[i])
405         {
406           j = i + 1;
407           while (-1 != lscp[j])
408             {
409               if (lscp[j] == tgt)
410                 {
411                   /* dup away */
412                   k = dup (lscp[j]);
413                   GNUNET_assert (-1 != k);
414                   GNUNET_assert (0 == close (lscp[j]));
415                   lscp[j] = k;
416                   break;
417                 }
418               j++;
419             }
420           if (lscp[i] != tgt)
421             {
422               /* Bury any existing FD, no matter what; they should all be closed
423                  on exec anyway and the important onces have been dup'ed away */
424               (void) close (tgt);             
425               GNUNET_assert (-1 != dup2 (lscp[i], tgt));
426             }
427           /* set close-on-exec flag */
428           flags = fcntl (tgt, F_GETFD);
429           GNUNET_assert (flags >= 0);
430           flags &= ~FD_CLOEXEC;
431           (void) fcntl (tgt, F_SETFD, flags);
432           tgt++;
433           i++;
434         }
435       GNUNET_snprintf (fds, sizeof (fds), "%u", i);
436       setenv ("LISTEN_FDS", fds, 1); 
437     }
438   GNUNET_array_grow (lscp, ls, 0);
439   execvp (filename, argv);
440   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
441   _exit (1);
442 #else
443   char **arg, **non_const_argv;
444   unsigned int cmdlen;
445   char *cmd, *idx;
446   STARTUPINFO start;
447   PROCESS_INFORMATION proc;
448   int argcount = 0;
449   char *non_const_filename = NULL;
450   int filenamelen = 0;
451
452   GNUNET_assert (lsocks == NULL);
453   /* Count the number of arguments */
454   arg = argv;
455   while (*arg)
456     {
457       arg++;
458       argcount++;
459     }
460
461   /* Allocate a copy argv */
462   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
463
464   /* Copy all argv strings */
465   argcount = 0;
466   arg = argv;
467   while (*arg)
468     {
469       non_const_argv[argcount] = GNUNET_strdup (*arg);
470       arg++;
471       argcount++;
472     }
473   non_const_argv[argcount] = NULL;
474
475   /* Fix .exe extension */
476   filenamelen = strlen (filename);
477   if (filenamelen <= 4 || stricmp (&filename[filenamelen - 4], ".exe") != 0)
478   {
479     non_const_filename = GNUNET_malloc (sizeof (char) * (filenamelen + 4 + 1));
480     non_const_filename = strcpy (non_const_filename, non_const_argv[0]);
481     strcat (non_const_filename, ".exe");
482     GNUNET_free (non_const_argv[0]);
483     non_const_argv[0] = non_const_filename;
484   }
485   else
486     non_const_filename = non_const_argv[0];
487
488   /* Count cmd len */
489   cmdlen = 1;
490   arg = non_const_argv;
491   while (*arg)
492     {
493       cmdlen = cmdlen + strlen (*arg) + 3;
494       arg++;
495     }
496
497   /* Allocate and create cmd */
498   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
499   arg = non_const_argv;
500   while (*arg)
501     {
502       idx += sprintf (idx, "\"%s\" ", *arg);
503       arg++;
504     }
505
506   memset (&start, 0, sizeof (start));
507   start.cb = sizeof (start);
508
509   if (!CreateProcess
510       (non_const_filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
511        &proc))
512     {
513       SetErrnoFromWinError (GetLastError ());
514       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
515       return -1;
516     }
517
518   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
519
520   CloseHandle (proc.hThread);
521   GNUNET_free (cmd);
522
523   while (argcount > 0)
524     GNUNET_free (non_const_argv[--argcount]);
525   GNUNET_free (non_const_argv);
526
527   return proc.dwProcessId;
528 #endif
529 }
530
531 /**
532  * Retrieve the status of a process
533  * @param proc process ID
534  * @param type status type
535  * @param code return code/signal number
536  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
537  */
538 int
539 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
540                           unsigned long *code)
541 {
542 #ifndef MINGW
543   int status;
544   int ret;
545
546   GNUNET_assert (0 != proc);
547   ret = waitpid (proc, &status, WNOHANG);
548   if (ret < 0)
549     {
550       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
551       return GNUNET_SYSERR;
552     }
553   if (0 == ret)
554     {
555       *type = GNUNET_OS_PROCESS_RUNNING;
556       *code = 0;
557       return GNUNET_NO;
558     }
559   if (proc != ret)
560     {
561       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
562       return GNUNET_SYSERR;
563     }
564   if (WIFEXITED (status))
565     {
566       *type = GNUNET_OS_PROCESS_EXITED;
567       *code = WEXITSTATUS (status);
568     }
569   else if (WIFSIGNALED (status))
570     {
571       *type = GNUNET_OS_PROCESS_SIGNALED;
572       *code = WTERMSIG (status);
573     }
574   else if (WIFSTOPPED (status))
575     {
576       *type = GNUNET_OS_PROCESS_SIGNALED;
577       *code = WSTOPSIG (status);
578     }
579 #ifdef WIFCONTINUED
580   else if (WIFCONTINUED (status))
581     {
582       *type = GNUNET_OS_PROCESS_RUNNING;
583       *code = 0;
584     }
585 #endif
586   else
587     {
588       *type = GNUNET_OS_PROCESS_UNKNOWN;
589       *code = 0;
590     }
591 #else
592   HANDLE h;
593   DWORD c;
594
595   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
596   if (INVALID_HANDLE_VALUE == h)
597     {
598       SetErrnoFromWinError (GetLastError ());
599       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
600       return GNUNET_SYSERR;
601     }
602
603   c = GetExitCodeProcess (proc, &c);
604   if (STILL_ACTIVE == c)
605     {
606       *type = GNUNET_OS_PROCESS_RUNNING;
607       *code = 0;
608       CloseHandle (h);
609       return GNUNET_NO;
610     }
611   *type = GNUNET_OS_PROCESS_EXITED;
612   *code = c;
613   CloseHandle (h);
614 #endif
615
616   return GNUNET_OK;
617 }
618
619 /**
620  * Wait for a process
621  * @param proc process ID to wait for
622  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
623  */
624 int
625 GNUNET_OS_process_wait (pid_t proc)
626 {
627 #ifndef MINGW
628   if (proc != waitpid (proc, NULL, 0))
629     return GNUNET_SYSERR;
630
631   return GNUNET_OK;
632 #else
633   HANDLE h;
634   int ret;
635
636   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
637   if (INVALID_HANDLE_VALUE == h)
638     {
639       SetErrnoFromWinError (GetLastError ());
640       return GNUNET_SYSERR;
641     }
642
643   if (WAIT_OBJECT_0 != WaitForSingleObject (h, INFINITE))
644     {
645       SetErrnoFromWinError (GetLastError ());
646       ret = GNUNET_SYSERR;
647     }
648   else
649     ret = GNUNET_OK;
650
651   CloseHandle (h);
652
653   return ret;
654 #endif
655 }
656
657
658 /* end of os_priority.c */