1 /* $Id: ezstream.c 16537 2009-08-30 21:55:24Z moritz $ */
2 /*
3 * ezstream - source client for Icecast with external en-/decoder support
4 * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
5 * Copyright (C) 2007, 2009 Moritz Grimm <mdgrimm@gmx.net>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "ezstream.h"
26
27 #ifdef HAVE_SIGNAL_H
28 # include <signal.h>
29 #endif
30
31 #include <shout/shout.h>
32
33 #include "configfile.h"
34 #include "metadata.h"
35 #include "playlist.h"
36 #include "strfctns.h"
37 #include "util.h"
38 #include "xalloc.h"
39
40 #define STREAM_DONE 0
41 #define STREAM_CONT 1
42 #define STREAM_SKIP 2
43 #define STREAM_SERVERR 3
44 #define STREAM_UPDMDATA 4
45
46 #ifdef HAVE___PROGNAME
47 extern char *__progname;
48 #else
49 char *__progname;
50 #endif /* HAVE___PROGNAME */
51
52 int nFlag;
53 int qFlag;
54 int sFlag;
55 int vFlag;
56 int metadataFromProgram;
57
58 EZCONFIG *pezConfig = NULL;
59 playlist_t *playlist = NULL;
60 int playlistMode = 0;
61 unsigned int resource_errors = 0;
62
63 #ifdef HAVE_SIGNALS
64 const int ezstream_signals[] = {
65 SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2
66 };
67
68 volatile sig_atomic_t rereadPlaylist = 0;
69 volatile sig_atomic_t rereadPlaylist_notify = 0;
70 volatile sig_atomic_t skipTrack = 0;
71 volatile sig_atomic_t queryMetadata = 0;
72 volatile sig_atomic_t quit = 0;
73 #else
74 int rereadPlaylist = 0;
75 int rereadPlaylist_notify = 0;
76 int skipTrack = 0;
77 int queryMetadata = 0;
78 int quit = 0;
79 #endif /* HAVE_SIGNALS */
80
81 typedef struct tag_ID3Tag {
82 char tag[3];
83 char trackName[30];
84 char artistName[30];
85 char albumName[30];
86 char year[3];
87 char comment[30];
88 char genre;
89 } ID3Tag;
90
91 int urlParse(const char *, char **, unsigned short *, char **);
92 void replaceString(const char *, char *, size_t, const char *,
93 const char *);
94 char * buildCommandString(const char *, const char *, metadata_t *);
95 char * getMetadataString(const char *, metadata_t *);
96 metadata_t * getMetadata(const char *);
97 int setMetadata(shout_t *, metadata_t *, char **);
98 FILE * openResource(shout_t *, const char *, int *, metadata_t **,
99 int *, long *);
100 int reconnectServer(shout_t *, int);
101 const char * getTimeString(long);
102 int sendStream(shout_t *, FILE *, const char *, int, const char *,
103 struct timeval *);
104 int streamFile(shout_t *, const char *);
105 int streamPlaylist(shout_t *, const char *);
106 char * getProgname(const char *);
107 void usage(void);
108 void usageHelp(void);
109 int ez_shutdown(int);
110
111 #ifdef HAVE_SIGNALS
112 void sig_handler(int);
113
114 # ifndef SIG_IGN
115 # define SIG_IGN (void (*)(int))1
116 # endif /* !SIG_IGN */
117
118 void
sig_handler(int sig)119 sig_handler(int sig)
120 {
121 switch (sig) {
122 case SIGTERM:
123 case SIGINT:
124 quit = 1;
125 break;
126 case SIGHUP:
127 rereadPlaylist = 1;
128 rereadPlaylist_notify = 1;
129 break;
130 case SIGUSR1:
131 skipTrack = 1;
132 break;
133 case SIGUSR2:
134 queryMetadata = 1;
135 break;
136 default:
137 break;
138 }
139 }
140 #endif /* HAVE_SIGNALS */
141
142 int
urlParse(const char * url,char ** hostname,unsigned short * port,char ** mountname)143 urlParse(const char *url, char **hostname, unsigned short *port,
144 char **mountname)
145 {
146 const char *p1, *p2, *p3;
147 char tmpPort[6] = "";
148 size_t hostsiz, mountsiz;
149 const char *errstr;
150
151 if (hostname == NULL || port == NULL || mountname == NULL) {
152 printf("%s: urlParse(): Internal error: Bad arguments\n",
153 __progname);
154 exit(1);
155 }
156
157 if (strncmp(url, "http://", strlen("http://")) != 0) {
158 printf("%s: Error: Invalid <url>: Not an HTTP address\n",
159 __progname);
160 return (0);
161 }
162
163 p1 = url + strlen("http://");
164 p2 = strchr(p1, ':');
165 if (p2 == NULL) {
166 printf("%s: Error: Invalid <url>: Missing port\n",
167 __progname);
168 return (0);
169 }
170 hostsiz = (p2 - p1) + 1;
171 *hostname = xmalloc(hostsiz);
172 strlcpy(*hostname, p1, hostsiz);
173
174 p2++;
175 p3 = strchr(p2, '/');
176 if (p3 == NULL || p3 - p2 >= (int)sizeof(tmpPort)) {
177 printf("%s: Error: Invalid <url>: Missing mountpoint or too long port number\n",
178 __progname);
179 xfree(*hostname);
180 return (0);
181 }
182
183 strlcpy(tmpPort, p2, (p3 - p2) + 1UL);
184 *port = (unsigned short)strtonum(tmpPort, 1LL, (long long)USHRT_MAX, &errstr);
185 if (errstr) {
186 printf("%s: Error: Invalid <url>: Port '%s' is %s\n",
187 __progname, tmpPort, errstr);
188 xfree(*hostname);
189 return (0);
190 }
191
192 mountsiz = strlen(p3) + 1;
193 *mountname = xmalloc(mountsiz);
194 strlcpy(*mountname, p3, mountsiz);
195
196 return (1);
197 }
198
199 void
replaceString(const char * source,char * dest,size_t size,const char * from,const char * to)200 replaceString(const char *source, char *dest, size_t size,
201 const char *from, const char *to)
202 {
203 const char *p1 = source;
204 const char *p2;
205
206 p2 = strstr(p1, from);
207 if (p2 != NULL) {
208 if ((unsigned int)(p2 - p1) >= size) {
209 printf("%s: replaceString(): Internal error: p2 - p1 >= size\n",
210 __progname);
211 abort();
212 }
213 strncat(dest, p1, (size_t)(p2 - p1));
214 strlcat(dest, to, size);
215 p1 = p2 + strlen(from);
216 }
217 strlcat(dest, p1, size);
218 }
219
220 char *
buildCommandString(const char * extension,const char * fileName,metadata_t * mdata)221 buildCommandString(const char *extension, const char *fileName,
222 metadata_t *mdata)
223 {
224 char *commandString = NULL;
225 size_t commandStringLen = 0;
226 char *encoder = NULL;
227 char *decoder = NULL;
228 char *newDecoder = NULL;
229 size_t newDecoderLen = 0;
230 char *newEncoder = NULL;
231 size_t newEncoderLen = 0;
232 char *localTitle = UTF8toCHAR(metadata_get_title(mdata),
233 ICONV_REPLACE);
234 char *localArtist = UTF8toCHAR(metadata_get_artist(mdata),
235 ICONV_REPLACE);
236 char *localMetaString = UTF8toCHAR(metadata_get_string(mdata),
237 ICONV_REPLACE);
238
239 decoder = xstrdup(getFormatDecoder(extension));
240 if (strlen(decoder) == 0) {
241 printf("%s: Unknown extension '%s', cannot decode '%s'\n",
242 __progname, extension, fileName);
243 xfree(localTitle);
244 xfree(localArtist);
245 xfree(localMetaString);
246 xfree(decoder);
247 return (NULL);
248 }
249 newDecoderLen = strlen(decoder) + strlen(fileName) + 1;
250 newDecoder = xcalloc(newDecoderLen, sizeof(char));
251 replaceString(decoder, newDecoder, newDecoderLen, TRACK_PLACEHOLDER,
252 fileName);
253 if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
254 size_t tmpLen = strlen(newDecoder) + strlen(localArtist) + 1;
255 char *tmpStr = xcalloc(tmpLen, sizeof(char));
256 replaceString(newDecoder, tmpStr, tmpLen, ARTIST_PLACEHOLDER,
257 localArtist);
258 xfree(newDecoder);
259 newDecoder = tmpStr;
260 }
261 if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
262 size_t tmpLen = strlen(newDecoder) + strlen(localTitle) + 1;
263 char *tmpStr = xcalloc(tmpLen, sizeof(char));
264 replaceString(newDecoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
265 localTitle);
266 xfree(newDecoder);
267 newDecoder = tmpStr;
268 }
269 /*
270 * if meta
271 * if (prog && format)
272 * metatoformat
273 * else
274 * if (!prog && title)
275 * emptymeta
276 * else
277 * replacemeta
278 */
279 if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
280 if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
281 char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
282 size_t tmpLen = strlen(newDecoder) + strlen(mdataString) + 1;
283 char *tmpStr = xcalloc(tmpLen, sizeof(char));
284 replaceString(newDecoder, tmpStr, tmpLen,
285 METADATA_PLACEHOLDER, mdataString);
286 xfree(newDecoder);
287 xfree(mdataString);
288 newDecoder = tmpStr;
289 } else {
290 if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
291 size_t tmpLen = strlen(newDecoder) + 1;
292 char *tmpStr = xcalloc(tmpLen, sizeof(char));
293 replaceString(newDecoder, tmpStr, tmpLen,
294 METADATA_PLACEHOLDER, "");
295 xfree(newDecoder);
296 newDecoder = tmpStr;
297 } else {
298 size_t tmpLen = strlen(newDecoder) + strlen(localMetaString) + 1;
299 char *tmpStr = xcalloc(tmpLen, sizeof(char));
300 replaceString(newDecoder, tmpStr, tmpLen,
301 METADATA_PLACEHOLDER,
302 localMetaString);
303 xfree(newDecoder);
304 newDecoder = tmpStr;
305 }
306 }
307 }
308
309 encoder = xstrdup(getFormatEncoder(pezConfig->format));
310 if (strlen(encoder) == 0) {
311 if (vFlag)
312 printf("%s: Passing through%s%s data from the decoder\n",
313 __progname,
314 (strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
315 pezConfig->format);
316 commandStringLen = strlen(newDecoder) + 1;
317 commandString = xcalloc(commandStringLen, sizeof(char));
318 strlcpy(commandString, newDecoder, commandStringLen);
319 xfree(localTitle);
320 xfree(localArtist);
321 xfree(localMetaString);
322 xfree(decoder);
323 xfree(encoder);
324 xfree(newDecoder);
325 return (commandString);
326 }
327
328 newEncoderLen = strlen(encoder) + strlen(localArtist) + 1;
329 newEncoder = xcalloc(newEncoderLen, sizeof(char));
330 replaceString(encoder, newEncoder, newEncoderLen, ARTIST_PLACEHOLDER,
331 localArtist);
332 if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
333 size_t tmpLen = strlen(newEncoder) + strlen(localTitle) + 1;
334 char *tmpStr = xcalloc(tmpLen, sizeof(char));
335 replaceString(newEncoder, tmpStr, tmpLen, TITLE_PLACEHOLDER,
336 localTitle);
337 xfree(newEncoder);
338 newEncoder = tmpStr;
339 }
340 if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
341 if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
342 char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
343 size_t tmpLen = strlen(newEncoder) + strlen(mdataString) + 1;
344 char *tmpStr = xcalloc(tmpLen, sizeof(char));
345 replaceString(newEncoder, tmpStr, tmpLen,
346 METADATA_PLACEHOLDER, mdataString);
347 xfree(newEncoder);
348 xfree(mdataString);
349 newEncoder = tmpStr;
350 } else {
351 if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
352 size_t tmpLen = strlen(newEncoder) + 1;
353 char *tmpStr = xcalloc(tmpLen, sizeof(char));
354 replaceString(newEncoder, tmpStr, tmpLen,
355 METADATA_PLACEHOLDER, "");
356 xfree(newEncoder);
357 newEncoder = tmpStr;
358 } else {
359 size_t tmpLen = strlen(newEncoder) + strlen(localMetaString) + 1;
360 char *tmpStr = xcalloc(tmpLen, sizeof(char));
361 replaceString(newEncoder, tmpStr, tmpLen,
362 METADATA_PLACEHOLDER,
363 localMetaString);
364 xfree(newEncoder);
365 newEncoder = tmpStr;
366 }
367 }
368 }
369
370 commandStringLen = strlen(newDecoder) + strlen(" | ") +
371 strlen(newEncoder) + 1;
372 commandString = xcalloc(commandStringLen, sizeof(char));
373 snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
374 newEncoder);
375
376 xfree(localTitle);
377 xfree(localArtist);
378 xfree(localMetaString);
379 xfree(decoder);
380 xfree(encoder);
381 xfree(newDecoder);
382 xfree(newEncoder);
383
384 return (commandString);
385 }
386
387 char *
getMetadataString(const char * format,metadata_t * mdata)388 getMetadataString(const char *format, metadata_t *mdata)
389 {
390 char *tmp, *str;
391 size_t len;
392
393 if (mdata == NULL) {
394 printf("%s: getMetadataString(): Internal error: NULL metadata_t\n",
395 __progname);
396 abort();
397 }
398
399 if (format == NULL)
400 return (NULL);
401
402 str = xstrdup(format);
403
404 if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
405 len = strlen(str) + strlen(metadata_get_artist(mdata)) + 1;
406 tmp = xcalloc(len, sizeof(char));
407 replaceString(str, tmp, len, ARTIST_PLACEHOLDER,
408 metadata_get_artist(mdata));
409 xfree(str);
410 str = tmp;
411 }
412 if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
413 len = strlen(str) + strlen(metadata_get_title(mdata)) + 1;
414 tmp = xcalloc(len, sizeof(char));
415 replaceString(str, tmp, len, TITLE_PLACEHOLDER,
416 metadata_get_title(mdata));
417 xfree(str);
418 str = tmp;
419 }
420 if (strstr(format, STRING_PLACEHOLDER) != NULL) {
421 len = strlen(str) + strlen(metadata_get_string(mdata)) + 1;
422 tmp = xcalloc(len, sizeof(char));
423 replaceString(str, tmp, len, STRING_PLACEHOLDER,
424 metadata_get_string(mdata));
425 xfree(str);
426 str = tmp;
427 }
428 if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
429 len = strlen(str) + strlen(metadata_get_filename(mdata)) + 1;
430 tmp = xcalloc(len, sizeof(char));
431 replaceString(str, tmp, len, TRACK_PLACEHOLDER,
432 metadata_get_filename(mdata));
433 xfree(str);
434 str = tmp;
435 }
436
437 return (str);
438 }
439
440 metadata_t *
getMetadata(const char * fileName)441 getMetadata(const char *fileName)
442 {
443 metadata_t *mdata;
444
445 if (metadataFromProgram) {
446 if ((mdata = metadata_program(fileName, nFlag)) == NULL)
447 return (NULL);
448
449 if (!metadata_program_update(mdata, METADATA_ALL)) {
450 metadata_free(&mdata);
451 return (NULL);
452 }
453 } else {
454 if ((mdata = metadata_file(fileName, nFlag)) == NULL)
455 return (NULL);
456
457 if (!metadata_file_update(mdata)) {
458 metadata_free(&mdata);
459 return (NULL);
460 }
461 }
462
463 return (mdata);
464 }
465
466 int
setMetadata(shout_t * shout,metadata_t * mdata,char ** mdata_copy)467 setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
468 {
469 shout_metadata_t *shout_mdata = NULL;
470 char *songInfo;
471 const char *artist, *title;
472 int ret = SHOUTERR_SUCCESS;
473
474 if (shout == NULL) {
475 printf("%s: setMetadata(): Internal error: NULL shout_t\n",
476 __progname);
477 abort();
478 }
479
480 if (mdata == NULL)
481 return 1;
482
483 if ((shout_mdata = shout_metadata_new()) == NULL) {
484 printf("%s: shout_metadata_new(): %s\n", __progname,
485 strerror(ENOMEM));
486 exit(1);
487 }
488
489 artist = metadata_get_artist(mdata);
490 title = metadata_get_title(mdata);
491
492 /*
493 * We can do this, because we know how libshout works. This adds
494 * "charset=UTF-8" to the HTTP metadata update request and has the
495 * desired effect of letting newer-than-2.3.1 versions of Icecast know
496 * which encoding we're using.
497 */
498 if (shout_metadata_add(shout_mdata, "charset", "UTF-8") != SHOUTERR_SUCCESS) {
499 /* Assume SHOUTERR_MALLOC */
500 printf("%s: shout_metadata_add(): %s\n", __progname,
501 strerror(ENOMEM));
502 exit(1);
503 }
504
505 if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
506 if (artist[0] == '\0' && title[0] == '\0')
507 songInfo = xstrdup(metadata_get_string(mdata));
508 else
509 songInfo = metadata_assemble_string(mdata);
510 if (artist[0] != '\0' && title[0] != '\0') {
511 if (shout_metadata_add(shout_mdata, "artist", artist) != SHOUTERR_SUCCESS) {
512 printf("%s: shout_metadata_add(): %s\n", __progname,
513 strerror(ENOMEM));
514 exit(1);
515 }
516 if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) {
517 printf("%s: shout_metadata_add(): %s\n", __progname,
518 strerror(ENOMEM));
519 exit(1);
520 }
521 } else {
522 if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
523 printf("%s: shout_metadata_add(): %s\n", __progname,
524 strerror(ENOMEM));
525 exit(1);
526 }
527 }
528 } else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) {
529 printf("%s: shout_metadata_add(): %s\n", __progname,
530 strerror(ENOMEM));
531 exit(1);
532 }
533
534 if ((ret = shout_set_metadata(shout, shout_mdata)) != SHOUTERR_SUCCESS)
535 printf("%s: shout_set_metadata(): %s\n",
536 __progname, shout_get_error(shout));
537
538 shout_metadata_free(shout_mdata);
539
540 if (ret == SHOUTERR_SUCCESS) {
541 if (mdata_copy != NULL && *mdata_copy == NULL)
542 *mdata_copy = xstrdup(songInfo);
543 }
544
545 xfree(songInfo);
546 return (ret);
547 }
548
549 FILE *
openResource(shout_t * shout,const char * fileName,int * popenFlag,metadata_t ** mdata_p,int * isStdin,long * songLen)550 openResource(shout_t *shout, const char *fileName, int *popenFlag,
551 metadata_t **mdata_p, int *isStdin, long *songLen)
552 {
553 FILE *filep = NULL;
554 char extension[25];
555 char *p = NULL;
556 char *pCommandString = NULL;
557 metadata_t *mdata;
558
559 if (mdata_p != NULL)
560 *mdata_p = NULL;
561 if (songLen != NULL)
562 *songLen = 0;
563
564 if (strcmp(fileName, "stdin") == 0) {
565 if (metadataFromProgram) {
566 if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
567 return (NULL);
568 if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
569 metadata_free(&mdata);
570 return (NULL);
571 }
572 if (mdata_p != NULL)
573 *mdata_p = mdata;
574 else
575 metadata_free(&mdata);
576 }
577
578 if (isStdin != NULL)
579 *isStdin = 1;
580 #ifdef WIN32
581 _setmode(_fileno(stdin), _O_BINARY);
582 #endif
583 filep = stdin;
584 return (filep);
585 }
586
587 if (isStdin != NULL)
588 *isStdin = 0;
589
590 extension[0] = '\0';
591 p = strrchr(fileName, '.');
592 if (p != NULL)
593 strlcpy(extension, p, sizeof(extension));
594 for (p = extension; *p != '\0'; p++)
595 *p = tolower((int)*p);
596
597 if (strlen(extension) == 0) {
598 printf("%s: Error: Cannot determine file type of '%s'\n",
599 __progname, fileName);
600 return (filep);
601 }
602
603 if (metadataFromProgram) {
604 if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
605 return (NULL);
606 } else {
607 if ((mdata = getMetadata(fileName)) == NULL)
608 return (NULL);
609 }
610 if (songLen != NULL)
611 *songLen = metadata_get_length(mdata);
612
613 *popenFlag = 0;
614 if (pezConfig->reencode) {
615 int stderr_fd = -1;
616
617 pCommandString = buildCommandString(extension, fileName, mdata);
618 if (mdata_p != NULL)
619 *mdata_p = mdata;
620 else
621 metadata_free(&mdata);
622 if (vFlag > 1)
623 printf("%s: Running command `%s`\n", __progname,
624 pCommandString);
625
626 if (qFlag) {
627 int fd;
628
629 stderr_fd = dup(fileno(stderr));
630 if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
631 printf("%s: Cannot open %s for redirecting STDERR output: %s\n",
632 __progname, _PATH_DEVNULL, strerror(errno));
633 exit(1);
634 }
635
636 dup2(fd, fileno(stderr));
637 if (fd > 2)
638 close(fd);
639 }
640
641 fflush(NULL);
642 errno = 0;
643 if ((filep = popen(pCommandString, "r")) == NULL) {
644 printf("%s: popen(): Error while executing '%s'",
645 __progname, pCommandString);
646 /* popen() does not set errno reliably ... */
647 if (errno)
648 printf(": %s\n", strerror(errno));
649 else
650 printf("\n");
651 } else {
652 *popenFlag = 1;
653 #ifdef WIN32
654 _setmode(_fileno(filep), _O_BINARY );
655 #endif
656 }
657 xfree(pCommandString);
658
659 if (qFlag)
660 dup2(stderr_fd, fileno(stderr));
661
662 if (stderr_fd > 2)
663 close(stderr_fd);
664
665 return (filep);
666 }
667
668 if (mdata_p != NULL)
669 *mdata_p = mdata;
670 else
671 metadata_free(&mdata);
672
673 if ((filep = fopen(fileName, "rb")) == NULL)
674 printf("%s: %s: %s\n", __progname, fileName,
675 strerror(errno));
676
677 return (filep);
678 }
679
680 int
reconnectServer(shout_t * shout,int closeConn)681 reconnectServer(shout_t *shout, int closeConn)
682 {
683 unsigned int i;
684 int close_conn = closeConn;
685
686 printf("%s: Connection to %s lost\n", __progname, pezConfig->URL);
687
688 i = 0;
689 while (++i) {
690 printf("%s: Attempting reconnection #", __progname);
691 if (pezConfig->reconnectAttempts > 0)
692 printf("%u/%u: ", i,
693 pezConfig->reconnectAttempts);
694 else
695 printf("%u: ", i);
696
697 if (close_conn == 0)
698 close_conn = 1;
699 else
700 shout_close(shout);
701 if (shout_open(shout) == SHOUTERR_SUCCESS) {
702 printf("OK\n%s: Reconnect to %s successful\n",
703 __progname, pezConfig->URL);
704 return (1);
705 }
706
707 printf("FAILED: %s\n", shout_get_error(shout));
708
709 if (pezConfig->reconnectAttempts > 0 &&
710 i >= pezConfig->reconnectAttempts)
711 break;
712
713 printf("%s: Waiting 5s for %s to come back ...\n",
714 __progname, pezConfig->URL);
715 if (quit)
716 return (0);
717 else
718 sleep(5);
719 };
720
721 printf("%s: Giving up\n", __progname);
722 return (0);
723 }
724
725 const char *
getTimeString(long seconds)726 getTimeString(long seconds)
727 {
728 static char str[20];
729 long secs, mins, hours;
730
731 if (seconds < 0)
732 return (NULL);
733
734 secs = seconds;
735 hours = secs / 3600;
736 secs %= 3600;
737 mins = secs / 60;
738 secs %= 60;
739
740 snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
741 return ((const char *)str);
742 }
743
744 int
sendStream(shout_t * shout,FILE * filepstream,const char * fileName,int isStdin,const char * songLenStr,struct timeval * tv)745 sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
746 int isStdin, const char *songLenStr, struct timeval *tv)
747 {
748 unsigned char buff[4096];
749 size_t bytes_read, total, oldTotal;
750 int ret;
751 double kbps = -1.0;
752 struct timeval timeStamp, *startTime = tv;
753
754 if (startTime == NULL) {
755 printf("%s: sendStream(): Internal error: startTime is NULL\n",
756 __progname);
757 abort();
758 }
759
760 timeStamp.tv_sec = startTime->tv_sec;
761 timeStamp.tv_usec = startTime->tv_usec;
762
763 total = oldTotal = 0;
764 ret = STREAM_DONE;
765 while ((bytes_read = fread(buff, 1UL, sizeof(buff), filepstream)) > 0) {
766 if (shout_get_connected(shout) != SHOUTERR_CONNECTED &&
767 reconnectServer(shout, 0) == 0) {
768 ret = STREAM_SERVERR;
769 break;
770 }
771
772 shout_sync(shout);
773
774 if (shout_send(shout, buff, bytes_read) != SHOUTERR_SUCCESS) {
775 printf("%s: shout_send(): %s\n", __progname,
776 shout_get_error(shout));
777 if (reconnectServer(shout, 1))
778 break;
779 else {
780 ret = STREAM_SERVERR;
781 break;
782 }
783 }
784
785 if (quit)
786 break;
787 if (rereadPlaylist_notify) {
788 rereadPlaylist_notify = 0;
789 if (!pezConfig->fileNameIsProgram)
790 printf("%s: SIGHUP signal received, will reread playlist after this file\n",
791 __progname);
792 }
793 if (skipTrack) {
794 skipTrack = 0;
795 ret = STREAM_SKIP;
796 break;
797 }
798 if (queryMetadata) {
799 queryMetadata = 0;
800 if (metadataFromProgram) {
801 ret = STREAM_UPDMDATA;
802 break;
803 }
804 }
805
806 total += bytes_read;
807 if (qFlag && vFlag) {
808 struct timeval tval;
809 double oldTime, newTime;
810
811 if (!isStdin && playlistMode) {
812 if (pezConfig->fileNameIsProgram) {
813 char *tmp = xstrdup(pezConfig->fileName);
814 printf(" [%s]",
815 local_basename(tmp));
816 xfree(tmp);
817 } else
818 printf(" [%4lu/%-4lu]",
819 playlist_get_position(playlist),
820 playlist_get_num_items(playlist));
821 }
822
823 oldTime = (double)timeStamp.tv_sec
824 + (double)timeStamp.tv_usec / 1000000.0;
825 ez_gettimeofday((void *)&tval);
826 newTime = (double)tval.tv_sec
827 + (double)tval.tv_usec / 1000000.0;
828 if (songLenStr == NULL)
829 printf(" [ %s]",
830 getTimeString(tval.tv_sec - startTime->tv_sec));
831 else
832 printf(" [ %s/%s]",
833 getTimeString(tval.tv_sec - startTime->tv_sec),
834 songLenStr);
835 if (newTime - oldTime >= 1.0) {
836 kbps = (((double)(total - oldTotal) / (newTime - oldTime)) * 8.0) / 1000.0;
837 timeStamp.tv_sec = tval.tv_sec;
838 timeStamp.tv_usec = tval.tv_usec;
839 oldTotal = total;
840 }
841 if (kbps < 0)
842 printf(" ");
843 else
844 printf(" [%8.2f kbps]", kbps);
845
846 printf(" \r");
847 fflush(stdout);
848 }
849 }
850 if (ferror(filepstream)) {
851 if (errno == EINTR) {
852 clearerr(filepstream);
853 ret = STREAM_CONT;
854 } else if (errno == EBADF && isStdin)
855 printf("%s: No (more) data available on standard input\n",
856 __progname);
857 else
858 printf("%s: sendStream(): Error while reading '%s': %s\n",
859 __progname, fileName, strerror(errno));
860 }
861
862 return (ret);
863 }
864
865 int
streamFile(shout_t * shout,const char * fileName)866 streamFile(shout_t *shout, const char *fileName)
867 {
868 FILE *filepstream = NULL;
869 int popenFlag = 0;
870 char *songLenStr = NULL;
871 int isStdin = 0;
872 int ret, retval = 0;
873 long songLen;
874 metadata_t *mdata;
875 struct timeval startTime;
876
877 if ((filepstream = openResource(shout, fileName, &popenFlag,
878 &mdata, &isStdin, &songLen))
879 == NULL) {
880 if (++resource_errors > 100) {
881 printf("%s: Too many errors -- giving up.\n", __progname);
882 return (0);
883 }
884 /* Continue with next resource on failure: */
885 return (1);
886 }
887 resource_errors = 0;
888
889 if (mdata != NULL) {
890 char *tmp, *metaData;
891
892 tmp = metadata_assemble_string(mdata);
893 if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL)
894 metaData = xstrdup("(unknown title)");
895 xfree(tmp);
896 printf("%s: Streaming ``%s''", __progname, metaData);
897 if (vFlag)
898 printf(" (file: %s)\n", fileName);
899 else
900 printf("\n");
901 xfree(metaData);
902
903 /* MP3 streams are special, so set the metadata explicitly: */
904 if (strcmp(pezConfig->format, MP3_FORMAT) == 0)
905 setMetadata(shout, mdata, NULL);
906
907 metadata_free(&mdata);
908 } else if (isStdin)
909 printf("%s: Streaming from standard input\n", __progname);
910
911 if (songLen > 0)
912 songLenStr = xstrdup(getTimeString(songLen));
913 ez_gettimeofday((void *)&startTime);
914 do {
915 ret = sendStream(shout, filepstream, fileName, isStdin,
916 songLenStr, &startTime);
917 if (quit)
918 break;
919 if (ret != STREAM_DONE) {
920 if ((skipTrack && rereadPlaylist) ||
921 (skipTrack && queryMetadata)) {
922 skipTrack = 0;
923 ret = STREAM_CONT;
924 }
925 if (queryMetadata && rereadPlaylist) {
926 queryMetadata = 0;
927 ret = STREAM_CONT;
928 }
929 if (ret == STREAM_SKIP || skipTrack) {
930 skipTrack = 0;
931 if (!isStdin && vFlag)
932 printf("%s: SIGUSR1 signal received, skipping current track\n",
933 __progname);
934 retval = 1;
935 ret = STREAM_DONE;
936 }
937 if (ret == STREAM_UPDMDATA || queryMetadata) {
938 queryMetadata = 0;
939 if (metadataFromProgram) {
940 char *mdataStr = NULL;
941 metadata_t *prog_mdata;
942
943 if (vFlag > 1)
944 printf("%s: Querying '%s' for fresh metadata\n",
945 __progname, pezConfig->metadataProgram);
946 if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
947 retval = 0;
948 ret = STREAM_DONE;
949 continue;
950 }
951 if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) {
952 retval = 0;
953 ret = STREAM_DONE;
954 continue;
955 }
956 metadata_free(&prog_mdata);
957 printf("%s: New metadata: ``%s''\n",
958 __progname, mdataStr);
959 xfree(mdataStr);
960 }
961 }
962 if (ret == STREAM_SERVERR) {
963 retval = 0;
964 ret = STREAM_DONE;
965 }
966 } else
967 retval = 1;
968 } while (ret != STREAM_DONE);
969
970 if (popenFlag)
971 pclose(filepstream);
972 else
973 fclose(filepstream);
974
975 if (songLenStr != NULL)
976 xfree(songLenStr);
977
978 return (retval);
979 }
980
981 int
streamPlaylist(shout_t * shout,const char * fileName)982 streamPlaylist(shout_t *shout, const char *fileName)
983 {
984 const char *song;
985 char lastSong[PATH_MAX];
986
987 if (playlist == NULL) {
988 if (pezConfig->fileNameIsProgram) {
989 if ((playlist = playlist_program(fileName)) == NULL)
990 return (0);
991 } else {
992 if ((playlist = playlist_read(fileName)) == NULL)
993 return (0);
994 if (vFlag && playlist_get_num_items(playlist) == 0)
995 printf("%s: Warning: Playlist '%s' is empty\n",
996 __progname, fileName);
997 }
998 } else {
999 /*
1000 * XXX: This preserves traditional behavior, however,
1001 * rereading the playlist after each walkthrough seems a
1002 * bit more logical.
1003 */
1004 playlist_rewind(playlist);
1005 }
1006
1007 if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
1008 playlist_shuffle(playlist);
1009
1010 while ((song = playlist_get_next(playlist)) != NULL) {
1011 strlcpy(lastSong, song, sizeof(lastSong));
1012 if (!streamFile(shout, song))
1013 return (0);
1014 if (quit)
1015 break;
1016 if (rereadPlaylist) {
1017 rereadPlaylist = rereadPlaylist_notify = 0;
1018 if (pezConfig->fileNameIsProgram)
1019 continue;
1020 printf("%s: Rereading playlist\n", __progname);
1021 if (!playlist_reread(&playlist))
1022 return (0);
1023 if (pezConfig->shuffle)
1024 playlist_shuffle(playlist);
1025 else {
1026 playlist_goto_entry(playlist, lastSong);
1027 playlist_skip_next(playlist);
1028 }
1029 continue;
1030 }
1031 }
1032
1033 return (1);
1034 }
1035
1036 /*
1037 * Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c.
1038 * Does not use xalloc on purpose, as the 9 bytes of memory that don't get
1039 * cleaned up in the end really don't matter.
1040 */
1041 char *
getProgname(const char * argv0)1042 getProgname(const char *argv0)
1043 {
1044 #ifdef HAVE___PROGNAME
1045 (void)argv0;
1046 return (strdup(__progname));
1047 #else
1048 char *p;
1049
1050 if (argv0 == NULL)
1051 return ((char *)"ezstream");
1052 p = strrchr(argv0, path_separators[0]);
1053 if (p == NULL)
1054 p = (char *)argv0;
1055 else
1056 p++;
1057
1058 return (strdup(p));
1059 #endif /* HAVE___PROGNAME */
1060 }
1061
1062 int
ez_shutdown(int exitval)1063 ez_shutdown(int exitval)
1064 {
1065 shout_shutdown();
1066 playlist_shutdown();
1067 freeConfig(pezConfig);
1068 xalloc_shutdown();
1069
1070 return (exitval);
1071 }
1072
1073 void
usage(void)1074 usage(void)
1075 {
1076 printf("usage: %s [-hnqVv] -c configfile\n", __progname);
1077 printf(" %s -s [playlist]\n", __progname);
1078 }
1079
1080 void
usageHelp(void)1081 usageHelp(void)
1082 {
1083 printf("\n");
1084 printf(" -c configfile use XML configuration in configfile (mandatory)\n");
1085 printf(" -h display this additional help and exit\n");
1086 printf(" -n normalize metadata strings\n");
1087 printf(" -q suppress STDERR output from external en-/decoders\n");
1088 printf(" -s [playlist] read lines from playlist (or STDIN), shuffle and print them to\n");
1089 printf(" STDOUT, then exit\n");
1090 printf(" -V print the version number and exit\n");
1091 printf(" -v verbose output (use twice for more effect)\n");
1092 printf("\n");
1093 printf("See the ezstream(1) manual for detailed information.\n");
1094 }
1095
1096 int
main(int argc,char * argv[])1097 main(int argc, char *argv[])
1098 {
1099 int c;
1100 char *configFile = NULL;
1101 char *host = NULL;
1102 unsigned short port = 0;
1103 char *mount = NULL;
1104 shout_t *shout;
1105 extern char *optarg;
1106 extern int optind;
1107 #ifdef HAVE_SIGNALS
1108 struct sigaction act;
1109 unsigned int i;
1110 #endif
1111
1112 #ifdef XALLOC_DEBUG
1113 xalloc_initialize_debug(2, NULL);
1114 #else
1115 xalloc_initialize();
1116 #endif /* XALLOC_DEBUG */
1117 playlist_init();
1118 shout_init();
1119
1120 __progname = getProgname(argv[0]);
1121 pezConfig = getEZConfig();
1122
1123 nFlag = 0;
1124 qFlag = 0;
1125 vFlag = 0;
1126
1127 while ((c = local_getopt(argc, argv, "c:hnqsVv")) != -1) {
1128 switch (c) {
1129 case 'c':
1130 if (configFile != NULL) {
1131 printf("Error: multiple -c arguments given\n");
1132 usage();
1133 return (ez_shutdown(2));
1134 }
1135 configFile = xstrdup(optarg);
1136 break;
1137 case 'h':
1138 usage();
1139 usageHelp();
1140 return (ez_shutdown(0));
1141 case 'n':
1142 nFlag = 1;
1143 break;
1144 case 'q':
1145 qFlag = 1;
1146 break;
1147 case 's':
1148 sFlag = 1;
1149 break;
1150 case 'V':
1151 printf("%s\n", PACKAGE_STRING);
1152 return (ez_shutdown(0));
1153 case 'v':
1154 vFlag++;
1155 break;
1156 case '?':
1157 usage();
1158 return (ez_shutdown(2));
1159 default:
1160 break;
1161 }
1162 }
1163 argc -= optind;
1164 argv += optind;
1165
1166 if (sFlag) {
1167 playlist_t *pl;
1168 const char *entry;
1169
1170 switch (argc) {
1171 case 0:
1172 pl = playlist_read(NULL);
1173 if (pl == NULL)
1174 return (ez_shutdown(1));
1175 break;
1176 case 1:
1177 pl = playlist_read(argv[0]);
1178 if (pl == NULL)
1179 return (ez_shutdown(1));
1180 break;
1181 default:
1182 printf("Error: Too many arguments.\n");
1183 return (ez_shutdown(2));
1184 }
1185
1186 playlist_shuffle(pl);
1187 while ((entry = playlist_get_next(pl)) != NULL)
1188 printf("%s\n", entry);
1189
1190 playlist_free(&pl);
1191
1192 return (ez_shutdown(0));
1193 }
1194
1195 if (configFile == NULL) {
1196 printf("You must supply a config file with the -c argument.\n");
1197 usage();
1198 return (ez_shutdown(2));
1199 } else {
1200 /*
1201 * Attempt to open configFile here for a more meaningful error
1202 * message. Where possible, do it with stat() and check for
1203 * safe config file permissions.
1204 */
1205 #ifdef HAVE_STAT
1206 struct stat st;
1207
1208 if (stat(configFile, &st) == -1) {
1209 printf("%s: %s\n", configFile, strerror(errno));
1210 usage();
1211 return (ez_shutdown(2));
1212 }
1213 if (vFlag && (st.st_mode & (S_IRGRP | S_IROTH)))
1214 printf("%s: Warning: %s is group and/or world readable\n",
1215 __progname, configFile);
1216 if (st.st_mode & (S_IWGRP | S_IWOTH)) {
1217 printf("%s: Error: %s is group and/or world writeable\n",
1218 __progname, configFile);
1219 return (ez_shutdown(2));
1220 }
1221 #else
1222 FILE *tmp;
1223
1224 if ((tmp = fopen(configFile, "r")) == NULL) {
1225 printf("%s: %s\n", configFile, strerror(errno));
1226 usage();
1227 return (ez_shutdown(2));
1228 }
1229 fclose(tmp);
1230 #endif /* HAVE_STAT */
1231 }
1232
1233 if (!parseConfig(configFile))
1234 return (ez_shutdown(2));
1235
1236 if (pezConfig->URL == NULL) {
1237 printf("%s: Error: Missing <url>\n", configFile);
1238 return (ez_shutdown(2));
1239 }
1240 if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
1241 printf("Must be of the form ``http://server:port/mountpoint''\n");
1242 return (ez_shutdown(2));
1243 }
1244 if (strlen(host) == 0) {
1245 printf("%s: Error: Invalid <url>: Missing server:\n", configFile);
1246 printf("Must be of the form ``http://server:port/mountpoint''\n");
1247 return (ez_shutdown(2));
1248 }
1249 if (strlen(mount) == 0) {
1250 printf("%s: Error: Invalid <url>: Missing mountpoint:\n", configFile);
1251 printf("Must be of the form ``http://server:port/mountpoint''\n");
1252 return (ez_shutdown(2));
1253 }
1254 if (pezConfig->password == NULL) {
1255 printf("%s: Error: Missing <sourcepassword>\n", configFile);
1256 return (ez_shutdown(2));
1257 }
1258 if (pezConfig->fileName == NULL) {
1259 printf("%s: Error: Missing <filename>\n", configFile);
1260 return (ez_shutdown(2));
1261 }
1262 if (pezConfig->format == NULL) {
1263 printf("%s: Warning: Missing <format>:\n", configFile);
1264 printf("Specify a stream format of either MP3, VORBIS or THEORA\n");
1265 }
1266
1267 xfree(configFile);
1268
1269 if ((shout = stream_setup(host, port, mount)) == NULL)
1270 return (ez_shutdown(1));
1271
1272 if (pezConfig->metadataProgram != NULL)
1273 metadataFromProgram = 1;
1274 else
1275 metadataFromProgram = 0;
1276
1277 #ifdef HAVE_SIGNALS
1278 memset(&act, 0, sizeof(act));
1279 act.sa_handler = sig_handler;
1280 # ifdef SA_RESTART
1281 act.sa_flags = SA_RESTART;
1282 # endif
1283 for (i = 0; i < sizeof(ezstream_signals) / sizeof(int); i++) {
1284 if (sigaction(ezstream_signals[i], &act, NULL) == -1) {
1285 printf("%s: sigaction(): %s\n",
1286 __progname, strerror(errno));
1287 return (ez_shutdown(1));
1288 }
1289 }
1290 /*
1291 * Ignore SIGPIPE, which has been seen to give a long-running ezstream
1292 * process trouble. EOF and/or EPIPE are also easier to handle.
1293 */
1294 act.sa_handler = SIG_IGN;
1295 if (sigaction(SIGPIPE, &act, NULL) == -1) {
1296 printf("%s: sigaction(): %s\n",
1297 __progname, strerror(errno));
1298 return (ez_shutdown(1));
1299 }
1300 #endif /* HAVE_SIGNALS */
1301
1302 if (shout_open(shout) == SHOUTERR_SUCCESS) {
1303 int ret;
1304
1305 printf("%s: Connected to http://%s:%hu%s\n", __progname,
1306 host, port, mount);
1307
1308 if (pezConfig->fileNameIsProgram ||
1309 strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
1310 strrcasecmp(pezConfig->fileName, ".txt") == 0)
1311 playlistMode = 1;
1312 else
1313 playlistMode = 0;
1314
1315 if (vFlag && pezConfig->fileNameIsProgram)
1316 printf("%s: Using program '%s' to get filenames for streaming\n",
1317 __progname, pezConfig->fileName);
1318
1319 ret = 1;
1320 do {
1321 if (playlistMode) {
1322 ret = streamPlaylist(shout,
1323 pezConfig->fileName);
1324 } else {
1325 ret = streamFile(shout, pezConfig->fileName);
1326 }
1327 if (quit)
1328 break;
1329 if (pezConfig->streamOnce)
1330 break;
1331 } while (ret);
1332
1333 shout_close(shout);
1334 } else
1335 printf("%s: Connection to http://%s:%hu%s failed: %s\n", __progname,
1336 host, port, mount, shout_get_error(shout));
1337
1338 if (quit)
1339 printf("\r%s: SIGINT or SIGTERM received\n", __progname);
1340
1341 if (vFlag)
1342 printf("%s: Exiting ...\n", __progname);
1343
1344 xfree(host);
1345 xfree(mount);
1346 playlist_free(&playlist);
1347
1348 return (ez_shutdown(0));
1349 }
1350