diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 2243aa72..8f28de16 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -164,15 +164,18 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve /* Also closes fd */ fclose(script); + char *link_path; + sasprintf(&link_path, "%s.nagbar_cmd", script_path); + symlink(get_exe_path(argv0), link_path); + char *terminal_cmd; - sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0); + sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); printf("argv0 = %s\n", argv0); printf("terminal_cmd = %s\n", terminal_cmd); - setenv("_I3_NAGBAR_CMD", script_path, 1); start_application(terminal_cmd); - unsetenv("_I3_NAGBAR_CMD"); + free(link_path); free(terminal_cmd); free(script_path); @@ -275,23 +278,35 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { } int main(int argc, char *argv[]) { - /* The following lines are a horrible kludge. Because terminal emulators - * have different ways of interpreting the -e command line argument (some - * need -e "less /etc/fstab", others need -e less /etc/fstab), we need to - * write commands to a script and then just run that script. However, since - * on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec, - * we cannot directly execute the script either. + /* The following lines are a terribly horrible kludge. Because terminal + * emulators have different ways of interpreting the -e command line + * argument (some need -e "less /etc/fstab", others need -e less + * /etc/fstab), we need to write commands to a script and then just run + * that script. However, since on some machines, $XDG_RUNTIME_DIR and + * $TMPDIR are mounted with noexec, we cannot directly execute the script + * either. * - * Therefore, we run i3-nagbar instead and pass the path to the script in - * the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh - * with that path in order to run that script. + * Initially, we tried to pass the command via the environment variable + * _I3_NAGBAR_CMD. But turns out that some terminal emulators such as + * xfce4-terminal run all windows from a single master process and only + * pass on the command (not the environment) to that master process. + * + * Therefore, we symlink i3-nagbar (which MUST reside on an executable + * filesystem) with a special name and run that symlink. When i3-nagbar + * recognizes it’s started as a binary ending in .nagbar_cmd, it strips off + * the .nagbar_cmd suffix and runs /bin/sh on argv[0]. That way, we can run + * a shell script on a noexec filesystem. * * From a security point of view, i3-nagbar is just an alias to /bin/sh in * certain circumstances. This should not open any new security issues, I * hope. */ char *cmd = NULL; - if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) { - unsetenv("_I3_NAGBAR_CMD"); + const size_t argv0_len = strlen(argv[0]); + if (argv0_len > strlen(".nagbar_cmd") && + strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) { + unlink(argv[0]); + cmd = strdup(argv[0]); + *(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0'; execl("/bin/sh", "/bin/sh", cmd, NULL); err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd); } diff --git a/include/libi3.h b/include/libi3.h index 53f3383d..b0141f1d 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -364,4 +364,12 @@ bool is_debug_build() __attribute__((const)); */ char *get_process_filename(const char *prefix); +/** + * This function returns the absolute path to the executable it is running in. + * + * The implementation follows http://stackoverflow.com/a/933996/712014 + * + */ +const char *get_exe_path(const char *argv0); + #endif diff --git a/libi3/get_exe_path.c b/libi3/get_exe_path.c new file mode 100644 index 00000000..8176aa76 --- /dev/null +++ b/libi3/get_exe_path.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "libi3.h" + +/* + * This function returns the absolute path to the executable it is running in. + * + * The implementation follows http://stackoverflow.com/a/933996/712014 + * + */ +const char *get_exe_path(const char *argv0) { + static char destpath[PATH_MAX]; + char tmp[PATH_MAX]; + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + /* Linux and Debian/kFreeBSD provide /proc/self/exe */ +#if defined(__linux__) || defined(__FreeBSD_kernel__) + const char *exepath = "/proc/self/exe"; +#elif defined(__FreeBSD__) + const char *exepath = "/proc/curproc/file"; +#endif + ssize_t linksize; + + if ((linksize = readlink(exepath, destpath, sizeof(destpath))) != -1) { + /* readlink() does not NULL-terminate strings, so we have to. */ + destpath[linksize] = '\0'; + + return destpath; + } +#endif + + /* argv[0] is most likely a full path if it starts with a slash. */ + if (argv0[0] == '/') + return argv0; + + /* if argv[0] contains a /, prepend the working directory */ + if (strchr(argv0, '/') != NULL && + getcwd(tmp, sizeof(tmp)) != NULL) { + snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0); + return destpath; + } + + /* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */ + char *path = getenv("PATH"); + size_t pathlen; + if (path == NULL) { + /* _CS_PATH is typically something like "/bin:/usr/bin" */ + pathlen = confstr(_CS_PATH, tmp, sizeof(tmp)); + sasprintf(&path, ":%s", tmp); + } else { + pathlen = strlen(path); + path = strdup(path); + } + const char *component; + char *str = path; + while (1) { + if ((component = strtok(str, ":")) == NULL) + break; + str = NULL; + snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0); + /* Of course this is not 100% equivalent to actually exec()ing the + * binary, but meh. */ + if (access(destpath, X_OK) == 0) { + free(path); + return destpath; + } + } + free(path); + + /* Last resort: maybe it’s in /usr/bin? */ + return "/usr/bin/i3bar"; +}