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