]> git.sur5r.net Git - i3/i3/commitdiff
i3-nagbar: take our terminal execution kludge to the next level
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 10 Jun 2013 20:55:39 +0000 (22:55 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 10 Jun 2013 20:55:39 +0000 (22:55 +0200)
Please read commit 2bf80528bdb2814331f9149289a0dd1e3422282b first.
Then read the comment within the code of this commit.
Then run in circles and cry loudly.

fixes #1002
fixes #1026

i3-nagbar/main.c
include/libi3.h
libi3/get_exe_path.c [new file with mode: 0644]

index 2243aa7265b9c869d3ce64b640a2f67f96ce8595..8f28de16a815072efd590ac896b8b3ccd9798cf6 100644 (file)
@@ -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.
+     *
+     * 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 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.
+     * 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);
     }
index 53f3383d35390671e0f2621e23c57150e1d1fc0a..b0141f1df9cc22c8eae3e6abf4b2961967ce0330 100644 (file)
@@ -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 (file)
index 0000000..8176aa7
--- /dev/null
@@ -0,0 +1,76 @@
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#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";
+}