1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  */
8 #include "libi3.h"
9 
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 /*
16  * This function returns the absolute path to the executable it is running in.
17  *
18  * The implementation follows https://stackoverflow.com/a/933996/712014
19  *
20  * Returned value must be freed by the caller.
21  */
get_exe_path(const char * argv0)22 char *get_exe_path(const char *argv0) {
23     size_t destpath_size = 1024;
24     size_t tmp_size = 1024;
25     char *destpath = smalloc(destpath_size);
26     char *tmp = smalloc(tmp_size);
27 
28 #if defined(__linux__) || defined(__DragonFly__) \
29  || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
30 /* Linux and Debian/kFreeBSD provide /proc/self/exe */
31 #if defined(__linux__) || defined(__FreeBSD_kernel__)
32     const char *exepath = "/proc/self/exe";
33 #elif defined(__FreeBSD__) || defined(__DragonFly__)
34     const char *exepath = "/proc/curproc/file";
35 #endif
36     ssize_t linksize;
37 
38     while ((linksize = readlink(exepath, destpath, destpath_size)) == (ssize_t)destpath_size) {
39         destpath_size = destpath_size * 2;
40         destpath = srealloc(destpath, destpath_size);
41     }
42     if (linksize != -1) {
43         /* readlink() does not NULL-terminate strings, so we have to. */
44         destpath[linksize] = '\0';
45         free(tmp);
46         return destpath;
47     }
48 #endif
49 
50     /* argv[0] is most likely a full path if it starts with a slash. */
51     if (argv0[0] == '/') {
52         free(tmp);
53         free(destpath);
54         return sstrdup(argv0);
55     }
56 
57     /* if argv[0] contains a /, prepend the working directory */
58     if (strchr(argv0, '/') != NULL) {
59         char *retgcwd;
60         while ((retgcwd = getcwd(tmp, tmp_size)) == NULL && errno == ERANGE) {
61             tmp_size = tmp_size * 2;
62             tmp = srealloc(tmp, tmp_size);
63         }
64         if (retgcwd != NULL) {
65             free(destpath);
66             sasprintf(&destpath, "%s/%s", tmp, argv0);
67             free(tmp);
68             return destpath;
69         }
70     }
71 
72     /* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */
73     char *path = getenv("PATH");
74     if (path == NULL) {
75         /* _CS_PATH is typically something like "/bin:/usr/bin" */
76         while (confstr(_CS_PATH, tmp, tmp_size) > tmp_size) {
77             tmp_size = tmp_size * 2;
78             tmp = srealloc(tmp, tmp_size);
79         }
80         sasprintf(&path, ":%s", tmp);
81     } else {
82         path = sstrdup(path);
83     }
84     const char *component;
85     char *str = path;
86     while (1) {
87         if ((component = strtok(str, ":")) == NULL)
88             break;
89         str = NULL;
90         free(destpath);
91         sasprintf(&destpath, "%s/%s", component, argv0);
92         /* Of course this is not 100% equivalent to actually exec()ing the
93          * binary, but meh. */
94         if (access(destpath, X_OK) == 0) {
95             free(path);
96             free(tmp);
97             return destpath;
98         }
99     }
100     free(destpath);
101     free(path);
102     free(tmp);
103 
104     /* Last resort: maybe it’s in /usr/bin? */
105     return sstrdup("/usr/bin/i3-nagbar");
106 }
107