]> git.sur5r.net Git - i3/i3status/commitdiff
format detection: simplify code, handle "sh" processes in the hierarchy
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 9 Nov 2013 13:34:12 +0000 (14:34 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 9 Nov 2013 13:37:05 +0000 (14:37 +0100)
i3 starts processes using /bin/sh now, not $SHELL. This increases the
likelihood with which we are started by dash, which tends to leave its
processes in the hierarchy, e.g.:

michael       1524  i3bar --bar_id=bar-0 --socket=/run/user/1000/i3/ipc-s
michael       1525   \_ /bin/sh -c i3status
michael       1526       \_ i3status

This case is now handled correctly — when the parent is “sh”, the parent
of sh will be used instead.

src/auto_detect_format.c

index 524e2e9d9b40a0737a2c45a5cd5eaca92b12965f..5e17e172316ed7b298bec5e7c3307456b7972610 100644 (file)
 
 #include "i3status.h"
 
+/*
+ * Reads /proc/<pid>/stat and returns (via pointers) the name and parent pid of
+ * the specified pid.
+ * When false is returned, parsing failed and the contents of outname and
+ * outpid are undefined.
+ *
+ */
+static bool parse_proc_stat(pid_t pid, char **outname, pid_t *outppid) {
+    char path[255];
+    /* the relevant contents (for us) are:
+     * <pid> (<program name>) <status> <ppid>
+     * which should well fit into one page of 4096 bytes */
+    char buffer[4096];
+
+    if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 ||
+        !slurp(path, buffer, sizeof(buffer)))
+        return false;
+
+    char *leftbracket = strchr(buffer, '(');
+    char *rightbracket = strrchr(buffer, ')');
+    if (!leftbracket ||
+        !rightbracket ||
+        sscanf(rightbracket + 2, "%*c %d", outppid) != 1)
+        return false;
+    *rightbracket = '\0';
+    *outname = strdup(leftbracket + 1);
+    return true;
+}
+
+static char *format_for_process(const char *name) {
+    if (strcasecmp(name, "i3bar") == 0)
+        return "i3bar";
+    else if (strcasecmp(name, "dzen2") == 0)
+        return "dzen2";
+    else if (strcasecmp(name, "xmobar") == 0)
+        return "xmobar";
+    else
+        return NULL;
+}
+
 /*
  * This function tries to automatically find out where i3status is being piped
  * to and choses the appropriate output format.
@@ -40,68 +80,37 @@ char *auto_detect_format(void) {
 
     DIR *dir;
     struct dirent *entry;
-    char path[255];
-    /* the relevant contents (for us) are:
-     * <pid> (<program name>) <status> <ppid>
-     * which should well fit into one page of 4096 bytes */
-    char buffer[4096];
 
     char *format = NULL;
 
-    char *parentname = NULL;
+    char *parentname;
+    pid_t parentpid;
 
-    if (!(dir = opendir("/proc")))
+    if (!parse_proc_stat(myppid, &parentname, &parentpid))
         return NULL;
 
-    /* First pass: get the executable name of the parent.
-     * Upon error, we directly return NULL as we cannot continue without the
-     * name of our parent process. */
-    while ((entry = readdir(dir)) != NULL) {
-        pid_t pid = (pid_t)atoi(entry->d_name);
-        if (pid != myppid)
-            continue;
-
-        if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 ||
-            !slurp(path, buffer, 4095))
-            goto out;
-
-        buffer[4095] = '\0';
-        char *leftbracket = strchr(buffer, '(');
-        char *rightbracket = strrchr(buffer, ')');
-        if (!leftbracket ||
-            !rightbracket ||
-            !(parentname = malloc((rightbracket - leftbracket))))
-            goto out;
-        *rightbracket = '\0';
-        strcpy(parentname, leftbracket + 1);
+    if (strcmp(parentname, "sh") == 0) {
+        pid_t tmp_ppid = parentpid;
+        free(parentname);
+        fprintf(stderr, "i3status: auto-detection: parent process is \"sh\", looking at its parent\n");
+        if (!parse_proc_stat(tmp_ppid, &parentname, &parentpid))
+            return NULL;
     }
 
-    if (!parentname)
-        goto out;
-
     /* Some shells, for example zsh, open a pipe in a way which will make the
      * pipe target the parent process of i3status. If we detect that, we set
      * the format and we are done. */
-    if (strcasecmp(parentname, "i3bar") == 0)
-        format = "i3bar";
-    else if (strcasecmp(parentname, "dzen2") == 0)
-        format = "dzen2";
-    else if (strcasecmp(parentname, "xmobar") == 0)
-        format = "xmobar";
-
-    if (format)
+    if ((format = format_for_process(parentname)) != NULL)
         goto out;
 
-    rewinddir(dir);
+    if (!(dir = opendir("/proc")))
+        goto out;
 
     while ((entry = readdir(dir)) != NULL) {
         pid_t pid = (pid_t)atoi(entry->d_name);
         if (pid == 0 || pid == mypid)
             continue;
 
-        if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1)
-            continue;
-
         char *name = NULL;
         pid_t ppid;
         int loopcnt = 0;
@@ -115,34 +124,23 @@ char *auto_detect_format(void) {
         do {
             /* give the scheduler a chance between each iteration, don’t hog
              * the CPU too much */
-            if (name)
+            if (name) {
                 usleep(50);
+                free(name);
+            }
 
-            if (!slurp(path, buffer, 4095))
+            if (!parse_proc_stat(pid, &name, &ppid))
                 break;
-            buffer[4095] = '\0';
-            char *leftbracket = strchr(buffer, '(');
-            char *rightbracket = strrchr(buffer, ')');
-            if (!leftbracket ||
-                !rightbracket ||
-                sscanf(rightbracket + 2, "%*c %d", &ppid) != 1 ||
-                ppid != myppid)
+            if (ppid != myppid)
                 break;
-            *rightbracket = '\0';
-            name = leftbracket + 1;
         } while (strcmp(parentname, name) == 0 && loopcnt++ < 10000);
 
         if (!name)
             continue;
 
         /* Check for known destination programs and set format */
-        char *newfmt = NULL;
-        if (strcasecmp(name, "i3bar") == 0)
-            newfmt = "i3bar";
-        else if (strcasecmp(name, "dzen2") == 0)
-            newfmt = "dzen2";
-        else if (strcasecmp(name, "xmobar") == 0)
-            newfmt = "xmobar";
+        char *newfmt = format_for_process(name);
+        free(name);
 
         if (newfmt && format && strcmp(newfmt, format) != 0) {
             fprintf(stderr, "i3status: cannot auto-configure, situation ambiguous (format \"%s\" *and* \"%s\" detected)\n", newfmt, format);
@@ -153,11 +151,11 @@ char *auto_detect_format(void) {
         }
     }
 
+    closedir(dir);
+
 out:
     if (parentname)
         free(parentname);
 
-    closedir(dir);
-
     return format;
 }