cmus-remote: Read command results
[cmus.git] / server.c
blobee18681796e59e0b2df198cba67287714c65ad48
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "server.h"
21 #include "prog.h"
22 #include "command_mode.h"
23 #include "options.h"
24 #include "xmalloc.h"
25 #include "player.h"
26 #include "file.h"
27 #include "compiler.h"
28 #include "debug.h"
30 #include <unistd.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/un.h>
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <errno.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <stdarg.h>
45 int server_socket;
46 LIST_HEAD(client_head);
48 static union {
49 struct sockaddr sa;
50 struct sockaddr_un un;
51 struct sockaddr_in in;
52 } addr;
54 #define MAX_CLIENTS 10
56 static void buf_grow(char **bufp, size_t *allocp, size_t *lenp, size_t n)
58 size_t alloc = *lenp + n + 1;
60 if (alloc > *allocp) {
61 size_t align = 4096;
62 alloc = (alloc + align) & ~(align - 1);
63 *bufp = xrealloc(*bufp, alloc);
64 *allocp = alloc;
68 __FORMAT(4, 5)
69 static void buf_addf(char **bufp, size_t *allocp, size_t *lenp, const char *fmt, ...)
71 char *buf = *bufp;
72 size_t alloc = *allocp;
73 size_t len = *lenp;
74 va_list ap;
75 int slen;
77 va_start(ap, fmt);
78 slen = vsnprintf(buf + len, alloc - len, fmt, ap);
79 va_end(ap);
81 if (slen > alloc - len - 1) {
82 buf_grow(&buf, &alloc, &len, slen);
83 *bufp = buf;
84 *allocp = alloc;
86 va_start(ap, fmt);
87 slen = vsnprintf(buf + len, alloc - len, fmt, ap);
88 va_end(ap);
91 *lenp = len + slen;
94 static const char *escape(const char *str)
96 static char *buf = NULL;
97 static size_t alloc = 0;
98 size_t len = strlen(str);
99 size_t need = len * 2 + 1;
100 int s, d;
102 if (need > alloc) {
103 alloc = (need + 16) & ~(16 - 1);
104 buf = xrealloc(buf, alloc);
107 d = 0;
108 for (s = 0; str[s]; s++) {
109 if (str[s] == '\\') {
110 buf[d++] = '\\';
111 buf[d++] = '\\';
112 continue;
114 if (str[s] == '\n') {
115 buf[d++] = '\\';
116 buf[d++] = 'n';
117 continue;
119 buf[d++] = str[s];
121 buf[d] = 0;
122 return buf;
125 static int cmd_status(struct client *client)
127 const char *status[] = { "stopped", "playing", "paused" };
128 const struct track_info *ti;
129 char *buf = NULL;
130 size_t alloc = 0;
131 size_t len = 0;
132 int i, ret;
134 buf_grow(&buf, &alloc, &len, 0);
136 player_info_lock();
137 buf_addf(&buf, &alloc, &len, "status %s\n", status[player_info.status]);
138 ti = player_info.ti;
139 if (ti) {
140 buf_addf(&buf, &alloc, &len, "file %s\n", escape(ti->filename));
141 buf_addf(&buf, &alloc, &len, "duration %d\n", ti->duration);
142 buf_addf(&buf, &alloc, &len, "position %d\n", player_info.pos);
143 for (i = 0; ti->comments[i].key; i++)
144 buf_addf(&buf, &alloc, &len, "tag %s %s\n",
145 ti->comments[i].key,
146 escape(ti->comments[i].val));
148 buf_addf(&buf, &alloc, &len, "\n");
149 player_info_unlock();
151 ret = write_all(client->fd, buf, strlen(buf));
152 free(buf);
153 return ret;
156 static void read_commands(struct client *client)
158 char buf[1024];
159 int pos = 0;
160 int authenticated = addr.sa.sa_family == AF_UNIX;
162 while (1) {
163 int rc, s, i;
165 rc = read(client->fd, buf + pos, sizeof(buf) - pos);
166 if (rc == -1) {
167 if (errno == EINTR)
168 continue;
169 if (errno == EAGAIN)
170 return;
171 goto close;
173 if (rc == 0)
174 goto close;
175 pos += rc;
177 s = 0;
178 for (i = 0; i < pos; i++) {
179 const char *line;
180 int ret;
182 if (buf[i] != '\n')
183 continue;
185 buf[i] = 0;
186 line = buf + s;
187 s = i + 1;
189 if (!authenticated) {
190 if (!server_password) {
191 d_print("password is unset, tcp/ip disabled\n");
192 goto close;
194 authenticated = !strcmp(line, server_password);
195 if (!authenticated) {
196 d_print("authentication failed\n");
197 goto close;
199 continue;
201 if (!strcmp(line, "status")) {
202 ret = cmd_status(client);
203 } else {
204 run_command(line);
205 ret = write_all(client->fd, "\n", 1);
207 if (ret < 0) {
208 d_print("write: %s\n", strerror(errno));
209 goto close;
212 memmove(buf, buf + s, pos - s);
213 pos -= s;
215 return;
216 close:
217 close(client->fd);
218 list_del(&client->node);
219 free(client);
222 void server_accept(void)
224 struct client *client;
225 struct sockaddr saddr;
226 socklen_t saddr_size = sizeof(saddr);
227 int fd;
229 fd = accept(server_socket, &saddr, &saddr_size);
230 if (fd == -1)
231 return;
233 fcntl(fd, F_SETFL, O_NONBLOCK);
235 client = xnew(struct client, 1);
236 client->fd = fd;
237 list_add_tail(&client->node, &client_head);
240 void server_serve(struct client *client)
242 /* unix connection is secure, other insecure */
243 run_only_safe_commands = addr.sa.sa_family != AF_UNIX;
244 read_commands(client);
245 run_only_safe_commands = 0;
248 static void gethostbyname_failed(void)
250 const char *error = "Unknown error.";
252 switch (h_errno) {
253 case HOST_NOT_FOUND:
254 case NO_ADDRESS:
255 error = "Host not found.";
256 break;
257 case NO_RECOVERY:
258 error = "A non-recoverable name server error.";
259 break;
260 case TRY_AGAIN:
261 error = "A temporary error occurred on an authoritative name server.";
262 break;
264 die("gethostbyname: %s\n", error);
267 void server_init(char *address)
269 int port = DEFAULT_PORT;
270 int addrlen;
272 if (strchr(address, '/')) {
273 addr.sa.sa_family = AF_UNIX;
274 strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
276 addrlen = sizeof(struct sockaddr_un);
277 } else {
278 char *s = strchr(address, ':');
279 struct hostent *hent;
281 if (s) {
282 *s++ = 0;
283 port = atoi(s);
285 hent = gethostbyname(address);
286 if (!hent)
287 gethostbyname_failed();
289 addr.sa.sa_family = hent->h_addrtype;
290 switch (addr.sa.sa_family) {
291 case AF_INET:
292 memcpy(&addr.in.sin_addr, hent->h_addr_list[0], hent->h_length);
293 addr.in.sin_port = htons(port);
295 addrlen = sizeof(addr.in);
296 break;
297 default:
298 die("unsupported address type\n");
302 server_socket = socket(addr.sa.sa_family, SOCK_STREAM, 0);
303 if (server_socket == -1)
304 die_errno("socket");
306 if (bind(server_socket, &addr.sa, addrlen) == -1) {
307 int sock;
309 if (errno != EADDRINUSE)
310 die_errno("bind");
312 /* address already in use */
313 if (addr.sa.sa_family != AF_UNIX)
314 die("cmus is already listening on %s:%d\n", address, port);
316 /* try to connect to server */
317 sock = socket(AF_UNIX, SOCK_STREAM, 0);
318 if (sock == -1)
319 die_errno("socket");
321 if (connect(sock, &addr.sa, addrlen) == -1) {
322 if (errno != ENOENT && errno != ECONNREFUSED)
323 die_errno("connect");
325 /* server not running => dead socket */
327 /* try to remove dead socket */
328 if (unlink(addr.un.sun_path) == -1 && errno != ENOENT)
329 die_errno("unlink");
330 if (bind(server_socket, &addr.sa, addrlen) == -1)
331 die_errno("bind");
332 } else {
333 /* server already running */
334 die("cmus is already listening on socket %s\n", address);
336 close(sock);
339 if (listen(server_socket, MAX_CLIENTS) == -1)
340 die_errno("listen");
343 void server_exit(void)
345 close(server_socket);
346 if (addr.sa.sa_family == AF_UNIX)
347 unlink(addr.un.sun_path);