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