listen socket passing support for ARM
[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 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       return ret;
395     }
396   if (lscp != NULL)
397     {
398       /* read systemd documentation... */
399       GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
400       setenv ("LISTEN_PID", lpid, 1);      
401       i = 0;
402       tgt = 3;
403       while (-1 != lscp[i])
404         {
405           j = i + 1;
406           while (-1 != lscp[j])
407             {
408               if (lscp[j] == tgt)
409                 {
410                   /* dup away */
411                   k = dup (lscp[j]);
412                   GNUNET_assert (-1 != k);
413                   GNUNET_assert (0 == close (lscp[j]));
414                   lscp[j] = k;
415                   break;
416                 }
417               j++;
418             }
419           if (lscp[i] != tgt)
420             {
421               /* Bury any existing FD, no matter what; they should all be closed
422                  on exec anyway and the important onces have been dup'ed away */
423               (void) close (tgt);             
424               GNUNET_assert (-1 != dup2 (lscp[i], tgt));
425             }
426           /* set close-on-exec flag */
427           flags = fcntl (tgt, F_GETFD);
428           GNUNET_assert (flags >= 0);
429           flags &= ~FD_CLOEXEC;
430           (void) fcntl (tgt, F_SETFD, flags);
431           tgt++;
432           i++;
433         }
434       GNUNET_snprintf (fds, sizeof (fds), "%u", i);
435       setenv ("LISTEN_FDS", fds, 1); 
436     }
437   GNUNET_array_grow (lscp, ls, 0);
438   execvp (filename, argv);
439   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
440   _exit (1);
441 #else
442   char **arg, **non_const_argv;
443   unsigned int cmdlen;
444   char *cmd, *idx;
445   STARTUPINFO start;
446   PROCESS_INFORMATION proc;
447   int argcount = 0;
448   char *non_const_filename = NULL;
449   int filenamelen = 0;
450
451   GNUNET_assert (lsocks == NULL);
452   /* Count the number of arguments */
453   arg = argv;
454   while (*arg)
455     {
456       arg++;
457       argcount++;
458     }
459
460   /* Allocate a copy argv */
461   non_const_argv = GNUNET_malloc (sizeof (char *) * (argcount + 1));
462
463   /* Copy all argv strings */
464   argcount = 0;
465   arg = argv;
466   while (*arg)
467     {
468       non_const_argv[argcount] = GNUNET_strdup (*arg);
469       arg++;
470       argcount++;
471     }
472   non_const_argv[argcount] = NULL;
473
474   /* Fix .exe extension */
475   filenamelen = strlen (filename);
476   if (filenamelen <= 4 || stricmp (&filename[filenamelen - 4], ".exe") != 0)
477   {
478     non_const_filename = GNUNET_malloc (sizeof (char) * (filenamelen + 4 + 1));
479     non_const_filename = strcpy (non_const_filename, non_const_argv[0]);
480     strcat (non_const_filename, ".exe");
481     GNUNET_free (non_const_argv[0]);
482     non_const_argv[0] = non_const_filename;
483   }
484   else
485     non_const_filename = non_const_argv[0];
486
487   /* Count cmd len */
488   cmdlen = 1;
489   arg = non_const_argv;
490   while (*arg)
491     {
492       cmdlen = cmdlen + strlen (*arg) + 3;
493       arg++;
494     }
495
496   /* Allocate and create cmd */
497   cmd = idx = GNUNET_malloc (sizeof (char) * cmdlen);
498   arg = non_const_argv;
499   while (*arg)
500     {
501       idx += sprintf (idx, "\"%s\" ", *arg);
502       arg++;
503     }
504
505   memset (&start, 0, sizeof (start));
506   start.cb = sizeof (start);
507
508   if (!CreateProcess
509       (non_const_filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start,
510        &proc))
511     {
512       SetErrnoFromWinError (GetLastError ());
513       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
514       return -1;
515     }
516
517   CreateThread (NULL, 64000, ChildWaitThread, proc.hProcess, 0, NULL);
518
519   CloseHandle (proc.hThread);
520   GNUNET_free (cmd);
521
522   while (argcount > 0)
523     GNUNET_free (non_const_argv[--argcount]);
524   GNUNET_free (non_const_argv);
525
526   return proc.dwProcessId;
527 #endif
528 }
529
530 /**
531  * Retrieve the status of a process
532  * @param proc process ID
533  * @param type status type
534  * @param code return code/signal number
535  * @return GNUNET_OK on success, GNUNET_NO if the process is still running, GNUNET_SYSERR otherwise
536  */
537 int
538 GNUNET_OS_process_status (pid_t proc, enum GNUNET_OS_ProcessStatusType *type,
539                           unsigned long *code)
540 {
541 #ifndef MINGW
542   int status;
543   int ret;
544
545   GNUNET_assert (0 != proc);
546   ret = waitpid (proc, &status, WNOHANG);
547   if (ret < 0)
548     {
549       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
550       return GNUNET_SYSERR;
551     }
552   if (0 == ret)
553     {
554       *type = GNUNET_OS_PROCESS_RUNNING;
555       *code = 0;
556       return GNUNET_NO;
557     }
558   if (proc != ret)
559     {
560       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
561       return GNUNET_SYSERR;
562     }
563   if (WIFEXITED (status))
564     {
565       *type = GNUNET_OS_PROCESS_EXITED;
566       *code = WEXITSTATUS (status);
567     }
568   else if (WIFSIGNALED (status))
569     {
570       *type = GNUNET_OS_PROCESS_SIGNALED;
571       *code = WTERMSIG (status);
572     }
573   else if (WIFSTOPPED (status))
574     {
575       *type = GNUNET_OS_PROCESS_SIGNALED;
576       *code = WSTOPSIG (status);
577     }
578 #ifdef WIFCONTINUED
579   else if (WIFCONTINUED (status))
580     {
581       *type = GNUNET_OS_PROCESS_RUNNING;
582       *code = 0;
583     }
584 #endif
585   else
586     {
587       *type = GNUNET_OS_PROCESS_UNKNOWN;
588       *code = 0;
589     }
590 #else
591   HANDLE h;
592   DWORD c;
593
594   h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, proc);
595   if (INVALID_HANDLE_VALUE == h)
596     {
597       SetErrnoFromWinError (GetLastError ());
598       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "OpenProcess");
599       return GNUNET_SYSERR;
600     }
601
602   c = GetExitCodeProcess (proc, &c);
603   if (STILL_ACTIVE == c)
604     {
605       *type = GNUNET_OS_PROCESS_RUNNING;
606       *code = 0;
607       CloseHandle (h);
608       return GNUNET_NO;
609     }
610   *type = GNUNET_OS_PROCESS_EXITED;
611   *code = c;
612   CloseHandle (h);
613 #endif
614
615   return GNUNET_OK;
616 }
617
618 /**
619  * Wait for a process
620  * @param proc process ID to wait for
621  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
622  */
623 int
624 GNUNET_OS_process_wait (pid_t proc)
625 {
626 #ifndef MINGW
627   if (proc != waitpid (proc, NULL, 0))
628     return GNUNET_SYSERR;
629
630   return GNUNET_OK;
631 #else
632   HANDLE h;
633   DWORD c;
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 */