support overriding GNUNET_DEFAULT_USER_CONFIG_FILE
[oweals/gnunet.git] / src / util / os_installation.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2006-2016 GNUnet e.V.
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 3, 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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file src/util/os_installation.c
23  * @brief get paths used by the program
24  * @author Milan
25  * @author Christian Fuchs
26  * @author Christian Grothoff
27  * @author Matthias Wachs
28  * @author Heikki Lindholm
29  * @author LRN
30  */
31 #include <sys/stat.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <unistr.h> /* for u16_to_u8 */
36
37 #include "platform.h"
38 #include "gnunet_util_lib.h"
39 #if DARWIN
40 #include <mach-o/ldsyms.h>
41 #include <mach-o/dyld.h>
42 #elif WINDOWS
43 #include <windows.h>
44 #endif
45
46
47 #define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__)
48
49 #define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util", syscall, filename)
50
51
52 /**
53  * Default project data used for installation path detection
54  * for GNUnet (core).
55  */
56 static const struct GNUNET_OS_ProjectData default_pd = {
57   .libname = "libgnunetutil",
58   .project_dirname = "gnunet",
59   .binary_name = "gnunet-arm",
60   .env_varname = "GNUNET_PREFIX",
61   .bug_email = "gnunet-developers@gnu.org",
62   .homepage = "http://www.gnu.org/s/gnunet/",
63   .config_file = "gnunet.conf",
64   .user_config_file = "~/.config/gnunet.conf"
65 };
66
67 /**
68  * Which project data do we currently use for installation
69  * path detection? Never NULL.
70  */
71 static const struct GNUNET_OS_ProjectData *current_pd = &default_pd;
72
73 /**
74  * Return default project data used by 'libgnunetutil' for GNUnet.
75  */
76 const struct GNUNET_OS_ProjectData *
77 GNUNET_OS_project_data_default (void)
78 {
79   return &default_pd;
80 }
81
82
83 /**
84  * @return current project data.
85  */
86 const struct GNUNET_OS_ProjectData *
87 GNUNET_OS_project_data_get ()
88 {
89   return current_pd;
90 }
91
92
93 /**
94  * Setup OS subsystem with project data.
95  *
96  * @param pd project data used to determine paths
97  */
98 void
99 GNUNET_OS_init (const struct GNUNET_OS_ProjectData *pd)
100 {
101   GNUNET_assert (NULL != pd);
102   current_pd = pd;
103 }
104
105
106 #if LINUX
107 /**
108  * Try to determine path by reading /proc/PID/exe
109  *
110  * @return NULL on error
111  */
112 static char *
113 get_path_from_proc_maps ()
114 {
115   char fn[64];
116   char line[1024];
117   char dir[1024];
118   FILE *f;
119   char *lgu;
120
121   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/maps", getpid ());
122   if (NULL == (f = FOPEN (fn, "r")))
123     return NULL;
124   while (NULL != fgets (line, sizeof (line), f))
125   {
126     if ((1 ==
127          SSCANF (line, "%*x-%*x %*c%*c%*c%*c %*x %*2x:%*2x %*u%*[ ]%1023s", dir)) &&
128         (NULL != (lgu = strstr (dir,
129                                 current_pd->libname))))
130     {
131       lgu[0] = '\0';
132       FCLOSE (f);
133       return GNUNET_strdup (dir);
134     }
135   }
136   FCLOSE (f);
137   return NULL;
138 }
139
140
141 /**
142  * Try to determine path by reading /proc/PID/exe
143  *
144  * @return NULL on error
145  */
146 static char *
147 get_path_from_proc_exe ()
148 {
149   char fn[64];
150   char lnk[1024];
151   ssize_t size;
152   char *lep;
153
154   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/exe", getpid ());
155   size = readlink (fn, lnk, sizeof (lnk) - 1);
156   if (size <= 0)
157   {
158     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
159     return NULL;
160   }
161   GNUNET_assert (size < sizeof (lnk));
162   lnk[size] = '\0';
163   while ((lnk[size] != '/') && (size > 0))
164     size--;
165   GNUNET_asprintf (&lep,
166                    "/%s/libexec/",
167                    current_pd->project_dirname);
168   /* test for being in lib/gnunet/libexec/ or lib/MULTIARCH/gnunet/libexec */
169   if ( (size > strlen (lep)) &&
170        (0 == strcmp (lep,
171                      &lnk[size - strlen (lep)])) )
172     size -= strlen (lep) - 1;
173   GNUNET_free (lep);
174   if ((size < 4) || (lnk[size - 4] != '/'))
175   {
176     /* not installed in "/bin/" -- binary path probably useless */
177     return NULL;
178   }
179   lnk[size] = '\0';
180   return GNUNET_strdup (lnk);
181 }
182 #endif
183
184
185 #if WINDOWS
186 static HINSTANCE dll_instance;
187
188
189 /**
190  * GNUNET_util_cl_init() in common_logging.c is preferred.
191  * This function is only for thread-local storage (not used in GNUnet)
192  * and hInstance saving.
193  */
194 BOOL WINAPI
195 DllMain (HINSTANCE hinstDLL,
196          DWORD fdwReason,
197          LPVOID lpvReserved)
198 {
199   switch (fdwReason)
200   {
201     case DLL_PROCESS_ATTACH:
202       dll_instance = hinstDLL;
203       break;
204     case DLL_THREAD_ATTACH:
205       break;
206     case DLL_THREAD_DETACH:
207       break;
208     case DLL_PROCESS_DETACH:
209       break;
210   }
211   return TRUE;
212 }
213
214
215 /**
216  * Try to determine path with win32-specific function
217  *
218  * @return NULL on error
219  */
220 static char *
221 get_path_from_module_filename ()
222 {
223   size_t pathlen = 512;
224   DWORD real_pathlen;
225   wchar_t *idx;
226   wchar_t *modulepath = NULL;
227   char *upath;
228   uint8_t *u8_string;
229   size_t u8_string_length;
230
231   /* This braindead function won't tell us how much space it needs, so
232    * we start at 1024 and double the space up if it doesn't fit, until
233    * it fits, or we exceed the threshold.
234    */
235   do
236   {
237     pathlen = pathlen * 2;
238     modulepath = GNUNET_realloc (modulepath,
239                                  pathlen * sizeof (wchar_t));
240     SetLastError (0);
241     real_pathlen = GetModuleFileNameW (dll_instance,
242                                        modulepath,
243                                        pathlen * sizeof (wchar_t));
244   } while (real_pathlen >= pathlen && pathlen < 16*1024);
245   if (real_pathlen >= pathlen)
246     GNUNET_assert (0);
247   /* To be safe */
248   modulepath[real_pathlen] = '\0';
249
250   idx = modulepath + real_pathlen;
251   while ((idx > modulepath) && (*idx != L'\\') && (*idx != L'/'))
252     idx--;
253   *idx = L'\0';
254
255   /* Now modulepath holds full path to the directory where libgnunetutil is.
256    * This directory should look like <GNUNET_PREFIX>/bin or <GNUNET_PREFIX>.
257    */
258   if (wcschr (modulepath, L'/') || wcschr (modulepath, L'\\'))
259   {
260     /* At least one directory component (i.e. we're not in a root directory) */
261     wchar_t *dirname = idx;
262     while ((dirname > modulepath) && (*dirname != L'\\') && (*dirname != L'/'))
263       dirname--;
264     *dirname = L'\0';
265     if (dirname > modulepath)
266     {
267       dirname++;
268       /* Now modulepath holds full path to the parent directory of the directory
269        * where libgnunetutil is.
270        * dirname holds the name of the directory where libgnunetutil is.
271        */
272       if (wcsicmp (dirname, L"bin") == 0)
273       {
274         /* pass */
275       }
276       else
277       {
278         /* Roll back our changes to modulepath */
279         dirname--;
280         *dirname = L'/';
281       }
282     }
283   }
284
285   /* modulepath is GNUNET_PREFIX */
286   u8_string = u16_to_u8 (modulepath, wcslen (modulepath), NULL, &u8_string_length);
287   if (NULL == u8_string)
288     GNUNET_assert (0);
289
290   upath = GNUNET_malloc (u8_string_length + 1);
291   memcpy (upath, u8_string, u8_string_length);
292   upath[u8_string_length] = '\0';
293
294   free (u8_string);
295   GNUNET_free (modulepath);
296
297   return upath;
298 }
299 #endif
300
301
302 #if DARWIN
303 /**
304  * Signature of the '_NSGetExecutablePath" function.
305  *
306  * @param buf where to write the path
307  * @param number of bytes available in @a buf
308  * @return 0 on success, otherwise desired number of bytes is stored in 'bufsize'
309  */
310 typedef int
311 (*MyNSGetExecutablePathProto) (char *buf,
312                                size_t *bufsize);
313
314
315 /**
316  * Try to obtain the path of our executable using '_NSGetExecutablePath'.
317  *
318  * @return NULL on error
319  */
320 static char *
321 get_path_from_NSGetExecutablePath ()
322 {
323   static char zero = '\0';
324   char *path;
325   size_t len;
326   MyNSGetExecutablePathProto func;
327
328   path = NULL;
329   if (NULL == (func =
330                (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT, "_NSGetExecutablePath")))
331     return NULL;
332   path = &zero;
333   len = 0;
334   /* get the path len, including the trailing \0 */
335   (void) func (path, &len);
336   if (0 == len)
337     return NULL;
338   path = GNUNET_malloc (len);
339   if (0 != func (path, &len))
340   {
341     GNUNET_free (path);
342     return NULL;
343   }
344   len = strlen (path);
345   while ((path[len] != '/') && (len > 0))
346     len--;
347   path[len] = '\0';
348   return path;
349 }
350
351
352 /**
353  * Try to obtain the path of our executable using '_dyld_image' API.
354  *
355  * @return NULL on error
356  */
357 static char *
358 get_path_from_dyld_image ()
359 {
360   const char *path;
361   char *p;
362   char *s;
363   unsigned int i;
364   int c;
365
366   c = _dyld_image_count ();
367   for (i = 0; i < c; i++)
368   {
369     if (((const void *) _dyld_get_image_header (i)) !=
370         ((const void *) &_mh_dylib_header) )
371       continue;
372     path = _dyld_get_image_name (i);
373     if ( (NULL == path) || (0 == strlen (path)) )
374       continue;
375     p = GNUNET_strdup (path);
376     s = p + strlen (p);
377     while ((s > p) && ('/' != *s))
378       s--;
379     s++;
380     *s = '\0';
381     return p;
382   }
383   return NULL;
384 }
385 #endif
386
387
388 /**
389  * Return the actual path to a file found in the current
390  * PATH environment variable.
391  *
392  * @param binary the name of the file to find
393  * @return path to binary, NULL if not found
394  */
395 static char *
396 get_path_from_PATH (const char *binary)
397 {
398   char *path;
399   char *pos;
400   char *end;
401   char *buf;
402   const char *p;
403
404   if (NULL == (p = getenv ("PATH")))
405     return NULL;
406 #if WINDOWS
407   /* On W32 look in CWD first. */
408   GNUNET_asprintf (&path, ".%c%s", PATH_SEPARATOR, p);
409 #else
410   path = GNUNET_strdup (p);     /* because we write on it */
411 #endif
412   buf = GNUNET_malloc (strlen (path) + strlen (binary) + 1 + 1);
413   pos = path;
414   while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
415   {
416     *end = '\0';
417     sprintf (buf, "%s/%s", pos, binary);
418     if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
419     {
420       pos = GNUNET_strdup (pos);
421       GNUNET_free (buf);
422       GNUNET_free (path);
423       return pos;
424     }
425     pos = end + 1;
426   }
427   sprintf (buf, "%s/%s", pos, binary);
428   if (GNUNET_YES == GNUNET_DISK_file_test (buf))
429   {
430     pos = GNUNET_strdup (pos);
431     GNUNET_free (buf);
432     GNUNET_free (path);
433     return pos;
434   }
435   GNUNET_free (buf);
436   GNUNET_free (path);
437   return NULL;
438 }
439
440
441 /**
442  * Try to obtain the installation path using the "GNUNET_PREFIX" environment
443  * variable.
444  *
445  * @return NULL on error (environment variable not set)
446  */
447 static char *
448 get_path_from_GNUNET_PREFIX ()
449 {
450   const char *p;
451
452   if ( (NULL != current_pd->env_varname) &&
453        (NULL != (p = getenv (current_pd->env_varname))) )
454     return GNUNET_strdup (p);
455   if ( (NULL != current_pd->env_varname_alt) &&
456        (NULL != (p = getenv (current_pd->env_varname_alt))) )
457     return GNUNET_strdup (p);
458   return NULL;
459 }
460
461
462 /**
463  * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
464  * @author Milan
465  *
466  * @return a pointer to the executable path, or NULL on error
467  */
468 static char *
469 os_get_gnunet_path ()
470 {
471   char *ret;
472
473   if (NULL != (ret = get_path_from_GNUNET_PREFIX ()))
474     return ret;
475 #if LINUX
476   if (NULL != (ret = get_path_from_proc_maps ()))
477     return ret;
478   /* try path *first*, before /proc/exe, as /proc/exe can be wrong */
479   if ( (NULL != current_pd->binary_name) &&
480        (NULL != (ret = get_path_from_PATH (current_pd->binary_name))) )
481     return ret;
482   if (NULL != (ret = get_path_from_proc_exe ()))
483     return ret;
484 #endif
485 #if WINDOWS
486   if (NULL != (ret = get_path_from_module_filename ()))
487     return ret;
488 #endif
489 #if DARWIN
490   if (NULL != (ret = get_path_from_dyld_image ()))
491     return ret;
492   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
493     return ret;
494 #endif
495   if ( (NULL != current_pd->binary_name) &&
496        (NULL != (ret = get_path_from_PATH (current_pd->binary_name))) )
497     return ret;
498   /* other attempts here */
499   LOG (GNUNET_ERROR_TYPE_ERROR,
500        _("Could not determine installation path for %s.  Set `%s' environment variable.\n"),
501        current_pd->project_dirname,
502        current_pd->env_varname);
503   return NULL;
504 }
505
506
507 /**
508  * @brief get the path to current app's bin/
509  * @return a pointer to the executable path, or NULL on error
510  */
511 static char *
512 os_get_exec_path ()
513 {
514   char *ret = NULL;
515
516 #if LINUX
517   if (NULL != (ret = get_path_from_proc_exe ()))
518     return ret;
519 #endif
520 #if WINDOWS
521   if (NULL != (ret = get_path_from_module_filename ()))
522     return ret;
523 #endif
524 #if DARWIN
525   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
526     return ret;
527 #endif
528   /* other attempts here */
529   return ret;
530 }
531
532
533 /**
534  * @brief get the path to a specific GNUnet installation directory or,
535  * with #GNUNET_OS_IPK_SELF_PREFIX, the current running apps installation directory
536  * @return a pointer to the dir path (to be freed by the caller)
537  */
538 char *
539 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
540 {
541   size_t n;
542   char *dirname;
543   char *execpath = NULL;
544   char *tmp;
545   char *multiarch;
546   char *libdir;
547   int isbasedir;
548
549   /* if wanted, try to get the current app's bin/ */
550   if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
551     execpath = os_get_exec_path ();
552
553   /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
554    * guess for the current app */
555   if (NULL == execpath)
556     execpath = os_get_gnunet_path ();
557
558   if (NULL == execpath)
559     return NULL;
560
561   n = strlen (execpath);
562   if (0 == n)
563   {
564     /* should never happen, but better safe than sorry */
565     GNUNET_free (execpath);
566     return NULL;
567   }
568   /* remove filename itself */
569   while ((n > 1) && (DIR_SEPARATOR == execpath[n - 1]))
570     execpath[--n] = '\0';
571
572   isbasedir = 1;
573   if ((n > 6) &&
574       ((0 == strcasecmp (&execpath[n - 6], "/lib32")) ||
575        (0 == strcasecmp (&execpath[n - 6], "/lib64"))))
576   {
577     if ( (GNUNET_OS_IPK_LIBDIR != dirkind) &&
578          (GNUNET_OS_IPK_LIBEXECDIR != dirkind) )
579     {
580       /* strip '/lib32' or '/lib64' */
581       execpath[n - 6] = '\0';
582       n -= 6;
583     }
584     else
585       isbasedir = 0;
586   }
587   else if ((n > 4) &&
588            ((0 == strcasecmp (&execpath[n - 4], "/bin")) ||
589             (0 == strcasecmp (&execpath[n - 4], "/lib"))))
590   {
591     /* strip '/bin' or '/lib' */
592     execpath[n - 4] = '\0';
593     n -= 4;
594   }
595   multiarch = NULL;
596   if (NULL != (libdir = strstr (execpath, "/lib/")))
597   {
598     /* test for multi-arch path of the form "PREFIX/lib/MULTIARCH/";
599        here we need to re-add 'multiarch' to lib and libexec paths later! */
600     multiarch = &libdir[5];
601     if (NULL == strchr (multiarch, '/'))
602       libdir[0] = '\0'; /* Debian multiarch format, cut of from 'execpath' but preserve in multicarch */
603     else
604       multiarch = NULL; /* maybe not, multiarch still has a '/', which is not OK */
605   }
606   /* in case this was a directory named foo-bin, remove "foo-" */
607   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
608     execpath[--n] = '\0';
609   switch (dirkind)
610   {
611   case GNUNET_OS_IPK_PREFIX:
612   case GNUNET_OS_IPK_SELF_PREFIX:
613     dirname = GNUNET_strdup (DIR_SEPARATOR_STR);
614     break;
615   case GNUNET_OS_IPK_BINDIR:
616     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR);
617     break;
618   case GNUNET_OS_IPK_LIBDIR:
619     if (isbasedir)
620     {
621       GNUNET_asprintf (&tmp,
622                        "%s%s%s%s%s%s%s",
623                        execpath,
624                        DIR_SEPARATOR_STR "lib",
625                        (NULL != multiarch) ? DIR_SEPARATOR_STR : "",
626                        (NULL != multiarch) ? multiarch : "",
627                        DIR_SEPARATOR_STR,
628                        current_pd->project_dirname,
629                        DIR_SEPARATOR_STR);
630       if (GNUNET_YES ==
631           GNUNET_DISK_directory_test (tmp, GNUNET_YES))
632       {
633         GNUNET_free (execpath);
634         return tmp;
635       }
636       GNUNET_free (tmp);
637       tmp = NULL;
638       dirname = NULL;
639       if (4 == sizeof (void *))
640       {
641         GNUNET_asprintf (&dirname,
642                          DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
643                          current_pd->project_dirname);
644         GNUNET_asprintf (&tmp,
645                          "%s%s",
646                          execpath,
647                          dirname);
648       }
649       if (8 == sizeof (void *))
650       {
651         GNUNET_asprintf (&dirname,
652                          DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
653                          current_pd->project_dirname);
654         GNUNET_asprintf (&tmp,
655                          "%s%s",
656                          execpath,
657                          dirname);
658       }
659
660       if ( (NULL != tmp) &&
661            (GNUNET_YES ==
662             GNUNET_DISK_directory_test (tmp, GNUNET_YES)) )
663       {
664         GNUNET_free (execpath);
665         GNUNET_free_non_null (dirname);
666         return tmp;
667       }
668       GNUNET_free (tmp);
669       GNUNET_free_non_null (dirname);
670     }
671     GNUNET_asprintf (&dirname,
672                      DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
673                      current_pd->project_dirname);
674     break;
675   case GNUNET_OS_IPK_DATADIR:
676     GNUNET_asprintf (&dirname,
677                      DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
678                      current_pd->project_dirname);
679     break;
680   case GNUNET_OS_IPK_LOCALEDIR:
681     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale" DIR_SEPARATOR_STR);
682     break;
683   case GNUNET_OS_IPK_ICONDIR:
684     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "icons" DIR_SEPARATOR_STR);
685     break;
686   case GNUNET_OS_IPK_DOCDIR:
687     GNUNET_asprintf (&dirname,
688                      DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "doc" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
689                      current_pd->project_dirname);
690     break;
691   case GNUNET_OS_IPK_LIBEXECDIR:
692     if (isbasedir)
693     {
694       GNUNET_asprintf (&dirname,
695                        DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR "libexec" DIR_SEPARATOR_STR,
696                        current_pd->project_dirname);
697       GNUNET_asprintf (&tmp,
698                        "%s%s%s%s",
699                        execpath,
700                        DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR,
701                        (NULL != multiarch) ? multiarch : "",
702                        dirname);
703       if (GNUNET_YES ==
704           GNUNET_DISK_directory_test (tmp, GNUNET_YES))
705       {
706         GNUNET_free (execpath);
707         GNUNET_free (dirname);
708         return tmp;
709       }
710       GNUNET_free (tmp);
711       tmp = NULL;
712       dirname = NULL;
713       if (4 == sizeof (void *))
714       {
715         GNUNET_asprintf (&dirname,
716                          DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR "libexec" DIR_SEPARATOR_STR,
717                          current_pd->project_dirname);
718         GNUNET_asprintf (&tmp,
719                          "%s%s",
720                          execpath,
721                          dirname);
722       }
723       if (8 == sizeof (void *))
724       {
725         GNUNET_asprintf (&dirname,
726                          DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR "libexec" DIR_SEPARATOR_STR,
727                          current_pd->project_dirname);
728         GNUNET_asprintf (&tmp,
729                          "%s%s",
730                          execpath,
731                          dirname);
732       }
733       if ( (NULL != tmp) &&
734            (GNUNET_YES ==
735             GNUNET_DISK_directory_test (tmp, GNUNET_YES)) )
736       {
737         GNUNET_free (execpath);
738         GNUNET_free_non_null (dirname);
739         return tmp;
740       }
741       GNUNET_free (tmp);
742       GNUNET_free_non_null (dirname);
743     }
744     GNUNET_asprintf (&dirname,
745                      DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR "libexec" DIR_SEPARATOR_STR,
746                      current_pd->project_dirname);
747     break;
748   default:
749     GNUNET_free (execpath);
750     return NULL;
751   }
752   GNUNET_asprintf (&tmp,
753                    "%s%s",
754                    execpath,
755                    dirname);
756   GNUNET_free (dirname);
757   GNUNET_free (execpath);
758   return tmp;
759 }
760
761
762 /**
763  * Given the name of a gnunet-helper, gnunet-service or gnunet-daemon
764  * binary, try to prefix it with the libexec/-directory to get the
765  * full path.
766  *
767  * @param progname name of the binary
768  * @return full path to the binary, if possible, otherwise copy of 'progname'
769  */
770 char *
771 GNUNET_OS_get_libexec_binary_path (const char *progname)
772 {
773   static char *cache;
774   char *libexecdir;
775   char *binary;
776
777   if ( (DIR_SEPARATOR == progname[0]) ||
778        (GNUNET_YES ==
779         GNUNET_STRINGS_path_is_absolute (progname,
780                                          GNUNET_NO,
781                                          NULL, NULL)) )
782     return GNUNET_strdup (progname);
783   if (NULL != cache)
784     libexecdir = cache;
785   else
786     libexecdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
787   if (NULL == libexecdir)
788     return GNUNET_strdup (progname);
789   GNUNET_asprintf (&binary,
790                    "%s%s",
791                    libexecdir,
792                    progname);
793   cache = libexecdir;
794   return binary;
795 }
796
797
798 /**
799  * Check whether an executable exists and possibly if the suid bit is
800  * set on the file.  Attempts to find the file using the current PATH
801  * environment variable as a search path.
802  *
803  * @param binary the name of the file to check.
804  *        W32: must not have an .exe suffix.
805  * @param check_suid input true if the binary should be checked for SUID (*nix)
806  *        W32: checks if the program has sufficient privileges by executing this
807  *             binary with the -d flag. -d omits a programs main loop and only
808  *             executes all privileged operations in an binary.
809  * @param params parameters used for w32 privilege checking (can be NULL for != w32 )
810  * @return #GNUNET_YES if the file is SUID (*nix) or can be executed with current privileges (W32),
811  *         #GNUNET_NO if not SUID (but binary exists),
812  *         #GNUNET_SYSERR on error (no such binary or not executable)
813  */
814 int
815 GNUNET_OS_check_helper_binary (const char *binary,
816                                int check_suid,
817                                const char *params)
818 {
819   struct stat statbuf;
820   char *p;
821   char *pf;
822 #ifdef MINGW
823   char *binaryexe;
824
825   GNUNET_asprintf (&binaryexe, "%s.exe", binary);
826   if ( (GNUNET_YES == GNUNET_STRINGS_path_is_absolute (binaryexe, GNUNET_NO,
827                                                        NULL, NULL)) ||
828        (0 == strncmp (binary, "./", 2)) )
829     p = GNUNET_strdup (binaryexe);
830   else
831   {
832     p = get_path_from_PATH (binaryexe);
833     if (NULL != p)
834     {
835       GNUNET_asprintf (&pf, "%s/%s", p, binaryexe);
836       GNUNET_free (p);
837       p = pf;
838     }
839   }
840   GNUNET_free (binaryexe);
841 #else
842   if ( (GNUNET_YES == GNUNET_STRINGS_path_is_absolute (binary, GNUNET_NO,
843                                                        NULL, NULL)) ||
844        (0 == strncmp (binary, "./", 2)) )
845     p = GNUNET_strdup (binary);
846   else
847   {
848     p = get_path_from_PATH (binary);
849     if (NULL != p)
850     {
851       GNUNET_asprintf (&pf, "%s/%s", p, binary);
852       GNUNET_free (p);
853       p = pf;
854     }
855   }
856 #endif
857   if (NULL == p)
858   {
859     LOG (GNUNET_ERROR_TYPE_INFO,
860          _("Could not find binary `%s' in PATH!\n"),
861          binary);
862     return GNUNET_SYSERR;
863   }
864   if (0 != ACCESS (p, X_OK))
865   {
866     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", p);
867     GNUNET_free (p);
868     return GNUNET_SYSERR;
869   }
870 #ifndef MINGW
871   if (0 == getuid ())
872   {
873     /* as we run as root, we don't insist on SUID */
874     GNUNET_free (p);
875     return GNUNET_OK;
876   }
877 #endif
878   if (0 != STAT (p, &statbuf))
879   {
880     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", p);
881     GNUNET_free (p);
882     return GNUNET_SYSERR;
883   }
884   if (check_suid){
885 #ifndef MINGW
886     if ((0 != (statbuf.st_mode & S_ISUID)) && (0 == statbuf.st_uid))
887     {
888       GNUNET_free (p);
889       return GNUNET_YES;
890     }
891     /* binary exists, but not SUID */
892 #else
893     STARTUPINFO start;
894     char parameters[512];
895     PROCESS_INFORMATION proc;
896     DWORD exit_value;
897
898     GNUNET_snprintf (parameters,
899                      sizeof (parameters),
900                      "-d %s", params);
901     memset (&start, 0, sizeof (start));
902     start.cb = sizeof (start);
903     memset (&proc, 0, sizeof (proc));
904
905
906     // Start the child process.
907     if ( ! (CreateProcess( p,   // current windows (2k3 and up can handle / instead of \ in paths))
908         parameters,           // execute dryrun/priviliege checking mode
909         NULL,           // Process handle not inheritable
910         NULL,           // Thread handle not inheritable
911         FALSE,          // Set handle inheritance to FALSE
912         CREATE_DEFAULT_ERROR_MODE, // No creation flags
913         NULL,           // Use parent's environment block
914         NULL,           // Use parent's starting directory
915         &start,            // Pointer to STARTUPINFO structure
916         &proc )           // Pointer to PROCESS_INFORMATION structure
917                                ))
918       {
919         LOG (GNUNET_ERROR_TYPE_ERROR,
920              _("CreateProcess failed for binary %s (%d).\n"),
921              p, GetLastError());
922         return GNUNET_SYSERR;
923     }
924
925     // Wait until child process exits.
926     WaitForSingleObject( proc.hProcess, INFINITE );
927
928     if ( ! GetExitCodeProcess (proc.hProcess, &exit_value)){
929         LOG (GNUNET_ERROR_TYPE_ERROR,
930              _("GetExitCodeProcess failed for binary %s (%d).\n"),
931              p, GetLastError() );
932         return GNUNET_SYSERR;
933       }
934     // Close process and thread handles.
935     CloseHandle( proc.hProcess );
936     CloseHandle( proc.hThread );
937
938     if (!exit_value)
939       return GNUNET_YES;
940 #endif
941     }
942   GNUNET_free (p);
943   return GNUNET_NO;
944 }
945
946
947 /* end of os_installation.c */