A tiling window manager
1/*
2 * communications.c -- Send commands to a running copy of sdorfehs.
3 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA.
18 */
19
20#include <X11/X.h>
21#include <X11/Xlib.h>
22#include <X11/Xutil.h>
23#include <X11/Xatom.h>
24#include <X11/Xproto.h>
25
26#include <sys/socket.h>
27#include <sys/un.h>
28#include <fcntl.h>
29#include <string.h>
30#include <unistd.h>
31#include <err.h>
32#include <errno.h>
33#include <stdlib.h>
34
35#include "sdorfehs.h"
36
37#define BUFSZ 1024
38
39void
40init_control_socket_path(void)
41{
42 char *config_dir;
43
44 config_dir = get_config_dir();
45 rp_glob_screen.control_socket_path = xsprintf("%s/control", config_dir);
46 free(config_dir);
47}
48
49void
50listen_for_commands(void)
51{
52 struct sockaddr_un sun;
53
54 if ((rp_glob_screen.control_socket_fd = socket(AF_UNIX,
55 SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) == -1)
56 err(1, "socket");
57
58 if (strlen(rp_glob_screen.control_socket_path) >= sizeof(sun.sun_path))
59 err(1, "control socket path too long: %s",
60 rp_glob_screen.control_socket_path);
61
62 strncpy(sun.sun_path, rp_glob_screen.control_socket_path,
63 sizeof(sun.sun_path)-1);
64 sun.sun_path[sizeof(sun.sun_path) - 1] = '\0';
65 sun.sun_family = AF_UNIX;
66
67 if (unlink(rp_glob_screen.control_socket_path) == -1 &&
68 errno != ENOENT)
69 err(1, "unlink %s",rp_glob_screen.control_socket_path);
70
71 if (bind(rp_glob_screen.control_socket_fd, (struct sockaddr *)&sun,
72 sizeof(sun)) == -1)
73 err(1, "bind %s", rp_glob_screen.control_socket_path);
74
75 if (chmod(rp_glob_screen.control_socket_path, 0600) == -1)
76 err(1, "chmod %s", rp_glob_screen.control_socket_path);
77
78 if (listen(rp_glob_screen.control_socket_fd, 2) == -1)
79 err(1, "listen %s", rp_glob_screen.control_socket_path);
80
81 PRINT_DEBUG(("listening for commands at %s\n",
82 rp_glob_screen.control_socket_path));
83}
84
85static ssize_t
86recv_unix(int fd, char **callerbuf)
87{
88 int firstloop;
89 char *message;
90 ssize_t len, count;
91
92 int flags = 0x0;
93 int retries = 0;
94
95#ifdef SENDCMD_DEBUG
96 pid_t pid = getpid();
97 char *dpfx = xsprintf("recv_unix_%d", pid);
98#endif
99 WARNX_DEBUG("%s: enter\n", dpfx);
100
101 message = xmalloc(BUFSZ);
102 memset(message, 0, BUFSZ);
103
104 len = 0;
105 firstloop = 1;
106
107 while ((count = recv(fd, message + len, BUFSZ, flags))) {
108 if (count == -1) {
109 switch (errno) {
110 /*
111 * message is complete
112 */
113 case ECONNRESET: /* sender finished and closed */
114 case EAGAIN: /* no more left to read */
115 WARNX_DEBUG("%s: done: e%d\n", dpfx, errno);
116 break;
117 /*
118 * transient conditions for retrying
119 */
120 case ECONNREFUSED:
121 case ENOMEM:
122 if (retries++ >= 5) {
123 warn("recv_unix: retries exhausted");
124 len = -1;
125 break;
126 }
127 usleep(200);
128 /* fallthrough */
129 case EINTR:
130 warn("recv_unix: trying again");
131 continue;
132 /*
133 * usage error or untenable situation:
134 * EBADF, EFAULT, EINVAL, ENOTCONN, ENOTSOCK
135 */
136 default:
137 warn("unanticipated receive error");
138 len = -1;
139 }
140 break;
141 }
142 if (firstloop) {
143 WARNX_DEBUG("%s: first recv: %zd\n", dpfx, count);
144 /*
145 * after blocking for the first buffer, we can
146 * keep reading until it blocks again, which
147 * should exhaust the message.
148 */
149 flags += MSG_DONTWAIT;
150 }
151 len += count;
152 message = xrealloc(message, len + BUFSZ);
153 memset(message + len, 0, BUFSZ);
154 WARNX_DEBUG("%s: looping after count %zd\n", dpfx, count);
155 firstloop = 0;
156 }
157#ifdef SENDCMD_DEBUG
158 free(dpfx);
159#endif
160 *callerbuf = message;
161 return len;
162}
163
164static ssize_t
165send_unix(int fd, char *buf, ssize_t sz)
166{
167 ssize_t ret, off = 0;
168
169 WARNX_DEBUG("entered send_unix with sz %zd\n", sz);
170
171 while (sz > 0) {
172 if (((ret = write(fd, buf + off, sz)) != sz) && ret == -1) {
173 if (errno == EINTR)
174 continue;
175 warn("send_unix: bad write");
176 break;
177 }
178 sz -= ret;
179 off += ret;
180 }
181
182 WARNX_DEBUG("leaving send_unix, off %zd, errno %d\n", off, errno);
183 return off;
184}
185
186int
187send_command(int interactive, char *cmd)
188{
189 struct sockaddr_un sun;
190 char *wcmd, *response;
191 char success = 0;
192 ssize_t len;
193 int fd;
194 FILE *outf = NULL;
195
196#ifdef SENDCMD_DEBUG
197 pid_t pid = getpid();
198 char *dpfx = xsprintf("send_command_%d", pid);
199#endif
200 WARNX_DEBUG("%s: enter\n", dpfx);
201
202 len = 1 + strlen(cmd) + 1;
203 wcmd = xmalloc(len);
204 *wcmd = (unsigned char)interactive;
205 strncpy(wcmd + 1, cmd, len - 1);
206
207 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
208 err(1, "socket");
209
210 if (strlen(rp_glob_screen.control_socket_path) >= sizeof(sun.sun_path))
211 err(1, "control socket path too long: %s",
212 rp_glob_screen.control_socket_path);
213
214 strncpy(sun.sun_path, rp_glob_screen.control_socket_path,
215 sizeof(sun.sun_path)-1);
216 sun.sun_path[sizeof(sun.sun_path) - 1] = '\0';
217 sun.sun_family = AF_UNIX;
218
219 if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
220 err(1, "failed to connect to control socket at %s",
221 rp_glob_screen.control_socket_path);
222
223 if (send_unix(fd, wcmd, len) != len)
224 err(1, "%s: aborting after bad write", __func__);
225
226 free(wcmd);
227
228 if ((len = recv_unix(fd, &response)) == -1)
229 warnx("send_message: aborted reply from receiver");
230
231 /* first byte is exit status */
232 success = *response;
233 outf = success ? stdout : stderr;
234
235 if (len > 1) {
236 /* command had some output */
237 if (response[len] != '\0') {
238 /* should not be possible, TODO remove */
239 warnx("%s\n", "last byte of response not null");
240 response[len] = '\0';
241 }
242 fprintf(outf, "%s", response + 1);
243 fflush(outf);
244 }
245 free(response);
246
247 WARNX_DEBUG("%s: no more bytes\n", dpfx);
248#ifdef SENDCMD_DEBUG
249 free(dpfx);
250#endif
251
252 return success;
253}
254
255void
256receive_command(void)
257{
258 cmdret *cmd_ret;
259 char *result, *rcmd, *cmd;
260 int cl, len = 0, interactive = 0;
261
262 PRINT_DEBUG(("have connection waiting on command socket\n"));
263
264 if ((cl = accept(rp_glob_screen.control_socket_fd, NULL, NULL)) == -1) {
265 warn("accept");
266 return;
267 }
268 if ((fcntl(cl, F_SETFD, FD_CLOEXEC)) == -1) {
269 warn("cloexec");
270 close(cl);
271 return;
272 }
273
274 if ((len = recv_unix(cl, &cmd)) <= 1) {
275 warnx("receive_command: %s\n",
276 (len == -1 ? "encountered error during receive"
277 : "received command was malformed"));
278 goto done;
279 }
280 if (cmd[len] != '\0') {
281 /* should not be possible, TODO remove */
282 warnx("%s\n", "last byte of sent command not null");
283 cmd[len] = '\0';
284 }
285 interactive = cmd[0];
286 rcmd = cmd + 1;
287
288 PRINT_DEBUG(("read %d byte(s) on command socket: %s\n", len, rcmd));
289
290 cmd_ret = command(interactive, rcmd);
291
292 /* notify the client of any text that was returned by the command */
293 len = 2;
294 if (cmd_ret->output) {
295 result = xsprintf("%c%s", cmd_ret->success ? 1 : 0,
296 cmd_ret->output);
297 len = 1 + strlen(cmd_ret->output) + 1;
298 } else if (cmd_ret->success)
299 result = xsprintf("%c", 1);
300 else
301 result = xsprintf("%c", 0);
302
303 cmdret_free(cmd_ret);
304
305 PRINT_DEBUG(("writing back %d to command client: %s", len, result + 1));
306
307 if (send_unix(cl, result, len) != len)
308 warnx("%s: proceeding after bad write", __func__);
309
310 PRINT_DEBUG(("receive_command: write finished, closing\n"));
311done:
312 free(cmd);
313 close(cl);
314}