ef3eda2ff21d877ac354229e8e6dd43418c37523
[oweals/gnunet.git] / src / util / os_installation.c
1 /*
2      This file is part of GNUnet.
3      (C) 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 src/util/os_installation.c
23  * @brief get paths used by the program
24  * @author Milan
25  */
26 #include <sys/stat.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <unistr.h> /* for u16_to_u8 */
31
32 #include "platform.h"
33 #include "gnunet_common.h"
34 #include "gnunet_configuration_lib.h"
35 #include "gnunet_disk_lib.h"
36 #include "gnunet_os_lib.h"
37 #include "gnunet_strings_lib.h"
38 #if DARWIN
39 #include <mach-o/ldsyms.h>
40 #include <mach-o/dyld.h>
41 #endif
42
43 #define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__)
44
45 #define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util", syscall, filename)
46
47 #if LINUX
48 /**
49  * Try to determine path by reading /proc/PID/exe
50  *
51  * @return NULL on error
52  */
53 static char *
54 get_path_from_proc_maps ()
55 {
56   char fn[64];
57   char line[1024];
58   char dir[1024];
59   FILE *f;
60   char *lgu;
61
62   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/maps", getpid ());
63   if (NULL == (f = FOPEN (fn, "r")))
64     return NULL;
65   while (NULL != fgets (line, sizeof (line), f))
66   {
67     if ((1 ==
68          SSCANF (line, "%*x-%*x %*c%*c%*c%*c %*x %*2u:%*2u %*u%*[ ]%1023s", dir)) &&
69         (NULL != (lgu = strstr (dir, "libgnunetutil"))))
70     {
71       lgu[0] = '\0';
72       FCLOSE (f);
73       return GNUNET_strdup (dir);
74     }
75   }
76   FCLOSE (f);
77   return NULL;
78 }
79
80
81 /**
82  * Try to determine path by reading /proc/PID/exe
83  *
84  * @return NULL on error
85  */
86 static char *
87 get_path_from_proc_exe ()
88 {
89   char fn[64];
90   char lnk[1024];
91   ssize_t size;
92
93   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/exe", getpid ());
94   size = readlink (fn, lnk, sizeof (lnk) - 1);
95   if (size <= 0)
96   {
97     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
98     return NULL;
99   }
100   GNUNET_assert (size < sizeof (lnk));
101   lnk[size] = '\0';
102   while ((lnk[size] != '/') && (size > 0))
103     size--;
104   /* test for being in lib/gnunet/libexec/ */
105   if ( (size > strlen ("/gnunet/libexec/")) &&
106        (0 == strcmp ("/gnunet/libexec/",
107                      &lnk[size - strlen ("/gnunet/libexec/")])) )
108     size -= strlen ("gnunet/libexec/");
109   if ((size < 4) || (lnk[size - 4] != '/'))
110   {
111     /* not installed in "/bin/" -- binary path probably useless */
112     return NULL;
113   }
114   lnk[size] = '\0';
115   return GNUNET_strdup (lnk);
116 }
117 #endif
118
119 #if WINDOWS
120
121 static HINSTANCE dll_instance;
122
123
124 /* GNUNET_util_cl_init() in common_logging.c is preferred.
125  * This function is only for thread-local storage (not used in GNUnet)
126  * and hInstance saving.
127  */
128 BOOL WINAPI
129 DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
130 {
131   switch (fdwReason)
132   {
133     case DLL_PROCESS_ATTACH:
134       dll_instance = hinstDLL;
135       break;
136     case DLL_THREAD_ATTACH:
137       break;
138     case DLL_THREAD_DETACH:
139       break;
140     case DLL_PROCESS_DETACH:
141       break;
142   }
143   return TRUE;
144 }
145
146 /**
147  * Try to determine path with win32-specific function
148  *
149  * @return NULL on error
150  */
151 static char *
152 get_path_from_module_filename ()
153 {
154   size_t pathlen = 512;
155   DWORD real_pathlen;
156   wchar_t *idx;
157   wchar_t *modulepath = NULL;
158   char *upath;
159   uint8_t *u8_string;
160   size_t u8_string_length;
161
162   /* This braindead function won't tell us how much space it needs, so
163    * we start at 1024 and double the space up if it doesn't fit, until
164    * it fits, or we exceed the threshold.
165    */
166   do
167   {
168     pathlen = pathlen * 2;
169     modulepath = GNUNET_realloc (modulepath, pathlen * sizeof (wchar_t));
170     SetLastError (0);
171     real_pathlen = GetModuleFileNameW (dll_instance, modulepath, pathlen * sizeof (wchar_t));
172   } while (real_pathlen >= pathlen && pathlen < 16*1024);
173   if (real_pathlen >= pathlen)
174     GNUNET_abort ();
175   /* To be safe */
176   modulepath[real_pathlen] = '\0';
177
178   idx = modulepath + real_pathlen;
179   while ((idx > modulepath) && (*idx != L'\\') && (*idx != L'/'))
180     idx--;
181   *idx = L'\0';
182
183   /* Now modulepath holds full path to the directory where libgnunetutil is.
184    * This directory should look like <GNUNET_PREFIX>/bin or <GNUNET_PREFIX>.
185    */
186   if (wcschr (modulepath, L'/') || wcschr (modulepath, L'\\'))
187   {
188     /* At least one directory component (i.e. we're not in a root directory) */
189     wchar_t *dirname = idx;
190     while ((dirname > modulepath) && (*dirname != L'\\') && (*dirname != L'/'))
191       dirname--;
192     *dirname = L'\0';
193     if (dirname > modulepath)
194     {
195       dirname++;
196       /* Now modulepath holds full path to the parent directory of the directory
197        * where libgnunetutil is.
198        * dirname holds the name of the directory where libgnunetutil is.
199        */
200       if (wcsicmp (dirname, L"bin") == 0)
201       {
202         /* pass */
203       }
204       else
205       {
206         /* Roll back our changes to modulepath */
207         dirname--;
208         *dirname = L'/';
209       }
210     }
211   }
212
213   /* modulepath is GNUNET_PREFIX */
214   u8_string = u16_to_u8 (modulepath, wcslen (modulepath), NULL, &u8_string_length);
215   if (NULL == u8_string)
216     GNUNET_abort ();
217
218   upath = GNUNET_malloc (u8_string_length + 1);
219   memcpy (upath, u8_string, u8_string_length);
220   upath[u8_string_length] = '\0';
221
222   free (u8_string);
223   GNUNET_free (modulepath);
224
225   return upath;
226 }
227 #endif
228
229 #if DARWIN
230 /**
231  * Signature of the '_NSGetExecutablePath" function.
232  *
233  * @param buf where to write the path
234  * @param number of bytes available in 'buf'
235  * @return 0 on success, otherwise desired number of bytes is stored in 'bufsize'
236  */
237 typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t * bufsize);
238
239
240 /**
241  * Try to obtain the path of our executable using '_NSGetExecutablePath'.
242  *
243  * @return NULL on error
244  */
245 static char *
246 get_path_from_NSGetExecutablePath ()
247 {
248   static char zero = '\0';
249   char *path;
250   size_t len;
251   MyNSGetExecutablePathProto func;
252
253   path = NULL;
254   if (NULL == (func =
255                (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT, "_NSGetExecutablePath")))
256     return NULL;
257   path = &zero;
258   len = 0;
259   /* get the path len, including the trailing \0 */
260   (void) func (path, &len);
261   if (0 == len)
262     return NULL;
263   path = GNUNET_malloc (len);
264   if (0 != func (path, &len))
265   {
266     GNUNET_free (path);
267     return NULL;
268   }
269   len = strlen (path);
270   while ((path[len] != '/') && (len > 0))
271     len--;
272   path[len] = '\0';
273   return path;
274 }
275
276
277 /**
278  * Try to obtain the path of our executable using '_dyld_image' API.
279  *
280  * @return NULL on error
281  */
282 static char *
283 get_path_from_dyld_image ()
284 {
285   const char *path;
286   char *p;
287   char *s;
288   unsigned int i;
289   int c;
290
291   c = _dyld_image_count ();
292   for (i = 0; i < c; i++)
293   {
294     if (_dyld_get_image_header (i) != &_mh_dylib_header)
295       continue;
296     path = _dyld_get_image_name (i);
297     if ( (NULL == path) || (0 == strlen (path)) )
298       continue;
299     p = GNUNET_strdup (path);
300     s = p + strlen (p);
301     while ((s > p) && ('/' != *s))
302       s--;
303     s++;
304     *s = '\0';
305     return p;
306   }
307   return NULL;
308 }
309 #endif
310
311
312 /**
313  * Return the actual path to a file found in the current
314  * PATH environment variable.
315  *
316  * @param binary the name of the file to find
317  * @return path to binary, NULL if not found
318  */
319 static char *
320 get_path_from_PATH (const char *binary)
321 {
322   char *path;
323   char *pos;
324   char *end;
325   char *buf;
326   const char *p;
327
328   if (NULL == (p = getenv ("PATH")))
329     return NULL;
330 #if WINDOWS
331   /* On W32 look in CWD first. */
332   GNUNET_asprintf (&path, ".%c%s", PATH_SEPARATOR, p);
333 #else
334   path = GNUNET_strdup (p);     /* because we write on it */
335 #endif
336   buf = GNUNET_malloc (strlen (path) + strlen (binary) + 1 + 1);
337   pos = path;
338   while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
339   {
340     *end = '\0';
341     sprintf (buf, "%s/%s", pos, binary);
342     if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
343     {
344       pos = GNUNET_strdup (pos);
345       GNUNET_free (buf);
346       GNUNET_free (path);
347       return pos;
348     }
349     pos = end + 1;
350   }
351   sprintf (buf, "%s/%s", pos, binary);
352   if (GNUNET_YES == GNUNET_DISK_file_test (buf))
353   {
354     pos = GNUNET_strdup (pos);
355     GNUNET_free (buf);
356     GNUNET_free (path);
357     return pos;
358   }
359   GNUNET_free (buf);
360   GNUNET_free (path);
361   return NULL;
362 }
363
364
365 /**
366  * Try to obtain the installation path using the "GNUNET_PREFIX" environment
367  * variable.
368  *
369  * @return NULL on error (environment variable not set)
370  */
371 static char *
372 get_path_from_GNUNET_PREFIX ()
373 {
374   const char *p;
375
376   if (NULL != (p = getenv ("GNUNET_PREFIX")))
377     return GNUNET_strdup (p);
378   return NULL;
379 }
380
381
382 /**
383  * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
384  * @author Milan
385  *
386  * @return a pointer to the executable path, or NULL on error
387  */
388 static char *
389 os_get_gnunet_path ()
390 {
391   char *ret;
392
393   if (NULL != (ret = get_path_from_GNUNET_PREFIX ()))
394     return ret;
395 #if LINUX
396   if (NULL != (ret = get_path_from_proc_maps ()))
397     return ret;
398   if (NULL != (ret = get_path_from_proc_exe ()))
399     return ret;
400 #endif
401 #if WINDOWS
402   if (NULL != (ret = get_path_from_module_filename ()))
403     return ret;
404 #endif
405 #if DARWIN
406   if (NULL != (ret = get_path_from_dyld_image ()))
407     return ret;
408   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
409     return ret;
410 #endif
411   if (NULL != (ret = get_path_from_PATH ("gnunet-arm")))
412     return ret;
413   /* other attempts here */
414   LOG (GNUNET_ERROR_TYPE_ERROR,
415        _
416        ("Could not determine installation path for %s.  Set `%s' environment variable.\n"),
417        "GNUnet", "GNUNET_PREFIX");
418   return NULL;
419 }
420
421
422 /**
423  * @brief get the path to current app's bin/
424  * @author Milan
425  *
426  * @return a pointer to the executable path, or NULL on error
427  */
428 static char *
429 os_get_exec_path ()
430 {
431   char *ret;
432
433 #if LINUX
434   if (NULL != (ret = get_path_from_proc_exe ()))
435     return ret;
436 #endif
437 #if WINDOWS
438   if (NULL != (ret = get_path_from_module_filename ()))
439     return ret;
440 #endif
441 #if DARWIN
442   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
443     return ret;
444 #endif
445   /* other attempts here */
446   return NULL;
447 }
448
449
450 /**
451  * @brief get the path to a specific GNUnet installation directory or,
452  * with GNUNET_IPK_SELF_PREFIX, the current running apps installation directory
453  * @author Milan
454  * @return a pointer to the dir path (to be freed by the caller)
455  */
456 char *
457 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
458 {
459   size_t n;
460   const char *dirname;
461   char *execpath = NULL;
462   char *tmp;
463   int isbasedir;
464
465   /* if wanted, try to get the current app's bin/ */
466   if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
467     execpath = os_get_exec_path ();
468
469   /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
470    * guess for the current app */
471   if (NULL == execpath)
472     execpath = os_get_gnunet_path ();
473
474   if (NULL == execpath)
475     return NULL;
476
477   n = strlen (execpath);
478   if (0 == n)
479   {
480     /* should never happen, but better safe than sorry */
481     GNUNET_free (execpath);
482     return NULL;
483   }
484   /* remove filename itself */
485   while ((n > 1) && (DIR_SEPARATOR == execpath[n - 1]))
486     execpath[--n] = '\0';
487
488   isbasedir = 1;
489   if ((n > 5) &&
490       ((0 == strcasecmp (&execpath[n - 5], "lib32")) ||
491        (0 == strcasecmp (&execpath[n - 5], "lib64"))))
492   {
493     if (GNUNET_OS_IPK_LIBDIR != dirkind)
494     {
495       /* strip '/lib32' or '/lib64' */
496       execpath[n - 5] = '\0';
497       n -= 5;
498     }
499     else
500       isbasedir = 0;
501   }
502   else if ((n > 3) &&
503            ((0 == strcasecmp (&execpath[n - 3], "bin")) ||
504             (0 == strcasecmp (&execpath[n - 3], "lib"))))
505   {
506     /* strip '/bin' or '/lib' */
507     execpath[n - 3] = '\0';
508     n -= 3;
509   }
510   /* in case this was a directory named foo-bin, remove "foo-" */
511   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
512     execpath[--n] = '\0';
513   switch (dirkind)
514   {
515   case GNUNET_OS_IPK_PREFIX:
516   case GNUNET_OS_IPK_SELF_PREFIX:
517     dirname = DIR_SEPARATOR_STR;
518     break;
519   case GNUNET_OS_IPK_BINDIR:
520     dirname = DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR;
521     break;
522   case GNUNET_OS_IPK_LIBDIR:
523     if (isbasedir)
524       dirname =
525           DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR;
526     else
527       dirname = DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR;
528     break;
529   case GNUNET_OS_IPK_DATADIR:
530     dirname =
531         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR;
532     break;
533   case GNUNET_OS_IPK_LOCALEDIR:
534     dirname =
535         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale" DIR_SEPARATOR_STR;
536     break;
537   case GNUNET_OS_IPK_ICONDIR:
538     dirname =
539         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "icons" DIR_SEPARATOR_STR;
540     break;
541   case GNUNET_OS_IPK_DOCDIR:
542     dirname =
543         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "doc" DIR_SEPARATOR_STR \
544         "gnunet" DIR_SEPARATOR_STR;
545     break;
546   case GNUNET_OS_IPK_LIBEXECDIR:
547     dirname =
548         DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR \
549         "libexec" DIR_SEPARATOR_STR;
550     break;
551   default:
552     GNUNET_free (execpath);
553     return NULL;
554   }
555   tmp = GNUNET_malloc (strlen (execpath) + strlen (dirname) + 1);
556   sprintf (tmp, "%s%s", execpath, dirname);
557   GNUNET_free (execpath);
558   return tmp;
559 }
560
561
562 /**
563  * Given the name of a gnunet-helper, gnunet-service or gnunet-daemon
564  * binary, try to prefix it with the libexec/-directory to get the
565  * full path.
566  *
567  * @param progname name of the binary
568  * @return full path to the binary, if possible, otherwise copy of 'progname'
569  */
570 char *
571 GNUNET_OS_get_libexec_binary_path (const char *progname)
572 {
573   char *libexecdir;
574   char *binary;
575
576   libexecdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
577   if (NULL == libexecdir)
578     return GNUNET_strdup (progname);
579   GNUNET_asprintf (&binary,
580                    "%s%s",
581                    libexecdir,
582                    progname);
583   GNUNET_free (libexecdir);
584   return binary;
585 }
586
587
588 /**
589  * Check whether an executable exists and possibly
590  * if the suid bit is set on the file.
591  * Attempts to find the file using the current
592  * PATH environment variable as a search path.
593  *
594  * @param binary the name of the file to check.
595  *        W32: must not have an .exe suffix.
596  * @return GNUNET_YES if the file is SUID,
597  *         GNUNET_NO if not SUID (but binary exists)
598  *         GNUNET_SYSERR on error (no such binary or not executable)
599  */
600 int
601 GNUNET_OS_check_helper_binary (const char *binary)
602 {
603   struct stat statbuf;
604   char *p;
605   char *pf;
606 #ifdef MINGW
607   SOCKET rawsock;
608   char *binaryexe;
609
610   GNUNET_asprintf (&binaryexe, "%s.exe", binary);
611   if ( (GNUNET_YES == GNUNET_STRINGS_path_is_absolute (binaryexe, GNUNET_NO,
612                                                        NULL, NULL)) ||
613        (0 == strncmp (binary, "./", 2)) )
614     p = GNUNET_strdup (binaryexe);
615   else
616   {
617     p = get_path_from_PATH (binaryexe);
618     if (NULL != p)
619     {
620       GNUNET_asprintf (&pf, "%s/%s", p, binaryexe);
621       GNUNET_free (p);
622       p = pf;
623     }
624   }
625   GNUNET_free (binaryexe);
626 #else
627   if ( (GNUNET_YES == GNUNET_STRINGS_path_is_absolute (binary, GNUNET_NO,
628                                                        NULL, NULL)) ||
629        (0 == strncmp (binary, "./", 2)) )
630     p = GNUNET_strdup (binary);
631   else
632   {
633     p = get_path_from_PATH (binary);
634     if (NULL != p)
635     {
636       GNUNET_asprintf (&pf, "%s/%s", p, binary);
637       GNUNET_free (p);
638       p = pf;
639     }
640   }
641 #endif
642   if (NULL == p)
643   {
644     LOG (GNUNET_ERROR_TYPE_INFO, _("Could not find binary `%s' in PATH!\n"),
645          binary);
646     return GNUNET_SYSERR;
647   }
648   if (0 != ACCESS (p, X_OK))
649   {
650     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", p);
651     GNUNET_free (p);
652     return GNUNET_SYSERR;
653   }
654 #ifndef MINGW
655   if (0 == getuid ())
656   {
657     /* as we run as root, we don't insist on SUID */
658     GNUNET_free (p);
659     return GNUNET_OK;
660   }
661 #endif
662   if (0 != STAT (p, &statbuf))
663   {
664     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", p);
665     GNUNET_free (p);
666     return GNUNET_SYSERR;
667   }
668 #ifndef MINGW
669   if ((0 != (statbuf.st_mode & S_ISUID)) && (0 == statbuf.st_uid))
670   {
671     GNUNET_free (p);
672     return GNUNET_YES;
673   }
674   /* binary exists, but not SUID */
675   GNUNET_free (p);
676   return GNUNET_NO;
677 #else
678   GNUNET_free (p);
679   {
680     static int once; /* remember result from previous runs... */
681
682     if (0 == once)
683     {
684       rawsock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
685       if (INVALID_SOCKET == rawsock)
686         {
687           DWORD err = GetLastError ();
688           
689           LOG (GNUNET_ERROR_TYPE_DEBUG,
690                "socket (AF_INET, SOCK_RAW, IPPROTO_ICMP) failed! GLE = %d\n", err);
691           once = -1;
692           return GNUNET_NO;           /* not running as administrator */
693         }
694       once = 1;
695       closesocket (rawsock);
696     }
697     if (-1 == once)
698       return GNUNET_NO;
699     return GNUNET_YES;
700   }
701 #endif
702 }
703
704
705 /* end of os_installation.c */