1 /** \file   archdep_program_path.c
2  * \brief   Retrieve path of currently running binary
3  * \author  Bas Wassink <b.wassink@ziggo.nl>
4  *
5  * Get path to running executable.
6  *
7  * OS support:
8  *  - Linux
9  *  - Windows
10  *  - MacOS
11  *  - BeOS/Haiku (untested)
12  */
13 
14 /*
15  * This file is part of VICE, the Versatile Commodore Emulator.
16  * See README for copyright notice.
17  *
18  *  This program is free software; you can redistribute it and/or modify
19  *  it under the terms of the GNU General Public License as published by
20  *  the Free Software Foundation; either version 2 of the License, or
21  *  (at your option) any later version.
22  *
23  *  This program is distributed in the hope that it will be useful,
24  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
25  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  *  GNU General Public License for more details.
27  *
28  *  You should have received a copy of the GNU General Public License
29  *  along with this program; if not, write to the Free Software
30  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
31  *  02111-1307  USA.
32  *
33  */
34 
35 #include "vice.h"
36 #include "archdep_defs.h"
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <errno.h>
42 
43 #include "lib.h"
44 #include "log.h"
45 
46 /* for readlink(2) */
47 #if defined(ARCHDEP_OS_UNIX) || defined(ARCHDEP_OS_BEOS)
48 # include <unistd.h>
49 # if defined(ARCHDEP_OS_BSD_FREE) || defined(ARCHDEP_OS_BSD_DRAGON)
50 #  include <sys/sysctl.h>
51 # endif
52 # ifdef ARCHDEP_OS_MACOS
53 #  include <libproc.h>
54 # endif
55 #endif
56 
57 /* for GetModuleFileName() */
58 #ifdef ARCHDEP_OS_WINDOWS
59 # include "windows.h"
60 # include <direct.h>
61 #endif
62 
63 #include "archdep_exit.h"
64 #include "archdep_join_paths.h"
65 #include "archdep_path_is_relative.h"
66 
67 #include "archdep_program_path.h"
68 
69 
70 /** \brief  Size of the buffer used to retrieve the path
71  *
72  */
73 #define PATH_BUFSIZE    4096
74 
75 
76 /** \brief  Reference to program path string
77  *
78  * Should be freed on emulator exit with archdep_program_path_free()
79  */
80 static char *program_path = NULL;
81 
82 
83 /** \brief  Reference to argv[0]
84  *
85  * Do not free this, the C standard guarantees argv is available during a
86  * program's lifetime, so this will work.
87  */
88 static char *argv0_ref = NULL;
89 
90 
91 /** \brief  Buffer used to retrieve pathnames
92  *
93  * Various OS calls use this buffer to store the path to the running binary, if
94  * such a call exists. The buffer should be large enough (I think Linux
95  * defines PATH_MAX as 4096 by default, but that can be changed).
96  */
97 static char buffer[PATH_BUFSIZE];
98 
99 
100 /** \brief  Set reference to argv[0]
101  *
102  * \param[in]    argv0   argv[0]
103  */
archdep_program_path_set_argv0(char * argv0)104 void archdep_program_path_set_argv0(char *argv0)
105 {
106     argv0_ref = argv0;
107 }
108 
109 
110 /** \brief  Fall back: try to get absolute path to exec via argv[0]
111  *
112  * This is unreliable and should only be used as a last resort.
113  *
114  * \return  bool (if this fails, we have to give up)
115  */
argv_fallback(void)116 static int argv_fallback(void)
117 {
118     char cwd_buf[4096];
119     char *result;
120     size_t res_len;
121 
122     if (argv0_ref == NULL) {
123         log_error(LOG_ERR, "argv[0] is NULL, giving up.");
124         return 0;
125     }
126     if (*argv0_ref == '\0') {
127         log_error(LOG_ERR, "argv[0] is empty, giving up.");
128         return 0;
129     }
130 
131     /* do we have an absolute path in argv[0]? */
132     if (!archdep_path_is_relative(argv0_ref)) {
133         strcpy(buffer, argv0_ref);
134         return 1;
135     }
136 
137     /*
138      * Relative path in argv[0], try to get cwd and join it with argv[0]
139      */
140     memset(cwd_buf, 0, 4096);
141 
142 #if defined(ARCHDEP_OS_UNIX) || defined(ARCHDEP_OS_BEOS)
143     if (getcwd(cwd_buf, 4096 - 1) == NULL) {
144         log_error(LOG_ERR, "failed to get cwd, giving up.");
145         return 0;
146     }
147 #elif defined(ARCHDEP_OS_WINDOWS)
148     if (_getcwd(cwd_buf, 4096 -1) == NULL) {
149         log_error(LOG_ERR, "failed to get cwd, giving up.");
150         return 0;
151     }
152 #else
153     log_error(LOG_ERR,"no getcwd() support for current OS, giving up.");
154     return 0;
155 #endif
156 
157     result = archdep_join_paths(cwd_buf, argv0_ref, NULL);
158     res_len = strlen(result);
159     if (res_len >= 4096) {
160         /* insufficient space */
161         log_error(LOG_ERR, "insufficient space for path, giving up.");
162         lib_free(result);
163         return 0;
164     }
165     memcpy(buffer, result, res_len + 1);
166     lib_free(result);
167     return 1;
168 }
169 
170 
171 
172 /** \brief  Get absolute path to the running executable
173  *
174  * Free with archdep_program_path_free() on emulator exit.
175  *
176  * \return  absolute path to running executable
177  */
archdep_program_path(void)178 const char *archdep_program_path(void)
179 {
180     if (program_path != NULL) {
181         /* already got it, return */
182         return program_path;
183     }
184 
185     /* zero out the buffer since readlink(2) doesn't add a nul character */
186     memset(buffer, 0, PATH_BUFSIZE);
187 
188 
189 #if defined(ARCHDEP_OS_WINDOWS)
190 
191     if (GetModuleFileName(NULL, buffer, PATH_BUFSIZE - 1) == PATH_BUFSIZE - 1) {
192         log_error(LOG_ERR,
193                 "failed to retrieve executable path, falling back"
194                 " to getcwd() + argv[0]");
195         if (!argv_fallback()) {
196             archdep_vice_exit(1);
197         }
198     }
199 
200 #elif defined(ARCHDEP_OS_UNIX)
201 
202     /* XXX: Only works on Linux, OSX and FreeBSD/NetBSD, anyone wanting support
203      *      for OpenBSD or DragonflyBSD will have to add it.
204      *
205      *      Linux:      readlink(/proc/self/exe)
206      *      MacOS:      _NSGetExecutablePath()
207      *      FreeBSD:    sysctl CTL_KERN_PROC KERN_PROC_PATHNAME - 1 (???)
208      *      NetBSD:     readlink(/proc/curproc/exe)
209      *      DFlyBSD:    ??? (errors out during build)
210      *      OpenBSD:    ??? (using argv[0] fallback)
211      */
212 
213 # ifdef ARCHDEP_OS_MACOS
214 
215     /* get path via libproc */
216     pid_t pid = getpid();
217     if (proc_pidpath(pid, buffer, PATH_BUFSIZE - 1) <= 0) {
218         log_error(LOG_ERR,
219                 "failed to retrieve executable path, falling back"
220                 " to getcwd() + argv[0]");
221         if (!argv_fallback()) {
222             archdep_vice_exit(1);
223         }
224     }
225 
226 # elif defined(ARCHDEP_OS_LINUX)
227 
228     /* Linux as a fallback (has it really come to this?) */
229     if (readlink("/proc/self/exe", buffer, PATH_BUFSIZE - 1) < 0) {
230         log_error(LOG_ERR,
231                 "failed to retrieve executable path, falling back"
232                 " to getcwd() + argv[0]");
233         if (!argv_fallback()) {
234             archdep_vice_exit(1);
235         }
236     }
237 
238     /* BSD's */
239 # elif defined(ARCHDEP_OS_BSD)
240 #  if defined(ARCHDEP_OS_BSD_FREE) || defined(ARCHDEP_OS_BSD_DRAGON)
241 
242     int mib[4];
243     size_t bufsize = PATH_BUFSIZE;
244 
245     /* /proc may not be available on FreeBSD */
246     if (readlink("/proc/curproc/file", buffer, PATH_BUFSIZE - 1) < 0) {
247         printf("%s(): failed to read /proc/curproc/file: %d: %s\n",
248                 __func__, errno, strerror(errno));
249         /* try sysctl call */
250         mib[0] = CTL_KERN;
251         mib[1] = KERN_PROC;
252         mib[2] = KERN_PROC_PATHNAME;
253         mib[3] = -1;
254 
255         if (sysctl(mib, 4, buffer, &bufsize, NULL, 0) < 0) {
256             log_error(LOG_ERR,
257                     "failed to retrieve executable path, falling back"
258                     " to getcwd() + argv[0]");
259             if (!argv_fallback()) {
260                 archdep_vice_exit(1);
261             }
262         }
263 #if 0
264         printf("SYSCTL: %s\n", buffer);
265 #endif
266     }
267 
268 #  elif defined(ARCHDEP_OS_BSD_NET)
269 
270     if (readlink("/proc/curproc/exe", buffer, PATH_BUFSIZE - 1) < 0) {
271         log_error(LOG_ERR,
272                 "failed to retrieve executable path, falling back"
273                 " to getcwd() + argv[0]");
274         if (!argv_fallback()) {
275             archdep_vice_exit(1);
276         }
277     }
278 
279 #  elif defined(ARCHDEP_OS_BSD_OPEN)
280     /*
281      * I couldn't find any non-argv[0] solution for OpenBSD, so this will have
282      * to do. --compyx
283      */
284     if (!argv_fallback()) {
285         archdep_vice_exit(1);
286     }
287 
288 #  endif    /* end BSD's */
289 
290 # endif /* end UNIX */
291 #else
292 
293     /*
294      * Other systems (BeOS etc)
295      */
296     if (!argv_fallback()) {
297         archdep_vice_exit(1);
298     }
299 
300 #endif
301     program_path = lib_strdup(buffer);
302 #if 0
303     printf("%s(): program_path = %s\n", __func__, program_path);
304 #endif
305     return program_path;
306 }
307 
308 
309 /** \brief  Free memory used by path to running executable
310  *
311  * Call from program exit
312  */
archdep_program_path_free(void)313 void archdep_program_path_free(void)
314 {
315     if (program_path != NULL) {
316         lib_free(program_path);
317         program_path = NULL;
318     }
319 }
320