]> git.sur5r.net Git - i3/i3/blob - i3bar/src/child.c
Merge branch 'next'
[i3/i3] / i3bar / src / child.c
1 /*
2  * i3bar - an xcb-based status- and ws-bar for i3
3  *
4  * © 2010 Axel Wagner and contributors
5  *
6  * See file LICNSE for license information
7  *
8  * src/child.c: Getting Input for the statusline
9  *
10  */
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <sys/types.h>
14 #include <signal.h>
15 #include <sys/wait.h>
16 #include <stdio.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <ev.h>
21
22 #include "common.h"
23
24 /* Global variables for child_*() */
25 pid_t child_pid;
26
27 /* stdin- and sigchild-watchers */
28 ev_io    *stdin_io;
29 ev_child *child_sig;
30
31 /*
32  * Stop and free() the stdin- and sigchild-watchers
33  *
34  */
35 void cleanup() {
36     ev_io_stop(main_loop, stdin_io);
37     ev_child_stop(main_loop, child_sig);
38     FREE(stdin_io);
39     FREE(child_sig);
40     FREE(statusline);
41 }
42
43 /*
44  * Callbalk for stdin. We read a line from stdin and store the result
45  * in statusline
46  *
47  */
48 void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
49     int fd = watcher->fd;
50     int n = 0;
51     int rec = 0;
52     int buffer_len = STDIN_CHUNK_SIZE;
53     char *buffer = malloc(buffer_len);
54     memset(buffer, '\0', buffer_len);
55     while(1) {
56         n = read(fd, buffer + rec, buffer_len - rec);
57         if (n == -1) {
58             if (errno == EAGAIN) {
59                 /* remove trailing newline and finish up */
60                 buffer[rec-1] = '\0';
61                 break;
62             }
63             ELOG("read() failed!\n");
64             exit(EXIT_FAILURE);
65         }
66         if (n == 0) {
67             if (rec == buffer_len) {
68                 buffer_len += STDIN_CHUNK_SIZE;
69                 buffer = realloc(buffer, buffer_len);
70             } else {
71                 /* remove trailing newline and finish up */
72                 buffer[rec-1] = '\0';
73                 break;
74             }
75         }
76         rec += n;
77     }
78     if (strlen(buffer) == 0) {
79         FREE(buffer);
80         return;
81     }
82     FREE(statusline);
83     statusline = buffer;
84     DLOG("%s\n", buffer);
85     draw_bars();
86 }
87
88 /*
89  * We received a sigchild, meaning, that the child-process terminated.
90  * We simply free the respective data-structures and don't care for input
91  * anymore
92  *
93  */
94 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
95     DLOG("Child (pid: %d) unexpectedly exited with status %d\n",
96            child_pid,
97            watcher->rstatus);
98     cleanup();
99 }
100
101 /*
102  * Start a child-process with the specified command and reroute stdin.
103  * We actually start a $SHELL to execute the command so we don't have to care
104  * about arguments and such.
105  * We also double-fork() to avoid zombies and pass the pid of the child through a
106  * temporary pipe back to i3bar
107  *
108  */
109 void start_child(char *command) {
110     child_pid = 0;
111     if (command != NULL) {
112         int fd[2], tmp[2];
113         /* This pipe will be used to communicate between e.g. i3status and i3bar */
114         pipe(fd);
115         /* We also need this temporary pipe to get back the pid of i3status */
116         pipe(tmp);
117         switch (fork()) {
118             case -1:
119                 ELOG("Couldn't fork()\n");
120                 exit(EXIT_FAILURE);
121             case 0:
122                 /* Double-fork(), so the child gets reparented to init */
123                 switch(child_pid = fork()) {
124                     case -1:
125                         ELOG("Couldn't fork() twice\n");
126                         exit(EXIT_FAILURE);
127                     case 0:
128                         /* Child-process. Reroute stdout and start shell */
129                         close(fd[0]);
130
131                         dup2(fd[1], STDOUT_FILENO);
132
133                         static const char *shell = NULL;
134
135                         if ((shell = getenv("SHELL")) == NULL)
136                             shell = "/bin/sh";
137
138                         execl(shell, shell, "-c", command, (char*) NULL);
139                         return;
140                     default:
141                         /* Temporary parent. We tell i3bar about the pid of i3status and exit */
142                         write(tmp[1], &child_pid, sizeof(int));
143                         close(tmp[0]);
144                         close(tmp[1]);
145                         exit(EXIT_SUCCESS);
146                     }
147             default:
148                 /* Parent-process. Rerout stdin */
149                 close(fd[1]);
150
151                 dup2(fd[0], STDIN_FILENO);
152
153                 /* We also need to get the pid of i3status from the temporary pipe */
154                 size_t rec = 0;
155                 while (rec < sizeof(int)) {
156                     rec += read(tmp[0], &child_pid, sizeof(int) - rec);
157                 }
158                 /* The temporary pipe is no longer needed */
159                 close(tmp[0]);
160                 close(tmp[1]);
161                 break;
162         }
163     }
164     wait(0);
165
166     /* We set O_NONBLOCK because blocking is evil in event-driven software */
167     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
168
169     stdin_io = malloc(sizeof(ev_io));
170     ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
171     ev_io_start(main_loop, stdin_io);
172
173     /* We must cleanup, if the child unexpectedly terminates */
174     child_sig = malloc(sizeof(ev_child));
175     ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
176     ev_child_start(main_loop, child_sig);
177
178 }
179
180 /*
181  * kill()s the child-process (if existent) and closes and
182  * free()s the stdin- and sigchild-watchers
183  *
184  */
185 void kill_child() {
186     if (child_pid != 0) {
187         kill(child_pid, SIGTERM);
188     }
189     cleanup();
190 }
191
192 /*
193  * Sends a SIGSTOP to the child-process (if existent)
194  *
195  */
196 void stop_child() {
197     if (child_pid != 0) {
198         kill(child_pid, SIGSTOP);
199     }
200 }
201
202 /*
203  * Sends a SIGCONT to the child-process (if existent)
204  *
205  */
206 void cont_child() {
207     if (child_pid != 0) {
208         kill(child_pid, SIGCONT);
209     }
210 }