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