LRN: Fix automake deps to allow -j* builds again
[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
31 #include "platform.h"
32 #include "gnunet_common.h"
33 #include "gnunet_configuration_lib.h"
34 #include "gnunet_disk_lib.h"
35 #include "gnunet_os_lib.h"
36 #if DARWIN
37 #include <mach-o/ldsyms.h>
38 #include <mach-o/dyld.h>
39 #endif
40
41 #if LINUX
42 /**
43  * Try to determine path by reading /proc/PID/exe
44  */
45 static char *
46 get_path_from_proc_maps ()
47 {
48   char fn[64];
49   char line[1024];
50   char dir[1024];
51   FILE *f;
52   char *lgu;
53
54   GNUNET_snprintf (fn,
55                    sizeof(fn), 
56                    "/proc/%u/maps", 
57                    getpid ());
58   f = fopen (fn, "r");
59   if (f == NULL)
60     return NULL;
61   while (NULL != fgets (line, sizeof(line), f))
62     {
63       if ((1 == sscanf (line,
64                         "%*x-%*x %*c%*c%*c%*c %*x %*2u:%*2u %*u%*[ ]%s",
65                         dir)) &&
66           (NULL != (lgu = strstr (dir, "libgnunetutil"))))
67         {
68           lgu[0] = '\0';
69           fclose (f);
70           return GNUNET_strdup (dir);
71         }
72     }
73   fclose (f);
74   return NULL;
75 }
76
77 /**
78  * Try to determine path by reading /proc/PID/exe
79  */
80 static char *
81 get_path_from_proc_exe ()
82 {
83   char fn[64];
84   char lnk[1024];
85   ssize_t size;
86
87   GNUNET_snprintf (fn, 
88                    sizeof(fn), "/proc/%u/exe", getpid ());
89   size = readlink (fn, lnk, sizeof (lnk)-1);
90   if (size <= 0)
91     {
92       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
93       return NULL;
94     }
95   GNUNET_assert (size < sizeof (lnk));
96   lnk[size] = '\0';
97   while ((lnk[size] != '/') && (size > 0))
98     size--;
99   if ((size < 4) || (lnk[size - 4] != '/'))
100     {
101       /* not installed in "/bin/" -- binary path probably useless */
102       return NULL;
103     }
104   lnk[size] = '\0';
105   return GNUNET_strdup (lnk);
106 }
107 #endif
108
109 #if WINDOWS
110 /**
111  * Try to determine path with win32-specific function
112  */
113 static char *
114 get_path_from_module_filename ()
115 {
116   char path[4097];
117   char *idx;
118
119   GetModuleFileName (NULL, path, sizeof(path)-1);
120   idx = path + strlen (path);
121   while ((idx > path) && (*idx != '\\') && (*idx != '/'))
122     idx--;
123   *idx = '\0';
124   return GNUNET_strdup (path);
125 }
126 #endif
127
128 #if DARWIN
129 typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t * bufsize);
130
131 static char *
132 get_path_from_NSGetExecutablePath ()
133 {
134   static char zero = '\0';
135   char *path;
136   size_t len;
137   MyNSGetExecutablePathProto func;
138   int ret;
139
140   path = NULL;
141   func =
142     (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT, "_NSGetExecutablePath");
143   if (!func)
144     return NULL;
145   path = &zero;
146   len = 0;
147   /* get the path len, including the trailing \0 */
148   func (path, &len);
149   if (len == 0)
150     return NULL;
151   path = GNUNET_malloc (len);
152   ret = func (path, &len);
153   if (ret != 0)
154     {
155       GNUNET_free (path);
156       return NULL;
157     }
158   len = strlen (path);
159   while ((path[len] != '/') && (len > 0))
160     len--;
161   path[len] = '\0';
162   return path;
163 }
164
165 static char *
166 get_path_from_dyld_image ()
167 {
168   const char *path;
169   char *p, *s;
170   int i;
171   int c;
172
173   p = NULL;
174   c = _dyld_image_count ();
175   for (i = 0; i < c; i++)
176     {
177       if (_dyld_get_image_header (i) == &_mh_dylib_header)
178         {
179           path = _dyld_get_image_name (i);
180           if (path != NULL && strlen (path) > 0)
181             {
182               p = strdup (path);
183               s = p + strlen (p);
184               while ((s > p) && (*s != '/'))
185                 s--;
186               s++;
187               *s = '\0';
188             }
189           break;
190         }
191     }
192   return p;
193 }
194 #endif
195
196 /**
197  * Return the actual path to a file found in the current
198  * PATH environment variable.
199  *
200  * @param binary the name of the file to find
201  * @return path to binary, NULL if not found
202  */
203 static char *
204 get_path_from_PATH (const char *binary)
205 {
206   char *path;
207   char *pos;
208   char *end;
209   char *buf;
210   const char *p;
211
212   p = getenv ("PATH");
213   if (p == NULL)
214     return NULL;
215   path = GNUNET_strdup (p);     /* because we write on it */
216   buf = GNUNET_malloc (strlen (path) + 20);
217   pos = path;  
218   while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
219     {
220       *end = '\0';
221       sprintf (buf, "%s/%s", pos, binary);
222       if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
223         {
224           pos = GNUNET_strdup (pos);
225           GNUNET_free (buf);
226           GNUNET_free (path);
227           return pos;
228         }
229       pos = end + 1;
230     }
231   sprintf (buf, "%s/%s", pos, binary);
232   if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
233     {
234       pos = GNUNET_strdup (pos);
235       GNUNET_free (buf);
236       GNUNET_free (path);
237       return pos;
238     }
239   GNUNET_free (buf);
240   GNUNET_free (path);
241   return NULL;
242 }
243
244 static char *
245 get_path_from_GNUNET_PREFIX ()
246 {
247   const char *p;
248
249   p = getenv ("GNUNET_PREFIX");
250   if (p != NULL)
251     return GNUNET_strdup (p);
252   return NULL;
253 }
254
255 /*
256  * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
257  * @author Milan
258  *
259  * @return a pointer to the executable path, or NULL on error
260  */
261 static char *
262 os_get_gnunet_path ()
263 {
264   char *ret;
265
266   ret = get_path_from_GNUNET_PREFIX ();
267   if (ret != NULL)
268     return ret;
269 #if LINUX
270   ret = get_path_from_proc_maps ();
271   if (ret != NULL)
272     return ret;
273   ret = get_path_from_proc_exe ();
274   if (ret != NULL)
275     return ret;
276 #endif
277 #if WINDOWS
278   ret = get_path_from_module_filename ();
279   if (ret != NULL)
280     return ret;
281 #endif
282 #if DARWIN
283   ret = get_path_from_dyld_image ();
284   if (ret != NULL)
285     return ret;
286   ret = get_path_from_NSGetExecutablePath ();
287   if (ret != NULL)
288     return ret;
289 #endif
290   ret = get_path_from_PATH ("gnunet-arm");
291   if (ret != NULL)
292     return ret;
293   /* other attempts here */
294   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
295               _
296               ("Could not determine installation path for %s.  Set `%s' environment variable.\n"),
297               "GNUnet",
298               "GNUNET_PREFIX");
299   return NULL;
300 }
301
302 /*
303  * @brief get the path to current app's bin/
304  * @author Milan
305  *
306  * @return a pointer to the executable path, or NULL on error
307  */
308 static char *
309 os_get_exec_path ()
310 {
311   char *ret;
312
313   ret = NULL;
314 #if LINUX
315   ret = get_path_from_proc_exe ();
316   if (ret != NULL)
317     return ret;
318 #endif
319 #if WINDOWS
320   ret = get_path_from_module_filename ();
321   if (ret != NULL)
322     return ret;
323 #endif
324 #if DARWIN
325   ret = get_path_from_NSGetExecutablePath ();
326   if (ret != NULL)
327     return ret;
328 #endif
329   /* other attempts here */
330   return ret;
331 }
332
333
334
335 /**
336  * @brief get the path to a specific GNUnet installation directory or,
337  * with GNUNET_IPK_SELF_PREFIX, the current running apps installation directory
338  * @author Milan
339  * @return a pointer to the dir path (to be freed by the caller)
340  */
341 char *
342 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
343 {
344   size_t n;
345   const char *dirname;
346   char *execpath = NULL;
347   char *tmp;
348   int isbasedir;
349
350   /* if wanted, try to get the current app's bin/ */
351   if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
352     execpath = os_get_exec_path ();
353
354   /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
355    * guess for the current app */
356   if (execpath == NULL)
357     execpath = os_get_gnunet_path ();
358
359   if (execpath == NULL)
360     return NULL;
361
362   n = strlen (execpath);
363   if (n == 0)
364     {
365       /* should never happen, but better safe than sorry */
366       GNUNET_free (execpath);
367       return NULL;
368     }
369   /* remove filename itself */
370   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
371     execpath[--n] = '\0';
372
373   isbasedir = 1;
374   if ((n > 5) &&
375       ((0 == strcasecmp (&execpath[n - 5], "lib32")) ||
376        (0 == strcasecmp (&execpath[n - 5], "lib64"))))
377     {
378       if (dirkind != GNUNET_OS_IPK_LIBDIR)
379         {
380           /* strip '/lib32' or '/lib64' */
381           execpath[n - 5] = '\0';
382           n -= 5;
383         }
384       else
385         isbasedir = 0;
386     }
387   else if ((n > 3) &&
388            ((0 == strcasecmp (&execpath[n - 3], "bin")) ||
389             (0 == strcasecmp (&execpath[n - 3], "lib"))))
390     {
391       /* strip '/bin' or '/lib' */
392       execpath[n - 3] = '\0';
393       n -= 3;
394     }
395   /* in case this was a directory named foo-bin, remove "foo-" */
396   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
397     execpath[--n] = '\0';
398   switch (dirkind)
399     {
400     case GNUNET_OS_IPK_PREFIX:
401     case GNUNET_OS_IPK_SELF_PREFIX:
402       dirname = DIR_SEPARATOR_STR;
403       break;
404     case GNUNET_OS_IPK_BINDIR:
405       dirname = DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR;
406       break;
407     case GNUNET_OS_IPK_LIBDIR:
408       if (isbasedir)
409         dirname =
410           DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR "gnunet"
411           DIR_SEPARATOR_STR;
412       else
413         dirname = DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR;
414       break;
415     case GNUNET_OS_IPK_DATADIR:
416       dirname =
417         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "gnunet"
418         DIR_SEPARATOR_STR;
419       break;
420     case GNUNET_OS_IPK_LOCALEDIR:
421       dirname =
422         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale"
423         DIR_SEPARATOR_STR;
424       break;
425     case GNUNET_OS_IPK_ICONDIR:
426       dirname =
427         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "icons" DIR_SEPARATOR_STR;
428       break;
429     default:
430       GNUNET_free (execpath);
431       return NULL;
432     }
433   tmp = GNUNET_malloc (strlen (execpath) + strlen (dirname) + 1);
434   sprintf (tmp, "%s%s", execpath, dirname);
435   GNUNET_free (execpath);
436   return tmp;
437 }
438
439
440 /**
441  * Check whether an executable exists and possibly
442  * if the suid bit is set on the file.
443  * Attempts to find the file using the current
444  * PATH environment variable as a search path.
445  *
446  * @param binary the name of the file to check
447  * @return GNUNET_YES if the file is SUID, 
448  *         GNUNET_NO if not SUID (but binary exists)
449  *         GNUNET_SYSERR on error (no such binary or not executable)
450  */
451 int
452 GNUNET_OS_check_helper_binary (const char *binary)
453 {
454   struct stat statbuf;
455   char *p;
456   char *pf;
457 #ifdef MINGW
458   SOCKET rawsock;
459   char *binaryexe;
460
461   GNUNET_asprintf (&binaryexe, "%s.exe", binary);
462   p = get_path_from_PATH (binaryexe);
463   if (p != NULL)
464     {
465       GNUNET_asprintf (&pf, "%s/%s", p, binaryexe);
466       GNUNET_free (p);
467       p = pf;
468     }
469   free (binaryexe);
470 #else
471   p = get_path_from_PATH (binary);
472   if (p != NULL)
473     {
474       GNUNET_asprintf (&pf, "%s/%s", p, binary);
475       GNUNET_free (p);
476       p = pf;
477     }
478 #endif
479   if (p == NULL)
480     {
481       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
482                   _("Could not find binary `%s' in PATH!\n"),
483                   binary);
484       return GNUNET_SYSERR;
485     }
486   if (0 != STAT (p, &statbuf))
487     {
488       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
489                   _("stat (%s) failed: %s\n"), 
490                   p,
491                   STRERROR (errno));
492       GNUNET_free (p);
493       return GNUNET_SYSERR;
494     }
495 #ifndef MINGW
496   if ( (0 != (statbuf.st_mode & S_ISUID)) &&
497        (statbuf.st_uid == 0) )
498     {
499       GNUNET_free (p);
500       return GNUNET_YES;
501     }
502   if (0 == ACCESS (p, X_OK))
503     {
504       GNUNET_free (p);
505       return GNUNET_NO;
506     }
507   GNUNET_free (p);
508   return GNUNET_SYSERR;
509 #else
510   GNUNET_free (p);
511   rawsock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
512   if (INVALID_SOCKET == rawsock)
513     {
514       DWORD err = GetLastError ();
515       GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
516                   "socket (AF_INET, SOCK_RAW, IPPROTO_ICMP) failed! GLE = %d\n", err);
517       return GNUNET_NO; /* not running as administrator */
518     }
519   closesocket (rawsock);
520   return GNUNET_YES;
521 #endif
522 }
523
524
525 /* end of os_installation.c */