]> git.sur5r.net Git - i3/i3status/blob - src/auto_detect_format.c
fix slurp(), it needs to read size-1 for the trailing NUL
[i3/i3status] / src / auto_detect_format.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  */
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <stdbool.h>
9 #include <stdint.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <fcntl.h>
13 #include <dirent.h>
14
15 #include "i3status.h"
16
17 /*
18  * This function tries to automatically find out where i3status is being piped
19  * to and choses the appropriate output format.
20  *
21  * It is a little hackish but should work for most setups :).
22  *
23  * By iterating through /proc/<number>/stat and finding out the parent process
24  * id (just like pstree(1) or ps(1) work), we can get all children of our
25  * parent. When the output of i3status is being piped somewhere, the shell
26  * (parent process) spawns i3status and the destination process, so we will
27  * find our own process and the pipe target.
28  *
29  * We then check whether the pipe target’s name is known and chose the format.
30  *
31  */
32 char *auto_detect_format(void) {
33     /* If stdout is a tty, we output directly to a terminal. */
34     if (isatty(STDOUT_FILENO)) {
35         return "term";
36     }
37
38     pid_t myppid = getppid();
39     pid_t mypid = getpid();
40
41     DIR *dir;
42     struct dirent *entry;
43     char path[255];
44     /* the relevant contents (for us) are:
45      * <pid> (<program name>) <status> <ppid>
46      * which should well fit into one page of 4096 bytes */
47     char buffer[4096];
48
49     char *format = NULL;
50
51     char *parentname = NULL;
52
53     if (!(dir = opendir("/proc")))
54         return NULL;
55
56     /* First pass: get the executable name of the parent.
57      * Upon error, we directly return NULL as we cannot continue without the
58      * name of our parent process. */
59     while ((entry = readdir(dir)) != NULL) {
60         pid_t pid = (pid_t)atoi(entry->d_name);
61         if (pid != myppid)
62             continue;
63
64         if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1 ||
65             !slurp(path, buffer, 4095))
66             goto out;
67
68         buffer[4095] = '\0';
69         char *leftbracket = strchr(buffer, '(');
70         char *rightbracket = strrchr(buffer, ')');
71         if (!leftbracket ||
72             !rightbracket ||
73             !(parentname = malloc((rightbracket - leftbracket))))
74             goto out;
75         *rightbracket = '\0';
76         strcpy(parentname, leftbracket + 1);
77     }
78
79     if (!parentname)
80         goto out;
81
82     /* Some shells, for example zsh, open a pipe in a way which will make the
83      * pipe target the parent process of i3status. If we detect that, we set
84      * the format and we are done. */
85     if (strcasecmp(parentname, "i3bar") == 0)
86         format = "i3bar";
87     else if (strcasecmp(parentname, "dzen2") == 0)
88         format = "dzen2";
89     else if (strcasecmp(parentname, "xmobar") == 0)
90         format = "xmobar";
91
92     if (format)
93         goto out;
94
95     rewinddir(dir);
96
97     while ((entry = readdir(dir)) != NULL) {
98         pid_t pid = (pid_t)atoi(entry->d_name);
99         if (pid == 0 || pid == mypid)
100             continue;
101
102         if (snprintf(path, sizeof(path), "/proc/%d/stat", pid) == -1)
103             continue;
104
105         char *name = NULL;
106         pid_t ppid;
107         int loopcnt = 0;
108         /* Now we need to find out the name of the process.
109          * To avoid the possible race condition of the process existing already
110          * but not executing the destination (shell after fork() and before
111          * exec()), we check if the name equals its parent.
112          *
113          * We try this for up to 0.5 seconds, then we give up.
114          */
115         do {
116             /* give the scheduler a chance between each iteration, don’t hog
117              * the CPU too much */
118             if (name)
119                 usleep(50);
120
121             if (!slurp(path, buffer, 4095))
122                 break;
123             buffer[4095] = '\0';
124             char *leftbracket = strchr(buffer, '(');
125             char *rightbracket = strrchr(buffer, ')');
126             if (!leftbracket ||
127                 !rightbracket ||
128                 sscanf(rightbracket + 2, "%*c %d", &ppid) != 1 ||
129                 ppid != myppid)
130                 break;
131             *rightbracket = '\0';
132             name = leftbracket + 1;
133         } while (strcmp(parentname, name) == 0 && loopcnt++ < 10000);
134
135         if (!name)
136             continue;
137
138         /* Check for known destination programs and set format */
139         char *newfmt = NULL;
140         if (strcasecmp(name, "i3bar") == 0)
141             newfmt = "i3bar";
142         else if (strcasecmp(name, "dzen2") == 0)
143             newfmt = "dzen2";
144         else if (strcasecmp(name, "xmobar") == 0)
145             newfmt = "xmobar";
146
147         if (newfmt && format && strcmp(newfmt, format) != 0) {
148             fprintf(stderr, "i3status: cannot auto-configure, situation ambiguous (format \"%s\" *and* \"%s\" detected)\n", newfmt, format);
149             format = NULL;
150             break;
151         } else {
152             format = newfmt;
153         }
154     }
155
156 out:
157     if (parentname)
158         free(parentname);
159
160     closedir(dir);
161
162     return format;
163 }