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