Small clean up to http_open()
[cmus.git] / server.c
blob8e98d3a23f0d81bc558c708b8e423b0f368ee8aa
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 char *cmd, *arg;
181 int ret;
183 if (buf[i] != '\n')
184 continue;
186 buf[i] = 0;
187 line = buf + s;
188 s = i + 1;
190 if (!authenticated) {
191 if (!server_password) {
192 d_print("password is unset, tcp/ip disabled\n");
193 goto close;
195 authenticated = !strcmp(line, server_password);
196 if (!authenticated) {
197 d_print("authentication failed\n");
198 goto close;
200 continue;
203 if (parse_command(line, &cmd, &arg)) {
204 if (!strcmp(cmd, "status")) {
205 ret = cmd_status(client);
206 } else {
207 run_parsed_command(cmd, arg);
208 ret = write_all(client->fd, "\n", 1);
210 free(cmd);
211 free(arg);
212 } else {
213 // don't hang cmus-remote
214 ret = write_all(client->fd, "\n", 1);
216 if (ret < 0) {
217 d_print("write: %s\n", strerror(errno));
218 goto close;
221 memmove(buf, buf + s, pos - s);
222 pos -= s;
224 return;
225 close:
226 close(client->fd);
227 list_del(&client->node);
228 free(client);
231 void server_accept(void)
233 struct client *client;
234 struct sockaddr saddr;
235 socklen_t saddr_size = sizeof(saddr);
236 int fd;
238 fd = accept(server_socket, &saddr, &saddr_size);
239 if (fd == -1)
240 return;
242 fcntl(fd, F_SETFL, O_NONBLOCK);
244 client = xnew(struct client, 1);
245 client->fd = fd;
246 list_add_tail(&client->node, &client_head);
249 void server_serve(struct client *client)
251 /* unix connection is secure, other insecure */
252 run_only_safe_commands = addr.sa.sa_family != AF_UNIX;
253 read_commands(client);
254 run_only_safe_commands = 0;
257 static void gethostbyname_failed(void)
259 const char *error = "Unknown error.";
261 switch (h_errno) {
262 case HOST_NOT_FOUND:
263 case NO_ADDRESS:
264 error = "Host not found.";
265 break;
266 case NO_RECOVERY:
267 error = "A non-recoverable name server error.";
268 break;
269 case TRY_AGAIN:
270 error = "A temporary error occurred on an authoritative name server.";
271 break;
273 die("gethostbyname: %s\n", error);
276 void server_init(char *address)
278 int port = DEFAULT_PORT;
279 int addrlen;
281 if (strchr(address, '/')) {
282 addr.sa.sa_family = AF_UNIX;
283 strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
285 addrlen = sizeof(struct sockaddr_un);
286 } else {
287 char *s = strchr(address, ':');
288 struct hostent *hent;
290 if (s) {
291 *s++ = 0;
292 port = atoi(s);
294 hent = gethostbyname(address);
295 if (!hent)
296 gethostbyname_failed();
298 addr.sa.sa_family = hent->h_addrtype;
299 switch (addr.sa.sa_family) {
300 case AF_INET:
301 memcpy(&addr.in.sin_addr, hent->h_addr_list[0], hent->h_length);
302 addr.in.sin_port = htons(port);
304 addrlen = sizeof(addr.in);
305 break;
306 default:
307 die("unsupported address type\n");
311 server_socket = socket(addr.sa.sa_family, SOCK_STREAM, 0);
312 if (server_socket == -1)
313 die_errno("socket");
315 if (bind(server_socket, &addr.sa, addrlen) == -1) {
316 int sock;
318 if (errno != EADDRINUSE)
319 die_errno("bind");
321 /* address already in use */
322 if (addr.sa.sa_family != AF_UNIX)
323 die("cmus is already listening on %s:%d\n", address, port);
325 /* try to connect to server */
326 sock = socket(AF_UNIX, SOCK_STREAM, 0);
327 if (sock == -1)
328 die_errno("socket");
330 if (connect(sock, &addr.sa, addrlen) == -1) {
331 if (errno != ENOENT && errno != ECONNREFUSED)
332 die_errno("connect");
334 /* server not running => dead socket */
336 /* try to remove dead socket */
337 if (unlink(addr.un.sun_path) == -1 && errno != ENOENT)
338 die_errno("unlink");
339 if (bind(server_socket, &addr.sa, addrlen) == -1)
340 die_errno("bind");
341 } else {
342 /* server already running */
343 die("cmus is already listening on socket %s\n", address);
345 close(sock);
348 if (listen(server_socket, MAX_CLIENTS) == -1)
349 die_errno("listen");
352 void server_exit(void)
354 close(server_socket);
355 if (addr.sa.sa_family == AF_UNIX)
356 unlink(addr.un.sun_path);