Blob


1 /* $OpenBSD: control.c,v 1.8 2021/03/02 04:10:07 jsg Exp $ */
3 /*
4 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include <sys/types.h>
19 #include <sys/queue.h>
20 #include <sys/stat.h>
21 #include <sys/socket.h>
22 #include <sys/uio.h>
23 #include <sys/un.h>
25 #include <netinet/in.h>
26 #include <net/if.h>
28 #include <errno.h>
29 #include <event.h>
30 #include <imsg.h>
31 #include <limits.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
37 #include "amused.h"
38 #include "log.h"
39 #include "control.h"
40 #include "playlist.h"
42 #define CONTROL_BACKLOG 5
44 struct {
45 struct event ev;
46 struct event evt;
47 int fd;
48 struct playlist play;
49 int tx;
50 } control_state = {.fd = -1, .tx = -1};
52 struct ctl_conn {
53 TAILQ_ENTRY(ctl_conn) entry;
54 int monitor; /* 1 if client is in monitor mode */
55 struct imsgev iev;
56 };
58 TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
60 struct ctl_conn *control_connbyfd(int);
61 struct ctl_conn *control_connbypid(pid_t);
62 void control_close(int);
64 int
65 control_init(char *path)
66 {
67 struct sockaddr_un sun;
68 int fd;
69 mode_t old_umask;
71 if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
72 0)) == -1) {
73 log_warn("%s: socket", __func__);
74 return (-1);
75 }
77 memset(&sun, 0, sizeof(sun));
78 sun.sun_family = AF_UNIX;
79 strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
81 if (unlink(path) == -1)
82 if (errno != ENOENT) {
83 log_warn("%s: unlink %s", __func__, path);
84 close(fd);
85 return (-1);
86 }
88 old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
89 if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
90 log_warn("%s: bind: %s", __func__, path);
91 close(fd);
92 umask(old_umask);
93 return (-1);
94 }
95 umask(old_umask);
97 if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
98 log_warn("%s: chmod", __func__);
99 close(fd);
100 (void)unlink(path);
101 return (-1);
104 return (fd);
107 int
108 control_listen(int fd)
110 if (control_state.fd != -1)
111 fatalx("%s: received unexpected controlsock", __func__);
113 control_state.fd = fd;
114 if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
115 log_warn("%s: listen", __func__);
116 return (-1);
119 event_set(&control_state.ev, control_state.fd, EV_READ,
120 control_accept, NULL);
121 event_add(&control_state.ev, NULL);
122 evtimer_set(&control_state.evt, control_accept, NULL);
124 return (0);
127 void
128 control_accept(int listenfd, short event, void *bula)
130 int connfd;
131 socklen_t len;
132 struct sockaddr_un sun;
133 struct ctl_conn *c;
135 event_add(&control_state.ev, NULL);
136 if ((event & EV_TIMEOUT))
137 return;
139 len = sizeof(sun);
140 if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
141 SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
142 /*
143 * Pause accept if we are out of file descriptors, or
144 * libevent will haunt us here too.
145 */
146 if (errno == ENFILE || errno == EMFILE) {
147 struct timeval evtpause = { 1, 0 };
149 event_del(&control_state.ev);
150 evtimer_add(&control_state.evt, &evtpause);
151 } else if (errno != EWOULDBLOCK && errno != EINTR &&
152 errno != ECONNABORTED)
153 log_warn("%s: accept4", __func__);
154 return;
157 if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
158 log_warn("%s: calloc", __func__);
159 close(connfd);
160 return;
163 imsg_init(&c->iev.ibuf, connfd);
164 c->iev.handler = control_dispatch_imsg;
165 c->iev.events = EV_READ;
166 event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
167 c->iev.handler, &c->iev);
168 event_add(&c->iev.ev, NULL);
170 TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
173 struct ctl_conn *
174 control_connbyfd(int fd)
176 struct ctl_conn *c;
178 TAILQ_FOREACH(c, &ctl_conns, entry) {
179 if (c->iev.ibuf.fd == fd)
180 break;
183 return (c);
186 struct ctl_conn *
187 control_connbypid(pid_t pid)
189 struct ctl_conn *c;
191 TAILQ_FOREACH(c, &ctl_conns, entry) {
192 if (c->iev.ibuf.pid == pid)
193 break;
196 return (c);
199 void
200 control_close(int fd)
202 struct ctl_conn *c;
204 if ((c = control_connbyfd(fd)) == NULL) {
205 log_warnx("%s: fd %d: not found", __func__, fd);
206 return;
209 /* abort the transaction if running by this user */
210 if (control_state.tx != -1 && c->iev.ibuf.fd == control_state.tx) {
211 playlist_free(&control_state.play);
212 control_state.tx = -1;
215 msgbuf_clear(&c->iev.ibuf.w);
216 TAILQ_REMOVE(&ctl_conns, c, entry);
218 event_del(&c->iev.ev);
219 close(c->iev.ibuf.fd);
221 /* Some file descriptors are available again. */
222 if (evtimer_pending(&control_state.evt, NULL)) {
223 evtimer_del(&control_state.evt);
224 event_add(&control_state.ev, NULL);
227 free(c);
230 void
231 control_notify(struct imsgev *iev, int type)
233 struct ctl_conn *c;
235 TAILQ_FOREACH(c, &ctl_conns, entry) {
236 if (&c->iev == iev || !c->monitor)
237 continue;
239 imsg_compose_event(&c->iev, IMSG_CTL_MONITOR, 0, 0,
240 -1, &type, sizeof(type));
244 void
245 control_dispatch_imsg(int fd, short event, void *bula)
247 struct ctl_conn *c;
248 struct imsg imsg;
249 struct player_repeat rp;
250 ssize_t n, off;
252 if ((c = control_connbyfd(fd)) == NULL) {
253 log_warnx("%s: fd %d: not found", __func__, fd);
254 return;
257 if (event & EV_READ) {
258 if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
259 n == 0) {
260 control_close(fd);
261 return;
264 if (event & EV_WRITE) {
265 if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
266 control_close(fd);
267 return;
271 for (;;) {
272 if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
273 control_close(fd);
274 return;
276 if (n == 0)
277 break;
279 switch (imsg.hdr.type) {
280 case IMSG_CTL_PLAY:
281 switch (play_state) {
282 case STATE_STOPPED:
283 main_playlist_resume();
284 break;
285 case STATE_PLAYING:
286 /* do nothing */
287 break;
288 case STATE_PAUSED:
289 play_state = STATE_PLAYING;
290 main_send_player(IMSG_RESUME, -1);
291 break;
293 control_notify(&c->iev, imsg.hdr.type);
294 break;
295 case IMSG_CTL_TOGGLE_PLAY:
296 switch (play_state) {
297 case STATE_STOPPED:
298 main_playlist_resume();
299 break;
300 case STATE_PLAYING:
301 play_state = STATE_PAUSED;
302 main_send_player(IMSG_PAUSE, -1);
303 break;
304 case STATE_PAUSED:
305 play_state = STATE_PLAYING;
306 main_send_player(IMSG_RESUME, -1);
307 break;
309 control_notify(&c->iev, imsg.hdr.type);
310 break;
311 case IMSG_CTL_PAUSE:
312 if (play_state != STATE_PLAYING)
313 break;
314 play_state = STATE_PAUSED;
315 main_send_player(IMSG_PAUSE, -1);
316 control_notify(&c->iev, imsg.hdr.type);
317 break;
318 case IMSG_CTL_STOP:
319 if (play_state == STATE_STOPPED)
320 break;
321 play_state = STATE_STOPPED;
322 main_send_player(IMSG_STOP, -1);
323 control_notify(&c->iev, imsg.hdr.type);
324 break;
325 case IMSG_CTL_RESTART:
326 main_send_player(IMSG_STOP, -1);
327 main_restart_track();
328 control_notify(&c->iev, imsg.hdr.type);
329 break;
330 case IMSG_CTL_FLUSH:
331 playlist_truncate();
332 control_notify(&c->iev, imsg.hdr.type);
333 break;
334 case IMSG_CTL_SHOW:
335 main_send_playlist(&c->iev);
336 break;
337 case IMSG_CTL_STATUS:
338 main_send_status(&c->iev);
339 break;
340 case IMSG_CTL_NEXT:
341 main_send_player(IMSG_STOP, -1);
342 main_playlist_advance();
343 control_notify(&c->iev, imsg.hdr.type);
344 break;
345 case IMSG_CTL_PREV:
346 main_send_player(IMSG_STOP, -1);
347 main_playlist_previous();
348 control_notify(&c->iev, imsg.hdr.type);
349 break;
350 case IMSG_CTL_JUMP:
351 main_playlist_jump(&c->iev, &imsg);
352 control_notify(&c->iev, imsg.hdr.type);
353 break;
354 case IMSG_CTL_REPEAT:
355 if (IMSG_DATA_SIZE(imsg) != sizeof(rp)) {
356 log_warnx("%s: got wrong size", __func__);
357 break;
359 memcpy(&rp, imsg.data, sizeof(rp));
360 if (rp.repeat_all != -1)
361 repeat_all = rp.repeat_all;
362 if (rp.repeat_one != -1)
363 repeat_one = rp.repeat_one;
364 control_notify(&c->iev, imsg.hdr.type);
365 break;
366 case IMSG_CTL_BEGIN:
367 if (control_state.tx != -1) {
368 main_senderr(&c->iev, "locked");
369 break;
371 control_state.tx = c->iev.ibuf.fd;
372 imsg_compose_event(&c->iev, IMSG_CTL_BEGIN, 0, 0, -1,
373 NULL, 0);
374 break;
375 case IMSG_CTL_ADD:
376 if (control_state.tx != -1 &&
377 control_state.tx != c->iev.ibuf.fd) {
378 main_senderr(&c->iev, "locked");
379 break;
381 main_enqueue(control_state.tx != -1,
382 &control_state.play,&c->iev, &imsg);
383 if (control_state.tx == -1)
384 control_notify(&c->iev, imsg.hdr.type);
385 break;
386 case IMSG_CTL_COMMIT:
387 if (control_state.tx != c->iev.ibuf.fd) {
388 main_senderr(&c->iev, "locked");
389 break;
391 if (IMSG_DATA_SIZE(imsg) != sizeof(off)) {
392 main_senderr(&c->iev, "wrong size");
393 break;
395 memcpy(&off, imsg.data, sizeof(off));
396 playlist_swap(&control_state.play, off);
397 memset(&control_state.play, 0,
398 sizeof(control_state.play));
399 control_state.tx = -1;
400 imsg_compose_event(&c->iev, IMSG_CTL_COMMIT, 0, 0, -1,
401 NULL, 0);
402 control_notify(&c->iev, imsg.hdr.type);
403 break;
404 case IMSG_CTL_MONITOR:
405 c->monitor = 1;
406 break;
407 default:
408 log_debug("%s: error handling imsg %d", __func__,
409 imsg.hdr.type);
410 break;
412 imsg_free(&imsg);
415 imsg_event_add(&c->iev);
418 int
419 control_imsg_relay(struct imsg *imsg)
421 struct ctl_conn *c;
423 if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
424 return (0);
426 return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
427 -1, imsg->data, IMSG_DATA_SIZE(*imsg)));