2 This file is part of GNUnet.
3 Copyright (C) 2006-2018 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
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 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file src/util/os_installation.c
23 * @brief get paths used by the program
25 * @author Christian Fuchs
26 * @author Christian Grothoff
27 * @author Matthias Wachs
28 * @author Heikki Lindholm
35 #include <unistr.h> /* for u16_to_u8 */
38 #include "gnunet_util_lib.h"
40 #include <mach-o/ldsyms.h>
41 #include <mach-o/dyld.h>
47 #define LOG(kind, ...) \
48 GNUNET_log_from (kind, "util-os-installation", __VA_ARGS__)
50 #define LOG_STRERROR_FILE(kind, syscall, filename) \
51 GNUNET_log_from_strerror_file (kind, \
52 "util-os-installation", \
58 * Default project data used for installation path detection
61 static const struct GNUNET_OS_ProjectData default_pd = {
62 .libname = "libgnunetutil",
63 .project_dirname = "gnunet",
64 .binary_name = "gnunet-arm",
65 .version = PACKAGE_VERSION " " VCS_VERSION,
66 .env_varname = "GNUNET_PREFIX",
67 .base_config_varname = "GNUNET_BASE_CONFIG",
68 .bug_email = "gnunet-developers@gnu.org",
69 .homepage = "http://www.gnu.org/s/gnunet/",
70 .config_file = "gnunet.conf",
71 .user_config_file = "~/.config/gnunet.conf",
73 .gettext_domain = PACKAGE,
78 * Which project data do we currently use for installation
79 * path detection? Never NULL.
81 static const struct GNUNET_OS_ProjectData *current_pd = &default_pd;
84 * Wether or not gettext has been initialized for the library.
85 * Note that the gettext initialization done within
86 * GNUNET_PROGRAM_run2 is for the specific application.
88 static int gettextinit = 0;
91 * Return default project data used by 'libgnunetutil' for GNUnet.
93 const struct GNUNET_OS_ProjectData *
94 GNUNET_OS_project_data_default (void)
101 * @return current project data.
103 const struct GNUNET_OS_ProjectData *
104 GNUNET_OS_project_data_get ()
106 if (0 == gettextinit)
108 char *path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
110 BINDTEXTDOMAIN (PACKAGE, path);
119 * Setup OS subsystem with project data.
121 * @param pd project data used to determine paths
124 GNUNET_OS_init (const struct GNUNET_OS_ProjectData *pd)
126 if (0 == gettextinit)
128 char *path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
130 BINDTEXTDOMAIN (PACKAGE, path);
134 GNUNET_assert (NULL != pd);
141 * Try to determine path by reading /proc/PID/exe
143 * @return NULL on error
146 get_path_from_proc_maps ()
154 GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/maps", getpid ());
155 if (NULL == (f = FOPEN (fn, "r")))
157 while (NULL != fgets (line, sizeof (line), f))
159 if ((1 == SSCANF (line,
160 "%*x-%*x %*c%*c%*c%*c %*x %*2x:%*2x %*u%*[ ]%1023s",
162 (NULL != (lgu = strstr (dir, current_pd->libname))))
166 return GNUNET_strdup (dir);
175 * Try to determine path by reading /proc/PID/exe
177 * @return NULL on error
180 get_path_from_proc_exe ()
187 GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/exe", getpid ());
188 size = readlink (fn, lnk, sizeof (lnk) - 1);
191 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
194 GNUNET_assert (((size_t) size) < sizeof (lnk));
196 while ((lnk[size] != '/') && (size > 0))
198 GNUNET_asprintf (&lep, "/%s/libexec/", current_pd->project_dirname);
199 /* test for being in lib/gnunet/libexec/ or lib/MULTIARCH/gnunet/libexec */
200 if ((((size_t) size) > strlen (lep)) &&
201 (0 == strcmp (lep, &lnk[size - strlen (lep)])))
202 size -= strlen (lep) - 1;
204 if ((size < 4) || (lnk[size - 4] != '/'))
206 /* not installed in "/bin/" -- binary path probably useless */
210 return GNUNET_strdup (lnk);
216 static HINSTANCE dll_instance;
220 * GNUNET_util_cl_init() in common_logging.c is preferred.
221 * This function is only for thread-local storage (not used in GNUnet)
222 * and hInstance saving.
225 DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
229 case DLL_PROCESS_ATTACH:
230 dll_instance = hinstDLL;
232 case DLL_THREAD_ATTACH:
234 case DLL_THREAD_DETACH:
236 case DLL_PROCESS_DETACH:
244 * Try to determine path with win32-specific function
246 * @return NULL on error
249 get_path_from_module_filename ()
251 size_t pathlen = 512;
254 wchar_t *modulepath = NULL;
257 size_t u8_string_length;
259 /* This braindead function won't tell us how much space it needs, so
260 * we start at 1024 and double the space up if it doesn't fit, until
261 * it fits, or we exceed the threshold.
265 pathlen = pathlen * 2;
266 modulepath = GNUNET_realloc (modulepath, pathlen * sizeof (wchar_t));
269 GetModuleFileNameW (dll_instance, modulepath, pathlen * sizeof (wchar_t));
270 } while (real_pathlen >= pathlen && pathlen < 16 * 1024);
271 if (real_pathlen >= pathlen)
274 modulepath[real_pathlen] = '\0';
276 idx = modulepath + real_pathlen;
277 while ((idx > modulepath) && (*idx != L'\\') && (*idx != L'/'))
281 /* Now modulepath holds full path to the directory where libgnunetutil is.
282 * This directory should look like <GNUNET_PREFIX>/bin or <GNUNET_PREFIX>.
284 if (wcschr (modulepath, L'/') || wcschr (modulepath, L'\\'))
286 /* At least one directory component (i.e. we're not in a root directory) */
287 wchar_t *dirname = idx;
288 while ((dirname > modulepath) && (*dirname != L'\\') && (*dirname != L'/'))
291 if (dirname > modulepath)
294 /* Now modulepath holds full path to the parent directory of the directory
295 * where libgnunetutil is.
296 * dirname holds the name of the directory where libgnunetutil is.
298 if (wcsicmp (dirname, L"bin") == 0)
304 /* Roll back our changes to modulepath */
311 /* modulepath is GNUNET_PREFIX */
313 u16_to_u8 (modulepath, wcslen (modulepath), NULL, &u8_string_length);
314 if (NULL == u8_string)
317 upath = GNUNET_malloc (u8_string_length + 1);
318 GNUNET_memcpy (upath, u8_string, u8_string_length);
319 upath[u8_string_length] = '\0';
322 GNUNET_free (modulepath);
331 * Signature of the '_NSGetExecutablePath" function.
333 * @param buf where to write the path
334 * @param number of bytes available in @a buf
335 * @return 0 on success, otherwise desired number of bytes is stored in 'bufsize'
337 typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t *bufsize);
341 * Try to obtain the path of our executable using '_NSGetExecutablePath'.
343 * @return NULL on error
346 get_path_from_NSGetExecutablePath ()
348 static char zero = '\0';
351 MyNSGetExecutablePathProto func;
355 (func = (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT,
356 "_NSGetExecutablePath")))
360 /* get the path len, including the trailing \0 */
361 (void) func (path, &len);
364 path = GNUNET_malloc (len);
365 if (0 != func (path, &len))
371 while ((path[len] != '/') && (len > 0))
379 * Try to obtain the path of our executable using '_dyld_image' API.
381 * @return NULL on error
384 get_path_from_dyld_image ()
392 c = _dyld_image_count ();
393 for (i = 0; i < c; i++)
395 if (((const void *) _dyld_get_image_header (i)) !=
396 ((const void *) &_mh_dylib_header))
398 path = _dyld_get_image_name (i);
399 if ((NULL == path) || (0 == strlen (path)))
401 p = GNUNET_strdup (path);
403 while ((s > p) && ('/' != *s))
415 * Return the actual path to a file found in the current
416 * PATH environment variable.
418 * @param binary the name of the file to find
419 * @return path to binary, NULL if not found
422 get_path_from_PATH (const char *binary)
430 if (NULL == (p = getenv ("PATH")))
433 /* On W32 look in CWD first. */
434 GNUNET_asprintf (&path, ".%c%s", PATH_SEPARATOR, p);
436 path = GNUNET_strdup (p); /* because we write on it */
438 buf = GNUNET_malloc (strlen (path) + strlen (binary) + 1 + 1);
440 while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
443 sprintf (buf, "%s/%s", pos, binary);
444 if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
446 pos = GNUNET_strdup (pos);
453 sprintf (buf, "%s/%s", pos, binary);
454 if (GNUNET_YES == GNUNET_DISK_file_test (buf))
456 pos = GNUNET_strdup (pos);
468 * Try to obtain the installation path using the "GNUNET_PREFIX" environment
471 * @return NULL on error (environment variable not set)
474 get_path_from_GNUNET_PREFIX ()
478 if ((NULL != current_pd->env_varname) &&
479 (NULL != (p = getenv (current_pd->env_varname))))
480 return GNUNET_strdup (p);
481 if ((NULL != current_pd->env_varname_alt) &&
482 (NULL != (p = getenv (current_pd->env_varname_alt))))
483 return GNUNET_strdup (p);
489 * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
492 * @return a pointer to the executable path, or NULL on error
495 os_get_gnunet_path ()
499 if (NULL != (ret = get_path_from_GNUNET_PREFIX ()))
502 if (NULL != (ret = get_path_from_proc_maps ()))
504 /* try path *first*, before /proc/exe, as /proc/exe can be wrong */
505 if ((NULL != current_pd->binary_name) &&
506 (NULL != (ret = get_path_from_PATH (current_pd->binary_name))))
508 if (NULL != (ret = get_path_from_proc_exe ()))
512 if (NULL != (ret = get_path_from_module_filename ()))
516 if (NULL != (ret = get_path_from_dyld_image ()))
518 if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
521 if ((NULL != current_pd->binary_name) &&
522 (NULL != (ret = get_path_from_PATH (current_pd->binary_name))))
524 /* other attempts here */
525 LOG (GNUNET_ERROR_TYPE_ERROR,
527 "Could not determine installation path for %s. Set `%s' environment variable.\n"),
528 current_pd->project_dirname,
529 current_pd->env_varname);
535 * @brief get the path to current app's bin/
536 * @return a pointer to the executable path, or NULL on error
544 if (NULL != (ret = get_path_from_proc_exe ()))
548 if (NULL != (ret = get_path_from_module_filename ()))
552 if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
555 /* other attempts here */
561 * @brief get the path to a specific GNUnet installation directory or,
562 * with #GNUNET_OS_IPK_SELF_PREFIX, the current running apps installation directory
563 * @return a pointer to the dir path (to be freed by the caller)
566 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
570 char *execpath = NULL;
576 /* if wanted, try to get the current app's bin/ */
577 if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
578 execpath = os_get_exec_path ();
580 /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
581 * guess for the current app */
582 if (NULL == execpath)
583 execpath = os_get_gnunet_path ();
585 if (NULL == execpath)
588 n = strlen (execpath);
591 /* should never happen, but better safe than sorry */
592 GNUNET_free (execpath);
595 /* remove filename itself */
596 while ((n > 1) && (DIR_SEPARATOR == execpath[n - 1]))
597 execpath[--n] = '\0';
600 if ((n > 6) && ((0 == strcasecmp (&execpath[n - 6], "/lib32")) ||
601 (0 == strcasecmp (&execpath[n - 6], "/lib64"))))
603 if ((GNUNET_OS_IPK_LIBDIR != dirkind) &&
604 (GNUNET_OS_IPK_LIBEXECDIR != dirkind))
606 /* strip '/lib32' or '/lib64' */
607 execpath[n - 6] = '\0';
613 else if ((n > 4) && ((0 == strcasecmp (&execpath[n - 4], "/bin")) ||
614 (0 == strcasecmp (&execpath[n - 4], "/lib"))))
616 /* strip '/bin' or '/lib' */
617 execpath[n - 4] = '\0';
621 if (NULL != (libdir = strstr (execpath, "/lib/")))
623 /* test for multi-arch path of the form "PREFIX/lib/MULTIARCH/";
624 here we need to re-add 'multiarch' to lib and libexec paths later! */
625 multiarch = &libdir[5];
626 if (NULL == strchr (multiarch, '/'))
628 '\0'; /* Debian multiarch format, cut of from 'execpath' but preserve in multicarch */
631 NULL; /* maybe not, multiarch still has a '/', which is not OK */
633 /* in case this was a directory named foo-bin, remove "foo-" */
634 while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
635 execpath[--n] = '\0';
638 case GNUNET_OS_IPK_PREFIX:
639 case GNUNET_OS_IPK_SELF_PREFIX:
640 dirname = GNUNET_strdup (DIR_SEPARATOR_STR);
642 case GNUNET_OS_IPK_BINDIR:
643 dirname = GNUNET_strdup (DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR);
645 case GNUNET_OS_IPK_LIBDIR:
648 GNUNET_asprintf (&tmp,
651 DIR_SEPARATOR_STR "lib",
652 (NULL != multiarch) ? DIR_SEPARATOR_STR : "",
653 (NULL != multiarch) ? multiarch : "",
655 current_pd->project_dirname,
657 if (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES))
659 GNUNET_free (execpath);
665 if (4 == sizeof (void *))
667 GNUNET_asprintf (&dirname,
668 DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR
669 "%s" DIR_SEPARATOR_STR,
670 current_pd->project_dirname);
671 GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
673 if (8 == sizeof (void *))
675 GNUNET_asprintf (&dirname,
676 DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR
677 "%s" DIR_SEPARATOR_STR,
678 current_pd->project_dirname);
679 GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
683 (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES)))
685 GNUNET_free (execpath);
686 GNUNET_free_non_null (dirname);
690 GNUNET_free_non_null (dirname);
692 GNUNET_asprintf (&dirname,
693 DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
694 current_pd->project_dirname);
696 case GNUNET_OS_IPK_DATADIR:
697 GNUNET_asprintf (&dirname,
698 DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
699 "%s" DIR_SEPARATOR_STR,
700 current_pd->project_dirname);
702 case GNUNET_OS_IPK_LOCALEDIR:
703 dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
704 "locale" DIR_SEPARATOR_STR);
706 case GNUNET_OS_IPK_ICONDIR:
707 dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
708 "icons" DIR_SEPARATOR_STR);
710 case GNUNET_OS_IPK_DOCDIR:
711 GNUNET_asprintf (&dirname,
712 DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
713 "doc" DIR_SEPARATOR_STR
714 "%s" DIR_SEPARATOR_STR,
715 current_pd->project_dirname);
717 case GNUNET_OS_IPK_LIBEXECDIR:
720 GNUNET_asprintf (&dirname,
721 DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR
722 "libexec" DIR_SEPARATOR_STR,
723 current_pd->project_dirname);
724 GNUNET_asprintf (&tmp,
727 DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR,
728 (NULL != multiarch) ? multiarch : "",
730 if (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES))
732 GNUNET_free (execpath);
733 GNUNET_free (dirname);
739 if (4 == sizeof (void *))
741 GNUNET_asprintf (&dirname,
742 DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR
743 "%s" DIR_SEPARATOR_STR
744 "libexec" DIR_SEPARATOR_STR,
745 current_pd->project_dirname);
746 GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
748 if (8 == sizeof (void *))
750 GNUNET_asprintf (&dirname,
751 DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR
752 "%s" DIR_SEPARATOR_STR
753 "libexec" DIR_SEPARATOR_STR,
754 current_pd->project_dirname);
755 GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
758 (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES)))
760 GNUNET_free (execpath);
761 GNUNET_free_non_null (dirname);
765 GNUNET_free_non_null (dirname);
767 GNUNET_asprintf (&dirname,
768 DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR
769 "libexec" DIR_SEPARATOR_STR,
770 current_pd->project_dirname);
773 GNUNET_free (execpath);
776 GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
777 GNUNET_free (dirname);
778 GNUNET_free (execpath);
784 * Given the name of a gnunet-helper, gnunet-service or gnunet-daemon
785 * binary, try to prefix it with the libexec/-directory to get the
788 * @param progname name of the binary
789 * @return full path to the binary, if possible, otherwise copy of 'progname'
792 GNUNET_OS_get_libexec_binary_path (const char *progname)
798 if ((DIR_SEPARATOR == progname[0]) ||
800 GNUNET_STRINGS_path_is_absolute (progname, GNUNET_NO, NULL, NULL)))
801 return GNUNET_strdup (progname);
805 libexecdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
806 if (NULL == libexecdir)
807 return GNUNET_strdup (progname);
808 GNUNET_asprintf (&binary, "%s%s", libexecdir, progname);
815 * Given the name of a helper, service or daemon binary construct the full
816 * path to the binary using the SUID_BINARY_PATH in the PATHS section of the
817 * configuration. If that option is not present, fall back to
818 * GNUNET_OS_get_libexec_binary_path. If @a progname is an absolute path, a
819 * copy of this path is returned.
821 * @param cfg configuration to inspect
822 * @param progname name of the binary
823 * @return full path to the binary, if possible, a copy of @a progname
827 GNUNET_OS_get_suid_binary_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
828 const char *progname)
836 GNUNET_STRINGS_path_is_absolute (progname, GNUNET_NO, NULL, NULL))
838 return GNUNET_strdup (progname);
843 GNUNET_CONFIGURATION_get_value_string (cfg,
847 if ((NULL == path)||(0 == strlen (path)))
848 return GNUNET_OS_get_libexec_binary_path (progname);
849 path_len = strlen (path);
850 GNUNET_asprintf (&binary,
853 (path[path_len - 1] == DIR_SEPARATOR) ? ""
862 * Check whether an executable exists and possibly if the suid bit is
863 * set on the file. Attempts to find the file using the current PATH
864 * environment variable as a search path.
866 * @param binary the name of the file to check.
867 * W32: must not have an .exe suffix.
868 * @param check_suid input true if the binary should be checked for SUID (*nix)
869 * W32: checks if the program has sufficient privileges by executing this
870 * binary with the -d flag. -d omits a programs main loop and only
871 * executes all privileged operations in an binary.
872 * @param params parameters used for w32 privilege checking (can be NULL for != w32 )
873 * @return #GNUNET_YES if the file is SUID (*nix) or can be executed with current privileges (W32),
874 * #GNUNET_NO if not SUID (but binary exists),
875 * #GNUNET_SYSERR on error (no such binary or not executable)
878 GNUNET_OS_check_helper_binary (const char *binary,
888 GNUNET_asprintf (&binaryexe, "%s.exe", binary);
890 GNUNET_STRINGS_path_is_absolute (binaryexe, GNUNET_NO, NULL, NULL)) ||
891 (0 == strncmp (binary, "./", 2)))
892 p = GNUNET_strdup (binaryexe);
895 p = get_path_from_PATH (binaryexe);
898 GNUNET_asprintf (&pf, "%s/%s", p, binaryexe);
903 GNUNET_free (binaryexe);
906 GNUNET_STRINGS_path_is_absolute (binary, GNUNET_NO, NULL, NULL)) ||
907 (0 == strncmp (binary, "./", 2)))
909 p = GNUNET_strdup (binary);
913 p = get_path_from_PATH (binary);
916 GNUNET_asprintf (&pf, "%s/%s", p, binary);
924 LOG (GNUNET_ERROR_TYPE_INFO,
925 _ ("Could not find binary `%s' in PATH!\n"),
927 return GNUNET_SYSERR;
929 if (0 != ACCESS (p, X_OK))
931 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", p);
933 return GNUNET_SYSERR;
938 /* as we run as root, we don't insist on SUID */
943 if (0 != STAT (p, &statbuf))
945 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", p);
947 return GNUNET_SYSERR;
953 if ((0 != (statbuf.st_mode & S_ISUID)) && (0 == statbuf.st_uid))
958 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
959 _ ("Binary `%s' exists, but is not SUID\n"),
961 /* binary exists, but not SUID */
964 char parameters[512];
965 PROCESS_INFORMATION proc;
968 GNUNET_snprintf (parameters, sizeof (parameters), "-d %s", params);
969 memset (&start, 0, sizeof (start));
970 start.cb = sizeof (start);
971 memset (&proc, 0, sizeof (proc));
974 // Start the child process.
975 if (! (CreateProcess (
976 p, // current windows (2k3 and up can handle / instead of \ in paths))
977 parameters, // execute dryrun/priviliege checking mode
978 NULL, // Process handle not inheritable
979 NULL, // Thread handle not inheritable
980 FALSE, // Set handle inheritance to FALSE
981 CREATE_DEFAULT_ERROR_MODE, // No creation flags
982 NULL, // Use parent's environment block
983 NULL, // Use parent's starting directory
984 &start, // Pointer to STARTUPINFO structure
985 &proc) // Pointer to PROCESS_INFORMATION structure
988 LOG (GNUNET_ERROR_TYPE_ERROR,
989 _ ("CreateProcess failed for binary %s (%d).\n"),
992 return GNUNET_SYSERR;
995 // Wait until child process exits.
996 WaitForSingleObject (proc.hProcess, INFINITE);
998 if (! GetExitCodeProcess (proc.hProcess, &exit_value))
1000 LOG (GNUNET_ERROR_TYPE_ERROR,
1001 _ ("GetExitCodeProcess failed for binary %s (%d).\n"),
1004 return GNUNET_SYSERR;
1006 // Close process and thread handles.
1007 CloseHandle (proc.hProcess);
1008 CloseHandle (proc.hThread);
1019 /* end of os_installation.c */